如何将两个remap()操作合并为一个?

Dim*_*ann 12 c++ opencv

我有一个紧凑的循环,我得到一个相机图像,不失真,并根据一些变换(例如透视变换)转换它.我已经想出cv::remap(...)用于每个操作,这已经比使用普通矩阵操作更有效.

根据我的理解,应该可以将查找映射组合成一个,并在每次循环迭代中只调用一次重映射.有没有规范的方法来做到这一点?我宁愿不自己实现所有插值的东西.

注意:该过程应适用于不同大小的地图.在我的特定情况下,不失真保留图像尺寸,而另一个转换将图像缩放到不同的尺寸.

插图代码:

// input arguments
const cv::Mat_<math::flt> intrinsic  = getIntrinsic();
const cv::Mat_<math::flt> distortion = getDistortion();
const cv::Mat mNewCameraMatrix = cv::getOptimalNewCameraMatrix(intrinsic, distortion, myImageSize, 0);

// output arguments
cv::Mat undistortMapX;
cv::Mat undistortMapY;

// computes undistortion maps
cv::initUndistortRectifyMap(intrinsic, distortion, cv::Mat(),
                            newCameraMatrix, myImageSize, CV_16SC2,
                            undistortMapX, undistortMapY);

// computes undistortion maps
// ...computation of mapX and mapY omitted
cv::convertMaps(mapX, mapY, skewMapX, skewMapY, CV_16SC2);

for(;;) {
    cv::Mat originalImage = getNewImage();

    cv::Mat undistortedImage;
    cv::remap(originalImage, undistortedImage, undistortMapX, undistortMapY, cv::INTER_LINEAR);

    cv::Mat skewedImage;
    cv::remap(undistortedImage, skewedImage, skewMapX, skewMapY, cv::INTER_LINEAR);

    outputImage(skewedImage);
}
Run Code Online (Sandbox Code Playgroud)

Mic*_*nov 6

您可以在undistortMapX和undistortMapY上应用重映射.

cv::remap(undistortMapX, undistrtSkewX, skewMapX, skewMapY, cv::INTER_LINEAR);
cv::remap(undistortMapY, undistrtSkewY, skewMapX, skewMapY, cv::INTER_LINEAR);
Run Code Online (Sandbox Code Playgroud)

比你可以使用:

cv::remap(originalImage , skewedImage, undistrtSkewX, undistrtSkewY, cv::INTER_LINEAR);
Run Code Online (Sandbox Code Playgroud)

它的工作原理是因为skewMaps和undistortMaps是图像中的坐标数组,所以它应该类似于获取位置的位置......

编辑(回答评论):

我想我需要做一些澄清.remap()函数根据旧图像的像素计算新图像中的像素.在线性插值的情况下,新图像中的每个像素是距旧图像4个像素的加权平均值.根据提供的映射的值,权重在像素之间不同.如果该值大于或等于整数,则大部分权重取自单个像素.结果,新图像将是原始图像.另一方面,如果该值远不是整数(即整数+0.5),则权重是相似的.这将产生平滑效果.要了解我在说什么,请查看未失真的图像.您将看到图像的某些部分比其他部分更清晰/更平滑.

现在回到关于将两个重映射操作合并为一个时发生的情况的说明.组合贴图中的坐标是正确的,即skewedImage中的像素是从具有正确权重的originalImage的正确4个像素计算的.但它与两次重映射操作的结果不同.undistortedImage中的每个像素是来自originalImage的4个像素的加权平均值.这意味着skewedImage的每个像素都是orginalImage的9-16像素的加权平均值.结论:使用单重映射()可以可能得到结果是相同的重映射的两种用法().

讨论两个可能的图像中的哪一个(单重映射()与双重映射()更好是非常复杂的.通常,尽可能少地进行插值是很好的,因为每个插值都会引入不同的伪像.特别是如果图像中的伪像不均匀(某些区域变得比其他区域更平滑).在某些情况下,这些伪像可能对图像具有良好的视觉效果 - 比如减少一些抖动.但如果这是你想要的,你可以用更便宜和更一致的方式实现这一目标.例如,通过在重映射之前平滑原始图像.


BCo*_*nic 5

在两个通用映射的情况下,别无选择,只能使用@MichaelBurdinov 建议的方法。

但是,在具有已知逆映射的两个映射的特殊情况下,另一种方法是手动计算映射。这种手动方法比双重映射方法更准确,因为它不涉及坐标图的插值

实际上,大多数有趣的应用程序都与这种特殊情况相匹配。在您的情况下也是如此,因为您的第一个地图对应于图像不失真(其逆操作是图像失真,它与众所周知的分析模型相关联)而您的第二个地图对应于透视变换(其逆可以解析表示)。

手动计算地图实际上很容易。如文档(链接)中所述,这些地图包含目标图像中每个像素的 (x,y) 坐标,可以在源图像中找到适当强度的位置。以下代码片段显示了如何在您的情况下手动计算地图:

int dst_width=...,dst_height=...;           // Initialize the size of the output image
cv::Mat Hinv=H.inv(), Kinv=K.inv();         // Precompute the inverse perspective matrix and the inverse camera matrix
cv::Mat map_undist_warped_x32f(dst_height,dst_width,CV_32F);    // Allocate the x map to the correct size (n.b. the data type used is float)
cv::Mat map_undist_warped_y32f(dst_height,dst_width,CV_32F);    // Allocate the y map to the correct size (n.b. the data type used is float)
// Loop on the rows of the output image
for(int y=0; y<dst_height; ++y) {
    std::vector<cv::Point3f> pts_undist_norm(dst_width);
    // For each pixel on the current row, first use the inverse perspective mapping, then multiply by the
    // inverse camera matrix (i.e. map from pixels to normalized coordinates to prepare use of projectPoints function)
    for(int x=0; x<dst_width; ++x) {
        cv::Mat_<float> pt(3,1); pt << x,y,1;
        pt = Kinv*Hinv*pt;
        pts_undist_norm[x].x = pt(0)/pt(2);
        pts_undist_norm[x].y = pt(1)/pt(2);
        pts_undist_norm[x].z = 1;
    }
    // For each pixel on the current row, compose with the inverse undistortion mapping (i.e. the distortion
    // mapping) using projectPoints function
    std::vector<cv::Point2f> pts_dist;
    cv::projectPoints(pts_undist_norm,cv::Mat::zeros(3,1,CV_32F),cv::Mat::zeros(3,1,CV_32F),intrinsic,distortion,pts_dist);
    // Store the result in the appropriate pixel of the output maps
    for(int x=0; x<dst_width; ++x) {
        map_undist_warped_x32f.at<float>(y,x) = pts_dist[x].x;
        map_undist_warped_y32f.at<float>(y,x) = pts_dist[x].y;
    }
}
// Finally, convert the float maps to signed-integer maps for best efficiency of the remap function
cv::Mat map_undist_warped_x16s,map_undist_warped_y16s;
cv::convertMaps(map_undist_warped_x32f,map_undist_warped_y32f,map_undist_warped_x16s,map_undist_warped_y16s,CV_16SC2);
Run Code Online (Sandbox Code Playgroud)

注意:H上面是你的透视变换,而K应该是与未失真图像相关的相机矩阵,所以它应该是你的代码中被调用的newCameraMatrix(顺便说一句,它不是 的输出参数initUndistortRectifyMap)。根据您的特定数据,可能还有一些其他情况需要处理(例如除以pt(2)可能为零等)。