关键点又称为感兴趣的点,是低层次视觉通往高层次视觉的捷径,抑或是高层次感知对低层次处理手段的妥协。
——三维视觉关键点检测
1.关键点,线,面
关键点=特征点;
关键线=边缘;
关键面=foreground;
上述三个概念在信息学中几乎占据了统治地位。比如1维的函数(信号),有各种手段去得到某个所谓的关键点,有极值点,拐点...二维的图像,特征点提取算法是标定算法的核心(harris),边缘提取算法更是备受瞩目(canny,LOG.....),当然,对二维的图像也有区域所谓的前景分割算法用于提取感兴趣的区域,但那属于较高层次的视觉,本文不讨论。 由此可以推断,三维视觉应该同时具备:关键点,关键线,关键面三种算法。本质上,关键面算法就是我们之前一文中讨论的分割算法(三维点云不是实心的)。关于关键点更多的信息可以参考:特征检测
ok,在这里我们了解到了,要在n维信息中提取n-1维信息是简单的,但n-2维信息会比n-1维要不稳定或者复杂的多。很容易想象,图像的边缘处理算法所得到的结果一般大同小异,但关键点提取算法的结果可以是千差万别的。主要原因是降维过大后,特征的定义很模糊,很难描述清楚对一幅图像来说,到底怎样的点才是关键点。所以,对3维点云来说,关键点的描述就更难了。点云也有1维边缘检测算法,本文不做讨论。单说说关键点提取。
2.来自点云的降维打击
图像的Harris角点算子将图像的关键点定义为角点。角点也就是物体边缘的交点,harris算子利用角点在两个方向的灰度协方差矩阵响应都很大,来定义角点。既然关键点在二维图像中已经被成功定义且使用了,看来在三维点云中可以沿用二维图像的定义...不过今天要讲的是另外一种思路,简单粗暴,直接把三维的点云投射成二维的图像不就好了。这种投射方法叫做range_image.
首先放上一张range_imge和点云图像的合照:
看起来像个眼睛的那玩意就是range_image. 至于它为什么像个眼睛,就要从它的出生开始说起了。三维点云有多种采集方式,最为著名的是结构光,飞秒相机,双目视觉。简而言之,采集都离不开相机。用相机拍照当然就存在相机的光心坐标原点 Oc 以及主光轴方向 Z. 从这个点,有一种办法可以将三维数据映射到2维平面上。首先,将某点到光心Oc的距离映射成深度图的灰度或颜色(灰度只有256级但颜色却可接近连续变化)。除此之外,再定义一下怎样将点云映射到图像的横纵坐标上就可以了。
任意一点都要和光心进行连线.....这么听起来很熟悉....好像有点像球坐标的意思。球坐标长下面这张图这样。
深度图中的横,纵坐标实际上是a和phi,如果要保证沿着场景中某条直线移动,a线性变化phi却先增大后减小。这也就造成了深度图像一个眼睛一样。但这并不妨碍什么,phi没有定义的地方可以使用深度无限大来代替。
将点云转成深度图,只需要确定一个直角坐标系,角分辨率,a范围,phi范围即可。毕竟这只是一个直角坐标转球坐标的工作而已。
显然,图像中的关键点检测算子就可以被移植到点云特征点求取中来了。
3.基于PCL的点云-深度图转换
//rangeImage也是PCL的基本数据结构 pcl::RangeImage rangeImage; //角分辨率 float angularResolution = (float) ( 1.0f * (M_PI/180.0f)); // 1.0 degree in radians //phi可以取360° float maxAngleWidth = (float) (360.0f * (M_PI/180.0f)); // 360.0 degree in radians //a取180° float maxAngleHeight = (float) (180.0f * (M_PI/180.0f)); // 180.0 degree in radians //半圆扫一圈就是整个图像了 //传感器朝向 Eigen::Affine3f sensorPose = (Eigen::Affine3f)Eigen::Translation3f(0.0f, 0.0f, 0.0f); //除了三维相机模式还可以选结构光模式 pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME; //noise level表示的是容差率,因为1°X1°的空间内很可能不止一个点,noise level = 0则表示去最远点的距离作为像素值,如果=0.05则表示在5cm范围内求个平均距离 float noiseLevel=0.00; //minRange表示深度最小值,如果=0则表示取1°X1°的空间内最远点,近的都忽略 float minRange = 0.0f; //bordersieze表示图像周边点 int borderSize = 1; //基本数据结构直接打印是ok的 std::cout << rangeImage << "\n";