前言普通摄像头只能获取二维彩色图像而深度相机除了能够获取 RGB 彩色图像还可以测量场景中各个像素点到相机的距离。借助深度相机我们可以进一步实现例如人体骨骼识别、障碍物检测、物体尺寸测量、三维点云生成、机器人视觉、距离检测等功能。但是对于第一次接触深度相机的初学者来说最困难的往往不是后续算法而是如何学习最基础的使用虽然基础却能够增强自信心和动力像是连接深度相机、安装驱动和开发 SDK、获取彩色帧和深度帧、将原始数据转换成 OpenCV 图像等等。本文以Microsoft Kinect v2为例从硬件连接、SDK 安装、Python 环境配置开始最终实现彩色图像、深度图像和融合图像的实时显示并支持鼠标点击读取某一点的深度距离。面向从未使用过深度相机、不了解 SDK、没有编写过相机连接程序的初学者。一、科普普通的彩色图像例如RGB相信大家都比较熟悉每一个像素通常包含三个通道。而在深度图中每一个像素不再表示颜色而是表示“该像素所对应空间位置到深度相机的距离”例如1000一般表示该点距离相机约为 1000 毫米也就是 1 米。Kinect v2 返回的深度图通常是一个二维数组424 行 × 512 列也就是说一帧深度图包含深度测量点的数量为424 × 512 217088二、下载与安装驱动和SDK很多初学者容易混淆驱动、SDK、PyKinect2 和 OpenCV 的作用这里也稍微再解释一下。当你有了深度相机的设备本体和连接好线路之后Windows 驱动让 Windows 操作系统能够识别 Kinect 设备SDK 全称为 Software Development Kit即软件开发工具包它向开发者提供Kinect 设备访问接口例如彩色帧、深度帧接口坐标映射接口设备检测工具示例程序等Kinect SDK 原生接口主要面向 C、C# 等语言PyKinect2 对 Kinect for Windows v2 API 进行了 Python 封装使我们可以在 Python 中调用 KinectOpenCV 主要用于图像缩放、图像显示、深度图伪彩色处理等高层功能的实现前两点可以通过下载官方KineticSDK-v2如下图所示的这个安装程序后运行当安装完成后会出现另外的三个快捷方式。微软官方说明Kinect for Windows SDK 2.0 包含 Kinect v2 驱动、API、设备接口和示例代码。其他的另外三个快捷方式基本上用不到不过有一个Browser是官方提供的一些编写好的示例功能有运行按钮可以点击尝试是否安装成功。三、确认事项首先要确保深度相机正确连接USB只提供与电脑的数据连接有独立供电中间的转接盒上面会亮起白灯这一部分不再详细指导了。Kinect 独立使用的 USB 3.0 控制器支持的操作系统为Windows 8 64位及以后像作者我本人使用的是64位Windows 10。四、配置 Python 环境PyKinect2 是一个比较早期的项目。PyPI 上的pykinect2 0.1.0发布于 2015 年页面标注的 Python 分类主要为 Python 2.7 和 Python 3.4。因此新版 Python 环境有可能出现兼容问题像作者本人我使用的是Python 3.9。直接pip安装即可pip install pykinect2其依赖包括 Python、NumPy、comtypes、Kinect for Windows SDK v2等因此也可以一次性安装pip install numpy opencv-python comtypes pykinect2五、示例功能代码下面的展示简单示例功能代码实现了实时读取深度相机的深度和彩色图像并且有一个叠加融合的效果。import cv2 import numpy as np import threading import queue import time from pykinect2 import PyKinectV2 from pykinect2 import PyKinectRuntime DEPTH_SCALE 4500.0 DISPLAY_WIDTH 640 # 显示宽度 DISPLAY_HEIGHT 360 # 显示高度 FPS_TARGET 30 # 目标帧率 class KinectViewer: def __init__(self): # 初始化Kinect self.kinect PyKinectRuntime.PyKinectRuntime( PyKinectV2.FrameSourceTypes_Color | PyKinectV2.FrameSourceTypes_Depth) # 帧队列 self.frame_queue queue.Queue(maxsize2) # 仅保留最新几帧 self.running True def capture_thread(self): 数据采集线程 while self.running: if self.kinect.has_new_color_frame() and self.kinect.has_new_depth_frame(): # 获取原始数据 color_frame self.kinect.get_last_color_frame() depth_frame self.kinect.get_last_depth_frame() # 如果队列未满放入否则跳过丢弃旧帧 if not self.frame_queue.full(): self.frame_queue.put((color_frame, depth_frame)) else: time.sleep(0.001) # 避免忙等待 def display_thread(self): 显示线程 last_time time.time() while self.running: try: # 非阻塞获取最新帧timeout0.01 color_frame, depth_frame self.frame_queue.get(timeout0.01) except queue.Empty: continue # 处理帧 color_img self.process_color(color_frame) depth_img self.process_depth(depth_frame) combined_img self.process_combined(color_img, depth_img) if color_img is not None and depth_img is not None else None # 限制帧率显示 current_time time.time() if current_time - last_time 1.0 / FPS_TARGET: if color_img is not None: cv2.imshow(Color, color_img) if depth_img is not None: cv2.imshow(Depth, depth_img) if combined_img is not None: cv2.imshow(Combined, combined_img) last_time current_time if cv2.waitKey(1) 0xFF ord(q): self.running False break def process_color(self, color_frame): 处理彩色图 color_img color_frame.reshape((1080, 1920, 4))[:, :, :3] return cv2.resize(color_img, (DISPLAY_WIDTH, DISPLAY_HEIGHT)) def process_depth(self, depth_frame): 处理深度图 depth_raw depth_frame.reshape((424, 512)) depth_raw cv2.resize(depth_raw, (DISPLAY_WIDTH, DISPLAY_HEIGHT)) depth_normalized np.clip(depth_raw / DEPTH_SCALE, 0, 1) * 255 depth_normalized depth_normalized.astype(np.uint8) return cv2.applyColorMap(depth_normalized, cv2.COLORMAP_JET) def process_combined(self, color_img, depth_img): 融合图 return cv2.addWeighted(color_img, 0.7, depth_img, 0.3, 0) def run(self): 启动线程 t_capture threading.Thread(targetself.capture_thread) t_display threading.Thread(targetself.display_thread) t_capture.start() t_display.start() t_capture.join() t_display.join() cv2.destroyAllWindows() self.kinect.close() if __name__ __main__: viewer KinectViewer() viewer.run()正常情况下会出现三个窗口Color 窗口显示 Kinect 的 RGB 彩色画面原始彩色帧尺寸为1920 × 1080 × 41920宽度 1080高度 4BGRA 四个通道之后经过如下代码color_img color_frame.reshape((1080, 1920, 4))[:, :, :3]删除 Alpha 通道只保留 BGR 三通道。由于 OpenCV 默认使用 BGR 顺序因此不需要再次交换通道。Depth 窗口显示深度伪彩色图每一帧为424*512的二维数值数组每个数值表示毫米距离。由于普通显示器无法直接显示 16 位毫米数值所以代码先将深度压缩到 0255再转换为 8 位图像生成伪彩色图只是为了方便人眼观察真正的深度值仍然保存在depth_raw中Combined 窗口将彩色图和深度伪彩色图进行融合只是把两张同尺寸图像叠加并没有进行真实的彩色相机与深度相机坐标对齐。二者不是同一个光学传感器两者分辨率不同、视场角不同、光心位置不同等多种因素。后续若需要把深度点准确投影到彩色图上需要使用 Kinect SDK 的坐标映射接口CoordinateMapper这里就不再详细展示细节了。六、使用上的细节使用线程是必要的如果所有操作都写在同一个循环中读取相机 → 转换图像 → 运行算法 → 显示图像 → 再读取下一帧一旦图像处理变慢摄像头采集也会被拖慢。所以使用两个线程比较好。采集线程只读取 Kinect 数据并放入队列显示线程从队列取数据转换并显示。帧队列不能太大显示线程处理不过来时队列会积累大量旧帧导致的效果例如人已经移动了窗口还在显示几秒前的画面延迟越来越大内存占用增加。实时相机程序通常不要求处理每一帧而要求尽量处理当前最新的画面所以队列满时删除旧帧。# 帧队列 self.frame_queue queue.Queue(maxsize2) # 仅保留最新几帧 self.running True #…… # 如果队列未满放入否则跳过丢弃旧帧 if not self.frame_queue.full(): self.frame_queue.put((color_frame, depth_frame))有时候可能会遇见深度值为 0 通常表示Kinect 没有在该位置获得有效深度常见原因包括物体距离过近或过远玻璃或镜面反射黑色吸光材料强烈红外光干扰物体边缘存在遮挡传感器噪声红外发射光无法正常返回因此使用深度值前要先判断if depth_mm 0:不能直接把 0 当成物体距离相机 0 毫米。参考资料Microsoft Kinect for Windows SDK 2.0 官方说明与安装要求。Microsoft Kinect for Windows Runtime 2.0 官方说明。Kinect/PyKinect2 官方 GitHub 项目说明。PyPI 中的 pykinect2 0.1.0 软件包信息。版权声明本文采用CC BY-NC-SA 4.0协议转载请注明出处。