今天在 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
函数通过对 x
和 y
的映射来实现对 src
图像的转换:
其中 dst
表示目标图像,其与 src
有相同的大小和类型。在我之前的认知中,认为 map1
和 map2
中存储的是 src(x,y)
去畸变后的 dst
图像的 x
和 y
坐标,但是并不是。根据OpenCV官方文档 Geometric Image Transformations 中提到的:In fact, to avoid sampling artifacts, the mapping is done in the reverse order, from destination to the source. 映射是从 dst
到 src
反向进行的^[2]^。因此映射函数的作用是查找 dst
像素在 src
中的位置,即过程是 dst \rightarrow src,也被成为反向映射。
寻找 src
在 dst
中的对应点
如果已经得到了 map1
和 map2
,想在此基础上找到 src
在 dst
中的对应的点。有两种方法。
- 使用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;
- 查表
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