原文:

http://blog.csdn.NET/chenyusiyuan/article/details/5970799

在获取到视差数据后,利用 OpenCV 的 reProjectImageTo3D 函数结合 Bouquet 校正方法得到的 Q 矩阵就可以得到环境的三维坐标数据,然后利用
OpenGL 来实现三维重构。 OpenCV 与 OpenGL 的编程范例,我在 学习笔记( 15 )中有详细的讨论,这里就不重复了,下面补充一些细节问题:

.

.

1.             reProjectImageTo3D 是怎样计算出三维坐标数据的?

图 22

.

相信看过 OpenCV 第 12 章的朋友对上图中的 Q 矩阵不会陌生,根据以上变换公式,按理说 OpenCV 应该也是通过矩阵运算的方式来计算出三维坐标数据的,但实际上仔细查看源代码,会发现 cvReprojectImageTo3D 用了比较奇怪的方法来实现,主要代码如下:

  1. 02737     for( y = 0; y < rows; y++ )
  2. 02738     {
  3. 02739         const float* sptr = (const float*)(src->data.ptr + src->step*y);   // 视差矩阵指针
  4. 02740         float* dptr0 = (float*)(dst->data.ptr + dst->step*y), *dptr = dptr0;   // 三维坐标矩阵指针
  5. // 每一行运算开始时,用 当前行号y 乘以Q阵第2列、再加上Q阵第4列,作为初始值
  6. // 记 qq=[qx, qy, qz, qw]’
  7. 02741         double qx = q[0][1]*y + q[0][3], qy = q[1][1]*y + q[1][3];
  8. 02742         double qz = q[2][1]*y + q[2][3], qw = q[3][1]*y + q[3][3];
  9. // 每算完一个像素的三维坐标,向量qq 累加一次q阵第1列
  10. // 即:qq = qq + q(:,1)
  11. 02769         for( x = 0; x < cols; x++, qx += q[0][0], qy += q[1][0], qz += q[2][0], qw += q[3][0] )
  12. 02770         {
  13. 02771             double d = sptr[x];
  14. // 计算当前像素三维坐标
  15. // 将向量qq 加上 Q阵第3列与当前像素视差d的乘积,用所得结果的第4元素除前三位元素即可
  16. // [X,Y,Z,W]’ = qq + q(:,3) * d;   iW = 1/W; X=X*iW; Y=Y*iW; Z=Z*iW;
  17. 02772             double iW = 1./(qw + q[3][2]*d);
  18. 02773             double X = (qx + q[0][2]*d)*iW;
  19. 02774             double Y = (qy + q[1][2]*d)*iW;
  20. 02775             double Z = (qz + q[2][2]*d)*iW;
  21. 02776             if( fabs(d-minDisparity) <= FLT_EPSILON )
  22. 02777                 Z = bigZ;   // 02713     const double bigZ = 10000.;
  23. 02778
  24. 02779             dptr[x*3] = (float)X;
  25. 02780             dptr[x*3+1] = (float)Y;
  26. 02781             dptr[x*3+2] = (float)Z;
  27. 02782         }

OpenCV 的这种计算方式比较令人费解,我的理解是可能这种方式的计算速度比较快。理论上,直接通过矩阵 Q 与向量 [x,y,d,1]’ 的乘积就可以得到相同的结果,下面用 Matlab 来验证一下两种方式是异曲同工的,用 Matlab 按照 OpenCV 计算方式得到的结果称为“ OpenCV method ”,直接按公式计算得到的结果称为“ Equation method ”,用 OpenCV 本身算出的三维坐标作为参考,程序代码如下 :

[c-sharp] view plain copy print?
  1. close all;clear all;clc
  2. im = imread('C:/Stereo IO Data/lfFrame_01.jpg');
  3. data = importdata('C:/Stereo IO Data/disparity_01.txt');
  4. r = data(1);    % 行数
  5. c = data(2);    % 列数
  6. disp = data(3:end); % 视差
  7. vmin = min(disp);
  8. vmax = max(disp);
  9. disp = reshape(disp, [c,r])'; % 将列向量形式的 disp 重构为 矩阵形式
  10. %  OpenCV 是行扫描存储图像,Matlab 是列扫描存储图像
  11. %  故对 disp 的重新排列是首先变成 c 行 r 列的矩阵,然后再转置回 r 行 c 列
  12. img = uint8( 255 * ( disp - vmin ) / ( vmax - vmin ) );
  13. q = [1. 0. 0. -1.5690376663208008e+002;...
  14. 0. 1. 0. -1.4282237243652344e+002;...
  15. 0. 0. 0. 5.2004731331639300e+002;...
  16. 0. 0. 1.0945105843175637e-002 0.]; % q(4,3) 原为负值,现修正为正值
  17. big_z = 1e5;
  18. pos1 = zeros(r,c,3);
  19. pos2 = zeros(r,c,3);
  20. for i = 1:r
  21. qq = q*[0 i 0 1]';
  22. for j = 1:c
  23. if disp(i,j)>0
  24. % OpenCV method
  25. vec = qq + q(:,3)*disp(i,j);
  26. vec = vec/vec(4);
  27. pos1(i,j,:) = vec(1:3);
  28. % Textbook method
  29. tmp = q*[j,i,disp(i,j),1]'; % j 是列数,i 是行数,分别对应公式中的 x 和 y
  30. pos2(i,j,:) = tmp(1:3)/tmp(4);
  31. else
  32. pos1(i,j,3) = big_z;
  33. pos2(i,j,3) = big_z;
  34. end
  35. qq = qq + q(:,1);
  36. end
  37. end
  38. subplot(221);
  39. imshow(im); title('Left Frame');
  40. subplot(222);
  41. imshow(img); title('Disparity map');
  42. % Matlab按OpenCV计算方式得到的三维坐标
  43. x = pos1(:,:,1);
  44. y = -pos1(:,:,2);  % 图像坐标系Y轴是向下为正方向,因此需添加负号来修正
  45. z = pos1(:,:,3);
  46. ind = find(z>10000);  % 以毫米为量纲
  47. x(ind)=NaN; y(ind)=NaN; z(ind)=NaN;
  48. subplot(234);
  49. mesh(x,z,y,double(im),'FaceColor','texturemap');  % Matlab 的 mesh、surf 函数支持纹理映射
  50. colormap(gray);
  51. axis equal;
  52. axis([-1000 1000 0 9000 -500 2000]);
  53. xlabel('Horizonal');ylabel('Depth');zlabel('Vertical'); title('OpenCV method');
  54. view([0 0]);  % 正视图
  55. % view([0 90]);   % 俯视图
  56. % view([90 0]);   % 侧视图
  57. % Matlab 按公式直接计算得到的三维坐标
  58. x = pos2(:,:,1);
  59. y = -pos2(:,:,2);
  60. z = pos2(:,:,3);
  61. ind = find(z>10000);  % 以毫米为量纲
  62. x(ind)=NaN; y(ind)=NaN; z(ind)=NaN;
  63. subplot(235);
  64. mesh(x,z,y,double(im),'FaceColor','texturemap');
  65. colormap(gray);
  66. axis equal;
  67. axis([-1000 1000 0 9000 -500 2000]);
  68. xlabel('Horizonal');ylabel('Depth');zlabel('Vertical'); title('Equation method');
  69. view([0 0]);
  70. % 读入OpenCV计算保存到本地的三维坐标作为参考
  71. data=importdata('C:/Stereo IO Data/xyz.txt');
  72. x=data(:,1); y=data(:,2); z=data(:,3);
  73. ind=find(z>1000);  % 以厘米为量纲
  74. x(ind)=NaN; y(ind)=NaN; z(ind)=NaN;
  75. x=reshape(x,[352 288])'; % 数据写入时是逐行进行的,而Matlab是逐列读取
  76. y=-reshape(y,[352 288])';
  77. z=reshape(z,[352 288])';
  78. subplot(236)
  79. mesh(x,z, y,double(im),'FaceColor','texturemap');
  80. colormap(gray);
  81. axis equal;axis([-100 100 0 900 -50 200]);
  82. xlabel('Horizonal');ylabel('Depth');zlabel('Vertical'); title('OpenCV result');
  83. view([0 0]);

图 23

.

.

2.             为什么利用修正了的 Q 矩阵所计算得到的三维数据中, Y 坐标数据是正负颠倒的?

图 24

.

这个问题我觉得可以从图像坐标系与摄像机坐标系的关系这一角度来解释。如上图所示,一般图像坐标系和摄像机坐标系都是以从左至右为 X 轴正方向,从上至下为 Y 轴正方向 ,摄像机坐标系的
Z 轴正方向则是从光心到成像平面的垂线方向。因此,我们得到的三维坐标数据中 Y 轴数据的正负与实际是相反的,在应用时要添加负号来修正。

.

.

3.             如何画出三维重建图像和景深图像?

.

利用 cvReprojectImageTo3D 计算出的三维坐标数据矩阵一般是三通道浮点型的,需要注意的是这个矩阵存储的是三维坐标数据,而不是 RGB 颜色值,所以是不能调用 cvShowImage() 或者 OpenCV2.1 版的 imshow() 等函数来显示这个矩阵,否则就会看到这种图像:

.

图 25

.

这里出现的明显的四个色块,其实应该是由三维坐标数据中的 X 轴和 Y 轴数据造成,不同象限的数据形成相应的色块。

要画出正确的三维重建图像,可以结合 OpenGL (可参考我的 学习笔记( 15 ))或者 Matlab (例如保存三维数据到本地然后用 Matlab 的 mesh 函数画出,例程见本文问题
1 ;也可以考虑在 OpenCV 中调用 Matlab 混合编程)来实现。

深度图像的显示相对比较简单,只要从三维坐标数据中分离出来(可用 cvSplit() 函数),经过适当的格式转换(例如转换为 CV_8U 格式),就可用 cvShowImage() 或者 OpenCV2.1 版的 imshow() 等函数来显示了,伪彩色的深度图 也可以参考我的 学习笔记(
18 )
问题 6 给出的例程 稍作修改即可实现。

.

4.             怎样把 OpenGL 窗口的图像复制到 OpenCV 中用 IplImage 格式显示和保存?

.

在 学习笔记( 15 )中详细给出了将 OpenCV 生成的 IplImage 图像和三维坐标数据复制到 OpenGL 中显示的例程,而在应用中,我们有时候也需要把 OpenGL 实时显示的三维图像复制到
OpenCV 中,用 IplImage 格式保存,以便和其它图像组合起来显示或保存为视频文件。这里给出相应的例程以供参考:

首先在创建 OpenGL 窗口时,显示模式要如下设置:

[c-sharp] view plain copy print?
  1. //***OpenGL Window
  2. glutInit(&argc, argv);
  3. glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
  4. glutInitWindowPosition(10,420);
  5. glutInitWindowSize(glWinWidth, glWinHeight);
  6. glutCreateWindow("3D disparity image");

在循环中的调用:

[c-sharp] view plain copy print?
  1. //////////////////////////////////////////////////////////////////////////
  2. // OpenGL显示
  3. img3dIpl = img3d;
  4. load3dDataToGL(&img3dIpl);      // 载入需要显示的图像(视差数据)
  5. loadTextureToGL(&img1roi);      // 显示纹理
  6. glutReshapeFunc (reshape);          // 窗口变化时重绘图像
  7. glutDisplayFunc(renderScene);       // 显示三维图像
  8. glutPostRedisplay();                // 刷新画面(不用此语句则不能动态更新图像)
  9. loadPixel2IplImage(imgGL);          // 将 OpenGL 生成的像素值存储到 IplImage 中

loadGLPixelToIplImage 函数定义:

[c-sharp] view plain copy print?
  1. //////////////////////////////////////////////////////////////////////////
  2. // 将OpenGL窗口像素存储到 IplImage 中
  3. void  loadGLPixelToIplImage(IplImage* img)
  4. {
  5. const int n = 3*glWinWidth*glWinHeight;
  6. float *pixels = (float *)malloc(n * sizeof(GL_FLOAT));
  7. IplImage *tmp = cvCreateImage(cvSize(glWinWidth, glWinHeight), 8, 3);
  8. tmp->origin = CV_ORIGIN_BL;
  9. /* 后台缓存的图像数据才是我们需要复制的,若复制前台缓存会把可能的叠加在OpenGL窗口上的对象(其它窗口或者鼠标指针)也复制进去*/
  10. glReadBuffer(GL_BACK);
  11. glReadPixels(0, 0, glWinWidth, glWinHeight, GL_RGB, GL_FLOAT, pixels);
  12. int k = 0;
  13. for(int i = 0 ; i < glWinHeight; i++)
  14. {
  15. for(int j = 0 ; j < glWinWidth; j++,k+=3)
  16. {
  17. CvPoint pt = {j, glWinHeight - i - 1};
  18. uchar* temp_ptr = &((uchar*)(tmp->imageData + tmp->widthStep*pt.y))[pt.x*3];
  19. //OpenGL采用的是BGR格式,所以,读出来以后,还要换一下R<->B,才能得到RGB
  20. temp_ptr[0] = pixels[k+2] * 255; //blue
  21. temp_ptr[1] = pixels[k+1] * 255; //green
  22. temp_ptr[2] = pixels[k] * 255;   //red
  23. }
  24. }
  25. cvResize(tmp, img);
  26. // 释放内存
  27. free(pixels);
  28. cvReleaseImage(&tmp);
  29. }

显示效果如下:

图26

【计算机视觉】双目测距(六)--三维重建及UI显示的更多相关文章

  1. Activity调用静态方法改变UI,使用Handler来改变UI显示

    本人菜鸟,请各位多多指点,不足之处,请斧正.没啥技术含量,就权当丰富下mono for android的小代码. Activity调用静态方法改变UI using System; using Andr ...

  2. WPF案例 (六) 动态切换UI布局

    原文:WPF案例 (六) 动态切换UI布局 这个Wpf示例对同一个界面支持以ListView或者CardView的布局方式呈现界面,使用控件ItemsControl绑定数据源,使用DataTempla ...

  3. Win10系统下软件UI显示不完整解决方案

    在最初升级win10的时候就想到了这些问题,例如和各种软件的不兼容性.当然,事实上win10并没有想象的那么糟,作为一个windows user 来说,win10的确是很高大上的,无论是颜值或者是体验 ...

  4. 学习笔记:使用opencv做双目测距(相机标定+立体匹配+测距).

    最近在做双目测距,觉得有必要记录点东西,所以我的第一篇博客就这么诞生啦~ 双目测距属于立体视觉这一块,我觉得应该有很多人踩过这个坑了,但网上的资料依旧是云里雾里的,要么是理论讲一大堆,最后发现还不知道 ...

  5. Xamarin XAML语言教程将XAML设计的UI显示到界面

    Xamarin XAML语言教程将XAML设计的UI显示到界面 如果通过XAML将UI设计好以后,就可以将XAML中的内容显示给用户了,也就是显示到界面上.由于创建XAML文件方式的不同,所以将XAM ...

  6. 学习OpenCV双目测距原理及常见问题解答

    学习OpenCV双目测距原理及常见问题解答 转自博客:https://blog.csdn.net/angle_cal/article/details/50800775 一. 整体思路和问题转化.  图 ...

  7. salesforce 零基础学习(四十六)动态美观显示列表中记录的审批状态

    项目中,申请者申请某些事项以后,常常需要在申请列表中查看当前申请的记录所在的审批状态,动态美观的显示状态可以使UI更符合客户要求,比如下面这样. 以Goods__c表为例,申请者申请的一些采购以前需要 ...

  8. android recovery 升级UI显示之资源文件

    Recovery只有在升级的时候才会呈现给用户,所以界面一般都很简单,没有android上层那么绚丽,所以recovery下面对图片的支持很有限,仅支持png图片显示,所以我们可以看到,recover ...

  9. Cocos2dx&amp;Lua - UI显示优化之怎样解决解析大量json文件

    GUIReader中有个widgetFromJsonFile的方法,此方法是用于解析json文件(cocostudio生成的UI的)并返回该文件的父节点(Widget),然后便于进一步的UI操作(如获 ...

随机推荐

  1. 推荐一款在IntelliJ IDEA中使用微信/QQ的插件

    SmartIM SmartIM4IntelliJ 是一个 IntelliJ IDEA 上的 SmartIM(原 SmartQQ)插件,可以在 IDEA 中使用 QQ 或微信聊天. 功能 收发文本消息 ...

  2. git 在eclipse中忽略上传文件

    在我们的工程项目中,有些文件是不需要上传到服务器上的,比如那些 */target/ */bin/*.settings/*.classpath*.gitignore*.project 我们将这些文件添加 ...

  3. 使用docker配置gitlab服务器

    下载gitlab镜像,导入 [root@gitlab ~]# docker load < gitlab_zh.tar 容器需要22端口,所以修改ssh的默认端口 [root@gitlab ~]# ...

  4. border-style

    border-style 语法: border-style:<line-style>{1,4} <line-style> = none | hidden | dotted | ...

  5. 玩转orangpi 之frpc远程管理+pcd8544(nokia5110 屏幕) 显示运行状态

    玩转orangpi 之frpc远程管理+pcd8544(nokia5110 lcd) 显示运行状态. 物件: orangepi一套(电源,网线,orangepiPC)110元 nokia 5110 l ...

  6. css偷懒神奇

    偷懒神奇链接:https://qishaoxuan.github.io/css_tricks/glass/

  7. Python数据类型之数值-Python基础前传(5)

    学习任何一门学科或者手艺,最忌讳的就是想的太多,做的太少: 有很多朋友私信问我:jacky,我们该如何选择Python的课程?或是我们该如何选择Mysql课程?到底谁的课件和书籍才是最好的? 借着今天 ...

  8. java代码连接oracle数据库的方法

    oracle连接数据库的方式和mysql是大同小异的,主要的困难点在于oracle的数据库驱动包和依赖只有官方提供,如果你是用maven添加依赖的话,需要自己从官网下载jar包安装到你本地的maven ...

  9. nodeJS 项目如何运行

    nodeJS 项目如何运行 一.总结 一句话总结: nodejs项目根目录中用node xx.js 或是 node xx运行 打开 window的 cmd 命令窗口,使用 cd 命令跳转到 nodeJ ...

  10. arcgis python 列出一个表所有字段

    import arcpy inFeature = arcpy.GetParameterAsText(0) #原始数据 try: fieldList = arcpy.ListFields(inFeatu ...