1、它是后面處理地圖的基礎,最簡單的點雲地圖就是把不同位置的點雲進行拼接得到的。
2、由於從RGB-D相機里可以采集到兩種形式的數據:彩色圖像和深度圖像。如果有kinect和ros,那么可以運行如下
roslaunch openni_launch openni.launch
來使Kinect工作。如果PC機連上Kinect,那么彩色圖像和深度圖像會發布在/camera/rgb/image_color 和 /camera/depth_registered/image_raw 中;可以通過運行如下
rosrun image_view image_view image:=/camera/rgb/image_color
來顯示彩色圖像,或者也可以在Rviz里看到圖像與點雲的可視化數據。
3、rgb圖像與對應的深度圖像,如下
這兩張圖是來自於數據集nyuv2:http://cs.nyu.edu/~silberman/datasets/ 原圖格式是ppm和pgm的,目前格式是png。
在實際的Kinect里(或其他rgb-d相機里)直接采到的RGB圖和深度圖可能會有些小問題,比如
1)有一些時差(約幾到十幾個毫秒)。這個時差的存在,會產生“RGB圖已經向右轉了,深度圖還沒轉”的感覺。
2)光圈中心未對齊。因為深度畢竟是靠另一個相機獲取的,所以深度傳感器和彩色傳感器參數可能不一致。
3)深度圖里有很多“洞”。因為RGB-D相機不是萬能的,它有一個探測距離的限制啦!太遠或太近的東西都是看不見的呢。關於這些“洞”,我們暫時睜一只眼閉一只眼,不去理它。以后我們也可以靠雙邊bayes濾波器去填這些洞。但是!這是RGB-D相機本身的局限性。軟件算法頂多給它修修補補,並不能完全彌補它的缺陷。
在以上給出的圖當中,都進行了預處理,可以認為“深度圖就是彩色圖里每個像素距傳感器的距離“。
4、現在需要把這兩個圖轉成點雲,因為計算每個像素的空間點位置,是后面配准、拼圖等一系列事情的基礎,比如:在配准時,必須知道特征點的3D位置,這時候就需要用到這里講的東西。
5、從2D到3D(數學部分)
上面兩張圖像給出了機器人外部世界的一個局部信息,假設這個世界由一個點雲來描述:X={x1,x2,...xn}。其中每一個點由六個分量組成:r,g,b,x,y,z,分別表示該點的顏色與空間位置;顏色方面,主要由彩色圖像記錄;而空間位置,可由圖像和相機模型、姿態一起計算出來。
對於常規相機,SLAM里使用針孔相機模型(圖來自http://www.comp.nus.edu.sg/~cs4243/lecture/camera.pdf ):
一個空間點[x,y,z]和它在圖像中的像素坐標[u,v,d](d指深度數據)的對應關系式這樣的:
其中,fx,fy指相機在x,y兩個軸上的焦距,cx,cy指相機的光圈中心,s指深度圖的縮放因子。
上面的公式是從(x,y,z)推導到(u,v,d),反之,也可以已知(u,v,d),推導到(x,y,z)的方式,推導如下
那么就可以根據上式構建點雲了。
通常把fx,fy,cx,cy這四個參數定義為相機的內參矩陣C,也就是相機做好之后就不會變得參數。相機的內參可以用很多方法來標定,詳細的步驟比較繁瑣;在給定內參之后,每個點的空間位置與像素坐標就可以用簡單的矩陣模型來表示了:
其中,R和t是相機的姿態。R代表旋轉矩陣,t代表位置矢量。目前我們做的是單幅圖像的點雲,故認為相機沒有旋轉和平移;所以把R射程單位矩陣I,把t設成了零。s是scaling factor,即深度圖里給的數據與實際距離的比例。由於深度圖給的都是short (mm單位),s通常為1000。
如果相機發生了位移和旋轉,那么只要對這些點進行位移和旋轉操作即可。
6、從2D到3D (編程部分)
完成從圖像到點雲的轉換,在上一節講到的代碼根目錄/src/ 文件夾中新建一個generatePointCloud.cpp文件:
touch src/generatePointCloud.cpp
在一個工程里可以有多個main函數,因為cmake允許你自己定義編譯的過程,我們會把這個cpp也編譯成一個可執行的二進制,只要在cmakelists.txt里作相應的更改便行了,更改如下
然后在新建的文件generatePointCloud.cpp里面編寫如下代碼
//c++標准庫
#include<iostream>
#include<string>
using namespace std;
//opencv庫
#include<opencv2/core/core.cpp>
#include<opencv2/highgui/highgui.hpp>
//PCL庫
#include<pcl/io/pcd_io.h>
#include<pcl/point_types.h>
//定義點雲類型
typedef pcl::PointXYZRGBA PointT;
typedef pcl::PointCloud<PointT> PointCloud;
//相機內參
const double camera_factor=1000;
const double camera_cx=325.5;
const double camera_cy=253.5;
const double camera_fx=518.0;
const double camera_fy=519.0;
//主函數
int main(int argc,char** argv)
{
//讀取./data/rgb.png和./data/depth.png,並轉化為點雲
//圖像矩陣
cv::Mat rgb,depth;
//使用cv::imread()來讀取圖像
//rgb圖像是8UC3的彩色圖像
rgb=cv::imread(./data/rgb.png);
//depth是16UC1的單通道圖像,注意flags設置為-1,表示讀取原始數據不做修改
depth=cv::imread("./data/depth.png",-1);
//點雲變量
//使用智能指針,創建一個空點雲。這種指針用完會自動釋放
PointCloud::Ptr cloud(new PointCloud);
//遍歷深度圖
for(int m=0;m<depth.rows;m++)
for(int n=0;n<depth.cols;n++)
{
//獲取深度圖中(m,n)處的值
ushort d=depth.ptr<ushort>(m)[n];
//d可能沒有值,若如此,跳過此點
if(d==0)
continue;
//d存在值,則向點雲增加一個點
PointT p;
//計算這個點的空間坐標
p.z=double(d)/camera_factor;
p.x=(n-camera_cx)*p.z/camera_fx;
p.y=(m-camera_cy)*p.z/camera_fy;
//從rgb圖像中獲取它的顏色
//rgb是三通道的BGR格式圖,所以按下面的順序獲取顏色
p.b=rgb.ptr<uchar>(m)[n*3];
p.g=rgb.ptr<uchar>(m)[n*3+1];
p.r=rgb.ptr<uchar>(m)[n*3+2];
//把p加入到點雲中
cloud->points.push_back(p);
}
//設置並保存點雲
cloud->height=1;
cloud->width=cloud->points.size();
cout<<"point cloud size="<<cloud->points.size()<<endl;
cloud->is_dense=false;
pcl::io::savePCDFile("./pointcloud.pcd",*cloud);
//清楚數據並保存
cloud->points.clear();
cout<<"Point cloud saved."<<endl;
return 0;
}
分析:
1.我們使用OpenCV的imread函數讀取圖片。在OpenCV2里,圖像是以矩陣(cv::MAt)作為基本的數據結構。Mat結構既可以幫你管理內存、像素信息,還支持一些常見的矩陣運算,是非常方便的結構。彩色圖像含有R,G,B三個通道,每個通道占8個bit(也就是unsigned char),故稱為8UC3(8位unsigend char, 3通道)結構。而深度圖則是單通道的圖像,每個像素由16個bit組成(也就是C++里的unsigned short),像素的值代表該點離傳感器的距離。通常1000的值代表1米,所以我們把camera_factor設置成1000. 這樣,深度圖里每個像素點的讀數除以1000,就是它離你的真實距離了。
2.在遍歷深度圖的時候,是按照“先列后行”的順序,由於m指圖像的行,n指圖像的列,它和空間點的坐標關系是如下:
對於深度圖第m行,第n列的數據可以用depth.ptr<ushort>(m)[n]來獲取。其中,cv::Mat的ptr函數會返回指向該圖像第m行數據的頭指針。然后加上位移n后,這個指針指向的數據就是我們需要讀取的數據。
計算三維點坐標的公式我們已經給出過了,代碼里原封不動地實現了一遍。我們根據這個公式,新增了一個空間點,並放入了點雲中。最后,把整個點雲存儲為 ./data/pointcloud.pcd 文件。
7、編譯並運行
cd build
cmake ..
make
編譯通過后,就可以到bin目錄下運行generate_pointcloud;運行之后,就可以在data目錄下生成點雲文件。安裝了pcl的話,就可以通過如下命令來查看點雲文件
pcl_viewer pointcloud.pcd
TIPS:
- 如果你打開點雲,只看到紅綠藍三個方塊,請按R重置視角。剛才你是站在原點盯着坐標軸看呢。
- 如果點雲沒有顏色,請按5顯示顏色。
- cmake過程可能有PCL的警告,如果你編譯成功了,無視它即可。這是程序員的本能。