转载声明:该文不是原作,发表这篇文章只为记录。
————————————————
版权声明:本文为CSDN博主「RanchoLin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/RanchoLin/article/details/114702996
问题1:点云分层、断层
如果出现以上类似效果,PCL点云像是很多2d平面图像层叠出来的问题,首先给出结论:转换点云时使用的视差图格式不对。
以OpenCV-sgbm为例,先调sgbm中的参数、至视差图既平滑又少空洞。
在阅读网上许多代码后发现,用于计算距离的视差图(CV_32F)和用于肉眼看的视差图(CV_8U)使用的格式不同,并且用于计算的视差图无需进行裁剪和归一化,这些只是为了显示的可读性和美观。所以,在对sgbm进行compute之后得到视差图disparity_sgbm,除以16得到用于计算的视差图disparity(除以16是因为每个像素值由一个16bit表示,其中低位的4位存储的是视差值得小数部分,所以真实视差值应该是该值除以16)。
cv::Mat disparity_sgbm, disparity, disparityCV_8U;
sgbm->compute(rectifyImageL, rectifyImageR, disparity_sgbm);
disparity_sgbm.convertTo(disparity, CV_32F, 1.0 / 16.0f); //用来测距
将compute后的视差图,转为8位,得到可视的视差图disparityCV_8U,可以作为sgbm调参的直观依据,没有后续的作用了。
disparity_sgbm.convertTo(disparityCV_8U, CV_8U, 255 / (NumDisparities * 16.)); //用来调参直观参考
imshow("disparityCV_8U",disparityCV_8U);
转换了正确的视差图格式之后,在计算点云时还要注意计算深度值的取点语句,数据类型也要对应好。
……
for (int i = 0; i < disparity.rows; i++){
for (int j = 0; j < disparity.cols; j++) {
PointXYZRGB p;
double d = disparity.ptr<float>(i)[j]; //注意数据类型
……
ok,关于点云分层、深度不连续的问题就解决啦,附上以上代码涉及到的两个知识点:图像格式(矩阵类型)和convertTo函数,配合阅读有助于深入认识。
图像格式(矩阵类型)
type()=0 CV_8U 八位无符号整型 (uchar) 0–255
1 CV_8S 八位有符号整型 (schar) -128–127
2 CV_16U 十六位无符号整型 (ushort) 0–65535
3 CV_16S 十六位有符号整型 (short) -32768–32767
4 CV_32S 三十二位有符号整型 (int) 0–65535
5 CV_32F 三十二位浮点数 (float) 0.0–1.0
6 CV_64F 六十四位浮点数 (double) 0.0–1.0
convertTo函数
xxx.convertTo(dst, type, scale, shift)
dst:目的矩阵;
type:需要的输出矩阵类型,或者更明确的,是输出矩阵的深度,如果是负值(常用-1)则输出矩阵和输入矩阵类型相同。转换位深度本质上就是对原深度下的数据做线性变换,使原位深度下的最小值和最大值分别对应转换后位深度下的最小值和最大值。
scale:范围比例因子;eg. 原来的图是255的灰度图,转成float型数据并归一化到0~1,那么就是scale=1.0/255
shift:将输入数组元素按比例缩放后添加的值;
目前OpenCV主要只支持单通道和3通道的图像,并要求其深度为8bit和16bit无符号(即CV_16U),所以其他是不支持的,比如说float型等。
如果Mat类型数据的深度不满足上面的要求,则需要使用convertTo()函数来进行转换。convertTo()函数负责转换数据类型不同的Mat,即可以将类似float型的Mat转换到imwrite()函数能够接受的类型。
问题2:点云放射、发散、锥形,总之不能看
如果出现以上类似效果,PCL点云是发散、放射、圆锥形的问题,先上结论:点云可能是对的,但发散出来大多都是无效点,除掉即可。
这个问题太过于细节,以致于排查时无从下手,反而去检查了许多点云坐标的计算和PCL的设置语句,也花了不少时间。其实仔细观察这个问题和上一类还不一样,这里的点云锥顶部份比较稠密,而且分层也不是那么规律。再考虑到相机硬件本身的局限性,基线的长度决定了测距的有效范围,超过范围的点误差就会很大,这些点也是没有意义的。
所以,解决的方法也很简单,在计算点云的双for循环中添加以下语句,把视差值小于80或者深度大于60cm的点剔除掉即可。(这里的数字不一定适配你的硬件,自行修改)
if (d<=80)continue; //d是double d = disparity.ptr<float>(i)[j]
在解决PCL生成点云分层过程中,也得知了其他解决方法。其一是标定参数出现问题,但我的标定参数是从MATLAB中取出的,所以应该没问题。其二是改用cv::stereoRectify(cameraMatrixL, distCoeffL, cameraMatrixR, distCoeffR,rectifyImageL.size(), R, t, Rl, Rr, Pl, Pr, Q )生成的Q重投影矩阵计算点云中的点,避免求点繁杂的计算,感觉也不是很关键。附上Q重投影矩阵的含义:
Cx:左相机光心在像素坐标系的x
Cy:左相机光心在像素坐标系的y
Cx’:右相机光心在像素坐标系的x
Tx:立体校正后光心距离
d:视差
f:焦距
总结:
视差图计算格式:(CV_32F),真实视差值应该是该值除以16
可视化格式:(CV_8U)
标定参数出现问题,从而导致重投影矩阵Q出现了问题。
附:
立体校正函数 cv::stereoRectify(cameraMatrixL, distCoeffL, cameraMatrixR, distCoeffR,rectifyImageL.size(), R, t, Rl, Rr, Pl, Pr, Q )
stereoRectify() 的作用是为每个摄像头计算立体校正的映射矩阵。所以其运行结果并不是直接将图片进行立体矫正,而是得出进行立体矫正所需要的映射矩阵。
cameraMatrix1-第一个摄像机的摄像机矩阵
distCoeffs1-第一个摄像机的畸变向量
cameraMatrix2-第二个摄像机的摄像机矩阵
distCoeffs1-第二个摄像机的畸变向量
imageSize-图像大小
R- stereoCalibrate() 求得的R矩阵
T- stereoCalibrate() 求得的T矩阵
R1-输出矩阵,第一个摄像机的校正变换矩阵(旋转变换)
R2-输出矩阵,第二个摄像机的校正变换矩阵(旋转矩阵)
P1-输出矩阵,第一个摄像机在新坐标系下的投影矩阵
P2-输出矩阵,第二个摄像机在想坐标系下的投影矩阵
Q-4*4的深度差异映射矩阵
版权声明:本文为CSDN博主「一只会走路的鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36537774/article/details/85005552