精华内容
参与话题
问答
  • 立体视觉

    千次阅读 2016-04-05 10:11:34
    尝试用OpenCV来实现立体视觉也有一段时间了,主要的参考资料就是Learning OpenCV十一、十二章和OpenCV论坛上一些前辈的讨论。过程中磕磕碰碰,走了不少弯路,终于在前不久解决了最头大的问题,把整个标定、校准、...

    尝试用OpenCV来实现立体视觉也有一段时间了,主要的参考资料就是Learning OpenCV十一、十二章和OpenCV论坛上一些前辈的讨论。过程中磕磕碰碰,走了不少弯路,终于在前不久解决了最头大的问题,把整个标定、校准、匹配的流程调试成功。(虽然还有一些问题至今尚未搞清)

    在这里写这篇文章,第一方面是给自己一个总结,第二方面是感觉OpenCV立体视觉方面的资料还是相当零散和不完整,新手入门需要花很长时间才能摸索出来,第三方面,也是自己在过程中有些问题仍旧迷迷糊糊,希望可以抛砖引玉。

     

    1. 摄像头

    我用的摄像头是淘宝上买的三维摄像头,两个USB Camera加一个可调节的支架。实物照片如下

    1.1 三维摄像头

    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. calib_gui

    2.1. calilb_gui面板

     

    我们先对右摄像头的标定,所以先把从右摄像头上采集到的棋盘图复制到工具箱目录下。

    点击Image names, 命令行窗口会提示你输入图片的basename以及图片的格式(比如你图片文件名是right1, right2, …, right10,basename就是right),然后Matlab会自动帮你读入这些图片,如下图所示,可以看到,读入了10幅右摄像头的棋盘图。

     

    采集棋盘图的时候要注意,尽量让棋盘占据尽可能多的画面,这样可以得到更多有关摄像头畸变方面的信息

     

    2.2.basename

    2.2. 图像basename读入

     

    2.3 棋盘图

    2.3. 读入的棋盘图

     

     

     

     

    然后再回到主控制界面,点击Extract grid corners,提取每幅图的角点


    2.1. calib_gui

    2.4. calib_gui面板


     

     

     

    点击完后,命令行会出现如下提示,主要是让你输入棋盘角点搜索窗口的大小。窗口定的大一点的话提取角点会比较方便点(即便点得偏离了也能找到),但也要注意不能大过一个方格的大小。剩下的两个选项,只要回车选用默认设置就可以了

     

    2.5.选择窗口大小

    2.5. 选择窗口大小

     

     

    然后就开始了角点的提取工作,按一定顺序分别提取棋盘的最边上的角点,程序会自动帮你找到所有对应的角点

    2.6.提取角点

    2.6. 提取角点

     

    2.7. 提取角点2

    2.7. 提取角点2

     

     

     

    在提取第一幅图的时候命令行窗口可能会提示你输入方格大小,这里输入你方格的实际大小就行,比如我方格是27mm,就输入27。这步事实上相当关键,它定义了空间的尺度,如果要对物体进行测量的话,这步是必须的。

     

    按相同的方法提取完10幅图后,点击Calibration,开始摄像头标定

     

     

    2.1. calib_gui

    2.8. calib_gui面板


     

     

    经过多次迭代后,程序会最终得到摄像头的内外参数,如下图所示(图中符号由于字体关系没有完全显示,中间的问号是表示误差的加减号)


    2.9. Calibration迭代过程及结果

     

    2.9. Calibration迭代过程及结果

     

     

    可以通过面板上的Show Extrinsic查看一下标定结果,可以验证一下标定外参数的结果

    2.10. 外部参数图示

    2.10. 外部参数图示

     

     

     

    验证标定结果无误之后,就点击面板上的Save按钮,程序会把标定结果放在一个叫Calib_Result.mat中,为了方便后续立体标定,把这个文件名改为Calib_Result_right.mat。

     

    左摄像头标定的方法与右摄像头相同,生成的Calib_Result.mat之后,将其改名为Calib_Result_left.mat就可以了

     

    左右摄像头都标定完成之后,就可以开始立体标定了。

     

     

    在Matlab命令行中键入stereo_gui启动立体标定面板,如下图所示


    2.11. 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. 立体标定结果

    2.12. 立体标定结果

     

     

    在面板上点击Show Extrinsics of stereo rig,可以看到如下图所示的双摄像头关系图,可以看到,两个摄像头基本是前向平行的


    2.13. 双摄像头与定标棋盘间的位置关系

    2.13. 双摄像头与定标棋盘间的位置关系

     

     

    得到了立体标定参数之后,就可以把参数放入xml文件,然后用cvLoad读入OpenCV了。具体的方法可以参照Learning OpenCV第11章的例子,上面就是用cvSave保存标定结果,然后再用cvLoad把之前的标定结果读入矩阵的

    2.14. xml文件示例

    2.14. xml文件示例

     

     

    这里需要注意的是Matlab标定结果中的om向量,这个向量是旋转矩阵通过Rodrigues变换之后得出的结果,如果要在cvStereoRectify中使用的话,需要首先将这个向量用cvRodrigues转换成旋转矩阵。关于Rodrigues变换,Learning OpenCV的第11章也有说明。

     

    2.15. 旋转矩阵的Rodrigues形式表示

    2.15. 旋转矩阵的Rodrigues形式表示

     

     

     

     

     

     

    3. 立体校准和匹配

    有了标定参数,校准的过程就很简单了。

    我使用的是OpenCV中的cvStereoRectify,得出校准参数之后用cvRemap来校准输入的左右图像。这部分的代码参考的是Learning OpenCV 十二章的例子。

     

    校准之后,就可以立体匹配了。立体匹配OpenCV里面有两种方法,一种是Block Matching,一种是Graph Cut。Block Matching用的是SAD方法,速度比较快,但效果一般。Graph Cut可以参考Kolmogrov03的那篇博士论文,效果不错,但是运行速度实在是慢到不能忍。所以还是选择BM。

     

    以下是我用BM进行立体匹配的参数设置


     

    1. BMState = cvCreateStereoBMState(CV_STEREO_BM_BASIC,0);  
    2. assert(BMState != 0);  
    3. BMState->preFilterSize=13;  
    4. BMState->preFilterCap=13;  
    5. BMState->SADWindowSize=19;  
    6. BMState->minDisparity=0;  
    7. BMState->numberOfDisparities=unitDisparity*16;  
    8. BMState->textureThreshold=10;  
    9. BMState->uniquenessRatio=20;  
    10. BMState->speckleWindowSize=13;  
     

     

    其中minDisparity这个参数我设置为0是由于我的两个摄像头是前向平行放置,相同的物体在左图中一定比在右图中偏右,如下图3.1所示。所以没有必要设置回搜的参数。

    如果为了追求更大的双目重合区域而将两个摄像头向内偏转的话,这个参数是需要考虑的。

     

    3.1. 校正后的左右视图

    3.1. 校正后的左右视图

     

     

    另外需要提的参数是uniquenessRatio,实验下来,我感觉这个参数对于最后的匹配结果是有很大的影响。uniquenessRatio主要可以防止误匹配,其主要作用从下面三幅图的disparity效果比对就可以看出。在立体匹配中,我们宁愿区域无法匹配,也不要误匹配。如果有误匹配的话,碰到障碍检测这种应用,就会很麻烦。

     

    3.2. UniquenessRatio为0时的匹配图,可以看到大片的误匹配区域

    3.2. UniquenessRatio为0时的匹配图,可以看到大片的误匹配区域

     

    3.3. UniquenessRatio为10时的disparity map, 可以看到误匹配被大量减少了

    3.3. UniquenessRatio为10时的disparity map, 可以看到误匹配被大量减少了, 但还是有噪点

     

    3.4. UniquenessRatio为20时的disparity map, 可以看到误匹配基本被去除了, 点云干净了很多

    3.4. UniquenessRatio为20时的disparity map, 可以看到误匹配基本被去除了, 点云干净了很多

     

     

     

    关于cvFindStereoCorrespondenceBM这个函数的源代码,曾经做过比较详细的研究,过一段时间也会把之前写的代码注释整理一下,发篇博文。

     

     

     

     

    4. 实际距离的测量

    在用cvFindStereoCorrespondenceBM得出disparity map之后,还需要通过cvReprojectImageTo3D这个函数将单通道Disparity Map转换成三通道的实际坐标矩阵。

    具体的数学原理可以参考下面这个公式(from chenyusiyuan http://blog.csdn.net/chenyusiyuan/archive/2009/12/25/5072597.aspx,实际深度的一些问题这篇博文中也有提到)

    距离转换公式

    4.1 距离转换公式

     

     

    但是在实际操作过程中,用cvReprojectImageTo3D得到的数据并未如实际所想,生成深度矩阵所定义的世界坐标系我就一直没弄清楚。这在下面的例子中会详细说明,希望这方面的专家能帮忙解答一下:


     

    图4.2是测量时的实际场景图,场景中主要测量的三个物体就是最前面的利乐包装盒、中间的纸杯、和最远的塑料瓶。

     

     

    4.2. 实际场景中三个待测物体的位置

    4.2. 实际场景中三个待测物体的位置

     

     

    图4.3是校准后的左右图和匹配出来的disparity map,disparity窗口中是实际的点云,object窗口是给disparity map加了个阈值之后得到的二值图,主要是为了分割前景和背景。可以看到要测的三个物体基本被正确地分割出来了

     

    4.3. 双目摄像头得到的disparity map

    4.3. 双目摄像头得到的disparity map

     

    图4.4是在disparity窗口中选取一个点后然后在实际坐标矩阵中得到的对应三维信息,在这里,我在三个物体的点云上各选一个点来代表一个物体实际的坐标信息。(这里通过鼠标获取一点坐标信息的方法参考的是opencv sample里的watershed.cpp)


     

    4.4. 对应点的三维坐标

    4.4. 对应点的三维坐标

     

     

    在这里可以看到,(265, 156)也就是利乐包装盒的坐标是(13, 12, -157),(137, 142)纸杯的坐标是(77, 30, -312),(95, 115)塑料瓶的坐标是(144, 63, -482)。

    补充一下:为了方便显示,所以视差图出来之后进行了一个0-255的normalize,所以value值的前一个是normalize之后点的灰度值,后一个是normalize之前点的实际视差图。

    由cvFindStereoCorrespondenceBM算法的源代码:

     

    dptr[y*dstep] = (short)(((ndisp - mind - 1 + mindisp)*256 + (d != 0 ? (p-n)*128/d : 0) + 15) >> 4);
    其中
    ndisp是ndisp = state->numberOfDisparities;
    mindisp是mindisp = state->minDisparity;
    mind就是sad得出的视差
    实际视差大约是(64-mind-1)*256=1163, 基本是对的, 后面一项修正值在通常情况下可以忽略

     

     

     

    目前我还是不是很清楚立体坐标系原点和尺度,但是从这三个点的z坐标可以大致看出这三个物体的距离差大概是1:2:3,基本与实际场景中物体的位置一致。因此,可以通过这种方法确定出物体的大致距离信息。

     

    但是,如果就从摄像头参数本身来测量距离的话,就不是很明白了,还求这方面的大牛解答。

     

    5.  一些问题

    5.1 关于StereoCalibrate

    OpenCV自带的cvStereoCalibrate感觉不怎么好用,用这个函数求出的内参外参和旋转平移矩阵进行校准,往往无法达到行对准,有时甚至会出现比较可怕的畸变。在看了piao的http://www.opencv.org.cn/forum/viewtopic.php?f=1&t=4603帖子之后,也曾经尝试过现用cvCalibrateCamera2单独标定(左右各20幅图),得出的结果基本和Matlab单独标定的相同,然后再在cvStereoCalibrate中将参数设成CV_CALIB_USE_INTRINSIC_GUESS,用来细化内参数和畸变参数,结果得出的标定结果就又走样了。

    不知道有谁在这方面有过成功经验的,可以出来分享一下。毕竟用Matlab工具箱还是麻烦了些。

     

     


    5.2 Translation向量以及立体匹配得出的世界坐标系

    Learning OpenCV中对于Translation和Rotation的图示是这样的

    5.1. Learning OpenCV中的图示

    5.1. Learning OpenCV中的图示

     

    可是在实验过程中发现,如果将Translation向量按尺度缩放,对于StereoRectify之后的左右视图不会有变化,比如将T = [ -226.73817   -0.62302  8.93984 ] ,变成T = [ -22.673817   -0.062302  0.893984 ],在OpenCV中显示的结果不会有任何变化。而且我如果修改其中的一个参量的话,左右视图发生的变化也不是图5.1中所示的那种变化(比如把x缩小,那么视图发生的变化不是往x轴方向的平移)。

     

    因此又回到了老问题,这里这些坐标的尺度究竟是什么?通过ReprojectTo3D那个函数得到的三维坐标又是以哪个点为原点,那三个方向为x,y,z轴的? 

     

    补充: 对这个问题的解答来自于和maxwellsdemon的讨论

    他的解释如下:rotation是两者的旋转角度的关系,但是你要把它矫正平行,也是需要translation matrix的。你可以设想,两个看似已经平行了的摄像头,但是深度上放置的有差距,那么在矫正的时候会议translation matrix所对应的角度或者直线为基准,二者旋转一个小角度,使得完全平行。

     

    PPS:摄像头网址 http://www.hytekautomation.com.cn/BNE001.aspx?productId=20

     

     //******************************************************************************************************************************************************************************************

    101楼 HiGoodBoy 2016-03-17 17:51发表 [回复]
        博主,您好,我想请问一下只知道两幅影像的投影矩阵的情况下,如何进行极线校正。采用stereorectify函数么?其中的cameramatrix,r,T参数都通过投影矩阵分解得到,但是畸变参数怎么得到呢?还望指点

    100楼 wyx_123456 2016-01-18 20:35发表 [回复]
        博主,标定完以后怎么判断标定结果好坏与否?

    99楼 qq_33504386 2016-01-04 11:28发表 [回复]
        感谢楼主分享,学了不少,我们也是做三维扫描方案开发的,浙大系,在杭州。现公司努力壮大研发团队,欢迎有兴趣的三维重建算法、三维点云后处理人才加入:QQ 2088190951

    98楼 Jefferyhxm 2015-12-02 07:06发表 [回复]
        博主,看了你的文章,我还是有些疑惑。在使用matlab toolbox计算得到左右相机的内参数和外参数以后,如何利用opencv来通过两个图像中的点坐标计算三维空间坐标呢?

    97楼 _ALittleMore_ 2015-11-16 09:43发表 [回复]
        博主,最近一个项目中要用到立体匹配,要看懂cvFindStereoCorrespondenceBM这个的源码,看到“关于cvFindStereoCorrespondenceBM这个函数的源代码,曾经做过比较详细的研究,过一段时间也会把之前写的代码注释整理一下,发篇博文”心中一热,去找博主的博文发现没有,看评论才发现博主后来没写了,刚接触图像处理和opencv不久,看这函数的源码实在是觉得看不懂,博主,该怎么去看呢?求助求助!

    96楼 jiullll 2015-08-06 19:14发表 [回复]
        mind就是sad得出的视差是指哪个数值呢

    95楼 jiullll 2015-08-06 17:44发表 [回复]
        请问一下BM参数中的unitDisparity是什么呢

    94楼 见贤思齐焉 2015-07-02 09:51发表 [回复]
        感谢楼主分享,但关于Matlab标定的结果,小弟有点看不明白,关于fc或cc参数,Matlab标定结果均有两个值。
        即
        fc=[827.16255 826.98935]?[8.66335 8.5461]
        cc=[341.20701,218.32537]?[16.91587,12.22932]
        前者应该是焦距和像主点的像素坐标吧,那后者是什么呢?是物理坐标么?
        还望楼主解答~~谢谢了。

    93楼 baidu_27903689 2015-05-24 20:34发表 [回复]
        讲的很好 学了很多

    92楼 Cason_wang 2015-04-03 14:57发表 [回复]
        你好,请教一下,我利用MATLAB toolbox分别标定两个摄像头,得到摄像头的内外参数。那我现在要做的事是,直接利用两个摄像头采集,各采集一帧进行标定,然后做图像拼接处理,这种思路对吗?

    91楼 放放风 2015-01-18 21:12发表 [回复]
        lz,我想请教下为什么我用reprojectimageto3d得出来的坐标值很离谱呢?

        Re: fox7xjw 2015-01-29 14:53发表 [回复]
            回复u010119009:reprojectimageto3d出来的坐标要乘以16之后才有点靠谱。。

            Re: sunlightli1216 2015-08-28 21:36发表 [回复]
                回复fox7xjw: cvReprojectImageTo3D得到的是什么?怎么把Z坐标换算成距离呢?希望解答,不胜感激

    90楼 fox7xjw 2015-01-17 15:20发表 [回复]
        我想请问下博主,matlab的stereo_gui里面有一个Rectify the calibration images,好像是把所有棋盘格图像做了校正,之后又获得了一组标定参数;
        我想问的是博主用到opencv中的参数是哪一组?还有这个Rectify the calibration images可不可以校正匹配要用的那一组真实场景图像对呢?
        谢谢~

    89楼 Joey_Tang 2014-10-14 16:16发表 [回复]
        博主的三维摄像头在哪里买的啊?!能给出链接吗?!

    88楼 司令 2014-07-16 03:26发表 [回复]
        不错啊,看着博主的这个博客搞定了!非常感谢!
        那个Matlab toolbox不是一般的强大,我需要的各种变换还有PnP都有相应的函数,太牛逼了!

    87楼 shiter 2014-06-22 22:42发表 [回复]
        同样想买摄像头的飘过。。。

    86楼 liuhyluguan 2014-05-12 12:35发表 [回复]
        博主你好,拜读了你的文章使我受益匪浅,但有一点不明,“把参数放入xml文件”这一步如何实现,有具体的实现代码或说明吗,wzyryan@163.com,恳请指导

        Re: yu88888jie 2015-12-22 20:48发表 [回复]
            回复liuhyluguan:就是你找一个xml文件 按照他那个格式把数据填进去就可以了。 xml文件找不到的话可以下载opencv十二章的源码,在里面把那个摄像头矩阵什么的保存为xml就可以了 十一章的代码里有说怎么保存。

        Re: zlllou 2014-12-04 21:14发表 [回复]
            回复liuhyluguan:请问你们解决了吗

    85楼 montgomerry 2014-05-04 22:20发表 [回复]
        看完你的博文受益良多,有个地方不太清楚我想问下,matlab里的reprojection error怎么看?看不懂,可以跟你说下吗?

    84楼 nangnima 2014-04-16 15:03发表 [回复]
        博主,不知道你还能不能看到我的评论,您提供的网址我找不到这种摄像头啊,求博主给个链接,感激不尽。

    83楼 Raby_GYL 2013-12-11 21:18发表 [回复]
        时间真快呀,三年过去了~我现在才学习到这部分~明年C++版本Learning OpenCV书籍出来了,不晓得对这部分有什么补充和改进~

    82楼 qingqingzjin 2013-11-14 15:08发表 [回复]
        相当好的文章,核你受用。不过楼主,你提供的两个链接:http://www.hytekautomation.com.cn/BNE001.aspx?productId=20
        和http://item.taobao.com/item.htm?id=4271696156
        都已经失效了,现在我很想做这个方面的研究,所以麻烦你提供下卖这个相机的联系方式吧,非常的感谢!
        我的QQ:2768676042
        email:qingqingzjin@126.com
        麻烦LZ!

        Re: 诺坎普10号 2014-05-08 20:05发表 [回复]
            回复haorenka2010:请问你买到摄像头了嘛?

            Re: qingqingzjin 2014-08-11 20:25发表 [回复]
                回复liangtingfengyileng:没有,不过我们买了一款国外的整套设备和软件

    81楼 CarsonGP 2013-08-30 15:46发表 [回复]
        楼主,我已经拜读了你的文章好几遍了,我的目前已经做好了单目的标定,用的是opencv,我想问下matlab标定的效果咋样?

    80楼 thunder60 2013-08-07 02:22发表 [回复]
        博主您好,请问matlab的标定结果怎么引用给openCV, xml文件的结构应该是怎样的?或者,openCV所要用的cvMat结构的参数矩阵是怎样的?

    79楼 mihu_1234 2013-06-24 21:18发表 [回复]
        楼主你好 我用单目标定的时候 SHOW EXTRINSIC 里面的Z轴方向的距离 和我的实际距离有差距 相差100左右 能否给我解答一下 多谢!!

    78楼 gugumumu2010 2013-04-22 11:17发表 [回复]
        你好,不知道您还会不会回复我,实在是卡住了,我用cvStereoRectify函数校正两幅图片(我的图片是MFC单文档对话框里的两个图片控件打开的),调试总是该函数的CV_CALIB_ZERO_DISPARITY参数出错,请问使用这个函数的时候对图片有要求吗?这个函数好像没用到图片啊。我用的是vc6.0+opencv1.1。还有cvRemap这个函数的输入输出图片格式是哪个,我的图片控件打开后是IplImage类型的,要进行转换吗?期待您的回复哦。

        Re: gugumumu2010 2013-04-22 16:30发表 [回复]
            回复gugumumu2010:是不是vc6.0不能用opencv1.1呀。我要疯了。

    77楼 u010147016 2013-04-18 23:01发表 [回复]
        USB Camera 两个摄像头的配件淘宝上没看到呀,能给个链接么

    76楼 chy258143826 2013-03-19 12:14发表 [回复]
        引用“mailang2008”的评论:
        您好,我按您的方法在校正的时候遇到一个问题,提示cvStereoRectify函数Formats o...

        我也碰到了相似的问题,同求解答

    75楼 chy258143826 2013-03-19 11:50发表 [回复]
        前辈在吗?????现在求助ing。。。。。。

    74楼 just_rookie 2013-02-28 15:53发表 [回复]
        这个帖子对于菜鸟来说太具有指导性了!!

    73楼 lsyyoyo 2013-02-22 21:02发表 [回复]
        楼主,双目视觉的这个过程,包括在vc或者vs中对opencv的配置,这些个校正、BM等的匹配函数,在PC机上可以实现。如果在DSP中,请问楼主你知道这些函数、这个过程cvStereoRectify()校正,cvInitUndistortRectifyMap()和cvRemap()极线对准,cvFindStereoCorrespondenceBM()立体匹配还有吗。还是就不一样了?

    72楼 chenruying 2013-01-07 12:43发表 [回复]
        你好,博主,

        我现在在一个机器人竞赛小组中,担任程序设计,但我的高度始终有问题。。

        可以向博主请教一下代码么
        我的邮箱 784255948@qq.com 或 reneechen1993@gmail.com

        O(∩_∩)O谢谢

    71楼 s001133 2013-01-04 15:12发表 [回复]
        前輩..你能教一下OpenCV那邊的程序嗎?或者可以分享一下你的源代碼嗎?~~謝謝?

    70楼 feifeiniao123 2012-12-24 15:42发表 [回复]
        前辈,您好,很好的文章,前辈能不能共享一下用cvStereoRectify 这个函数实现校正的代码,我现在处于初学阶段,恳请前辈不吝赐教……我的邮箱yffeng@yeah.net。多谢您了

        Re: richmanrain 2012-12-26 11:56发表 [回复]
            回复feifeiniao123:同求,到这步就不是很清楚了,感谢了,邮箱richmanrain@163.com

    69楼 kaiseu 2012-12-14 18:50发表 [回复]
        在matlab下标定,用OPencv校准、匹配,remap得到的校正图明显不在一条水平线上(一上一下),matlab下标定的误差显示很小了,可能是什么原因啊?望指正!

    68楼 yl2135315 2012-10-31 08:54发表 [回复]
        楼主你好,我目前也在弄这个三维测距,现在我能够求出视差图。不过用cvReprojectImageTo3D(CvArr * disparityImage, CvArr * result3DImage, CvMat * Q)。这个函数求出深度图的时候,求出的结果很奇怪。result3DImage求出来的点。Z通道也就是深度很多点都是同一个结果。也就是景深一样。并且大得惊人。还是个负数。不知道这些个原因楼主遇到过没有。如果不介意希望可以发一份代码到我邮箱。yanglong227@126.com。不尽感激。

        Re: hx_ywzq 2015-11-19 14:49发表 [回复]
            回复yl2135315:我也有这个问题请问你解决了吗?

    67楼 lsyyoyo 2012-10-25 15:02发表 [回复]
        嗯,博客里写文时截的这些图片,我咋粘不上去

    66楼 hit2038 2012-10-17 21:02发表 [回复]
        楼主你好,我一直在研究你这篇博文,我想知道“在disparity窗口中选取一个点后然后在实际坐标矩阵中得到的对应三维信息”这一步是如何实现的?能把代码给我发一下吗,272310401@qq.com 谢谢。

    65楼 chgm_456D 2012-10-05 15:36发表 [回复]
        博主,你好!非常有幸拜读你的文章,非常好,感谢你的分享。现在我有个问题想问一下:
        图4.2 和4.3中 的物体的 那个框框,是程序自己检测的,还是手动画上去的?非常感谢!

        Re: scyscyao 2012-10-17 16:14发表 [回复]
            回复chgm_456D:用opencv里面的一个函数画上去的,好像是cvRectangle还是什么

    64楼 allanpk716 2012-09-14 12:02发表 [回复]
        我只想问问,博主你之前买的三维摄像头,我看淘宝没得卖了,找了半天,不知道还有什么地方能找到得到不?不然就得买家用摄像头然后弄个固定架了

        Re: scyscyao 2012-10-17 16:19发表 [回复]
            回复allanpk716:好像市面上双目摄像头不少吧 刚刚淘宝搜了下立体摄像头 就有很多此类产品

        Re: scyscyao 2012-10-17 16:17发表 [回复]
            回复allanpk716:刚刚试了一下博文最后的链接 好像失效了。。。估计是那家公司倒闭了。。。是家小型创业公司

    63楼 lsyyoyo 2012-08-26 09:53发表 [回复]
        想问一下博主,你的图片是怎么显示的。我写博客的时候,截的这个图片怎么没法显示呢?

        Re: scyscyao 2012-10-17 16:19发表 [回复]
            回复lsyyoyo:博客里的图片?还是opencv显示的图片?

    62楼 qqbb1987 2012-08-02 13:01发表 [回复]
        “value值的前一个是normalize之后点的灰度值,后一个是normalize之前点的实际视差图。”这话有问题吧。实际视差怎么y方向还有值,经过前向校正之后,只有x方向有视差值。还有这个y值是怎么得到的呢?想了好久没搞清楚。。。

        Re: scyscyao 2012-10-17 16:25发表 [回复]
            回复qqbb1987:抱歉这里可能说不太清楚 这里的value是指point is xx,xx value is xx,xx的value 跟x y 好像没有关系

    61楼 qqbb1987 2012-08-02 12:51发表 [回复]
        这个帖子我每天看四五遍,有些问题一直没搞清楚。希望能有志同道合一起探讨。请假QQ:532382142。附言:csdn立体视觉。

    60楼 sunanger_wang 2012-07-13 14:32发表 [回复]
        楼主太好了,谢谢

    59楼 xfortius 2012-07-03 17:24发表 [回复]
        赞。。原来没有仔细阅读,现在飙泪中啊。。多多学习。。

    58楼 cbib_cat 2012-03-31 17:08发表 [回复]
        LZ你好!
        “关于cvFindStereoCorrespondenceBM这个函数的源代码,曾经做过比较详细的研究,过一段时间也会把之前写的代码注释整理一下,发篇博文”
        我们目前在看这个算法,想参考LZ的博文。

        Re: scyscyao 2012-10-17 16:22发表 [回复]
            回复cbib_cat:哎 当年毕设时候一腔热情啊,后来没写。。。。不过那个算法写得真的很高效,直接操控内存地址来增加效率。。。。而且 其实现在研究这个算法的意义不太大了吧。。。很多新算法又出来了。。比如graph cut相关的,研究这些算法比较有前景

    57楼 lizhao0211 2012-02-27 16:22发表 [回复]
        您好 我也在做三维重建的项目,能把您得到点的三维信息的程序给我发下吗?想参考一下。谢谢
        wangshuo1987sure@gmai.com

        Re: zlllou 2014-12-04 21:19发表 [回复]
            回复lizhao0211:你们的标定怎么解决的,大神,帮帮忙,我们现在卡在标定这里了,得到结果了,但是不知道怎么把MATLAB的结果导入opencv,邮箱619258732@qq.com

    56楼 shihaos 2012-02-25 23:45发表 [回复]
        博主,你好,急切想知道一些问题,我在利用cvReprojectImageTo3D函数输出的矩阵数据读出三个坐标时是较小的数值,2.09323等,物体实际距离约为35cm,想请问这是什么原因吗?博主的三维坐标输出后是否经过什么处理(乘以16外)

    55楼 jixiangbeijing 2012-02-24 23:11发表 [回复]
        楼主您好,
        “得到了立体标定参数之后,就可以把参数放入xml文件”这句话能不能讲的再清楚点,我就是这个点过不去。万分感谢!

        Re: yu88888jie 2015-12-22 20:49发表 [回复]
            回复jixiangbeijing:就是你找一个xml文件 按照他那个格式把数据填进去就可以了。 xml文件找不到的话可以下载opencv十二章的源码,在里面把那个摄像头矩阵什么的保存为xml就可以了 十一章的代码里有说怎么保存。

        Re: leizishenlan 2013-04-21 16:05发表 [回复]
            回复jixiangbeijing:你好,你现在研究出来了“如何利用matlab摄像头标定得出的参数转化为xml文件”,自己也卡在了这里,特想你请教一下

        Re: chy258143826 2013-03-19 12:00发表 [回复]
            回复jixiangbeijing:您也是这个点过不去啊。/。。。。我也是啊。。半天得不到正确的xml文件。程序提醒数据结构不对,烦躁死了。

            Re: leizishenlan 2013-04-21 16:06发表 [回复]
                回复chy258143826:你好,你现在研究出来了“如何利用matlab摄像头标定得出的参数转化为xml文件”,自己也卡在了这里,特想你请教一下

                Re: zlllou 2014-12-04 21:20发表 [回复]
                    回复leizishenlan:你们解决了吗,我也卡在这里了

    54楼 budong000ni 2012-02-12 12:54发表 [回复]
        LZ的摄像头是两个单独的拼起来的吗?分享一下,想做毕业设计

    53楼 冰水冰 2011-12-24 21:50发表 [回复]
        不知博主能否发我一份 shilylucky@qq.com,做学习用,谢谢

    52楼 qi_lanfeng 2011-12-07 11:16发表 [回复]
        你好,请问楼主还在吗?
        我有个问题想跟你请教一下关于cvStereoRectify里面的R和T参数的问题。我的内外参数都已经得到,于是我用R=R2*R1t
        T=T2-R*T1,但是最后得到的结果却不对,请问应该如何修改
        此外有的时候矫正之后不是行对准而是列对准,这个时候一般如何处理比较好?再次旋转??

    51楼 feira 2011-11-20 13:43发表 [回复]
        还有,你三维摄像头从哪个店买的,给个地址吧

    50楼 feira 2011-11-20 11:33发表 [回复]
        你好,openCV中,利用 cvFindFundametalMat 获得基础矩阵F,然后使用 cvComputeCorrespondEpiLines计算对应点在另一幅图像中的对应极线。那么,调用cvFindFundametalMat时,输入图片是校正好的吗?计算极线时,也输入校正好的图片吗?还有,好多人说校正好的图片,匹配时,只需要在同一行找就行了,那是不是就不用计算极线了?

    49楼 gankeliang 2011-09-23 22:16发表 [回复]
        你好,请问按照你图中标记出几个物体,怎么实现的呢?还有就是我测的距离貌似不怎么准确?貌似是一半这样子。cvReprojectImageTo3D得到的坐标不是很准。但是摄像头的Tx的距离是相当准的。

    48楼 gankeliang 2011-08-24 09:49发表 [回复]
        你好,谢谢你的文章,请问我也是做三维测距的,然后标定都完成了,得到摄像头的一系列参数,然后再匹配的时候我采用的BM的方法,当我自己载入自己的标定参数的时候,匹配视差图确什么都没有,是一片漆黑的。拿别人的标定结果载入,发觉有那么点点视差。会不会是我的标定参数错误呢?一直有点不懂?请问能指导下我原因吗?我的邮箱283299176@qq.com,期待您的指导,可以把你的建议发邮箱或则回复给我嘛?谢谢了。这个问题一直纠结好几天了,还是解决不了。谢谢

    47楼 huxingfu34072 2011-08-03 15:19发表 [回复]
        请问matlab工具箱里面测距文件stereo_triangulation.m中ccd图像坐标到左相机坐标是怎么变换的,那代码写的好像跟说明文件不一样啊,谢谢!!!

    46楼 sunnywish 2011-07-15 10:15发表 [回复]
        用这个方法标定 不能得到本征矩阵和基础矩阵啊

    45楼 zjl1980 2011-06-13 13:33发表 [回复]
        沈程杳,您好,您能不能在百忙之中给我发一份BM算法的程序,我学习一下,前面的标定都会,可是到如何读入xml文件,如何使用其进行立体匹配的这些后面具体的东西就不会了,谢谢您了。我的邮箱是feng198086@sina.com

    44楼 gold2011 2011-05-28 17:23发表 [回复]
        不错[e01]

    43楼 gofi 2011-05-07 14:19发表 [回复]
        你好,我想问下,得到的MAT格式文件如何转换为XML格式

    42楼 wanghs2419 2011-05-05 22:50发表 [回复]
        楼主你好,读了你的文章真的是受益匪浅. 我也正在做双目视觉,刚刚开始,还有很多不懂的地方. 如果可以的话,楼主能不能发一份从opencv读入xml文件开始,到双目摄像头得到disparity map的程序代码. 仅供我学习使用.万分感谢! wanghs2419@hotmail.com
        谢谢!

    41楼 rongtian_ye 2011-04-20 14:03发表 [回复]
        请问,用cvReprojectImageTo3D得到的数据用什么方法可以输出来看?[e07]

    40楼 rongtian_ye 2011-04-01 11:11发表 [回复]
        看了楼主写的,对用matlab得出的参数和后续的校准、匹配之间的衔接还不是很清楚,xml文件是自己创建的吗?放在哪里?程序要作何修改呢?

        Re: feng533 2011-04-11 10:08发表 [回复]
            回复 rongtian_ye:您好,我也在做这方面的研究,遇到的问题和你一样,不知道你解决没呢?希望和你探讨下,我的邮箱:jian329089094@163.com

            Re: rongtian_ye 2011-04-20 14:06发表 [回复]
                回复 feng533:我后来就没有用matlab了,欢迎探讨,lihao_scut@163.com


    //****************************************************************************************************************************************************************************************





    展开全文
  • 计算机双目立体视觉

    2018-04-10 10:38:11
    《计算机双目立体视觉》是一本比较全面和系统研究计算机双目立体视觉理论、技术及其应用的学术专著。 《计算机双目立体视觉》在介绍双目视觉基本原理的基础上,阐述了双目立体视觉的实现技术。同时,本书针对传统...
  • 双目立体视觉技术.pdf

    2020-05-09 13:53:50
    基于双目立体视觉技术的燃烧诊断方法研究 对于燃烧流场三维信息的获取始终是燃烧诊断学科追求的目标,论文所做的 工作也是出于这个目的。论文将双目立体视觉技术引入燃烧诊断领域,实现了对 非预混冲击火焰表面的三维...
  • 双目立体视觉是计算机视觉的一个重要分支,即由不同位置的两台或者一台摄像机(CCD)经过移动或旋转拍摄同一幅场景,通过计算空间点在两幅国像中的视差,获得该点的三维坐标值。80年代美国麻省理工学院人工智能实验...
  • 并利用立体视觉原理计算目标相对左右相机的**空间三维坐标以及空间距离**。 圆点靶标相对于棋盘格靶标来说,具有一定的局限性,同时又有其独特的优势。 优点:在针对一些诸如投影仪和相机的标定过程中,需要知道...
  • 双目立体视觉测量系统是工业测量中的重要手段,三维重建是双目立体视觉测量体统中非常重要的一环。基于视差原理的传统三维重建模型是对双目立体视觉系统的一种理想化抽象。通过分析由平面到三维点的实际映射过程,...
  • OpenCV+OpenGL 双目立体视觉三维重建

    万次阅读 多人点赞 2016-08-08 00:02:47
    0.绪论这篇文章主要为了研究双目立体视觉的最终目标——三维重建,系统的介绍了三维重建的整体步骤。双目立体视觉的整体流程包括:图像获取,摄像机标定,特征提取(稠密匹配中这一步可以省略),立体匹配,三维重建...

    0.绪论

    这篇文章主要为了研究双目立体视觉的最终目标——三维重建,系统的介绍了三维重建的整体步骤。双目立体视觉的整体流程包括:图像获取,摄像机标定,特征提取(稠密匹配中这一步可以省略),立体匹配,三维重建。我在做双目立体视觉问题时,主要关注的点是立体匹配,本文主要关注最后一个步骤三维重建中的:三角剖分和纹理贴图以及对应的OpenCV+OpenGL代码实现。

    1.视差计算

    1.1基于视差信息的三维重建

    特征提取
    由双目立体视觉进行三位重建的第一步是立体匹配,通过寻找两幅图像中的对应点获取视差。OpenCV 中的features2d库中包含了很多常用的算法,其中特征点定位的算法有FAST, SIFT, SURF ,MSER, HARRIS等,特征点描述算法有SURF, SIFT等,还有若干种特征点匹配算法。这三个步骤的算法可以任选其一,自由组合,非常方便。经过实验,选择了一种速度、特征点数量和精度都比较好的组合方案:FAST角点检测算法+SURF特征描述子+FLANN(Fast Library for Approximate Nearest Neighbors) 匹配算法。

    在匹配过程中需要有一些措施来过滤误匹配。一种比较常用的方法是比较第一匹配结果和第二匹配结果的得分差距是否足够大,这种方法可以过滤掉一些由于相似造成的误匹配。还有一种方法是利用已经找到的匹配点,使用RANSAC算法求得两幅视图之间的单应矩阵,然后将左视图中的坐标P用单应矩阵映射到右视图的Q点,观察与匹配结果Q’的欧氏距离是否足够小。当然由于图像是具有深度的,Q与Q’必定会有差距,因此距离阈值可以设置的稍微宽松一些。我使用了这两种过滤方法。

    另外,由于图像有些部分的纹理较多,有些地方则没有什么纹理,造成特征点疏密分布不均匀,影响最终重建的效果,因此我还采取了一个措施:限制特征点不能取的太密。如果新加入的特征点与已有的某一特征点距离太小,就舍弃之。最终匹配结果如下图所示,精度和均匀程度都较好。
    这里写图片描述

    代码:

    // choose the corresponding points in the stereo images for 3d reconstruction
    void GetPair( Mat &imgL, Mat &imgR, vector<Point2f> &ptsL, vector<Point2f> &ptsR ) 
    {
        Mat descriptorsL, descriptorsR;
        double tt = (double)getTickCount();
    
       Ptr<FeatureDetector> detector = FeatureDetector::create( DETECTOR_TYPE ); // factory mode
        vector<KeyPoint> keypointsL, keypointsR; 
        detector->detect( imgL, keypointsL );
        detector->detect( imgR, keypointsR );
    
        Ptr<DescriptorExtractor> de = DescriptorExtractor::create( DESCRIPTOR_TYPE );
        //SurfDescriptorExtractor de(4,2,true);
        de->compute( imgL, keypointsL, descriptorsL );
        de->compute( imgR, keypointsR, descriptorsR );
    
        tt = ((double)getTickCount() - tt)/getTickFrequency(); // 620*555 pic, about 2s for SURF, 120s for SIFT
    
        Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create( MATCHER_TYPE );
        vector<vector<DMatch>> matches;
        matcher->knnMatch( descriptorsL, descriptorsR, matches, 2 ); // L:query, R:train
    
        vector<DMatch> passedMatches; // save for drawing
        DMatch m1, m2;
        vector<Point2f> ptsRtemp, ptsLtemp;
        for( size_t i = 0; i < matches.size(); i++ )
        {
            m1 = matches[i][0];
            m2 = matches[i][1];
            if (m1.distance < MAXM_FILTER_TH * m2.distance)
            {
                ptsRtemp.push_back(keypointsR[m1.trainIdx].pt);
                ptsLtemp.push_back(keypointsL[i].pt);
                passedMatches.push_back(m1);
            }
        }
    
        Mat HLR;
        HLR = findHomography( Mat(ptsLtemp), Mat(ptsRtemp), CV_RANSAC, 3 );
        cout<<"Homography:"<<endl<<HLR<<endl;
        Mat ptsLt; 
        perspectiveTransform(Mat(ptsLtemp), ptsLt, HLR);
    
        vector<char> matchesMask( passedMatches.size(), 0 );
        int cnt = 0;
        for( size_t i1 = 0; i1 < ptsLtemp.size(); i1++ )
        {
            Point2f prjPtR = ptsLt.at<Point2f>((int)i1,0); // prjx = ptsLt.at<float>((int)i1,0), prjy = ptsLt.at<float>((int)i1,1);
             // inlier
            if( abs(ptsRtemp[i1].x - prjPtR.x) < HOMO_FILTER_TH &&
                abs(ptsRtemp[i1].y - prjPtR.y) < 2) // restriction on y is more strict
            {
                vector<Point2f>::iterator iter = ptsL.begin();
                for (;iter!=ptsL.end();iter++)
                {
                    Point2f diff = *iter - ptsLtemp[i1];
                    float dist = abs(diff.x)+abs(diff.y);
                    if (dist < NEAR_FILTER_TH) break;
                }
                if (iter != ptsL.end()) continue;
    
                ptsL.push_back(ptsLtemp[i1]);
                ptsR.push_back(ptsRtemp[i1]);
                cnt++;
                if (cnt%1 == 0) matchesMask[i1] = 1; // don't want to draw to many matches
            }
        }
    
        Mat outImg;
        drawMatches(imgL, keypointsL, imgR, keypointsR, passedMatches, outImg, 
            Scalar::all(-1), Scalar::all(-1), matchesMask, DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
        char title[50];
        sprintf_s(title, 50, "%.3f s, %d matches, %d passed", tt, matches.size(), cnt);
        imshow(title, outImg);
        waitKey();
    }

    p.s. 源代码中的基于特征点的视差计算有点问题,还在调试中,希望有经验的大牛共同解决一下。


    最新回复,特别鸣谢大神:G3fire(update 20180718)

    代码在opencv2.4.9版本下运行的,由于SIFT和SURF的专利约束需要nofree的引用.
    
    在Reconstuction3d.cpp中添加initModule_nonfree();
    同样在head.h中添加
    #pragma comment(lib,"opencv_nonfree249d.lib"),
    把Algorithm g_algo 改成= FEATURE_PT
    就可以运行基于特征点的视差计算了 
    
    楼24的修改特征点检测的创建方法没有运行通

    特别感谢24楼的回复

    博主的特征点匹配这边运行时会崩溃,我用VS2013+opencv2.4.10版本,然后修改特征点检测的创建方法就可以用了。

    例如:

    SurfFeatureDetector detector;
    detector.detect(imgL1, keypointsL); 
    detector.detect(imgR1, keypointsR);

    1.2基于块匹配的视差计算

    上面提取特征点的过程中实际上忽略了一个辅助信息:对应点应当是取在对应极线上的一个区间内的。利用这个信息可以大幅简化对应点的匹配,事实上只要用L1距离对一个像素周围的block计算匹配距离就可以了,也就是OpenCV中实现的块匹配算法的基本思路。比起特征点匹配,这是一种“稠密”的匹配算法,精度也可以接受。下图中浅色表示视差较大,对应深度较浅。左侧有一块区域是左右视图不相交的部分,因此无法计算视差。
    这里写图片描述
    可以发现视差计算结果中有很多噪声。事实上在纹理平滑的区域,还有左右视图中不同遮挡的区域,是很难计算视差的。因此我利用最近邻插值和数学形态学平滑的方法对视差图进行了修复(见cvFuncs2.cpp中的FixDisparity函数):
    这里写图片描述

    // roughly smooth the glitches on the disparity map
    void FixDisparity( Mat_<float> & disp, int numberOfDisparities ) 
    {
        Mat_<float> disp1;
        float lastPixel = 10;
        float minDisparity = 23;// algorithm parameters that can be modified
        for (int i = 0; i < disp.rows; i++)
        {
            for (int j = numberOfDisparities; j < disp.cols; j++)
            {
                if (disp(i,j) <= minDisparity) disp(i,j) = lastPixel;
                else lastPixel = disp(i,j);
            }
        }
         int an = 4;    // algorithm parameters that can be modified
        copyMakeBorder(disp, disp1, an,an,an,an, BORDER_REPLICATE);
        Mat element = getStructuringElement(MORPH_ELLIPSE, Size(an*2+1, an*2+1));
        morphologyEx(disp1, disp1, CV_MOP_OPEN, element);
        morphologyEx(disp1, disp1, CV_MOP_CLOSE, element);
        disp = disp1(Range(an, disp.rows-an), Range(an, disp.cols-an)).clone();
    }

    对应点的选取
    上面提到,为了获得较好的重构效果,特征点最好取在深度变化较大的区域。基于这种猜想,我首先对上面的视差图求梯度,然后找到梯度最大的点,观察梯度的方向,如果是偏x方向,就在该点左右若干像素各取一个点;否则就在上下若干像素各取一个点。然后根据这两个点的视差值就可以计算出另外一个视图中的对应点的坐标。特征点还不能分布过密,因此我取完一对特征点后,将其周围一圈像素的梯度置零,然后在寻找下一个梯度最大值,这样一直下去,直到取够特征点数。
    特征点也不能全取在深度变化剧烈的区域,在平坦的区域也可以取一些。最终我取的特征点如下图:
    这里写图片描述
    其中紫色的点是在较平坦的区域取到的,其他颜色是在边界区域取到的。这些算法实现在ChooseKeyPointsBM函数中。

    2.计算世界坐标

    一般双目立体视觉中使用的实验图像都是经过外极线矫正的,计算3D坐标也比较方便,其实利用外极线约束(以及其他的约束条件)可以极大的降低立体匹配的计算量。见下图:
    这里写图片描述
    如果(x1,y1),(x2,y2)用各自图像上的像素坐标表示,L和(X,Y,Z)用毫米表示,f用像素表示的话,用相似三角形的知识就可以推出:
    这里写图片描述
    其中W和H是图像的宽高(像素数),y是y1和y2的均值,Z加负号是为了保持右手坐标系,而Y加负号是由于图像成像过程中上下发生了倒转。三维世界原点取为左摄像机的焦点。计算的代码见cvFunc.cpp中的StereoTo3D函数。

     // calculate 3d coordinates.
    // for rectified stereos: pointLeft.y == pointRight.y
    // the origin for both image is the top-left corner of the left image.
    // the x-axis points to the right and the y-axis points downward on the image.
    // the origin for the 3d real world is the optical center of the left camera
    // object -> optical center -> image, the z value decreases.
    
    void StereoTo3D( vector<Point2f> ptsL, vector<Point2f> ptsR, vector<Point3f> &pts3D,
                    float focalLenInPixel, float baselineInMM, Mat img,
                    Point3f &center3D, Vec3f &size3D) // output variable, the center coordinate and the size of the object described by pts3D
    {
        vector<Point2f>::iterator iterL = ptsL.begin(),
            iterR = ptsR.begin();
    
        float xl, xr, ylr;
        float imgH = float(img.rows), imgW = float(img.cols);
        Point3f pt3D;
        float minX = 1e9, maxX = -1e9;
        float minY = 1e9, maxY = -1e9;
        float minZ = 1e9, maxZ = -1e9;
    
        Mat imgShow = img.clone();
        char str[100];
        int ptCnt = ptsL.size(), showPtNum = 30, cnt = 0;
        int showIntv = max(ptCnt/showPtNum, 1);
        for ( ; iterL != ptsL.end(); iterL++, iterR++)
        {
            xl = iterL->x;
            xr = iterR->x; // need not add baseline
            ylr = (iterL->y + iterR->y)/2;
    
            //if (yl-yr>5 || yr-yl>5) // may be wrong correspondence, discard. But vector can't be changed during iteration
            //{}
    
            pt3D.z = -focalLenInPixel * baselineInMM / (xl-xr); // xl should be larger than xr, if xl is shot by the left camera
            pt3D.y = -(-ylr + imgH/2) * pt3D.z / focalLenInPixel;
            pt3D.x = (imgW/2 - xl) * pt3D.z / focalLenInPixel;
    
            minX = min(minX, pt3D.x); maxX = max(maxX, pt3D.x);
            minY = min(minY, pt3D.y); maxY = max(maxY, pt3D.y);
            minZ = min(minZ, pt3D.z); maxZ = max(maxZ, pt3D.z);
            pts3D.push_back(pt3D);
    
            if ((cnt++)%showIntv == 0)
            {
                Scalar color = CV_RGB(rand()&64,rand()&64,rand()&64);
                sprintf_s(str, 100, "%.0f,%.0f,%.0f", pt3D.x, pt3D.y, pt3D.z);
                putText(imgShow, str, Point(xl-13,ylr-3), FONT_HERSHEY_SIMPLEX, .3, color);
                circle(imgShow, *iterL, 2, color, 3);
            }
    
        }
    
        imshow("back project", imgShow);
        waitKey();
    
        center3D.x = (minX+maxX)/2;
        center3D.y = (minY+maxY)/2;
        center3D.z = (minZ+maxZ)/2;
        size3D[0] = maxX-minX;
        size3D[1] = maxY-minY;
        size3D[2] = maxZ-minZ;
    }

    3.三角剖分

    3.1 三角剖分简介

    三角剖分是为了之后的纹理贴图,我用了OpenCV中的Delaunay三角剖分函数,这种剖分算法的可以使所形成的三角形的最小角最大。剖分的示例如下:

    这里写图片描述
    OpenCV使用Delaunay算法将平面分割成小的三角形区域(该三角形确保包括所有的分割点)开始不断迭代完成。在这种情况下,对偶划分就是输入的二维点集的Voronoi图表。这种划分可以用于对一个平面进行三维分段变换、形态变换、平面点的快速 定位以及建立特定的图结构(如NNG,RNG)。

    这里写图片描述
    同时由表可以看出,三角网生成法的时间效率最低,分治算法的时间效率最高,逐点插入法效率居中。

    3.2 Bowyer-Watson算法

    目前采用逐点插入方式生成的Delaunay三角网的算法主要基于Bowyer-Watson算法,Bowyer-Watson算法的主要步骤如下:

    1)建立初始三角网格:针对给定的点集V,找到一个包含该点集的矩形R,我们称R为辅助窗口。连接R的任意一条对角线,形成两个三角形,作为初始Delaunay三角网格。

    2)逐点插入:假设目前已经有一个Delaunay三角网格T,现在在它里面再插入一个点P,需要找到该点P所在的三角形。从P所在的三角形开始,搜索该三角形的邻近三角形,并进行空外接圆检测。找到外接圆包含点P的所有的三角形并删除这些三角形,形成一个包含P的多边形空腔,我们称之为Delaunay空腔。然后连接P与Delaunay腔的每一个顶点,形成新的Delaunay三角网格。

    3)删除辅助窗口R:重复步骤2),当点集V中所有点都已经插入到三角形网格中后,将顶点包含辅助窗口R的三角形全部删除。

    在这些步骤中,快速定位点所在的三角形、确定点的影响并构建Delaunay腔的过程是每插入一个点都会进行的。随着点数的增加,三角形数目增加很快,因此缩短这两个过程的计算时间,是提高算法效率的关键。
    算法执行图示如下:
    这里写图片描述

    3.3 三角剖分代码分析

    三角剖分的代码见cvFuncs.cpp中的TriSubDiv函数,我将特征点存储到一个vector变量中,剖分结果存储到一个vector变量中,Vec3i中存储的是3个表示顶点编号的整数。

    我们需要存储Delaunay的内存空间和一个外接矩形(该矩形盒子用来确定虚拟三角形)

    // STORAGE AND STRUCTURE FOR DELAUNAY SUBDIVISION //存储和结构 for三角剖分  
    //  
    CvRect rect = { 0, 0, 600, 600 };  //Our outer bounding box //我们的外接边界盒子  
    CvMemStorage* storage;    //Storage for the Delaunay subdivsion //用来存储三角剖分  
    storage = cvCreateMemStorage(0);    //Initialize the storage //初始化存储器  
    CvSubdiv2D* subdiv; //The subdivision itself // 细分  
    subdiv = init_delaunay( storage, rect);   //See this function below //函数返回CvSubdiv类型指针  
    init_delaunay函数如下,它是一个OpenCV函数,是一个包含一些OpenCV函数的函数包。
    //INITIALIZATION CONVENIENCE FUNCTION FOR DELAUNAY SUBDIVISION //为三角剖分初始化便利函数  
    //  
    CvSubdiv2D* init_delaunay(CvMemStorage* storage,CvRect rect) {  
    CvSubdiv2D* subdiv;  
    subdiv = cvCreateSubdiv2D(CV_SEQ_KIND_SUBDIV2D,sizeof(*subdiv),sizeof(CvSubdiv2DPoint),sizeof(CvQuadEdge2D),storage);//为数据申请空间  
    cvInitSubdivDelaunay2D( subdiv, rect ); //rect sets the bounds  
    return subdiv;//返回申请空间的指针  
    }  

    我们知道三角剖分是对散点集进行处理的,我们知道了散点集就可以获得点集的三角剖分。如何传入(插入)散点集呢?
    这些点必须是32位浮点型,并通过下面的方式插入点:

    CvPoint2D32f fp; //This is our point holder//这是我们点的持有者(容器)  
    for( i = 0; i < as_many_points_as_you_want; i++ ) {  
    // However you want to set points //如果我们的点集不是32位的,在这里我们将其转为CvPoint2D32f,如下两种方法。  
    //  
    fp = your_32f_point_list[i];  
    cvSubdivDelaunay2DInsert( subdiv, fp );  
    }  

    转换为CvPoint2D32f的两种方法:
    1)通过宏cvPoint2D32f(double x,double y)
    2)通过cxtype.h下的cvPointTo32f(CvPoint point)函数将整形点方便的转换为32位浮点型。
    当可以通过输入点(散点集)得到Delaunay三角剖分后,接下来,我们用一下两个函数设置和清除相关的Voronoi划分:

    cvCalcSubdivVoronoi2D( subdiv ); // Fill out Voronoi data in subdiv //在subdiv中填充Vornoi的数据  
    cvClearSubdivVoronoi2D( subdiv ); // Clear the Voronoi from subdiv//从subdiv中清除Voronoi的数据  

    CvSubdiv2D结构如下:

    #define CV_SUBDIV2D_FIELDS() \  
    CV_GRAPH_FIELDS() \  
    int quad_edges; \  
    int is_geometry_valid; \  
    CvSubdiv2DEdge recent_edge; \  
    CvPoint2D32f topleft; \  
    CvPoint2D32f bottomright;  
    typedef struct CvSubdiv2D  
    {  
    CV_SUBDIV2D_FIELDS()  
    }  
    CvSubdiv2D;  
    #define CV_GRAPH_FIELDS()               \  
    CV_SET_FIELDS() /* set of vertices */   \  
    CvSet *edges;  /* set of edges    */  
    #define CV_SET_FIELDS()                                            \  
    CV_SEQUENCE_FIELDS()             /*inherits from [#CvSeq CvSeq] */ \  
    struct CvSetElem* free_elems;   /*list of free nodes           */  

    整体代码如下:

    void TriSubDiv( vector<Point2f> &pts, Mat &img, vector<Vec3i> &tri ) 
    {
        CvSubdiv2D* subdiv;//The subdivision itself // 细分 
        CvMemStorage* storage = cvCreateMemStorage(0); ;//Storage for the Delaunay subdivsion //用来存储三角剖分 
        Rect rc = Rect(0,0, img.cols, img.rows); //Our outer bounding box //我们的外接边界盒子 
    
        subdiv = cvCreateSubdiv2D( CV_SEQ_KIND_SUBDIV2D, sizeof(*subdiv),
            sizeof(CvSubdiv2DPoint),
            sizeof(CvQuadEdge2D),
            storage );//为数据申请空间  
    
        cvInitSubdivDelaunay2D( subdiv, rc );//rect sets the bounds 
    
        //如果我们的点集不是32位的,在这里我们将其转为CvPoint2D32f,如下两种方法。
        for (size_t i = 0; i < pts.size(); i++)
        {
            CvSubdiv2DPoint *pt = cvSubdivDelaunay2DInsert( subdiv, pts[i] );
            pt->id = i;
        }
    
        CvSeqReader reader;
        int total = subdiv->edges->total;
        int elem_size = subdiv->edges->elem_size;
    
        cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );
        Point buf[3];
        const Point *pBuf = buf;
        Vec3i verticesIdx;
        Mat imgShow = img.clone();
    
        srand( (unsigned)time( NULL ) );   
        for( int i = 0; i < total; i++ ) 
        {   
            CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);   
    
            if( CV_IS_SET_ELEM( edge )) 
            {
                CvSubdiv2DEdge t = (CvSubdiv2DEdge)edge; 
                int iPointNum = 3;
                Scalar color = CV_RGB(rand()&255,rand()&255,rand()&255);
    
                //bool isNeg = false;
                int j;
                for(j = 0; j < iPointNum; j++ )
                {
                    CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );
                    if( !pt ) break;
                    buf[j] = pt->pt;
                    //if (pt->id == -1) isNeg = true;
                    verticesIdx[j] = pt->id;
                    t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
                }
                if (j != iPointNum) continue;
                if (isGoodTri(verticesIdx, tri))
                {
                    //tri.push_back(verticesIdx);
                    polylines( imgShow, &pBuf, &iPointNum, 
                        1, true, color,
                        1, CV_AA, 0);
                    //printf("(%d, %d)-(%d, %d)-(%d, %d)\n", buf[0].x, buf[0].y, buf[1].x, buf[1].y, buf[2].x, buf[2].y);
                    //printf("%d\t%d\t%d\n", verticesIdx[0], verticesIdx[1], verticesIdx[2]);
                    //imshow("Delaunay", imgShow);
                    //waitKey();
                }
    
                t = (CvSubdiv2DEdge)edge+2;
    
                for(j = 0; j < iPointNum; j++ )
                {
                    CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );
                    if( !pt ) break;
                    buf[j] = pt->pt;
                    verticesIdx[j] = pt->id;
                    t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
                }   
                if (j != iPointNum) continue;
                if (isGoodTri(verticesIdx, tri))
                {
                    //tri.push_back(verticesIdx);
                    polylines( imgShow, &pBuf, &iPointNum, 
                        1, true, color,
                        1, CV_AA, 0);
                    //printf("(%d, %d)-(%d, %d)-(%d, %d)\n", buf[0].x, buf[0].y, buf[1].x, buf[1].y, buf[2].x, buf[2].y);
                    //printf("%d\t%d\t%d\n", verticesIdx[0], verticesIdx[1], verticesIdx[2]);
                    //imshow("Delaunay", imgShow);
                    //waitKey();
                }
            }
    
            CV_NEXT_SEQ_ELEM( elem_size, reader );
    
        }
    
        //RemoveDuplicate(tri);
        char title[100];
        sprintf_s(title, 100, "Delaunay: %d Triangles", tri.size());
        imshow(title, imgShow);
        waitKey();
    }

    平面划分是将一个平面分割为一组不重叠的、能够覆盖整个平面的区域。结构CvSubdiv2D描述了建立在二维点集上的划分结构,其中点集互相连接且构成平面图形,该图形通过结合一些无线连接外部划分点(称为凸形点)的边缘,将一个平面用按照其边缘划分成很多小区域。

    对于每一个划分操作,都有一个对偶划分与之对应。对偶的意思是小区域与点(划分的顶点)变换角色,即在对偶划分中,小区域被当做一个顶点(以下称为虚拟点)而原始的划分顶点被当做小区域。如下图所示,原始的划分用实线表示,而对偶划分用虚线表示。

    4.三维重构

    为了保证三维重建的效果,一般地要对深度图像进行后续处理。要从深度图像中恢复高质量的视差图,对深度图像的要求有:
    ①深度图像中,物体的边界必需与图像中物体的边界对齐;
    ②在场景图中,深度图像要尽可能均勻和平滑,即对图像进行平滑处理。

    三维重构的思路很简单,用OpenGL中纹理贴图功能,将平面图像中的三角形逐个贴到计算出的三维坐标上去就可以了。为了便于观察3D效果,我还设计了交互功能:用方向键可以上下左右旋转重构的模型,用鼠标滚轮可以放大或缩小。用gluLookAt函数可以实现视点旋转的功能。三维重构的代码实现在glFuncs.cpp中。

    纹理贴图:

    GLuint Create3DTexture( Mat &img, vector<Vec3i> &tri, 
                           vector<Point2f> pts2DTex, vector<Point3f> &pts3D, 
                            Point3f center3D, Vec3f size3D ) 
    {
        GLuint tex = glGenLists(1);
        int error = glGetError();
        if (error != GL_NO_ERROR) 
            cout << "An OpenGL error has occured: " << gluErrorString(error) << endl;
        if (tex == 0) return 0;
    
        Mat texImg;
        cvtColor(img, img, CV_BGR2RGB);
        resize(img, texImg, Size(512,512)); // seems no need to do this
    
        glNewList(tex, GL_COMPILE);
    
        vector<Vec3i>::iterator iterTri = tri.begin();
        //vector<Point3f>::iterator iterPts3D = pts3D.begin();
        Point2f pt2D[3];
        Point3f pt3D[3];
    
        glDisable(GL_BLEND);
        glEnable(GL_TEXTURE_2D);
        for ( ; iterTri != tri.end(); iterTri++)
        {
            Vec3i &vertices = *iterTri;
            int ptIdx;
            for (int i = 0; i < 3; i++)
            {
                ptIdx = vertices[i];
                if (ptIdx == -1) break;
                //else cout<<ptIdx<<"\t";
                pt2D[i].x = pts2DTex[ptIdx].x / img.cols;
                pt2D[i].y = pts2DTex[ptIdx].y / img.rows;
                pt3D[i] = (pts3D[ptIdx] - center3D) * (1.f / max(size3D[0],size3D[1]));
                //pt3D[i].z -= offset;
            }
    
            if (ptIdx != -1)
            {
                MapTexTri(texImg, pt2D, pt3D);
                //cout<<endl;
            }
        }
        glDisable(GL_TEXTURE_2D);
    
        glEndList();
        return tex;
    
    }

    效果展示及不足
    Cloth图像是重构效果比较好的一组:

    这里写图片描述

    可以比较明显的看出3D效果,也比较符合直觉。然而其他图像效果就差强人意了:

    这里写图片描述

    仔细分析造成这种效果的原因,一方面,特征点的匹配可能有些误差,造成3D坐标的计算不太精确,但大部分坐标还是准确的。另一方面,左右视图可能会有不同的遮挡、偏移等情况,因此匹配得到的特征点可能实际上并不是3维世界中的同一点,这种误差是无法消除的。但造成效果变差的最重要的原因,还是图像中深度变化较大,而特征点选取的比较稀疏,因此正面看还比较正常,一旦旋转纹理就显得扭曲变形了。为了解决这个问题,应当试图把特征点取到深度变化较剧烈的地方,一般是图像中的边界处。然而特征点检测一般都检测出的是角点和纹理密集的区域,因此可以考虑更换对应点匹配的方法。

    如果要进一步改进效果,可以先对视差图像进行分割,将图像分成视差比较连续的几块区域分别贴图,视差变化剧烈的区域就不必把扭曲的纹理贴上去了。我尝试了以下分割的效果,如下图所示,应该可以达到更好的效果,不过由于时间所限,就没有进一步实现下去了。

    关于上面实现的两种求取视差的算法,在main函数的前面设置了一个变量g_algo,可以用来切换不同的算法。

    参考文献:

    立体匹配原理:

    http://blog.csdn.net/wangyaninglm/article/details/51533549
    http://blog.csdn.net/wangyaninglm/article/details/51531333
    三维重建原理:

    http://blog.csdn.net/wangyaninglm/article/details/51558656
    http://blog.csdn.net/wangyaninglm/article/details/51558310
    三角剖分原理:

    http://blog.csdn.net/newthinker_wei/article/details/45598769
    http://www.learnopencv.com/delaunay-triangulation-and-voronoi-diagram-using-opencv-c-python/
    这篇文章其实主要是针对早期下到的一个代码和文档的总结,和一些个人资料的总结,由于时间比较早,找不到出处了,如果原作者看到了觉的不妥,那我就把它改成转载啦,嘿嘿嘿。

    代码下载

    CSDN: http://download.csdn.net/detail/wangyaninglm/9597622
    github:https://github.com/wynshiter/OpenCV-OpenGL–Reconstuction3d

    展开全文
  • 双目立体视觉

    2013-09-26 15:09:30
    双目立体视觉,采用两张图像恢复三维形状,外国人的一个开源项目
  • 最新的立体匹配综述文献,内容很全面和基础,基本攘括了以往常用的立体匹配方法。可作为学习和深入了解立体匹配的参考。
  • 双目立体视觉技术模拟人类双眼处理景物的方式,运用双目摄像头从不同角度同时获取目标物的左右两幅数字图像,综合应用相机标定、特征点检测、立体匹配、三维重建等关键技术还原出物体的三维几何信息。 以双目立体...
  • 在计算机视觉系统中,双目立体视觉测量一般由双摄像机从不同的角度同时获取周围景物的两幅图像,或有单摄像机在不同时刻从不同角度获取周围景物的两幅数字图像,并基于视差原理即可恢复出物体的三维几何模型,重建...
  • 针对雷达不能利用目标的颜色等属性和水面多路径反射的干扰等问题,建立无人水面艇双目立体视觉系统,为实现无人水面艇的自主避障和目标跟踪等任务提供基础。采用两个IEEE 1394接口的数字摄像机在无人水面艇上构建...
  • 基于Stixel world及特征融合的双目立体视觉行人检测.pdf,针对单目视觉行人检测无法获得深度信息从而导致冗余信息较多、检测效率和准确度存在局限性的问题,首先,在图像的预处理阶段提出了一种利用双目立体视觉产生的...
  • 立体视觉和三维激光系统的联合标定方法.pdf,双目立体视觉和三维激光扫描是移动机器人环境探测与建模的常见传感测量方法。为实现两个系统的数据融合应用,必须为二者的测量坐标系建立数学关系,即对其进行传感器之间...
  • 双目立体视觉系统的几何参数影响双目视觉的测量精度, 且各几何参数间存在着一定的约 束关系. 通过分析双目视觉系统的几何参数及约束关系, 讨论了两相机光轴和基线之间的夹角α 1和 α 2、 基线距B、 投影角等...
  • 基于立体视觉对移动物体的重建,由视觉导航探测
  • 本文提出了一种基于双目立体视觉的图像增强算法。该算法首先通过立体匹配处理求解场景视差图像,然后构建广义双边滤波估计图像照度分量,根据Retinex原理求解图像反射分量,实现图像的增强。实验证明,该算法能够...
  • 多目立体视觉

    2015-01-27 14:40:05
    多目立体视觉三维重建系统的设计,设计多目摄像机的集中标定及算法
  • 双目立体视觉源代码

    热门讨论 2012-02-11 14:08:48
    双目立体视觉源代码,包括标定,匹配,三维重建
  • 针对柔性手术机器人较难实时获取末端柔性段位置和形状信息的问题,提出一种基于双目立体视觉技术的线驱动蛇形机器人形状重建算法。该算法采用双目摄像机采集20组蛇形机器人柔性段不同角度的图片信息作为样本,利用...
  • 双目立体视觉的数学原理

    万次阅读 多人点赞 2016-10-25 21:58:24
    双目立体视觉是基于视差原理,由多幅图像获取物体三维几何信息的方法。在机器视觉系统中,双目视觉一般由双摄像机从不同角度同时获取周围景物的两幅数字图像,或有由单摄像机在不同时刻从不同角度获取周围景物的两幅...

    1.前言戏说

    双目立体视觉是基于视差原理,由多幅图像获取物体三维几何信息的方法。在机器视觉系统中,双目视觉一般由双摄像机从不同角度同时获取周围景物的两幅数字图像,或有由单摄像机在不同时刻从不同角度获取周围景物的两幅数字图像,并基于视差原理即可恢复出物体三维几何信息,重建周围景物的三维形状与位置。
    双目视觉有的时候我们也会把它称为体视,是人类利用双眼获取环境三维信息的主要途径。从目前来看,随着机器视觉理论的发展,双目立体视觉在机器视觉研究中发回来看了越来越重要的作用。本篇帖子主要研究了双目视觉的数学原理。

    2.双目立体视觉的数学原理

    双目立体视觉是基于视差,由三角法原理进行三维信息的获取,即由两个摄像机的图像平面和北侧物体之间构成一个三角形。一直两个摄像机之间的位置关系,便可以获得两摄像机公共视场内物体的三维尺寸及空间物体特征点的三维坐标。所以,双目视觉系统一般由两个摄像机构成。

    2.1 双目立体视觉三维测量原理


    上图所示为简单的平视双目立体成像原理图,两摄像机的投影中心连线的距离,即基线距离B。两摄像机在同一时刻观看时空物体的同一特征点P,分别在“左眼”和“右眼”上获取了点P的图像,他们的坐标分别为Pleft=(Xleft,Yleft);Pright=(Xright,Yright)。将定两摄像机的图像在同一平面上,则特征点P的图像坐标的Y坐标一定是相同的,即Yleft = Yright =Y。由三角几何关系可以得到如下关系式:

    则视差为:Disparity=Xleft-Xright.由此可以计算出特征点P在摄像机坐标系下的三维坐标:

    因此,左摄像机像面上的任意一点只要能在右摄像机像面上找到对应的匹配点,就完全可以确定该点的三维坐标。这种方法是点对点的运算,像平面上所有点只要存在相应的匹配点,就可以参与上述运算,从而获取对应的三维坐标。

    2.2 双目立体视觉数学模型


    在分析了最简单的平视双目立体视觉的三维测量原理基础上,现在我们就有能力来考虑一般情况。如上图所示,设左摄像机O-xyz位于世界坐标系原点,且没有发生旋转,图像坐标系为Ol-X1Y1,有效焦距为fl;右摄像机坐标系为Or-xyz,图像坐标系为Or-XrYr,有效焦距为fr。那么根据摄像机的投射模型我们就能得到如下关系式:
      
    因为O-xyz坐标系与Or-xryrzr坐标系之间的位置关系可通过空间转换矩阵MLr表示为:

    同理,对于O-xyz坐标系中的空间点,两个摄像机面点之间的对应关系可以表示为:

    于是,空间点三维坐标可以表示为
    因此,只要我们通过计算机标定技术获得左右计算机内参数/焦距fr,fl和空间点在左右摄像机中的图像坐标,就能够重构出被测点的三维空间坐标。

    展开全文
  • 摘要:为了提高智能车弯道中定位的鲁棒性,将室内弯道特征信息进行特征提取与匹配来建立路径,利用自身前景立体视觉感知信息获得车体弯道转向的判断和偏航角度的给定,并采用BP控制策略对智能小车实现转弯控制,避免...
  • 立体视觉小项目

    2018-12-24 18:04:56
    该工程中实现了很多东西。包括标定,双目匹配算法,视差等等,详情可以查看https://blog.csdn.net/qq_31112205/article/details/85236892

空空如也

1 2 3 4 5 ... 20
收藏数 2,720
精华内容 1,088
关键字:

立体视觉