FunnyWii
FunnyWii
Published on 2024-07-01 / 115 Visits
0
0

OpenCV 的 remap() 函数中的 map1 和 map2

今天在 StackOverflow 上看到这么个问题:想把原图像中的一个点 ​g,通过内参和畸变参数映射到去畸变图像中的点 ​p,并获取这个点的坐标。

你以为我接下来会说:“xxx和我想的一样,但是我想错了,其实并不是这样....”

现在,我想说的只有“其实并不是这样”,因为我连map1和map2是啥我都不知道,我只会无脑用函数(抱歉

remap

像素重映射(Pixel Remapping)是指将图像从一个坐标系映射到另一个坐标系的过程。在OpenCV中,cv::remap()函数可用用来实现像素重映射。

void cv::remap(
    InputArray src,          // 输入图像
    OutputArray dst,         // 输出图像
    InputArray map1,         // 1. (x,y)映射 2. x映射
    InputArray map2,         // 1. 空映射    2. y映射
    int interpolation,       // 插值方法
    int borderMode = BORDER_CONSTANT,  // (可选)边界模式,用于处理超出图像边界的像素
    const Scalar& borderValue = Scalar()  // (可选)边界值,用于设置超出图像边界的像素的值
);

remap 函数通过对 xy的映射来实现对 src 图像的转换:

dst(x,y) = src[map_x(x,y), map_y(x,y)]

其中 dst表示目标图像,其与 src有相同的大小和类型。在我之前的认知中,认为 map1map2中存储的是 src(x,y)去畸变后的 dst图像的 xy坐标,但是并不是。根据OpenCV官方文档 Geometric Image Transformations 中提到的:In fact, to avoid sampling artifacts, the mapping is done in the reverse order, from destination to the source. 映射是从 dstsrc 反向进行的^[2]^。因此映射函数的作用是查找 dst 像素在 src 中的位置,即过程是 ​dst \rightarrow src,也被成为反向映射。

寻找 srcdst 中的对应点

如果已经得到了 map1map2,想在此基础上找到 srcdst 中的对应的点。有两种方法。

  1. 使用OpenCV的 undistortPoints() 函数
    cv::Mat distortedPoints = (cv::Mat_<cv::Point2f>(4, 1) << cv::Point2f(877, 403), cv::Point2f(877, 403), cv::Point2f(877, 403), cv::Point2f(877, 403));
    cv::Mat undistortedPoints;
    auto start = std::chrono::high_resolution_clock::now();
    cv::fisheye::undistortPoints(distortedPoints, undistortedPoints, K, D);
    double fx = K.at<double>(0, 0); // 焦距x
    double fy = K.at<double>(1, 1); // 焦距y
    double cx = K.at<double>(0, 2); // 主点x
    double cy = K.at<double>(1, 2); // 主点y
    cv::Mat undistortedPointsPix;
    undistortedPoints.convertTo(undistortedPointsPix, CV_32F); // 确保坐标是浮点数
    for (int i = 0; i < undistortedPointsPix.rows; ++i) {
        undistortedPointsPix.at<cv::Point2f>(i).x = fx * undistortedPointsPix.at<cv::Point2f>(i).x + cx;
        undistortedPointsPix.at<cv::Point2f>(i).y = fy * undistortedPointsPix.at<cv::Point2f>(i).y + cy;
    }
    auto end = std::chrono::high_resolution_clock::now();
    // 输出校正后的点
    std::cout << "Distorted Points:" << std::endl << distortedPoints << std::endl;
    std::cout << "Undistorted Points:" << std::endl << undistortedPointsPix << std::endl;
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    std::cout << "Execution time: " << duration.count() << " microseconds" << std::endl;
  1. 查表

std::pair<int, int> mapLookUp(cv::Mat map1, cv::Mat map2, cv::Mat src, cv::Mat dst, int u, int v){
    float min_cost = 1e5;
    int min_u = 0;
    int min_v = 0;
    int height = src.rows;
    int width = src.cols;
    for(int i = 0; i < height; i++){
        for(int j = 0; j < width; j++){
            float u1 = map1.at<float>(i,j);
            float v1 = map2.at<float>(i,j);

            // double dist = sqrt((u1 - u)*(u1 - u) + (v1 -v)*(v1 -v));
            float dist = abs(u1 - (float)u)+ abs(v1 -(float)v);

            if(min_cost > dist){
                min_cost = dist;
                min_u = j;
                min_v = i;
            }
        }
    }
    return std::make_pair(min_u,min_v);
}

两种方式能取得非常接近的结果,但是第二种遍历查表的方法,效率会比较低。在i7-11700上测试,运行时间超过1000ms。而Opencv的函数就很快,运行时间在几十ms。

参考

[1] cv.remap() 重映射学习笔记/map1 map2易混点
[2] Can I get the point position using remap in OpenCV


Comment