1.背景
目前有個(gè)項(xiàng)目,需要用到熱成像相機(jī)。但是這個(gè)熱成像相機(jī)它的畸變比較厲害,因此需要用標(biāo)定板進(jìn)行標(biāo)定,從而消除鏡頭畸變。
同時(shí)需要實(shí)現(xiàn)用戶用鼠標(biāo)點(diǎn)擊校正后的畫(huà)面后,顯示用戶點(diǎn)擊位置的像素所代表的溫度。
另外熱成像sdk中還有個(gè)功能:選定一個(gè)rect,可以返回這個(gè)rect中的最高最低溫度以及其各自的位置。假如我們需要這個(gè)功能,那么又需要知道從src到dst的關(guān)系了。
2.需求分析
消除鏡頭畸變后,就不能直接使用熱成像sdk提供的函數(shù)來(lái)查詢像素對(duì)應(yīng)的溫度。
因?yàn)樵诓樵兒瘮?shù)中,有個(gè)像素坐標(biāo)的形參,要求傳入原來(lái)的熱成像圖像A的像素坐標(biāo),函數(shù)返回此像素位置的溫度。
而我們經(jīng)過(guò)畸變消除后,得到畫(huà)面B。B上面的特定像素所處的坐標(biāo)和原圖不一定一樣。
因此,假如用戶想查詢畫(huà)面B上的某個(gè)像素點(diǎn)的有效溫度,就必須要取得此像素點(diǎn)在原圖A上的位置坐標(biāo)。
而在知道原圖的最高最低溫度點(diǎn)的位置后,需要知道其在校正后的畫(huà)面中的位置,才能準(zhǔn)確繪制出來(lái)。
總結(jié)一下,需要實(shí)現(xiàn)以下功能:
a、鏡頭畸變校正
b、知道校正后的畫(huà)面坐標(biāo)(x,y),求其在原畫(huà)面的坐標(biāo)(x’,y’)
c、知道原畫(huà)面坐標(biāo)(x1,y1),求其在校正后的畫(huà)面坐標(biāo)(x2,y2)
3.解決方案
其實(shí)很簡(jiǎn)單,opencv本身就提供了。
3.1.鏡頭畸變校正
在經(jīng)過(guò) findChessboardCorners、calibrateCamera之后,我們就已經(jīng)獲得了相機(jī)矩陣cameraMatrix、畸變矩陣distCoeffs。
然后,我們利用getOptimalNewCameraMatrix,獲得了一個(gè)相對(duì)容易控制畫(huà)面取舍的新相機(jī)矩陣newCamMatrix。
接下來(lái),就有兩種方式對(duì)畫(huà)面進(jìn)行校正:
a、直接undistort。
b、先利用initUndistortRectifyMap得到map1、map2,然后再利用remap進(jìn)行畫(huà)面校正。
后面的代碼把兩種都演示了。
3.2.知道校正后的畫(huà)面坐標(biāo)(x, y),求其在原畫(huà)面的坐標(biāo)(x’, y’)
其實(shí),我們真正需要的是第二種。
關(guān)鍵就在于map1、map2。
這兩個(gè)矩陣是什么玩意呢?
其實(shí)你先看看他們的尺寸、通道數(shù),再查閱一下資料就知道了:
map1、map2的尺寸與目標(biāo)圖像(校正后的圖像)的尺寸一致,而通道數(shù)為1(這個(gè)其實(shí)不一定,與其他參數(shù)有關(guān),暫時(shí)先這樣認(rèn)為)。我們假設(shè),最終圖像(x,y)處的像素來(lái)源于源圖像(x’,y’)處,那么,map1中存儲(chǔ)了坐標(biāo)(x’,y’)中的x’,而map2中存儲(chǔ)了y’。
雖然我描述得很混亂,但是你配合代碼應(yīng)該明白我在說(shuō)什么。??
所以,我們直接利用這個(gè)map1、map2就可以實(shí)現(xiàn)從消除畸變后的畫(huà)面坐標(biāo)轉(zhuǎn)換到原畫(huà)面的坐標(biāo)了。
initUndistortRectifyMap(cameraMatrix, distCoeffs, cv::Mat(), newCamMatrix, imageSize, CV_32FC1, map1, map2);
......
Point dstPt(400, 109);
double pt_x = map1.at<float>(dstPt);
double pt_y = map2.at<float>(dstPt);
3.2.知道原畫(huà)面坐標(biāo)(x1, y1),求其在校正后的畫(huà)面坐標(biāo)(x2, y2)
這個(gè)可以利用opencv的undistortPoints函數(shù)進(jìn)行求解。
假如你是單目相機(jī)標(biāo)定,需要注意第三個(gè)參數(shù)使用相機(jī)矩陣、第五個(gè)參數(shù)使用空矩陣、第六個(gè)參數(shù)使用新相機(jī)矩陣。這些參數(shù)需要和initUndistortRectifyMap的相對(duì)應(yīng)起來(lái)。
vector<Point2f> srcPts;
srcPts.push_back(Point2f(300, 145));
vector<Point2f> dstPts;
undistortPoints(srcPts, dstPts, cameraMatrix, distCoeffs, Mat(), newCamMatrix);
假如你用的是立體相機(jī)標(biāo)定的話【opencv/samples/cpp/stereo_calib.cpp】,可以直接把得到的R、P填上去:
stereoRectify(cameraMatrix[0], distCoeffs[0],
cameraMatrix[1], distCoeffs[1],
imageSize, R, T, R1, R2, P1, P2, Q,
CALIB_ZERO_DISPARITY, 1, imageSize, &validRoi[0], &validRoi[1]);
......
vector<Point2f> srcPts;
srcPts.push_back(Point2f(110, 205));
vector<Point2f> dstPts;
undistortPoints(srcPts, dstPts, cameraMatrix[0], distCoeffs[0], R1, P1);
4.效果
由于一些原因,我不能直接展示我的效果圖。這里用opencv自帶的圖像來(lái)演示吧。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-625479.html
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());
}
// 存放相機(jī)的圖像的角點(diǎn)位置
vector<vector<Point2f>> imagePoints;
// 存放實(shí)際的物體坐標(biāo)
vector<vector<Point3f>> objectPoints;
Size imageSize;
int i, j, nimages = imageList.size();
imagePoints.resize(nimages);
// 存放能夠順利找到角點(diǎn)的圖像的路徑
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())
continue;
imageSize = img.size();
// 找角點(diǎn)
bool found = false;
vector<Point2f>& corners = imagePoints[j];
found = findChessboardCorners(img, boardSize, corners,
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
if(found == false)
{
continue;
}
// 再進(jìn)行一次亞像素查找
cornerSubPix(img, corners, Size(11,11), Size(-1,-1),
TermCriteria(TermCriteria::COUNT+TermCriteria::EPS,
30, 0.01));
// 顯示查找的結(jié)果
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;
}
// 截取長(zhǎng)度,保留有用的數(shù)據(jù)
imagePoints.resize(nimages);
// 填充3d數(shù)據(jù)
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)); //內(nèi)參矩陣3*3
cv::Mat distCoeffs(1, 5, CV_32FC1, cv::Scalar::all(0)); //畸變矩陣1*5
vector<cv::Mat> rotationMat; //旋轉(zhuǎn)矩陣
vector<cv::Mat> translationMat; //平移矩陣
//!標(biāo)定
/**
* points3D_all_images: 真實(shí)三維坐標(biāo)
* points_all_images: 提取的角點(diǎn)
* image_size: 圖像尺寸
* camera_K : 內(nèi)參矩陣K
* distCoeffs: 畸變參數(shù)
* rotationMat: 每個(gè)圖片的旋轉(zhuǎn)向量
* translationMat: 每個(gè)圖片的平移向量
* */
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中存儲(chǔ)的分別是最終圖像對(duì)應(yīng)像素的x,y坐標(biāo)
// 知道dst的坐標(biāo),求src的相應(yīng)坐標(biāo)
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的坐標(biāo),求dst的相應(yīng)坐標(biāo)
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);
}
參考:
【關(guān)于OpenCV中的去畸變】
【用OpenCV進(jìn)行相機(jī)標(biāo)定(張正友標(biāo)定,有代碼)】
【《opencv學(xué)習(xí)筆記》-- 重映射】文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-625479.html
到了這里,關(guān)于opencv對(duì)相機(jī)進(jìn)行畸變校正,及校正前后的坐標(biāo)對(duì)應(yīng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!