文章目录
- 1.背景
- 2.需求分析
- 3.解决方案
- 3.1.镜头畸变矫正
- 3.2.知道矫正后的画面坐标(x,y),求其在原画面的坐标(x',y')
- 3.2.知道原画面坐标(x1,y1),求其在矫正后的画面坐标(x2,y2)
- 4.效果
- 5.代码
1.背景
目前有个项目,需要用到热成像相机。但是这个热成像相机它的畸变比较厉害,因此需要用标定板进行标定,从而消除镜头畸变。
同时需要实现用户用鼠标点击矫正后的画面后,显示用户点击位置的像素所代表的温度。
另外热成像sdk中还有个功能:选定一个rect,可以返回这个rect中的最高、最小温度以及其各自的位置。假如我们需要这个功能,那么又需要知道从src到dst的关系了。
2.需求分析
消除镜头畸变后,就不能直接使用热成像sdk提供的函数来查询像素对应的温度。
因为在查询函数中,有个像素坐标的形参,要求传入原来的热成像图像A的像素坐标,函数返回此像素位置的温度。
而我们经过畸变消除后,得到画面B。B上面的特定像素所处的坐标和原图不一定一样。
因此,假如用户想查询画面B上的某个像素点的有效温度,就必须要取得此像素点在原图A上的位置坐标。
而在知道原图的最高最低温度点的位置后,需要知道其在纠正后的画面中的位置,才能准确绘制出来。
总结一下,需要实现以下功能:
a、镜头畸变矫正
b、知道矫正后的画面坐标(x,y),求其在原画面的坐标(x’,y’)
c、知道原画面坐标(x1,y1),求其在矫正后的画面坐标(x2,y2)
3.解决方案
其实很简单,opencv本身就提供了。
3.1.镜头畸变矫正
在经过 findChessboardCorners、calibrateCamera之后,我们就已经获得了相机矩阵cameraMatrix、畸变矩阵distCoeffs。
然后,我们利用getOptimalNewCameraMatrix,获得了一个相对容易控制画面取舍的新相机矩阵newCamMatrix。
接下来,就有两种方式对画面进行矫正:
a、直接undistort。
b、先利用initUndistortRectifyMap得到map1、map2,然后再利用remap进行画面矫正。
后面的代码把两种都演示了。
3.2.知道矫正后的画面坐标(x,y),求其在原画面的坐标(x’,y’)
其实,我们真正需要的是第二种。
关键就在于map1、map2。
这两个矩阵是什么玩意呢?
其实你先看看他们的尺寸、通道数,再查阅一下资料就知道了:
map1、map2的尺寸与目标图像(矫正后的图像)的尺寸一致,而通道数为1(这个其实不一定,与其他参数有关,暂时先这样认为)。我们假设,最终图像(x,y)处的像素来源于源图像(x’,y’)处,那么,map1中存储了坐标(x’,y’)中的x’,而map2中存储了y’。
虽然我描述得很混乱,但是你配合代码应该明白我在说什么。😁
所以,我们直接利用这个map1、map2就可以实现从消除畸变后的画面坐标转换到原画面的坐标了。
3.2.知道原画面坐标(x1,y1),求其在矫正后的画面坐标(x2,y2)
这个可以利用opencv的undistortPoints函数进行求解。
需要注意的是第三个参数使用相机矩阵、第五个参数使用空矩阵、第六个参数使用新相机矩阵。这些参数需要和initUndistortRectifyMap的想对应起来。
vector<Point2f> srcPts;
srcPts.push_back(Point2f(300, 145));
vector<Point2f> dstPts;
undistortPoints(srcPts, dstPts, cameraMatrix, distCoeffs, Mat(), newCamMatrix);
4.效果
由于一些原因,我不能直接展示我的效果图。这里用opencv自带的图像来演示吧。
5.代码
int cameraCalibration()
{
Size boardSize = {9, 6};
float squareSize = 0.05;
bool displayCorners = false;
vector<string> imageList;
for(int i = 0; i < 9; i++)
{
QString leftImgFile = QString("../data/left%1.jpg").arg(i + 1, 2, 10, QLatin1Char('0'));
imageList.push_back(leftImgFile.toStdString());
}
// 存放相机的图像的角点位置
vector<vector<Point2f>> imagePoints;
// 存放实际的物体坐标
vector<vector<Point3f>> objectPoints;
Size imageSize;
int i, j, nimages = imageList.size();
imagePoints.resize(nimages);
// 存放能够顺利找到角点的图像的路径
vector<string> goodImageList;
for(i = 0, j = 0; i < nimages; i++ )
{
const string& filename = imageList[i];
Mat img = imread(filename, IMREAD_GRAYSCALE);
// 检查图像是否为空
if(img.empty())
break;
imageSize = img.size();
// 找角点
bool found = false;
vector<Point2f>& corners = imagePoints[j];
found = findChessboardCorners(img, boardSize, corners,
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
if(found == false)
{
break;
}
// 再进行一次亚像素查找
cornerSubPix(img, corners, Size(11,11), Size(-1,-1),
TermCriteria(TermCriteria::COUNT+TermCriteria::EPS,
30, 0.01));
// 显示查找的结果
if(displayCorners)
{
cout << "found:" << filename.c_str() << endl;
Mat cimg;
cvtColor(img, cimg, COLOR_GRAY2BGR);
drawChessboardCorners(cimg, boardSize, corners, found);
imshow("corners", cimg);
char c = (char)waitKey(100);
}
goodImageList.push_back(imageList[i]);
j++;
}
nimages = j;
if( nimages < 2 )
{
cout << "Error: too little data to run the calibration\n";
return -1;
}
// 截取长度,保留有用的数据
imagePoints.resize(nimages);
// 填充3d数据
objectPoints.resize(nimages);
for(int i = 0; i < nimages; i++ )
{
for(int j = 0; j < boardSize.height; j++ )
for(int k = 0; k < boardSize.width; k++ )
objectPoints[i].push_back(Point3f(k*squareSize, j*squareSize, 0));
}
cv::Mat cameraMatrix(3, 3, CV_32FC1, cv::Scalar::all(0)); //内参矩阵3*3
cv::Mat distCoeffs(1, 5, CV_32FC1, cv::Scalar::all(0)); //畸变矩阵1*5
vector<cv::Mat> rotationMat; //旋转矩阵
vector<cv::Mat> translationMat; //平移矩阵
//!标定
/**
* points3D_all_images: 真实三维坐标
* points_all_images: 提取的角点
* image_size: 图像尺寸
* camera_K : 内参矩阵K
* distCoeffs: 畸变参数
* rotationMat: 每个图片的旋转向量
* translationMat: 每个图片的平移向量
* */
calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rotationMat, translationMat, 0);
Mat testImg = imread(imageList[0], IMREAD_COLOR);
cv::Rect validROI;
Mat newCamMatrix = getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1.0, imageSize, &validROI);
// Mat undistortedImg;
// undistort(testImg, undistortedImg, cameraMatrix, distCoeffs, newCamMatrix);
// cv::rectangle(undistortedImg, validROI, Scalar(255, 0, 0));
// imshow("undistorted image", undistortedImg);
Mat undistortedImg2;
Mat map1, map2;
initUndistortRectifyMap(cameraMatrix, distCoeffs, cv::Mat(), newCamMatrix, imageSize, CV_32FC1, map1, map2);
// cout << "map1 size" << map1.size() << "," << map1.channels() << endl;
// cout << "map2 size" << map2.size() << "," << map2.channels() << endl;
remap(testImg, undistortedImg2, map1, map2, INTER_LINEAR);
cv::rectangle(undistortedImg2, validROI, Scalar(255, 0, 0));
cout << "calibration completed\r\n";
// map1 map2中存储的分别是最终图像对应像素的x,y坐标
// 知道dst的坐标,求src的相应坐标
Point dstPt(400, 109);
double pt_x = map1.at<float>(dstPt);
double pt_y = map2.at<float>(dstPt);
cout << "dstPt:" << dstPt << "; " << "origin pt:" << pt_x << ", "<< pt_y << endl;
cv::circle(testImg, Point(pt_x, pt_y), 5, Scalar(255, 0, 0), 2);
cv::circle(undistortedImg2, dstPt, 5, Scalar(255,0, 0), 2);
// 知道src的坐标,求dst的相应坐标
vector<Point2f> srcPts;
srcPts.push_back(Point2f(300, 145));
vector<Point2f> dstPts;
undistortPoints(srcPts, dstPts, cameraMatrix, distCoeffs, Mat(), newCamMatrix);
cout << "the dst:" << dstPts << endl;
circle(testImg, srcPts[0], 8, Scalar(0, 255, 0));
circle(undistortedImg2, dstPts[0], 8, Scalar(0, 255, 0));
imshow("src to dst: src", testImg);
imshow("src to dst: dst", undistortedImg2);
}
参考:
【关于OpenCV中的去畸变】
【用OpenCV进行相机标定(张正友标定,有代码)】
【《opencv学习笔记》-- 重映射】