从玩具小车到机器人:如何用你的双目摄像头(OpenCV/C++)生成第一张深度图?
从玩具小车到机器人:如何用你的双目摄像头(OpenCV/C++)生成第一张深度图?
当你第一次看到玩具小车自动避开障碍物时,是否好奇它如何"看见"三维世界?双目视觉正是赋予机器深度感知能力的关键技术。本文将带你从标定文件开始,逐步实现立体匹配、深度计算与点云生成,最终让简单的机器人获得环境感知能力。
1. 双目视觉基础与环境准备
双目摄像头通过模拟人眼视差原理获取深度信息。两个摄像头拍摄同一场景的左右视图,通过特征点匹配计算视差(disparity),再根据三角测量原理转换为深度值。整个过程涉及三个核心环节:
- 立体校正:消除镜头畸变并将图像对齐到同一平面
- 立体匹配:建立左右图像像素对应关系
- 深度计算:通过视差图生成深度图
开发环境配置(Ubuntu 20.04为例):
# 安装OpenCV和必要工具 sudo apt install build-essential cmake libopencv-dev python3-opencv验证安装:
#include <opencv2/opencv.hpp> int main() { std::cout << "OpenCV版本: " << CV_VERSION << std::endl; return 0; }2. 从标定文件到立体校正
假设已完成标定并得到stereo_calib.yml文件,关键参数包括:
| 参数类型 | 变量名 | 作用描述 |
|---|---|---|
| 相机内参 | cameraMatrix | 焦距、主点坐标等固有参数 |
| 畸变系数 | distCoeffs | 径向和切向畸变校正参数 |
| 旋转矩阵 | R | 左右相机坐标系间的旋转关系 |
| 平移向量 | T | 左右相机间的基线距离(毫米) |
| 重投影矩阵 | Q | 将视差转换为深度的关键矩阵 |
加载标定数据:
cv::FileStorage fs("stereo_calib.yml", cv::FileStorage::READ); cv::Mat cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, R, T, Q; fs["left_camera_matrix"] >> cameraMatrix1; fs["left_distortion_coefficients"] >> distCoeffs1; // 同理读取右相机参数...立体校正实现:
cv::Mat R1, R2, P1, P2; cv::stereoRectify(cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q); // 生成校正映射 cv::Mat map11, map12, map21, map22; cv::initUndistortRectifyMap(cameraMatrix1, distCoeffs1, R1, P1, imageSize, CV_16SC2, map11, map12); // 右相机同理...3. 立体匹配与深度图生成
OpenCV提供多种立体匹配算法,这里以SGBM(半全局块匹配)为例:
cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create( 0, // minDisparity 96, // numDisparities 5, // blockSize 600, // P1 2400, // P2 10, // disp12MaxDiff 16, // preFilterCap 1, // uniquenessRatio 100, // speckleWindowSize 32, // speckleRange cv::StereoSGBM::MODE_SGBM_3WAY ); cv::Mat disparity_sgbm; sgbm->compute(leftRectified, rightRectified, disparity_sgbm);关键参数调优建议:
- numDisparities:必须是16的整数倍,值越大检测范围越远
- blockSize:奇数,3-11之间,过大导致边缘模糊
- P1/P2:控制视差平滑度,P2通常为P1的3-4倍
视差转深度图:
cv::Mat depthMap; cv::reprojectImageTo3D(disparity_sgbm, depthMap, Q, true);4. 点云可视化与机器人集成
使用PCL库可视化点云:
#include <pcl/visualization/cloud_viewer.h> void showPointCloud(const cv::Mat& depthMap) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); for(int y=0; y<depthMap.rows; y+=2) { // 降采样 for(int x=0; x<depthMap.cols; x+=2) { cv::Vec3f point = depthMap.at<cv::Vec3f>(y,x); if(fabs(point[2]) < 1000) // 过滤无效点 cloud->points.push_back(pcl::PointXYZ(point[0],point[1],point[2])); } } pcl::visualization::CloudViewer viewer("Point Cloud"); viewer.showCloud(cloud); while(!viewer.wasStopped()) {} }机器人避障应用示例:
bool checkObstacle(const cv::Mat& depthMap, float threshold=0.5) { cv::Mat roi = depthMap(cv::Rect(depthMap.cols/4, depthMap.rows/2, depthMap.cols/2, depthMap.rows/3)); double minDepth; cv::minMaxLoc(roi, &minDepth, nullptr); return minDepth < threshold; // 单位:米 }5. 实战优化与性能提升
常见问题解决方案:
边缘锯齿严重:
- 尝试WLS滤波器(加权最小二乘):
cv::Ptr<cv::ximgproc::DisparityWLSFilter> wls_filter; wls_filter = cv::ximgproc::createDisparityWLSFilter(sgbm); cv::Mat filtered_disp; wls_filter->filter(disparity_sgbm, leftRectified, filtered_disp);实时性优化:
- 降低图像分辨率(640x480足够)
- 使用CUDA加速:
cv::Ptr<cv::cuda::StereoBM> stereo_bm = cv::cuda::createStereoBM(64,15); cv::cuda::GpuMat d_left, d_right, d_disp; d_left.upload(leftRectified); d_right.upload(rightRectified); stereo_bm->compute(d_left, d_right, d_disp);光照影响:
- 使用直方图均衡化预处理:
cv::Mat leftGray, rightGray; cv::cvtColor(leftRectified, leftGray, cv::COLOR_BGR2GRAY); cv::equalizeHist(leftGray, leftGray); // 右图同理...
深度图后处理技巧:
// 中值滤波去噪 cv::medianBlur(depthMap, depthMap, 5); // 空洞填充(适用于静态场景) void fillHoles(cv::Mat& depth) { cv::Mat mask = (depth == 0); cv::inpaint(depth, mask, depth, 3, cv::INPAINT_TELEA); }