You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

OpenCV中cv::remap使用double类型映射的替代方法咨询

问题: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_32FC1cv::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

火山引擎 最新活动