尝试用OpenCV来实现立体视觉也有一段时间了,主要的参考资料就是Learning OpenCV十一、十二章和OpenCV论坛上一些前辈的讨论。过程中磕磕碰碰,走了不少弯路,终于在前不久解决了最头大的问题,把整个标定、校准、匹配的流程调试成功。(虽然还有一些问题至今尚未搞清)
在这里写这篇文章,第一方面是给自己一个总结,第二方面是感觉OpenCV立体视觉方面的资料还是相当零散和不完整,新手入门需要花很长时间才能摸索出来,第三方面,也是自己在过程中有些问题仍旧迷迷糊糊,希望可以抛砖引玉。
1. 摄像头
我用的摄像头是淘宝上买的三维摄像头,两个USB Camera加一个可调节的支架。实物照片如下
1.1 三维摄像头实物图
双USB摄像头的OpenCV驱动可以参考以下链接
http://www.opencv.org.cn/index.php/使用DirectShow采集图像
将上面代码复制到自己的工程之后还需要对工程或者编译环境做一下设置
VC6下的详尽设置可以见代码的注释(修改工程的属性)
VS2008中的设置也可以参照代码注释中VC++2005的设置(修改编译环境)
2. 标定
由于OpenCV中cvStereoCalibrate总是会得到很夸张的结果(见下文5.1问题描述),所以最后还是决定用Bouguet的Matlab标定工具箱立体标定,再将标定的结果读入OpenCV,来进行后续图像校准和匹配。
Matlab标定工具箱的参考链接如下:
http://www.vision.caltech.edu/bouguetj/calib_doc/
上面有详细的使用步骤,用起来相当的方便。
以下是我个人用Matlab工具箱进行立体标定的步骤,供参考,如果需要更详细步骤的话还是参照上面的链接
把Matlab工具箱的文件copy到对应目录下,把所要标定的棋盘图也放到.m文件所在的目录下,然后在Matlab命令行窗口中打入calib_gui,选择Standard之后便出现以下窗口
2.1. calilb_gui面板
我们先对右摄像头的标定,所以先把从右摄像头上采集到的棋盘图复制到工具箱目录下。
点击Image names, 命令行窗口会提示你输入图片的basename以及图片的格式(比如你图片文件名是right1, right2, …, right10,basename就是right),然后Matlab会自动帮你读入这些图片,如下图所示,可以看到,读入了10幅右摄像头的棋盘图。
采集棋盘图的时候要注意,尽量让棋盘占据尽可能多的画面,这样可以得到更多有关摄像头畸变方面的信息
2.2. 图像basename读入
2.3. 读入的棋盘图
然后再回到主控制界面,点击Extract grid corners,提取每幅图的角点
2.4. calib_gui面板
点击完后,命令行会出现如下提示,主要是让你输入棋盘角点搜索窗口的大小。窗口定的大一点的话提取角点会比较方便点(即便点得偏离了也能找到),但也要注意不能大过一个方格的大小。剩下的两个选项,只要回车选用默认设置就可以了
2.5. 选择窗口大小
然后就开始了角点的提取工作,按一定顺序分别提取棋盘的最边上的角点,程序会自动帮你找到所有对应的角点
2.6. 提取角点
2.7. 提取角点2
在提取第一幅图的时候命令行窗口可能会提示你输入方格大小,这里输入你方格的实际大小就行,比如我方格是27mm,就输入27。这步事实上相当关键,它定义了空间的尺度,如果要对物体进行测量的话,这步是必须的。
按相同的方法提取完10幅图后,点击Calibration,开始摄像头标定
2.8. calib_gui面板
经过多次迭代后,程序会最终得到摄像头的内外参数,如下图所示(图中符号由于字体关系没有完全显示,中间的问号是表示误差的加减号)
2.9. Calibration迭代过程及结果
可以通过面板上的Show Extrinsic查看一下标定结果,可以验证一下标定外参数的结果
2.10. 外部参数图示
验证标定结果无误之后,就点击面板上的Save按钮,程序会把标定结果放在一个叫Calib_Result.mat中,为了方便后续立体标定,把这个文件名改为Calib_Result_right.mat。
左摄像头标定的方法与右摄像头相同,生成的Calib_Result.mat之后,将其改名为Calib_Result_left.mat就可以了
左右摄像头都标定完成之后,就可以开始立体标定了。
在Matlab命令行中键入stereo_gui启动立体标定面板,如下图所示
2.11. stereo_gui面板
点击Load left and right calibration files并在命令行中选择默认的文件名(Calib_Result_left.mat和Calib_Result_right.mat)之后就可以开始Run stereo calibration了,run之后的结果如下图所示,左右摄像头的参数都做了修正,并且也求出了两个摄像头之间的旋转和平移关系向量(om和T)
2.12. 立体标定结果
在面板上点击Show Extrinsics of stereo rig,可以看到如下图所示的双摄像头关系图,可以看到,两个摄像头基本是前向平行的
2.13. 双摄像头与定标棋盘间的位置关系
得到了立体标定参数之后,就可以把参数放入xml文件,然后用cvLoad读入OpenCV了。具体的方法可以参照Learning OpenCV第11章的例子,上面就是用cvSave保存标定结果,然后再用cvLoad把之前的标定结果读入矩阵的
2.14. xml文件示例
这里需要注意的是Matlab标定结果中的om向量,这个向量是旋转矩阵通过Rodrigues变换之后得出的结果,如果要在cvStereoRectify中使用的话,需要首先将这个向量用cvRodrigues转换成旋转矩阵。关于Rodrigues变换,Learning OpenCV的第11章也有说明。
2.15. 旋转矩阵的Rodrigues形式表示
3. 立体校准和匹配
有了标定参数,校准的过程就很简单了。
我使用的是OpenCV中的cvStereoRectify,得出校准参数之后用cvRemap来校准输入的左右图像。这部分的代码参考的是Learning OpenCV 十二章的例子。
校准之后,就可以立体匹配了。立体匹配OpenCV里面有两种方法,一种是Block Matching,一种是Graph Cut。Block Matching用的是SAD方法,速度比较快,但效果一般。Graph Cut可以参考Kolmogrov03的那篇博士论文,效果不错,但是运行速度实在是慢到不能忍。所以还是选择BM。
以下是我用BM进行立体匹配的参数设置