算法设计思路
(1)读取16位深度图像到待处理图像帧组;
(2)ROI区域计算
由于kinect 彩色摄像头和红外深度摄像头是存在视角偏差的,经过视角对齐后,得到的深度图像是有黑边的。此处通过取帧组第一帧图像计算感兴趣区域ROI(注:kinect的摄像头视角是固定的,ROI区域也是固定的,所以只需要计算一次就够了,后续处理只需要使用计算好的就可以了)。ROI计算好,我们便可以在ROI区域做相应的图像处理操作了。
(3)多帧中值滤波
如果当前帧(i, j)处灰度不为0,不进行处理;如果为0,对图像帧组的各帧图像(i, j)位置的不为0的像素取出放入的data数组中,插入排序,取中值,代替(i, j)位置像素值。经过中值滤波后,很多黑洞已经被填充好了。
(4)空间1_近邻滤波
对待处理的图像帧,此时依然存在黑洞(由于数据集采集时,每一帧相差不是很远,有些地方在帧组的所有帧中都是黑洞。)。此时,采用的是空间近邻滤波方法,在(i, j)位置周围寻找像素值不为0的点来填充黑洞。【可以采取更好的方法】
主要代码实现
1. ROI区域计算
void kinectDenoising::setImageROI(bool isUpdate)
{
if (!isUpdate) // 若为false,定义ROI区域范围。
{
imageROI = cvRect(22, 44, 591, 434);
}
else // 若为true,表示计算出ROI区域范围
{
IplImage* image8u = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); // 灰度图像格式
IplImage* bitImage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);// 二值图像格式
// cvShowImage("0", frameSet[0]);
// 查看像素--正确
for(int i=0;i<height;i++)
{
for(int j=0;j<width;j++)
{
double ImgPixelVal = cvGetReal2D( frameSet[0], i, j );
//输出像素值
cout <<ImgPixelVal<<" ";
}
cout << "\n"<<endl;
}
cout << "\n"<<endl;
cout << "\n"<<endl;
// 像素约束到[0, 255]空间中
cvConvertScale(frameSet[0], image8u, 255.0 / 4096.0, 0); // 输入,输出,比例因子,平移因子
// cvThreshold只对单通道数组进行固定阈值操作【8UC1(8位无符号整型单通道矩阵)、32FC1(32位浮点型单通道矩阵)】
// 典型应用:对灰度图像进行阈值操作得到二值图像
cvThreshold(image8u, bitImage, 0, 1, CV_THRESH_BINARY); // 二值化--> bitImage
// 分配矩阵空间---必须为CV_32FC1,因为16U和8U不能满足cvReduce()结果的空间需求
CvMat* rowReduced = cvCreateMat(1, bitImage->width, CV_32FC1); // 行数、列数、预定义类型
CvMat* colReduced = cvCreateMat(bitImage->height, 1, CV_32FC1);
// 将二维数组转化为向量
// 参数:输入矩阵、输出矩阵、维数(0表示矩阵处理成1行,1表示处理成1列)、输出矩阵的所有行/列的和。
cvReduce(bitImage, rowReduced, 0, CV_REDUCE_SUM);
cvReduce(bitImage, colReduced, 1, CV_REDUCE_SUM);
// compute imageROI.x
for (int i = 0; i<rowReduced->cols; i++) // 1行,cols列
{
// CV_MAT_ELEM参数:(输入矩阵、提取元素类型、行、列)---> 从Mat矩阵中获取元素
float temp = CV_MAT_ELEM(*rowReduced, float, 0, i);
// 判断条件:当bitImage整列元素和大于其高度的1/3时,视为所需的点。
if (temp > bitImage->height / 3)
{
imageROI.x = i;
break;
}
}
// compute imageROI.width
for (int i = rowReduced->cols; i > 0; i--)
{
float temp = CV_MAT_ELEM(*rowReduced, float, 0, i - 1);
if (temp > bitImage->height / 3)
{
imageROI.width = i - imageROI.x;
break;
}
}
// compute imageROI.y
for (int i = 0; i<colReduced->rows; i++)
{
float temp = CV_MAT_ELEM(*colReduced, float, i, 0);
if (temp>bitImage->height / 3)
{
imageROI.y = i;
break;
}
}
// compute imageROI.height
for (int i = colReduced->rows; i > 0; i--)
{
float temp = CV_MAT_ELEM(*colReduced, float, i - 1, 0);
if (temp > bitImage->height / 3)
{
imageROI.height = i - imageROI.y;
break;
}
}
// 释放内存
cvReleaseImage(&bitImage);
cvReleaseImage(&image8u);
cvReleaseMat(&rowReduced);
cvReleaseMat(&colReduced);
}
}
2. 多帧中值滤波
1 void kinectDenoising::medianFiltering()
2 {
3 // set result image zero
4 cvSetZero(denoisedImage);
5
6 unsigned short data[nFrames];
7 int total;
8 // x : 4 width : 591
9 // y : 36 height: 442
10 // 行
11 for (int i = imageROI.y; i < imageROI.y + imageROI.height; i++)
12 {
13 // denoiseImageData[j]表示的是image图像中第i行第j列的像素值
14 unsigned short* denoisedImageData = (unsigned short*)(denoisedImage->imageData + i * denoisedImage->widthStep);
15 // 列
16 for (int j = imageROI.x; j < imageROI.x + imageROI.width; j++)
17 {
18 if(CV_IMAGE_ELEM(frameSet[4], unsigned short, i, j) != 0){
19 denoisedImageData[j] =CV_IMAGE_ELEM(frameSet[4], unsigned short, i, j);
20 }
21 else
22 {
23
24 total = 0; // 表示长度
25 for (int k = 0; k < nFrames; k++)
26 {
27 // cout << CV_IMAGE_ELEM(frameSet[k], unsigned short, i, j)<< " " ;
28 // 多帧图像
29 // CV_IMAGE_ELEM(数据指针, 数据类型,像素行坐标、像素列坐标) --> 访问图像帧组(i,j)位置的数据
30 insertSort(data, total, CV_IMAGE_ELEM(frameSet[k], unsigned short, i, j));
31 }
32 if (total != 0)
33 {
34 // 将(i,j)位置的像素值设置为时间域取中值
35 denoisedImageData[j] = data[total / 2];
36 // cout << denoisedImageData[j] << " " ;
37 }
38 }
39 }
40 cout << "\n" << endl;
41 }
42 }
43
44 // 插入排序
45 void insertSort(unsigned short* data, int& len, unsigned short newData)
46 {
47 if (newData != 0)
48 {
49 if (len == 0)
50 {
51 data[len++] = newData;
52 }
53 else
54 {
55 int i = len;
56 while ((i > 0) && (data[i - 1] > newData) )
57 {
58 data[i] = data[i - 1];
59 i--;
60 }
61 data[i] = newData;
62 len++;
63 }
64 }
65 }
3. 空间1_近邻滤波
1 void kinectDenoising::nearestFiltering()
2 {
3 CvPoint topLeft, downRight;
4 IplImage* tempImage = cvCloneImage(denoisedImage); // 复制整个IplImage结构,连同ROI等参数
5 // 行 [y, y + height]
6 for (int i = imageROI.y; i < imageROI.y + imageROI.height; i++)
7 {
8 // denoiseImageData[j]表示的是image图像中第i行第j列的像素值
9 unsigned short* data = (unsigned short*)(denoisedImage->imageData + denoisedImage->widthStep*i);
10 // 列 [x, x + width]
11 for (int j = imageROI.x; j < imageROI.x + imageROI.width; j++)
12 {
13 // 如果(i, j)位置像素值为0,视为无效点,向周围查找有效点!!
14 for (int k = 1; data[j] == 0; k++) // k从1逐渐增大,直到data[j] != 0 是一个有效点
15 {
16 // 左上点和右下点
17 // (j-k,i-k) ( j ,i-k) (j+k,i-k)
18 // (j-k, i ) ( j , i ) (j+k, i )
19 // (j-k,i+k) ( j ,i+k) (j+k,i+k)
20 topLeft = cvPoint(j - k, i - k); // j为列数 i为行数【注意分别】
21 downRight = cvPoint(j + k, i + k);
22
23 /************************************************************/
24 for (int m = topLeft.x; (m <= downRight.x) && (data[j] == 0); m++)
25 {
26 if (m<0) continue;
27 if (m >= width) break;
28 if (topLeft.y >= 0)
29 {
30 // 获取中心点(j,i)左上角(topLeft.y,m)位置数据
31 unsigned short temp = CV_IMAGE_ELEM(tempImage, unsigned short, topLeft.y, m);
32 if (temp > 0) // 从该像素左上角查找,找到有效的点,代替中心点的像素
33 {
34 data[j] = temp;
35 break;
36 }
37 }
38 if (downRight.y < height)
39 {
40 // 获取中心点(j,i)右下角(downRight.y,m)位置数据
41 unsigned short temp = CV_IMAGE_ELEM(tempImage, unsigned short, downRight.y, m);
42 if (temp > 0)
43 {
44 data[j] = temp;
45 break;
46 }
47 }
48 }
49 // (j-k,i-k) ( j ,i-k) (j+k,i-k)
50 // (j-k, i ) ( j , i ) (j+k, i )
51 // (j-k,i+k) ( j ,i+k) (j+k,i+k)
52 for (int m = topLeft.y; (m<downRight.y) && (data[j] == 0); m++)
53 {
54 if (m<0) continue;
55 if (m >= height) break;
56 if (topLeft.x>0)
57 {
58 unsigned short temp = CV_IMAGE_ELEM(tempImage, unsigned short, m, topLeft.x);
59 if (temp > 0)
60 {
61 data[j] = temp;
62 break;
63 }
64 }
65
66 if (downRight.x<width)
67 {
68 unsigned short temp = CV_IMAGE_ELEM(tempImage, unsigned short, m, downRight.x);
69 if (temp > 0)
70 {
71 data[j] = temp;
72 break;
73 }
74 }
75 }
76 /************************************************************/
77 }
78 }
79 }
80 cvReleaseImage(&tempImage);
81 }
算法实现效果
1. 原图
彩色图
深度图
2. 中值处理
3. 近邻处理
完整代码,待更新。
耗时统计
medianFiltering timeUsed = 13.263
nearestFiltering timeUsed = 12.807
filter timeUsed = 26.07
点云效果
载入点云:pcl_viewer ./data/pointcloud.pcd
原点云图
> Loading ./data/pointcloud.pcd [done, 963 ms : 236836 points]
去噪点云图
Loading ./data/pointcloud.pcd [done, 1065 ms : 258867 points]