国外课栈 - 国外电子信息技术视角栈

聚焦Python和OpenCV使用深度传感器的手势识别

 二维码 16

使用深度传感器的手势识别

准备

首先,从此链接开始安装OpenKinect和libfreenect。

计划App

最终的应用程序将包含以下模块和脚本:

  • gestures:这是一个模块,由识别手势的算法组成。
  • gestures.process::此函数可实现手势识别的整个过程。 它接受单通道深度图像(从Kinect深度传感器获取),并返回带注释的蓝色,绿色,红色(BGR)彩色图像,并带有扩展手指的估计数量。

最终结果如下:

无论伸出多少手指,该算法都会正确分割手区域(白色),绘制相应的凸包(围绕手的绿线),找到所有属于手指之间空间的凸缺陷(绿色点),而忽略其他手指(红色的小点),并推断出正确的伸出手指数(右下角的数字),即使是拳头也是如此。

设置App

在深入了解手势识别算法之前,我们需要确保可以访问深度传感器并显示深度帧流。

访问Kinect 3D传感器

访问Kinect传感器的最简单方法是使用称为freenect的OpenKinect模块。

freenect模块具有sync_get_depth()和sync_get_video()之类的函数,分别用于分别从深度传感器和相机传感器同步获取图像。在此,我们只需要Kinect深度图,它是单通道(灰度)图像,其中每个像素值是从相机到视觉场景中特定表面的估计距离。

在这里,我们将设计一个函数,该函数将从传感器读取一帧并将其转换为所需的格式,并返回该帧以及成功状态,如下所示:

def read_frame(): -> Tuple[bool,np.ndarray]:

该函数包括以下步骤:

  1. 抓住frame; 如果未获取帧,则终止函数,如下所示:
    frame, timestamp = freenect.sync_get_depth()
     if frame is None:
     return False, None

    sync_get_depth方法同时返回深度图和时间戳。 默认情况下,映射为11位格式。 传感器的最后10位用于描述深度,而第一位则表示距离估算等于1时不成功。

  1. 将数据标准化为8位精度格式是个好主意,因为11位格式不适用于cv2.imshow以及将来的可视化。 我们可能要使用一些以不同格式返回的不同传感器,如下所示:
    np.clip(depth, 0, 2**10-1, depth)
    depth >>= 2

    在前面的代码中,我们首先将值裁剪为1,023(或2 ** 10-1)以适合10位。 这种削波导致将未检测到的距离分配给尽可能远的点。 接下来,我们向右移动2位以使距离适合8位。

  1. 最后,我们将图像转换为8位无符号整数数组,并返回结果,如下所示:
return True, depth.astype(np.uint8)

现在,深度图像可以如下所示:

cv2.imshow("depth", read_frame()[1])

使用与OpenNI兼容的传感器

要使用兼容OpenNI的传感器,必须首先确保已安装OpenNI2,并且您的OpenCV版本是在OpenNI支持下构建的。 构建信息可以按以下方式打印:

import cv2
print(cv2.getBuildInformation())

如果您的版本是使用OpenNI支持构建的,则可以在“视频I / O”部分找到它。 否则,您将必须使用OpenNI支持来重建OpenCV,这是通过将-D WITH_OPENNI2 = ON标志传递给cmake来完成的。

安装过程完成后,您可以使用cv2.VideoCapture与其他视频输入设备类似地访问传感器。 在此应用中,为了使用兼容OpenNI的传感器而不是Kinect 3D传感器,您必须完成以下步骤:

  1. 创建连接到与OpenNI兼容的传感器的视频捕获,如下所示:
    device = cv2.cv.CV_CAP_OPENNI
    capture = cv2.VideoCapture(device)

    如果要连接到Asus Xtion,则应将设备变量分配给cv2.CV_CAP_OPENNI_ASUS值。

  1. 将输入帧大小更改为标准视频图形阵列(VGA)分辨率,如下所示:
    capture.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, 640)
    capture.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, 480)


  1. 在此之前,我们设计了read_frame函数,该函数使用freenect访问Kinect传感器。 为了从视频捕获中读取深度图像,您必须将该函数更改为以下函数:
    def read_frame():
     if not capture.grab():
     return False,None
     return capture.retrieve(cv2.CAP_OPENNI_DEPTH_MAP)

    您会注意到,我们使用了grab和retrieve方法而不是read方法。 原因是当我们需要同步一组摄像机或多头摄像机(例如Kinect)时,cv2.VideoCapture的读取方法不合适。

    对于此类情况,您可以在某个时刻使用抓取方法从多个传感器抓取帧,然后使用检索方法检索感兴趣的传感器的数据。 例如,在您自己的应用中,您可能还需要检索BGR框架(标准相机框架),可以通过将cv2.CAP_OPENNI_BGR_IMAGE传递给检索方法来完成。

运行应用程序和主函数例程


识别函数负责识别手势,我们将在后面部分进行介绍。 为了方便起见,我们还将之前编写的read_frame方法放在单独的脚本中。

为了简化分割任务,我们将指示用户将手放在屏幕中央。 为了对此提供视觉帮助,我们创建以下函数:

def draw_helpers(img_draw: np.ndarray) -> None:
 # draw some helpers for correctly placing hand
 height, width = img_draw.shape[:2]
 color = (0,102,255)
 cv2.circle(img_draw, (width // 2, height // 2), 3, color, 2)
 cv2.rectangle(img_draw, (width // 3, height // 3),
 (width * 2 // 3, height * 2 // 3), color, 2)

该功能在图像中心周围绘制一个矩形,并以橙色突出显示图像的中心像素。

所有繁重的工作都是由main函数完成的,如以下代码块所示:

def main():
 for _, frame in iter(read_frame, (False, None)):

该函数在Kinect的灰度帧上进行迭代,并且在每次迭代中,它涵盖以下步骤:

  1. 使用识别功能识别手势,该功能返回估计的伸出手指数(num_fingers)和带注释的BGR彩色图像,如下所示:
    num_fingers, img_draw = recognize(frame)
  1. 调用带注释的BGR图像上的draw_helpers函数,以便为手放置提供视觉帮助,如下所示:
    draw_helpers(img_draw)
  1. 最后,main函数在带注释的帧上绘制手指的数量,使用cv2.imshow显示结果,并设置终止条件,如下所示:
    # print number of fingers on image
     cv2.putText(img_draw, str(num_fingers), (30, 30),
     cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255))
     cv2.imshow("frame", img_draw)
     # Exit on escape
     if cv2.waitKey(10) == 27:
     break

因此,既然我们有了主脚本,您将注意到,我们缺少的唯一功能是识别功能。 为了跟踪手势,我们需要编写此功能,我们将在下一部分中进行操作。

实时跟踪手势

了解手部区域分割

找到图像中心区域最突出的深度

应用形态学闭合进行平滑

在分段掩膜中查找连接的组件

手部形状分析

确定分割的手部区域的轮廓

查找轮廓区域的凸包

手势识别

区分凸度缺陷的不同原因

根据伸出的手指数对手势进行分类


阅读完整文档

文章分类: 编程碎语PythonOpenCV