记录:我的第一代深度图像物体识别

刚开始学习Kinect,第一步选择学习深度图像的相关知识,第一个任务是给单片机传输目标位置数据,引导机器能够正确“抓”到目标。这篇文章会分为两部分,第一部分是没有物体识别的,第二部分是加上了物体识别的。

工具:
(1)C++及visual studio 2013
(2)opencv工具库
(3)PCOMM通信工具

我们都知道,如果只获取深度图像,会根据周围环境存在不同程度的噪点,影响图像数据的准确,所以最好的方法就是切割可视域,这里我的可视域还是一个长方体(确定左上角和右下角两点的坐标决定一个矩形,后通过深度决定长度),之后会考虑如何切割球体或者一些不规则的空间形状。

切割过程中,首先考虑的是那些可能超出了 Kinect 的深度数据获取范围的物体数据,这些位置的数据会为0,如果没有处理就照常规处理数据就会造成很多未知的错误,在这个任务中,最明显的就是摄像头坐标系中 x , y 轴的值的计算,具体可以参考我的上一篇文章。在获得图像数据后,目前我使用的是遍历整个图像的像素点,第一步就要提供一个每个像素点是否置0的标志位,这里置0有两种可能,无数据或者空间坐标系中的坐标超出我的切割范围;第二步,通过像素转毫米的公式获得 x , y 轴组成的平面的相应的坐标以及距离数据,然后进入选择是否在切割范围内的分支,如果该点在指定 x , y 范围内,继续判断是否在深度范围内,如果不在,将通过 Mat 类型对象的 ptr 指针指向的地址赋值直接改变原图像的数据,这里置0 ;在深度值满足条件的情况下,置0标志赋值为 false ,考虑是否已经找到左上角的点,对相应的 bool 类型的全局变量进行修改,并赋值给 minX,minY maxX,maxY ,在将所有在需要的 x,y 范围内的点遍历并判断完后,考虑是否矩形左上角是否存在,且是否存在右下角,前者 true ,后者为 false 的时候,第一个不满足矩形的点就是我们需要的矩形的右下角;最后,对于没有将置0标志位置 false 的点就将深度值置0,在图像中显示就是黑色,确定矩形 Rect 的四个参数(左上角的 x,y 和长度,高度)。

当然考虑到有可能在视野中真的没有物体,这样的算法会导致 width,height 为0 ,对后面的imshow()函数的参数 Mat 的规定不符,会报错,所以这里在遍历所有的点,确定了长方形的位置和大小后,设置默认值为(0 ,0 ,640 ,480),利用所得到的矩形的参数获得一个在原始图像上的新的 Mat 类型的对象,之后可以二值化显示,也可以通过改变图像的深度值(我这里使左移3位,即深度值乘以8)使显示时能够亮一些。不管怎么做,这个矩形已经能够满足我切割的需要了。

一开始我的想法很简单,就只需要获取视野中最右侧的点的坐标就判断是物体,显然局限性太大。并且这样的思考方式就相当于是一个激光雷达的作用–面内的数据判断,和摄像头的空间概念差别太大,所以选择下面的扫描方法。

下面将是我对物体识别的思路解释:
由于我处理的是深度图像,所以,对于图像中的所有的点的数据只有距离,想象一下,在黑暗中,没有色彩的情况下,我们“看到”的物体应该只是通过感受长度进行猜测,所以我只能给目标物体的相关长度参数,越规则的物体形状,需要的参数确定一个物体越少,这次我的目标物体是一个类似长方体的物体,参数只需要长度,宽度,高度。
如何确定一个物体的这三个参数呢,我当时想的是轮廓的存在在深度图像中的最大特征就是距离的不同,换言之就是深度值上的跳变,所以,寻找跳变点就成为了获取同行距离(宽度),同列距离(高度)的基础。
数据的处理和存储我选择了单向链表,有效针对数据量大且数据个数无法估计的问题(这里我犯了个错误就是忘记C语言对指针分配空间后的手动释放内存空间,报错后才意识到),对图像中所有像素点遍历,选择与上一个(这里是同一行的上一个)像素点的深度值差值的绝对值在设定阈值之外的点作为一个潜在跳沿点,因为我这里需要考虑下跳沿到0的点,这个点还需要一个粗略处理,后面我会提到。我将这些点以 x(y)+distance 的结构体存储。在对所有行或者列挑选完后,在分别对列或者行挑选,之后会以 y(x)+distance 的结构存储。
这里我还是用了另一个自定义的结构体: x(y)+x(y) ,用于存储两个在同一列(行)且宽度(高度)在设定可接受的阈值内的相邻的跳沿点。这里说一下对下跳沿点的粗略处理,由于要通过摄像头坐标系中的差值获得宽度/高度,所以还是要用到像素转毫米公式(这个公式我上一篇文章中提到了),必须要保证distance这个深度值为非0,那么在下跳沿点中,如果这个点的深度数据为0,,那么必须要存一个有数据的点,不难想象,同一行中,这个点的上一个点就一定是一个深度值不为0的点,也是我们能够用的数据。
这个过程具体就是通过每行(列)扫描获得每行(列)的跳沿点,选出符合指定宽度(高度)的成对的跳沿点,取 y(x) 的中间点判断长度进行识别。

对于之后的改进,我打算重点放在跳沿点的筛选方法上,比如采用变化率在距离图像坐标系原点越远的点越大,靠近原点采样点更加密集的方法,比如 y=x3 的曲线,当然,细心的读者会发现在原点时的0变化率将导致采样点无法自动增加,以为着会在原点的行(列)循环。如果读者有好的建议麻烦在评论区中说明一下,感激不尽。

这里先mark一下A-律,我之后会学习一下。

下面还有就是在调试过程中出现的问题,如果有看过PCOMM通信协议的语法的读者可能知道串口通信与缓冲区的问题,那么这里有必要提一下,一开始我认为如果多次取数据并计算平均值的话能够某种程度的减少噪声的误差,所以单片机中下载的程序取连续10个数据然后计算平均值作为坐标设置。事实情况是单片机中运行过程中Kinect发送数据的过程会出现明显的延迟,其实仔细想来,就是单片机端串口的缓冲区填充需要时间,造成定位与位置数据获取存在时间差,导致定位出错。

附:
这里也可以解释一下为什么我一开始要介绍切割可视域,其一是在没有意识到指针内存释放的问题之前我是认为掉帧的原因在于处理速度过慢导致阻塞,所以决定使用切割的方式减小行和列的数量;其二是为了尽可能的过滤掉图像显示中的噪点对跳沿点判断的影响。

你可能感兴趣的:(计算机视觉)