opencv实现图像去畸变——几种实现方式(含完整代码)&&效果对比图&&详细参数说明&&核心参数变化对应变化效果图&&常见问题
以下介绍下opencv实现图像去畸变的几种方式以及详细参数说明,含项目案例,含扩展的相关知识
① cv::fisheye::initUndistortRectifyMap 和 ② cv::initUndistortRectifyMap 都是 OpenCV 库中的函数,用于摄像机的畸变校正和图像的矫正。二者的区别在于,cv::fisheye::initUndistortRectifyMap 适用于鱼眼相机的畸变校正和图像矫正,而 cv::initUndistortRectifyMap 适用于普通相机的畸变校正和图像矫正。
具体来说,cv::fisheye::initUndistortRectifyMap 函数可以用于鱼眼相机的畸变校正和图像矫正,它的参数包括输入图像的大小、相机内参矩阵、畸变系数、旋转矩阵和投影矩阵等。函数的返回值是两个映射矩阵,可以用于后续的图像矫正操作。
而 cv::initUndistortRectifyMap 函数则可以用于普通相机的畸变校正和图像矫正,它的参数包括输入图像的大小、相机内参矩阵、畸变系数、旋转矩阵和投影矩阵等。函数的返回值同样是两个映射矩阵,可以用于后续的图像矫正操作。
需要注意的是,cv::fisheye::initUndistortRectifyMap 函数适用于鱼眼相机的畸变校正和图像矫正,但是它的运行速度相对较慢,因此在实际应用中需要根据具体情况选择合适的函数。cv::fisheye::initUndistortRectifyMap效果如下图:
去畸变前:
去畸变后:
另外,介绍一个效果比较好的扩展库:opencv_contrib。提供③ cv::omnidir::initUndistortRectifyMap接口实现去畸变效果,如下图:
去畸变前:
去畸变后:
以下则以方案 ③为例更为详细的说明一下:
- 具体参数(以下示例参数是1920*1080分辨率的)
{ "distcoeff":{ "distortMatrix":[ 1.1445103126389398, -1.5381069838860892, -0.0002758264626124696, 0.0005343518274311642, 0 ] }, "extrinsic":{ "rotation":{ "w":-0.4503108282902849, "x":0.54120891939059612, "y":-0.531561574479537, "z":0.47090907406145438 }, "translation":{ "x":4.6313664462542317, "y":0.016680599912867258, "z":0.85922674428639156 } }, "intrinsic":{ // 以下"matrix"(相机内参需要根据分辨率的不同动态变化, 需要重新计算或者对鱼眼相机重新标定) "height":1080, "matrix":[ 1559.081393096325, -0.05575693563188319, 964.2708291230333, 0, 1557.3809973911518, 519.5834214435208, 0, 0, 1 ], "width":1920 }, "name":"H60L-D12310650", "topic":"/sensor/camera/sensing/image_raw_fisheye_front", "type":"SENSING_195", "xi":2.045049153257521 }
- 以下是两种方式对应的完整c++代码实现(注意:扩展库需要依赖lib库&拷贝dll库)
lib: opencv_world460.lib, opencv_ccalib460.lib这两个
dll:如下这几个依赖库
下面是两个版本的代码,可以对比着看效果更好:
#include #include #include #include #include /*********************************************去畸变全局数据开始************************************************************/ /**内参矩阵K * fx 0 cx * 0 fy cy * 0 0 1 */ double g_fx = 1541.2289276548197, g_fy = 1541.4827807859522, g_cx = 952.65903854976341, g_cy = 530.2873317817764; // 畸变参数 double g_k1 = 1.1691361330036689, g_k2 = -1.5203988059068219, g_p1 = 0.001154300156796058, g_p2 = -0.00094038834164979646; cv::Mat g_map1, g_map2; const double g_alpha = 1; const double g_undistor_banlence = 0; // 控制输出图片尺寸, 根据需求调整即可 int g_centerX = 0; int g_centerY = 0; /*********************************************去畸变全局数据结束************************************************************/ void OpenGLDisplay::dealDedistort() { cv::Mat k = cv::Mat::eye(3, 3, CV_32FC1); // 内参矩阵 k.at(0, 0) = g_fx; k.at(0, 2) = g_cx; k.at(1, 1) = g_fy; k.at(1, 2) = g_cy; cv::Mat d = cv::Mat::zeros(1, 4, CV_32FC1); // 畸变系数矩阵 顺序是[k1, k2, p1, p2] d.at(0, 0) = g_k1; d.at(0, 1) = g_k2; d.at(0, 2) = g_p1; d.at(0, 3) = g_p2; cv::Mat xi = cv::Mat::zeros(1, 1, CV_32FC1); xi.at(0, 0) = 2.0363457667452303; cv::Mat intrisicCvMat720; if (impl->mVideoH == VIDEOHEIGHT720) { convertCalibration(K, intrisicCvMat720); K= intrisicCvMat720; } cv::Mat newCameraMatrix = getOptimalNewCameraMatrix(K, D, cv::Size(impl->mVideoW, impl->mVideoH), g_alpha, imageSize, 0); newCameraMatrix = cv::Mat::eye(3, 3, CV_64FC1); // 相机焦距, 与世界尺度相关 newCameraMatrix.at(0, 0) = 300.0; newCameraMatrix.at(1, 1) = 300.0; GLvoid * mBuffer = nullptr; if (impl->mVideoH == VIDEOHEIGHT1080) { mBuffer = impl->mBufYuv_1080; g_centerX = 0; g_centerY = 0; } else if (impl->mVideoH == VIDEOHEIGHT720) { mBuffer = impl->mBufYuv_720; g_centerX = 0; g_centerY = -17; // 针对输出图像中心点做调整(当进行图像去畸变操作时,会对图像进行扭曲矫正以纠正镜头畸变,但是这可能会导致图像的拉伸和变形。当中心点下移时,可能会影响到图像的视觉效果和后续处理操作), 这个值可能随着相机数据的变化而变。具体可以看上面对比图:很明显处理后的图像往下偏了几十个像素。需要给它调整回去以避免图像上其他元素去畸变后又跟图像有位置偏差问题。 } else if (impl->mVideoH == VIDEOHEIGHT480) { mBuffer = impl->mBufYuv_480; g_centerX = 0; g_centerY = 0; } // 主点位置,与图像中心点相关 newCameraMatrix.at(0, 2) = static_cast(imageSize.width / 2) + static_cast(g_centerX); newCameraMatrix.at(1, 2) = static_cast(imageSize.height / 2) + static_cast(g_centerY); 1. 效果不好、不是很明显 cv::initUndistortRectifyMap(K, D, cv::Mat(), NewCameraMatrix, imageSize, CV_16SC2, map1, map2); 2. 效果不是很明显 cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat(), NewCameraMatrix, imageSize, CV_16SC2, map1, map2); 3. 效果不错。对应扩展库是opencv_contrib cv::omnidir::initUndistortRectifyMap(K, D, xi, cv::Mat::eye(3, 3, CV_64FC1), newCameraMatrix, imageSize, CV_32FC1, g_map1, g_map2, cv::omnidir::RECTIFY_PERSPECTIVE); cv::Mat yuvImg(impl->mVideoH * 3 / 2, impl->mVideoW, 0, mBuffer); cv::Mat bgrImg; cv::cvtColor(yuvImg, bgrImg, COLOR_YUV2BGR_I420); cv::Mat distortImage; cv::remap(bgrImg, distortImage, g_map1, g_map2, cv::INTER_LINEAR); int evenRows = distortImage.rows / 2 * 2; int evenCols = distortImage.cols / 2 * 2; cv::Rect select = Rect(0, 0, evenCols, evenRows); Mat cropImg = distortImage(select); Mat yuvImgNew; cv::cvtColor(cropImg, yuvImgNew, COLOR_BGR2YUV_I420); if (mBuffer != nullptr) { std::memcpy(mBuffer, yuvImgNew.data, static_cast(impl->mVideoH * 3 / 2) * static_cast(impl->mVideoW)); } }
// 第二个版本 void OpenGLDisplay::dealDedistort() { Eigen::Matrix3d intrisicMat; // 相机内参 3*3矩阵 if (m_vehicleCalibrationData.sensing120().intrinsic().matrix().size() mVideoH == VIDEOHEIGHT720) { convertCalibration(intrisicCvMat, intrisicCvMat720); intrisicCvMat = intrisicCvMat720; } cv::Mat newCameraMatrix = getOptimalNewCameraMatrix(intrisicCvMat, distCoeffs, cv::Size(impl->mVideoW, impl->mVideoH), g_alpha, imageSize, 0); newCameraMatrix = cv::Mat::eye(3, 3, CV_64FC1); // 相机焦距, 与世界尺度相关 newCameraMatrix.at(0, 0) = 300.0; newCameraMatrix.at(1, 1) = 300.0; GLvoid * mBuffer = nullptr; if (impl->mVideoH == VIDEOHEIGHT1080) { mBuffer = impl->mBufYuv_1080; g_centerX = 0; g_centerY = 0; } else if (impl->mVideoH == VIDEOHEIGHT720) { mBuffer = impl->mBufYuv_720; g_centerX = 0; g_centerY = -17; // 针对输出图像中心点做调整, 这个值可能随着相机数据的变化而变 } else if (impl->mVideoH == VIDEOHEIGHT480) { mBuffer = impl->mBufYuv_480; g_centerX = 0; g_centerY = 0; } // 主点位置,与图像中心点相关 newCameraMatrix.at(0, 2) = static_cast(imageSize.width / 2) + static_cast(g_centerX); newCameraMatrix.at(1, 2) = static_cast(imageSize.height / 2) + static_cast(g_centerY); cv::omnidir::initUndistortRectifyMap(intrisicCvMat, distCoeffs, xi, cv::Mat::eye(3, 3, CV_64FC1), newCameraMatrix, imageSize, CV_32FC1, g_map1, g_map2, cv::omnidir::RECTIFY_PERSPECTIVE); if (mBuffer == nullptr) return; cv::Mat yuvImg(impl->mVideoH * 3 / 2, impl->mVideoW, 0, mBuffer); cv::Mat bgrImg; cv::cvtColor(yuvImg, bgrImg, COLOR_YUV2BGR_I420); cv::Mat distortImage; cv::remap(bgrImg, distortImage, g_map1, g_map2, cv::INTER_LINEAR); int evenRows = distortImage.rows / 2 * 2; int evenCols = distortImage.cols / 2 * 2; cv::Rect select = Rect(0, 0, evenCols, evenRows); cv::Mat cropImg; cropImg = distortImage(select); cv::Rect rect((cropImg.size().width - impl->mVideoW) / 2, (cropImg.size().height - impl->mVideoH) / 2, impl->mVideoW, impl->mVideoH); cropImg = cropImg(rect); Mat yuvImgNew; cv::cvtColor(cropImg, yuvImgNew, COLOR_BGR2YUV_I420); if (mBuffer != nullptr) { std::memcpy(mBuffer, yuvImgNew.data, static_cast(impl->mVideoH * 3 / 2) * static_cast(impl->mVideoW)); } }
- 以下是几个核心工程化可以自行调整的参数&&其变化对应的效果图的展示(方便理解参数原理);另外以下代码片段可以在上面的代码块中找到位置。
1. const double g_alpha = 1; // 值可以取0/1 参数调整对比效果如下图:
0 (视场会放大)
1 (视场不变)
可以看出:alpha=0,视场会放大,alpha=1,视场不变。
2. const double g_undistor_banlence = 0; // 控制输出图片尺寸, 根据需求调整。参数调整对比效果如下图:
0(1:1)
0.5(1.5倍)
具体效果自己细品,跟alpha调整效果有点类似。
3. int g_centerX = 0; int g_centerY = 0;
...
// 主点位置,与图像中心点相关
newCameraMatrix.at(0, 2) = static_cast(imageSize.width / 2) + static_cast(g_centerX);
newCameraMatrix.at(1, 2) = static_cast(imageSize.height / 2) + static_cast(g_centerY); (0, 0)--》(300, 300)参数调整对比效果如下图:
(0, 0)
(300, 300)
可以明显看到,图像中心点整体往右下移动了。
4. // 相机焦距, 与世界尺度相关. 控制输出图像是否放大展示, 值越大, 图像越被放大
newCameraMatrix.at(0, 0) = 100.0;
newCameraMatrix.at(1, 1) = 100.0; 参数调整(100.0, 100.0)--》(300.0, 300.0)对比效果如下图:
(100.0, 100.0)
(300.0, 300.0)
可以明显看到,图像明显放大了并且渲染区域相对填满了些。
以下是针对图像上的其他元素:如辅助线、导航线、施工、事件等。涉及点位信息计算去畸变的代码(以达到跟去畸变后图像相对匹配的效果):
cv::Point2f LayoutCtr::getDedistortPoint(const cv::Point &point) { std::vector srcPoints = { point }; cv::Mat srcMat(srcPoints.size(), 1, CV_32FC2); for (int i = 0; i
对比图如下:
可以看到,经过调整输出图像的中心点后,去畸变前后辅助线头部相对图像的位置基本没有偏移了。
问题
1. 鱼眼相机标定的1080分辨率的辅助线的点位跟720分辨率的辅助线的点位有什么区别?
鱼眼相机标定的辅助线是用来提供参考的点位,用于在图像中对鱼眼畸变进行校正,以获得更加准确的测量结果。辅助线的点位与图像分辨率有关,因为不同分辨率的图像中包含的像素数量和像素密度不同。
对于同一鱼眼相机,使用不同分辨率的辅助线,其点位位置会有所不同。具体来说,使用1080分辨率的辅助线时,辅助线上的点位数量更多,而且点位之间的间距更小。这是因为1080分辨率的图像包含更多的像素,因此可以更细致地刻画鱼眼畸变的特征。
相反,使用720分辨率的辅助线时,辅助线上的点位数量较少,而且点位之间的间距较大。这是因为720分辨率的图像包含较少的像素,不能像1080分辨率那样提供更精细的畸变特征。
因此,在进行鱼眼相机标定时,应该选择与所使用的图像分辨率相匹配的辅助线,以确保标定结果的准确性。
2. 输出的图像中心点有点下移
当对鱼眼相机采集的图像进行去畸变操作时,由于鱼眼镜头的特殊形状,图像中心点可能会发生偏移。这是因为鱼眼相机的畸变模型比较特殊,不同于普通相机,无法通过简单的内参矩阵修正。在去畸变时,除了畸变参数,还需要考虑鱼眼相机的畸变模型,以及对称中心和缩放因子等因素的影响。
如果经过去畸变后输出的图像中心点有一定的偏移,可以通过调整相机内参矩阵中的图像中心点坐标来实现修正。具体来说,可以通过修改相机内参矩阵的第三行第一列和第二列的值来调整图像中心点的位置。例如,如果想将输出图像中心点沿x轴方向向右移动delta_x个像素,沿y轴方向向下移动delta_y个像素,可以将相机内参矩阵的第三行第一列和第二列的值分别增加delta_x和delta_y,这样就可以调整输出图像中心点的位置。
需要注意的是,修改相机内参矩阵的值需要谨慎,因为这可能会影响畸变矫正的结果。在进行调整之前,建议先进行一些实验来确定合适的参数。此外,还可以尝试调整畸变矫正算法的参数或使用其他的畸变矫正算法,以达到更好的去畸变效果。
3. git发完版打tag的目的是啥
在Git中,打tag是为了给一个特定的提交打上标签,以便在日后可以轻松地找到它。打tag可以用来标记版本发布、重要的里程碑或者任何你认为有必要标记的点。
打tag的目的有以下几个方面:
- 标记版本:打tag可以标记代码的版本,方便以后回溯和管理。特别是在软件发布时,打tag可以方便地标记发布版本,方便用户下载和使用。
- 方便回溯:打tag可以方便地找到历史上某个特定的提交或版本,尤其是在大型项目中或者团队协作中,方便快捷地找到特定版本十分重要。
- 保护代码:打tag可以保护某个特定版本的代码不被误操作或者覆盖,保证代码的安全性和可追溯性。
- 标记重要里程碑:打tag可以标记某个特定阶段或者重要里程碑的代码,例如一个大的功能开发完成或者项目的一个重要阶段完成等,方便日后的参考和回溯。
总之,打tag可以让我们更好地管理代码,方便回溯历史版本,保护代码安全,并且可以方便地标记版本发布和重要里程碑,是Git中一个非常有用的功能。
- 以下是几个核心工程化可以自行调整的参数&&其变化对应的效果图的展示(方便理解参数原理);另外以下代码片段可以在上面的代码块中找到位置。
- 以下是两种方式对应的完整c++代码实现(注意:扩展库需要依赖lib库&拷贝dll库)