OpenCV中cv::remap使用double类型映射的替代方法咨询
我在图像处理场景中使用double类型的图像坐标,期望通过OpenCV的
cv::remap()函数生成畸变图像。但发现该函数仅支持CV_32FC1(float)作为映射表类型,无法直接使用CV_64FC1(double)类型。除了将double类型坐标强制转换为float后再使用浮点型映射表的方案外,是否存在其他可行的解决办法?相关代码片段如下:
Eigen::Vector2d distort(Eigen::Vector2d & pointUndistorted); int main(int argc, char** argv) { cv::Mat image, map_x, map_y; // ... 这里是生成double类型map_x、map_y的逻辑,调用distort函数计算每个坐标的畸变后位置 // 尝试用cv::remap但因为类型不匹配报错 cv::remap(image, distortedImage, map_x, map_y, cv::INTER_LINEAR); }
嘿,我来给你梳理几个除了直接转float之外,能解决这个问题的办法:
1. 自定义重映射实现
既然cv::remap()不支持double,咱们可以自己写一套重映射逻辑,全程用double类型计算,完全保留精度。核心思路是遍历畸变图像的每个像素,用你的distort()函数算出对应的原始图像坐标,再手动实现插值(比如和OpenCV默认对齐的双线性插值)。
示例代码大概是这样:
cv::Mat customRemap(const cv::Mat& src, std::function<Eigen::Vector2d(Eigen::Vector2d)> distortFunc) { cv::Mat dst(src.size(), src.type()); for (int y = 0; y < dst.rows; ++y) { for (int x = 0; x < dst.cols; ++x) { // 将当前像素坐标转为double,计算畸变前的原始坐标 Eigen::Vector2d dstPoint(x, y); Eigen::Vector2d srcPoint = distortFunc(dstPoint); // 双线性插值(这里仅演示单通道,多通道可循环处理每个通道) if (srcPoint.x() >= 0 && srcPoint.x() < src.cols - 1 && srcPoint.y() >= 0 && srcPoint.y() < src.rows - 1) { int x0 = static_cast<int>(srcPoint.x()); int y0 = static_cast<int>(srcPoint.y()); double fx = srcPoint.x() - x0; double fy = srcPoint.y() - y0; uchar* p00 = src.ptr<uchar>(y0, x0); uchar* p01 = src.ptr<uchar>(y0, x0 + 1); uchar* p10 = src.ptr<uchar>(y0 + 1, x0); uchar* p11 = src.ptr<uchar>(y0 + 1, x0 + 1); dst.at<uchar>(y, x) = static_cast<uchar>( (1 - fx) * (1 - fy) * p00[0] + fx * (1 - fy) * p01[0] + (1 - fx) * fy * p10[0] + fx * fy * p11[0] ); } else { // 边界外的像素可设为0或其他填充值 dst.at<uchar>(y, x) = 0; } } } return dst; }
这个方法的好处是全程无精度损失,但缺点是纯CPU实现,速度比OpenCV优化过的cv::remap()慢不少,处理大图像时要考虑性能成本。
2. 中间double计算+最终转float的折中方案
这个思路是保留中间所有坐标计算的double精度,只在最后把映射表转成CV_32FC1给cv::remap()用,既兼顾精度,又能利用OpenCV的优化插值速度:
cv::Mat src = ...; // 原始图像 cv::Size dstSize = src.size(); cv::Mat dst(dstSize, src.type()); // 生成所有像素的double类型坐标矩阵 cv::Mat coords(dstSize.height * dstSize.width, 2, CV_64FC1); for (int i = 0; i < dstSize.height; ++i) { for (int j = 0; j < dstSize.width; ++j) { coords.at<double>(i * dstSize.width + j, 0) = j; coords.at<double>(i * dstSize.width + j, 1) = i; } } // 对每个坐标应用畸变函数 cv::Mat distortedCoords(coords.size(), CV_64FC1); for (int i = 0; i < coords.rows; ++i) { Eigen::Vector2d point(coords.at<double>(i,0), coords.at<double>(i,1)); Eigen::Vector2d distorted = distort(point); distortedCoords.at<double>(i,0) = distorted.x(); distortedCoords.at<double>(i,1) = distorted.y(); } // 最后一步转成float类型的映射表 cv::Mat distortedCoordsFloat; distortedCoords.convertTo(distortedCoordsFloat, CV_32FC1); // 调整映射表形状后调用cv::remap distortedCoordsFloat = distortedCoordsFloat.reshape(2, dstSize.height); cv::Mat map_x = distortedCoordsFloat.col(0).clone(); cv::Mat map_y = distortedCoordsFloat.col(1).clone(); cv::remap(src, dst, map_x, map_y, cv::INTER_LINEAR);
这种方案平衡了精度和速度,是大部分场景下的最优选择。
3. 先验证float精度是否足够
其实大部分图像处理场景中,float的精度(约7位有效数字)已经完全够用——图像像素坐标一般不会超过几万,float的精度完全能覆盖这个范围。你可以先测试一下:把double映射表转成float后,计算两者的差值,如果差值小于0.5个像素,那对最终图像的影响几乎可以忽略(毕竟插值本身也会带来一定误差)。
内容的提问来源于stack exchange,提问作者Dorian




