• OpenCV C++双目三维重建:双目摄像头实现双目测距


    OpenCV C++双目三维重建:双目摄像头实现双目测距

    目录

    OpenCV C++双目三维重建:双目摄像头实现双目测距

    1.目录结构

    2.依赖库

     (1) Ubuntu 18.04配置开发环境

     (2) Windows配置开发环境

    3.双目相机标定

     (1)双目相机标定-Python版

     (2)双目相机标定-Matlab版

    4.相机参数配置

    5. 双目测距

    6. 运行Demo

    7.双目测距的误差说明

    8. 双目三维重建项目代码(C/C++版本)

    (1)效果图

    (2)源码下载

    9. 双目三维重建项目代码(Python版本)

    10. 双目三维重建项目代码(Android版本)

    11.参考资料


    本篇博文是《双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python​​​​​​》的续作,我们将搭建一个OpenCV C++版本的双目三维重建系统。由于我们只考虑三维重建实现双目测距效果,因而去除了PCL和Open3d库三维显示效果,但依然保留了视差图,深度图等可视化效果,用户可以通过鼠标点击图像,即可获得对应的世界坐标以及深度距离信息。

    从效果来看,C++版本的双目测距和Python版本的效果几乎一致,性能更优,速度更快,基本可以达到工业级别测距精度,可在Linux开发板运行,非常适合应用于无人机,智能小车测距避障等场景。​

    来~先看一下Demo的效果图(鼠标点击,终端会打印对应距离信息): 

    c4e2af59d2c64999bbf53e1c68832d9b.gif

    OpenCV C++双目摄像头实现双目测距主要支持:

    • 支持双USB连接线的双目摄像头
    • 支持使用WLS滤波器对视差图进行滤波
    • 支持双目测距(鼠标点击图像即可获得其深度距离)
    • 提供配套的opencv-4.3.0和opencv_contrib-4.3.0源码 (Linux系统需要自行编译;Windows10系统已提供opencv_contrib编译文件,可直接复用,无需重新编译
    • 相比Python版本,C++版本性能更优,速度更快,可在Linux开发板运行,非常适合应用于无人机,智能小车测距避障等场景。
    • 支持Linux系统:项目源码已在Ubuntu 18.04系统验证通过(需要自行编译opencv-4.3.0和opencv_contrib-4.3.0
    • 支持Windows10系统:项目源码已在Windows10系统验证通过,配套了Visual Studio 2017项目,可直接使用
    • 其他系统平台开发,请在配置好opencv和opencv_contrib开发环境

    诚然,网上有很多C++版本双测距的代码,但项目都不是十分完整,而且恢复视差图效果也一般,难以达到商业实际应用,究其原因,主要有下面几个:

    • 双目摄像头质量问题,
    • 双目标定存在问题,导致校准误差较大
    • 没有使用WLS滤波器对视差图进行滤波,该方法可以极大提高视差图的效果

    双目测距Demo视频

    如果你需要Python版本的双目测距, 请查看鄙人另一篇博客《双目三维重建:双目摄像头实现双目测距(Python

    本篇将着重介绍OpenCV C++项目实现双目测距的过程,关于双目相机标定+双目校正+双目匹配等内容,请查看鄙人另一篇博客 《双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python​​​​​​》

    【项目源码下载地址OpenCV C++双目摄像头实现双目测距

    【尊重原则,转载请注明出处】https://blog.csdn.net/guyuealian/article/details/127446435


    1.目录结构

    1. .
    2. ├── configs # 相机参数文件
    3. ├── data # 相机采集的数据
    4. ├── docs # 一些文档图片
    5. ├── src # C++源码
    6. ├── build.sh # 构建build脚本
    7. ├── main.cpp # 主程序
    8. ├── CMakeLists.txt # CMake文件
    9. └── README.md # 说明文档

    2.依赖库

     (1) Ubuntu 18.04配置开发环境

    • 系统平台:Ubuntu 18.04
    • opencv-4.3.0 (opencv-3.4.0以上亦可)
    • opencv_contrib-4.3.0 (opencv_contrib-3.4.0以上亦可),WLS滤波器需要用到opencv_contrib库

    Ubuntu 18.04 opencv安装教程,请参考文章:Ubuntu18.04安装opencv和opencv_contrib

    PS: 需确保opencv和opencv_contrib的版本号一致,避免版本差异导致编译错误。

     (2) Windows配置开发环境

    • 系统平台:Windows10
    • opencv-4.3.0 (opencv-3.4.0以上亦可)
    • opencv_contrib-4.3.0 (opencv_contrib-3.4.0以上亦可),WLS滤波器需要用到opencv_contrib库

    Windows opencv安装教程,请参考文章:Visual Studio 2017环境cmake编译opencv 4.3.0+opencv_contrib 4.3.0

    特别说明:

    1. 鄙人是在Ubuntu 18.04平台使用CLion工具进行开发(默认编码格式utf-8),其他平台可能会出现编码格式异常的问题;特别注意,如果你在Windows系统使用Visual Studio 进行开发(默认编码格式GBK),会出现如【本地函数定义是非法的】语法异常错误等问题;解决方法也很简单,只需要在属性页命令行中,添加/utf-8即可
    2. 项目配套VS工程,可直接在Visual Studio 2017使用,其他VS版本如VS2019,VS2021需要自己配置好OpenCV路径,才能正常运行
    3. 项目提供opencv 4.3.0+opencv_contrib 4.3.0,其他版本请自行编译

    项目源码已经在Ubuntu 18.04和Windows10系统进行了验证;第三方依赖库只有opencv和opencv_contrib,如果你在其他系统平台开发,请自行配置好opencv和opencv_contrib开发环境;


    3.双目相机标定

     (1)双目相机标定-Python版

    请参考鄙人另一篇博客,无需Matlab,即可进行相机标定:双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python

    该方法双目标定完成后,会得到一个双目相机内外参数信息(stereo_cam.yml)文件:

    1. %YAML:1.0
    2. ---
    3. size: !!opencv-matrix
    4. rows: 2
    5. cols: 1
    6. dt: d
    7. data: [ 640., 480. ]
    8. K1: !!opencv-matrix
    9. rows: 3
    10. cols: 3
    11. dt: d
    12. data: [ 7.6159209686584518e+02, 0., 3.2031427422505453e+02, 0.,
    13. 7.6167321445963728e+02, 2.2467546927337131e+02, 0., 0., 1. ]
    14. D1: !!opencv-matrix
    15. rows: 1
    16. cols: 5
    17. dt: d
    18. data: [ 3.4834574885170888e-02, -5.5261651661983137e-02,
    19. 5.7491952731614823e-04, -4.2764224824172658e-05,
    20. 1.8477350140315381e-02 ]
    21. K2: !!opencv-matrix
    22. rows: 3
    23. cols: 3
    24. dt: d
    25. data: [ 7.6327773941976670e+02, 0., 2.8768149948082271e+02, 0.,
    26. 7.6350419442870850e+02, 2.1897333598636970e+02, 0., 0., 1. ]
    27. D2: !!opencv-matrix
    28. rows: 1
    29. cols: 5
    30. dt: d
    31. data: [ 3.5020972475517692e-02, -4.0770660841280497e-02,
    32. -4.4231087565750534e-04, -1.0552562170995372e-03,
    33. -9.7749906830348537e-02 ]
    34. R: !!opencv-matrix
    35. rows: 3
    36. cols: 3
    37. dt: d
    38. data: [ 9.9999370552351063e-01, 7.8563885326366346e-04,
    39. 3.4600122760633780e-03, -7.9503151737356746e-04,
    40. 9.9999600079883766e-01, 2.7140949167922721e-03,
    41. -3.4578661403601796e-03, -2.7168286517956050e-03,
    42. 9.9999033095517087e-01 ]
    43. T: !!opencv-matrix
    44. rows: 3
    45. cols: 1
    46. dt: d
    47. data: [ -6.0005833133148414e+01, 1.7047017063672587e-01,
    48. 6.0300223404957642e-01 ]
    49. E: !!opencv-matrix
    50. rows: 3
    51. cols: 3
    52. dt: d
    53. data: [ -1.1005724987007073e-04, -6.0346296076620343e-01,
    54. 1.6883191705475561e-01, 3.9550629985097430e-01,
    55. -1.6255182474732952e-01, 6.0007339329190145e+01,
    56. -1.2276256904913259e-01, -6.0005727085740176e+01,
    57. -1.6345135556766910e-01 ]
    58. F: !!opencv-matrix
    59. rows: 3
    60. cols: 3
    61. dt: d
    62. data: [ -6.7250769136371160e-10, -3.6870834234286016e-06,
    63. 1.6143104894409041e-03, 2.4160347372858321e-06,
    64. -9.9287680075344234e-07, 2.7862421257891157e-01,
    65. -1.1014218394645766e-03, -2.7856049650040260e-01, 1. ]
    66. R1: !!opencv-matrix
    67. rows: 3
    68. cols: 3
    69. dt: d
    70. data: [ 9.9997618806974742e-01, -2.0278309638726887e-03,
    71. -6.5963016213173775e-03, 2.0367881225372914e-03,
    72. 9.9999701250432615e-01, 1.3514719999064883e-03,
    73. 6.5935413581266105e-03, -1.3648750875444691e-03,
    74. 9.9997733090723306e-01 ]
    75. R2: !!opencv-matrix
    76. rows: 3
    77. cols: 3
    78. dt: d
    79. data: [ 9.9994547731576255e-01, -2.8407384289991728e-03,
    80. -1.0048512373976153e-02, 2.8270879178959596e-03,
    81. 9.9999506202764499e-01, -1.3724045434755307e-03,
    82. 1.0052361397026631e-02, 1.3439216883706559e-03,
    83. 9.9994857062992937e-01 ]
    84. P1: !!opencv-matrix
    85. rows: 3
    86. cols: 4
    87. dt: d
    88. data: [ 7.3741438842621210e+02, 0., 3.1126281356811523e+02, 0., 0.,
    89. 7.3741438842621210e+02, 2.2189782714843750e+02, 0., 0., 0., 1.,
    90. 0. ]
    91. P2: !!opencv-matrix
    92. rows: 3
    93. cols: 4
    94. dt: d
    95. data: [ 7.3741438842621210e+02, 0., 3.1126281356811523e+02,
    96. -4.4251577456670653e+04, 0., 7.3741438842621210e+02,
    97. 2.2189782714843750e+02, 0., 0., 0., 1., 0. ]
    98. Q: !!opencv-matrix
    99. rows: 4
    100. cols: 4
    101. dt: d
    102. data: [ 1., 0., 0., -3.1126281356811523e+02, 0., 1., 0.,
    103. -2.2189782714843750e+02, 0., 0., 0., 7.3741438842621210e+02, 0.,
    104. 0., 1.6664137886344466e-02, 0. ]

    参数说明: 

    • 参数size,对应图像宽高(width,height)
    • 参数K1,对应左目相机内参矩阵(3×3)
    • 参数D1,对应左目相机畸变系数矩阵(5×1)
    • 参数K2,对应右目相机内参矩阵(3×3)
    • 参数D2,对应右目相机畸变系数矩阵(5×1)
    • 参数T,对应双目相机平移向量T(3×1)
    • 参数R,对应双目相机旋转矩阵R(3×3)
    • 至于配置文件中的参数,如R1, R2, P1, P2, Q这些重投影矩阵,可默写即可,不用修改,这些在运行时,会重新计算。

     (2)双目相机标定-Matlab版

    网上已经存在很多Matlab双目相机标定的教程,请自行百度哈 ;使用Matlab工具箱进行双目相机标定后,请对应参数进行修改

    需要注意的是:旋转矩阵R是(3×3)二维矩阵,而Matlab给出的是旋转向量om(1×3),请使用cv2.Rodrigues()将旋转向量转为旋转矩阵,参考下面的代码进行转换

    1. import cv2
    2. import numpy as np
    3. # 定义旋转矩阵R,旋转向量om
    4. R = [[9.9999370551606337e-01, 7.8563882630048958e-04, 3.4600144345510440e-03],
    5. [-7.9503149273969136e-04, 9.9999600080163187e-01, 2.7140938945082542e-03],
    6. [-3.4578682997252063e-03, -2.7168276311286426e-03, 9.9999033095047696e-01]]
    7. R = np.asarray(R)
    8. print(f"旋转矩阵R:\n {R}")
    9. # 把旋转矩阵R转化为旋转向量om
    10. om, _ = cv2.Rodrigues(R)
    11. print(f"旋转向量om:\n {om}")
    12. # 把旋转向量om转换为旋转矩阵R
    13. R1, _ = cv2.Rodrigues(om)
    14. print(f"旋转矩阵R1:\n {R1}")

    4.相机参数配置

    • 双目相机标定完成后,得到了相机内外参数信息
    • 根据自己相机参数定义C++的CameraParam即可
    • 下面C++代码中,定义了双目相机CameraParam变量camera1,用户需要根据自己的双目相机,修改对应的相机内外参数。
    1. /**
    2. * 双目摄像头的相机参数
    3. */
    4. struct CameraParam {
    5. int width; //图像的宽度width
    6. int height; //图像的高度height
    7. Mat cameraMatrixL; //左相机内参K1(3×3)
    8. Mat distCoeffL; //左相机畸变系数D1(5×1)
    9. Mat cameraMatrixR; //右相机内参K2(3×3)
    10. Mat distCoeffR; //右相机畸变系数D2(5×1)
    11. Mat T; //平移向量T(3×1)
    12. Mat R; //旋转矩阵R(3×3),如果是(3×1)旋转向量,请使用cv::Rodrigues()进行变换转为(3×3)旋转矩阵R
    13. };
    14. /***
    15. * 设置摄像头参数,需要根据双目摄像头标定结果进行填写
    16. */
    17. static CameraParam camera1 = {
    18. 640,//width
    19. 480,//height
    20. (Mat_<double>(3, 3)
    21. << 7.6159209686633153e+02, 0., 3.2031427422691633e+02, 0., 7.6167321446015626e+02, 2.2467546926913309e+02, 0., 0., 1.),//cameraMatrixL
    22. (Mat_<double>(5, 1)
    23. << 3.4834574887256914e-02, -5.5261651680159028e-02, 5.7491952534806736e-04, -4.2764223950233445e-05, 1.8477350164208820e-02),//distCoeffL
    24. (Mat_<double>(3, 3)
    25. << 7.6327773983796783e+02, 0., 2.8768149776326379e+02, 0., 7.6350419482215057e+02, 2.1897333669573928e+02, 0., 0., 1.),
    26. (Mat_<double>(5, 1)
    27. << 3.5020967512300320e-02, -4.0770565902033332e-02, -4.4231049297594003e-04, -1.0552565496142535e-03, -9.7750314807571667e-02),
    28. (Mat_<double>(3, 1)
    29. << -6.0005833075452117e+01, 1.7047023105446815e-01, 6.0300273851103448e-01),
    30. (Mat_<double>(3, 3)
    31. << 9.9999370551606337e-01, 7.8563882630048958e-04, 3.4600144345510440e-03, -7.9503149273969136e-04, 9.9999600080163187e-01, 2.7140938945082542e-03, -3.4578682997252063e-03, -2.7168276311286426e-03, 9.9999033095047696e-01),
    32. };

    5. 双目测距

    OpenCV C++版本的双目测距与Python版本双目测距的效果几乎一致,且性能更优,速度更快,基本可以达到工业级别测距精度。由于我们只考虑三维重建实现双目测距效果,因而去除了PCL和Open3d库三维显示效果,但依然保留了视差图,深度图等可视化效果,用户可以通过鼠标点击图像,即可获得对应的世界坐标以及深度距离信息。

    函数接口声明,都已经给出了详细的参数说明,为了方便大家学习,函数命名和实现逻辑与Python版本的几乎一致:

    1. //
    2. // Created by 390737991@qq.com on 2022/10/6.
    3. //
    4. #ifndef CAMERA_CALIBRATION_RECONSTRUCT_CPP_STEREO_RECONSTRUCT_H
    5. #define CAMERA_CALIBRATION_RECONSTRUCT_CPP_STEREO_RECONSTRUCT_H
    6. #include
    7. #include
    8. using namespace std;
    9. using namespace cv;
    10. cv::Mat xyz_coord; //用于存放每个像素点距离相机镜头的三维坐标
    11. cv::Point start; //鼠标按下的起始点
    12. cv::Rect buttonRect; //定义矩形选框
    13. bool buttonStatus = false; //是否选择对象
    14. /**
    15. * 双目摄像头的相机参数
    16. */
    17. struct CameraParam {
    18. int width; //图像的宽度width
    19. int height; //图像的高度height
    20. Mat cameraMatrixL; //左相机内参K1(3×3)
    21. Mat distCoeffL; //左相机畸变系数D1(5×1)
    22. Mat cameraMatrixR; //右相机内参K2(3×3)
    23. Mat distCoeffR; //右相机畸变系数D2(5×1)
    24. Mat T; //平移向量T(3×1)
    25. Mat R; //旋转矩阵R(3×3),如果是(3×1)旋转向量,请使用cv::Rodrigues()进行变换转为(3×3)旋转矩阵R
    26. };
    27. /***
    28. * 设置摄像头参数,需要根据双目摄像头标定结果进行填写
    29. */
    30. static CameraParam camera1 = {640,//width
    31. 480,//height
    32. (Mat_<double>(3, 3)
    33. << 7.6159209686633153e+02, 0., 3.2031427422691633e+02, 0., 7.6167321446015626e+02, 2.2467546926913309e+02, 0., 0., 1.),//cameraMatrixL
    34. (Mat_<double>(5, 1)
    35. << 3.4834574887256914e-02, -5.5261651680159028e-02, 5.7491952534806736e-04, -4.2764223950233445e-05, 1.8477350164208820e-02),//distCoeffL
    36. (Mat_<double>(3, 3)
    37. << 7.6327773983796783e+02, 0., 2.8768149776326379e+02, 0., 7.6350419482215057e+02, 2.1897333669573928e+02, 0., 0., 1.),
    38. (Mat_<double>(5, 1)
    39. << 3.5020967512300320e-02, -4.0770565902033332e-02, -4.4231049297594003e-04, -1.0552565496142535e-03, -9.7750314807571667e-02),
    40. (Mat_<double>(3, 1)
    41. << -6.0005833075452117e+01, 1.7047023105446815e-01, 6.0300273851103448e-01),
    42. (Mat_<double>(3, 3)
    43. << 9.9999370551606337e-01, 7.8563882630048958e-04, 3.4600144345510440e-03, -7.9503149273969136e-04, 9.9999600080163187e-01, 2.7140938945082542e-03, -3.4578682997252063e-03, -2.7168276311286426e-03, 9.9999033095047696e-01),
    44. };
    45. /***
    46. * 鼠标响应回调函数
    47. * @param event
    48. * @param x
    49. * @param y
    50. */
    51. static void onMouse(int event, int x, int y, int, void *) {
    52. if (buttonStatus) {
    53. buttonRect.x = MIN(x, start.x);
    54. buttonRect.y = MIN(y, start.y);
    55. buttonRect.width = std::abs(x - start.x);
    56. buttonRect.height = std::abs(y - start.y);
    57. }
    58. switch (event) {
    59. case EVENT_LBUTTONDOWN: //鼠标左按钮按下的事件
    60. start = Point(x, y);
    61. buttonRect = Rect(x, y, 0, 0);
    62. buttonStatus = true;
    63. cout << "image(x,y)=" << start;
    64. cout << " world coords=(x,y,depth)=" << xyz_coord.at(start) << endl;
    65. break;
    66. case EVENT_LBUTTONUP: //鼠标左按钮释放的事件
    67. buttonStatus = false;
    68. if (buttonRect.width > 0 && buttonRect.height > 0)
    69. break;
    70. }
    71. }
    72. /***
    73. * 显示图像
    74. * @param winname 窗口名称
    75. * @param image 图像
    76. * @param delay 显示延迟,0表示阻塞显示
    77. * @param flags 显示方式
    78. */
    79. static void show_image(const string &winname, cv::Mat &image, int delay = 0, int flags = cv::WINDOW_AUTOSIZE) {
    80. cv::namedWindow(winname, flags);
    81. cv::imshow(winname, image);
    82. cv::waitKey(delay);
    83. }
    84. /***
    85. * 读取视频文件
    86. * @param video_file 视频文件
    87. * @param cap 视频流对象
    88. * @param width 设置图像的宽度
    89. * @param height 设置图像的高度
    90. * @param fps 设置视频播放频率
    91. * @return
    92. */
    93. bool get_video_capture(string video_file, cv::VideoCapture &cap, int width = -1, int height = -1, int fps = -1) {
    94. //VideoCapture video_cap;
    95. cap.open(video_file);
    96. if (width > 0 && height > 0) {
    97. cap.set(cv::CAP_PROP_FRAME_WIDTH, width); //设置图像的宽度
    98. cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); //设置图像的高度
    99. }
    100. if (fps > 0) {
    101. cap.set(cv::CAP_PROP_FPS, fps);
    102. }
    103. if (!cap.isOpened())//判断是否读取成功
    104. {
    105. return false;
    106. }
    107. return true;
    108. }
    109. /***
    110. * 读取摄像头
    111. * @param camera_id 摄像头ID号,默认从0开始
    112. * @param cap 视频流对象
    113. * @param width 设置图像的宽度
    114. * @param height 设置图像的高度
    115. * @param fps 设置视频播放频率
    116. * @return
    117. */
    118. bool get_video_capture(int camera_id, cv::VideoCapture &cap, int width = -1, int height = -1, int fps = -1) {
    119. //VideoCapture video_cap;
    120. cap.open(camera_id); //摄像头ID号,默认从0开始
    121. if (width > 0 && height > 0) {
    122. cap.set(cv::CAP_PROP_FRAME_WIDTH, width); //设置捕获图像的宽度
    123. cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); //设置捕获图像的高度
    124. }
    125. if (fps > 0) {
    126. cap.set(cv::CAP_PROP_FPS, fps);
    127. }
    128. if (!cap.isOpened()) //判断是否成功打开相机
    129. {
    130. return false;
    131. }
    132. return true;
    133. }
    134. class StereoReconstruct {
    135. public:
    136. /***
    137. * 构造函数,初始化StereoReconstruct
    138. * @param camera 双目相机参数
    139. * @param use_wls 是否使用WLS滤波器对视差图进行滤波
    140. */
    141. StereoReconstruct(CameraParam camera, bool use_wls = true);
    142. /***
    143. * release
    144. */
    145. ~StereoReconstruct();
    146. /***
    147. * 开始双目测距任务
    148. * @param frameL
    149. * @param frameR
    150. */
    151. void task(Mat frameL, Mat frameR, int delay = 0);
    152. /***
    153. * 畸变校正和立体校正
    154. * @param imgL 左视图
    155. * @param imgR 右视图
    156. * @param rectifiedL 校正后左视图
    157. * @param rectifiedR 校正后右视图
    158. */
    159. void get_rectify_image(Mat &imgL, Mat &imgR, Mat &rectifiedL, Mat &rectifiedR);
    160. /***
    161. * 获得视差图
    162. * @param imgL 畸变校正和立体校正后的左视图
    163. * @param imgR 畸变校正和立体校正后的右视图
    164. * @param dispL 返回视差图
    165. * @param use_wls 是否使用WLS滤波器对视差图进行滤波
    166. */
    167. void get_disparity(Mat &imgL, Mat &imgR, Mat &dispL, bool use_wls = true);//SGBM匹配算法
    168. /***
    169. * 计算像素点的3D坐标(左相机坐标系下)
    170. * @param disp 视差图
    171. * @param points_3d 返回三维坐标points_3d,三个通道分布表示(X,Y,Z),其中Z是深度图depth, 即距离,单位是毫米(mm)
    172. * @param scale 单位变换尺度,默认scale=1.0,单位为毫米
    173. */
    174. void get_3dpoints(Mat &disp, Mat &points_3d, float scale = 1.0);
    175. /***
    176. * 将输入深度图转换为伪彩色图,方面可视化
    177. * @param depth
    178. * @param colormap
    179. */
    180. void get_visual_depth(cv::Mat &depth, cv::Mat &colormap, float clip_max = 6000.0);
    181. /***
    182. * 显示矫正效果
    183. * @param rectifiedL
    184. * @param rectifiedR
    185. */
    186. void show_rectify_result(cv::Mat rectifiedL, cv::Mat rectifiedR);
    187. /***
    188. * 可视化视差图和深度图
    189. * @param frameL
    190. * @param frameR
    191. * @param points_3d
    192. * @param disp
    193. * @param delay
    194. */
    195. void show_2dimage(Mat &frameL, Mat &frameR, Mat &points_3d, Mat &disp, int delay);
    196. /***
    197. * 显示Mat的最大最小值
    198. * @param src
    199. * @param vmin 最小值下限
    200. * @param vmax 最大值下限
    201. */
    202. void clip(cv::Mat &src, float vmin, float vmax);
    203. /***
    204. * 显示Mat的最大最小值
    205. * @param src
    206. * @param th
    207. * @param vmin
    208. */
    209. void clip_min(cv::Mat &src, float th, float vmin);
    210. public:
    211. string depth_windows = "depth-color"; // 深度图的窗口名称
    212. int use_wls; // 是否使用WLS滤波器对视差图进行滤波
    213. Size image_size; // 图像宽高(width,height)
    214. Rect validROIL; // 图像校正之后,会对图像进行裁剪,这里的左视图裁剪之后的区域
    215. Rect validROIR; // 图像校正之后,会对图像进行裁剪,这里的右视图裁剪之后的区域
    216. Mat mapLx, mapLy, mapRx, mapRy; // 映射表
    217. Mat Rl, Rr, Pl, Pr, Q; // 校正后的旋转矩阵R,投影矩阵P, 重投影矩阵Q
    218. cv::Ptr sgbm;
    219. };
    220. #endif //CAMERA_CALIBRATION_RECONSTRUCT_CPP_STEREO_RECONSTRUCT_H

    6. 运行Demo

    • 主程序main.cpp实现了三个Demo
    1. 测试demo视频文件: 这是使用摄像头录制的双目视频文件,用于测试效果双目测距的效果
    2. 测试双目摄像头(双USB连接线的双目摄像头):用于测试双目摄像头,需要根据自己的摄像头修改ID号
    3. 测试一对左右相机图像效果
    1. //
    2. // 双目测距Demo
    3. // Created by AI吃大瓜 on 2022/10/6.
    4. //
    5. #include
    6. #include
    7. #include "stereo_reconstruct.h"
    8. /***
    9. * 测试demo视频文件
    10. * @return
    11. */
    12. int test_video_file() {
    13. CameraParam camera = camera1;//双目相机参数
    14. bool use_wls = true; //是否使用WLS滤波器对视差图进行滤波
    15. StereoReconstruct *detector = new StereoReconstruct(camera, use_wls);
    16. int imageWidth = camera1.width; //单目图像的宽度
    17. int imageHeight = camera1.height; //单目图像的高度
    18. string left_video = "../data/lenacv-video/left_video.avi";
    19. string right_video = "../data/lenacv-video/right_video.avi";
    20. VideoCapture capL, capR;
    21. bool retL = get_video_capture(left_video, capL, imageWidth, imageHeight);
    22. bool retR = get_video_capture(right_video, capR, imageWidth, imageHeight);
    23. Mat frameL, frameR;
    24. while (retL && retR) {
    25. capL >> frameL;
    26. capR >> frameR;
    27. if (frameL.empty() or frameR.empty()) break;
    28. detector->task(frameL, frameR, 20);
    29. }
    30. capL.release(); //释放对相机的控制
    31. capR.release(); //释放对相机的控制
    32. delete detector;
    33. return 0;
    34. }
    35. /***
    36. * 测试双目摄像头(双USB连接线的双目摄像头)
    37. * @return
    38. */
    39. int test_camera() {
    40. CameraParam camera = camera1;//双目相机参数
    41. bool use_wls = true; //是否使用WLS滤波器对视差图进行滤波
    42. StereoReconstruct *detector = new StereoReconstruct(camera, use_wls);
    43. int imageWidth = camera1.width; //单目图像的宽度
    44. int imageHeight = camera1.height; //单目图像的高度
    45. int camera1 = 0; //左摄像头ID号(请修改成自己左摄像头ID号)
    46. int camera2 = 1; //右摄像头ID号(请修改成自己右摄像头ID号)
    47. VideoCapture capL, capR;
    48. bool retL = get_video_capture(camera1, capL, imageWidth, imageHeight);
    49. bool retR = get_video_capture(camera2, capR, imageWidth, imageHeight);
    50. Mat frameL, frameR;
    51. while (retL && retR) {
    52. capL >> frameL;
    53. capR >> frameR;
    54. if (frameL.empty() or frameR.empty()) break;
    55. detector->task(frameL, frameR, 20);
    56. }
    57. capL.release(); //释放对相机的控制
    58. capR.release(); //释放对相机的控制
    59. delete detector;
    60. return 0;
    61. }
    62. /***
    63. * 测试一对左右图像
    64. * @return
    65. */
    66. int test_pair_image_file() {
    67. CameraParam camera = camera1;//双目相机参数
    68. bool use_wls = true; //是否使用WLS滤波器对视差图进行滤波
    69. StereoReconstruct *detector = new StereoReconstruct(camera, use_wls);
    70. Mat frameL = imread("../data/left.png", IMREAD_COLOR);
    71. Mat frameR = imread("../data/right.png", IMREAD_COLOR);
    72. detector->task(frameL, frameR, 0);
    73. delete detector;
    74. return 0;
    75. }
    76. int main() {
    77. //测试一对左右图像
    78. test_pair_image_file();
    79. //测试demo视频文件
    80. test_video_file();
    81. //测试双目摄像头(双USB连接线的双目摄像头)
    82. test_camera();
    83. return 0;
    84. }
    • 终端运行脚本:bash build.sh
    1. #!/usr/bin/env bash
    2. if [ ! -d "build/" ];then
    3. mkdir "build"
    4. else
    5. echo "exist build"
    6. fi
    7. cd build
    8. cmake ..
    9. make -j4
    10. sleep 1
    11. ./Demo

    7.双目测距的误差说明

     双目测距的误差和精度说明:

    • 有网友反馈,测量精度较差,在评估测量精度前,请严格按照博文进行相机标定,标定误差不能超过0.1,否则测距误差较大
    • 理论上双目的测量精度可以达到毫米(mm)级别,但并非无条件的,根据上式可以看出,某点像素的深度精度取决于该点处估计的视差d的精度。假设视差d的误差恒定,当测量距离越远,得到的深度精度则越差,因此使用双目相机不适宜测量太远的目标。
    • 如果想要对与较远的目标能够得到较为可靠的深度,一方面需要提高相机的基线距离,但是基线距离越大,左右视图的重叠区域就会变小,内容差异变大,从而提高立体匹配的难度,另一方面可以选择更大焦距的相机,然而焦距越大,相机的视域则越小,导致离相机较近的物体的距离难以估计。
    • 理论上,深度方向的测量误差与测量距离的平方成正比,而X/Y方向的误差与距离成正比;而距离很近时,由于存在死角,会导致难以匹配的问题;想象一下,如果你眼前放置一块物体,那你左眼只能看到物体左侧表面,右眼同理只能看到物体右侧表面,这时由于配准失败,导致视差计算失败;这个问题在基线越长,问题就越严重
    • 下图给出双目测距误差和测量距离的关系,一般有效的测量距离是0.6米到6米之间

    8. 双目三维重建项目代码(C/C++版本)

    (1)效果图

    C++版本的双目测距与Python版本的效果几乎一致。从重建效果来看,未使用WLS滤波,其视差图出现了很多空洞,存在很多误匹配点;但使用WLS滤波后,视差图变得比较平滑,整体效果都有明显改善。

    左视图右视图
    5a3336fbf4af441a841d588af792f80a.pngebd6856f807944fcb860151375cd4e87.png
    视差图(未滤波)深度图(未滤波)
    f8d06f5f839142eca3b921b19661c18b.pngd2b1c330bc164790abbc72ead1ee7bd5.png
    视差图(滤波后)深度图(滤波后)
    5e265a93d39442ba9a861a3090bd2b58.pngb883a3e05140451499e1aeb99ae12434.png
    •  运行主程序后,鼠标点击depth-color窗口的图像任意区域,终端会打印对应距离信息

    132861d6b5fd489f9e35ea7926eb5857.gif


    (2)源码下载

    OpenCV C++版本双目测距项目代码包含:OpenCV C++双目摄像头实现双目测距

     【项目源码下载地址OpenCV C++双目摄像头实现双目测距

    • 支持双USB连接线的双目摄像头
    • 支持使用WLS滤波器对视差图进行滤波
    • 支持双目测距(鼠标点击图像即可获得其深度距离)
    • 提供配套的opencv-4.3.0和opencv_contrib-4.3.0源码 (Linux系统需要自行编译;Windows10系统已提供opencv_contrib编译文件,可直接复用,无需重新编译
    • 相比Python版本,C++版本性能更优,速度更快,可在Linux开发板运行,非常适合应用于无人机,智能小车测距避障等场景。
    • 支持Linux系统:项目源码已在Ubuntu 18.04系统验证通过(需要自行编译opencv-4.3.0和opencv_contrib-4.3.0
    • 支持Windows10系统:项目源码已在Windows10系统验证通过,配套了Visual Studio 2017项目,可直接使用;其他VS版本,如VS2019,VS2021需要自己配置好OpenCV路径,才能正常运行
    • 其他系统平台开发,请在配置好opencv和opencv_contrib开发环境

    9. 双目三维重建项目代码(Python版本)

    如果你需要Python版本的双目测距, 请查看鄙人另一篇博客《双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python

    双目测距Demo视频


    10. 双目三维重建项目代码(Android版本)

    如果你需要Android版本的双目测距, 请查看鄙人另一篇博客《Android OpenCV实现双目三维重建:双目摄像头实现双目测距

           


    11.参考资料

    1. OpenCV C++双目三维重建:双目摄像头实现双目测距
    2. 双目三维重建:双目摄像头实现双目测距(Python)
    3. 双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python
    4. Ubuntu18.04安装opencv和opencv_contrib
  • 相关阅读:
    HCSC: Hierarchical Contrastive Selective Coding
    推荐20个开源的不错前端低代码项目
    从Matlab实例学习蚁群算法(2)
    Ubuntu配置OpenCV及多版本OpenCV共存
    AIGC ChatGPT 4 与 Python 进行数据分析与可视化
    【ChatGPT】如何修复access denied you do not have access to chat.openai.com
    第十一章 将对象映射到 XML - 控制流属性的映射形式
    android代码中打开系统设置界面(无线和网络设置,日期和时间设置, 辅助功能设置,电池信息)
    WinDbg 远程调试遇到IP为:169.254.xx.xx 的处理
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java计算机专业建设管理系统3286d
  • 原文地址:https://blog.csdn.net/guyuealian/article/details/127446435