OpenCV 畸变校正函数undistortPoints()与remap()详解及校正效果对比
一.概述
????前面寫過一篇博客–“疑問:undistortPoints()與remap()畸變校正后,結果相差很大”,博客中對比了OpenCV中自帶畸變校正函數undistortPoints()與remap()的結果,但二者校正效果相差很大。不久前,有伙伴在博客下留言,自己也對比了二者校正效果,差異不大。因此,本人也根據博友的鏈接重新驗證了一下,發現二者校正效果相當,今天根據實驗結果重新寫篇博客。在此,非常感謝在博客下留言的伙伴!!!先將本人的前一篇博客和這位博友的博客鏈接貼一下:
1.疑問:undistortPoints()與remap()畸變校正后,結果相差很大
2.OpenCV 不同畸變校正函數的使用說明
二.測試思路
????主要測試OpenCV兩個畸變校正函數undistortPoints()、remap()校正效果,共測試5張棋盤格分別分布在圖像邊緣、中間等不同位置的圖片。
(一)程序流程
- 讀取相機內參及畸變系數
- 計算畸變映射,函數initUndistortRectifyMap()
- 讀圖并提取角點,提取角點函數findChessboardCorners()
- 角點優化,優化函數cornerSubPix()
- 畸變校正方法(1),remap(),校正后用findChessboardCorners()提取校正后圖像角點并優化
- 畸變校正方法(2),undistortPoints結合步驟3中提取的角點進行校正
- 保存步驟3提取的角點、步驟5校正方法1校正后的角點、步驟6校正方法2校正后的角點、步驟5角點與步驟3的角點差值、步驟6與步驟3角點差值、步驟6與步驟5角點差值的絕對值,將差值進行比較。
(二)相關函數詳解
1).提取角點函數 findChessboardCorners()
findChessboardCorners( InputArray image,Size patternSize,OutputArray corners, int flags = CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)
功能:
????????找到標定板內角點位置(標定板是專用器具,需要有嚴格的規格控制,標定板的制作精度直接影響標定精度;角點是指黑白色相接的方塊定點部分;內角點是不與標定板邊緣接觸的內部角點)
參數:
(1). 輸入的圖像矩陣,必須是8-bit灰度圖或者彩色圖像,在圖像傳入函數之前,一般經過灰度處理,還有濾波操作。
(2). 內角點的size,表示方式是定義Size PatSize(m,n),將PatSize作為參數傳入。這里是內角點的行列數,不包括邊緣角點行列數;行數和列數不要相同,這樣的話函數會辨別出標定板的方向,如果行列數相同,那么函數每次畫出來的角點起始位置會變化,不利于標定。
(3). 存儲角點的數組,一般用vector<Point2f>
(4). 標志位,有默認值。
????????CV_CALIB_CB_ADAPTIVE_THRESH:該函數的默認方式是根據圖像的平均亮度值進行圖像 二值化,設立此標志位的含義是采用變化的閾值進行自適應二值化;
????????CV_CALIB_CB_NORMALIZE_IMAGE:在二值化之前,調用EqualizeHist()函數進行圖像歸一化處理;
????????CV_CALIB_CB_FILTER_QUADS:二值化完成后,函數開始定位圖像中的四邊形(這里不應該稱之為正方形,因為存在畸變),這個標志設立后,函數開始使用面積、周長等參數來篩選方塊,從而使得角點檢測更準確更嚴格。
????????CALIB_CB_FAST_CHECK:快速檢測選項,對于檢測角點極可能不成功檢測的情況,這個標志位可以使函數效率提升。
總結:該函數的功能就是判斷圖像內是否包含完整的棋盤圖,如果能夠檢測完全,就把他們的角點坐標按 順序(逐行,從左到右)記錄下來,并返回非0數,否則返回0。 這里對size參數要求非常嚴格,函數必須檢測到相同的size才會返回非0,否則返回0,這里一定要注意。
該函數檢測的角點的坐標是不精確的,要想精確結果,需要使用 cornerSubPix()函數,進行亞像素精度的調整。
2).角點優化–亞像素提取函數
void cv::cornerSubPix(cv::InputArray image, // 輸入圖像cv::InputOutputArray corners, // 角點(既作為輸入也作為輸出)cv::Size winSize, // 區域大小為 NXN; N=(winSize*2+1)cv::Size zeroZone, // 類似于winSize,但是總具有較小的范圍,Size(-1,-1)表示忽略cv::TermCriteria criteria // 停止優化的標準);
????????第一個參數是輸入圖像,和cv::goodFeaturesToTrack()中的輸入圖像是同一個圖像。
????????第二個參數是檢測到的角點,即是輸入也是輸出。
????????第三個參數是計算亞像素角點時考慮的區域的大小,大小為NXN; N=(winSize*2+1)。
????????第四個參數作用類似于winSize,但是總是具有較小的范圍,通常忽略(即Size(-1, -1))。
????????第五個參數用于表示計算亞像素時停止迭代的標準,可選的值有cv::TermCriteria::MAX_ITER 、cv::TermCriteria::EPS(可以是兩者其一,或兩者均選),前者表示迭代次數達到了最大次數時停止,后者表示角點位置變化的最小值已經達到最小時停止迭代。二者均使用cv::TermCriteria()構造函數進行指定。
(三)測試方案
????????為了比較兩種方法在圖像各個方位的畸變校正效果,本程序測試了5張圖片,棋盤格分別分布在圖片中間、邊緣等不同方位。
????????同時程序增加了對findChessboardCorners()設置不同標志位及是否進行角點優化的校正效果進行對比,程序測試了設計了多種方案進行測試,具體測試方案如下:
(1)版本1,提取角點,設置findChessboardCorners()標志位為CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS,角點優化,具體調用如下:
int found=0;
found = findChessboardCorners(img, boardSize, pointbuf,CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS);
if (found) {cornerSubPix(img, pointbuf, cv::Size(11, 11), cv::Size(-1, -1),cv::TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));}
(2)版本2,提取角點,標志位設置如版本1,未進行角點優化;
(3)版本3,提取角點,標志位設置為CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE,角點優化,具體調用如下:
int found=0;
found = findChessboardCorners(img, boardSize, pointbuf,CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);
if (found) {cornerSubPix(img, pointbuf, cv::Size(11, 11), cv::Size(-1, -1),cv::TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));}
(4)版本4,提取角點,標志位設置如版本3,未進行角點優化。
(四)完整程序及測試結果
????????程序編寫環境,VS2013+openCV2.4.13。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <io.h>
#include <vector>using namespace std;
using namespace cv;#define CamIntrinRes "./CamIntrinCalibRes.yml"
#define _debug_printDebug 1void getFiles(string path, string file_format, vector<string>& files);
int ImportLastCaliRes_1intrinsic(Mat& cam_intrin, Mat& distcoeffs, int& w, int& h, float& squareSize);
void writeRes(char* path, vector<Point2f> point0, vector<Point2f> point1, vector<Point2f> point2, vector<Point2f> point3, vector<Point2f> point4,vector<Point2f> point5, int flag_i);int main()
{string pic_path = "./img";string pic_format = "bmp";vector<string> pic_list;Mat img,img_undis,img_undis1, cam_intrin,distcoeffs;int w=0, h = 0;float squareSize = 0;//讀內參int flag1 = 0;flag1 = ImportLastCaliRes_1intrinsic(cam_intrin, distcoeffs, w, h, squareSize);distcoeffs.at<double>(4, 0) = 0;//讀圖getFiles(pic_path, pic_format, pic_list);int found = 0;Size boardSize(8, 6);vector<Point2f> pointbuf, pointbuf1, pointbuf2, delta_2less0, delta_1less0,delta_2less1_abs;Size imageSize = Size(1920,1200);Mat map1, map2;initUndistortRectifyMap(cam_intrin, distcoeffs, cv::Mat(), cam_intrin, imageSize, CV_32FC1, map1, map2);for (int i = 0; i < pic_list.size();++i){img = imread(pic_list[i], 0);found = findChessboardCorners(img, boardSize, pointbuf,CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE/*CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS*/);if (found) {cornerSubPix(img, pointbuf, cv::Size(11, 11), cv::Size(-1, -1),cv::TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));}remap(img, img_undis, map1, map2, cv::INTER_LINEAR);found = findChessboardCorners(img_undis, boardSize, pointbuf1,CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE/*CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS*/);if (found) {cornerSubPix(img_undis, pointbuf1, cv::Size(11, 11), cv::Size(-1, -1),cv::TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));}undistortPoints(pointbuf, pointbuf2, cam_intrin, distcoeffs, cv::Mat(), cam_intrin);for (int j = 0; j < pointbuf2.size();++j){delta_1less0.push_back(Point2f(pointbuf1[j].x - pointbuf[j].x, pointbuf1[j].y - pointbuf[j].y));delta_2less0.push_back(Point2f(pointbuf2[j].x - pointbuf[j].x, pointbuf2[j].y - pointbuf[j].y));delta_2less1_abs.push_back(Point2f(fabsf(delta_2less0[j].x - delta_1less0[j].x), fabsf(delta_2less0[j].y - delta_1less0[j].y)));}writeRes("pointbuf.txt", pointbuf, pointbuf1, pointbuf2, delta_1less0, delta_2less0, delta_2less1_abs, i);pointbuf.clear();pointbuf1.clear();pointbuf2.clear();delta_1less0.clear();delta_2less0.clear();delta_2less1_abs.clear();}system("pause");
}void getFiles(string path, string file_format, vector<string>& files)
{intptr_t hFile = 0;struct _finddata_t fileinfo;string p, file_formatName;if (0 != strcmp(file_format.c_str(), "")){file_formatName = "\\*." + file_format;}else{file_formatName = "\\*";}if ((hFile = _findfirst(p.assign(path).append(file_formatName).c_str(), &fileinfo)) != -1){do{files.push_back(p.assign(path).append("\\").append(fileinfo.name));} while (_findnext(hFile, &fileinfo) == 0);_findclose(hFile);}
}int ImportLastCaliRes_1intrinsic(Mat& cam_intrin,Mat& distcoeffs,int& w,int& h,float& squareSize)
{int flag = 0;long handle;struct _finddata_t fileinfo; //讀取內參結果handle = _findfirst(CamIntrinRes, &fileinfo);if (handle != -1){FileStorage fs_1(CamIntrinRes, FileStorage::READ);FileNode arr_node;FileNodeIterator fstart;FileNodeIterator fend;if (!fs_1.isOpened()){flag = -1;
#if _debug_printDebugprintf("Errcode:%d Failed to open the 'CamIntrinRes' file, please check!\n", flag);
#endifreturn flag;}fs_1["camera_matrix"] >> cam_intrin;fs_1["distortion_coefficients"] >> distcoeffs;//傳參給界面if (cam_intrin.cols&&distcoeffs.rows &&((!cam_intrin.at<double>(0, 0)) || (!cam_intrin.at<double>(0, 2)) || (!cam_intrin.at<double>(1, 1))|| (!cam_intrin.at<double>(1, 2)) || (!distcoeffs.at<double>(0, 0)) || (!distcoeffs.at<double>(1, 0))|| (!distcoeffs.at<double>(2, 0)) || (!distcoeffs.at<double>(3, 0)) /*|| (!camAndchessParaset.CamDistCoeffs.at<double>(4, 0))*/)) //k3可以為0{flag = -2;
#if _debug_printDebugprintf("Errcode:%d Internal parameters are not accurate, please re-calibration first!\n", flag);
#endif return flag;}else if ((!cam_intrin.cols) || (!distcoeffs.rows)){flag = -3;
#if _debug_printDebugprintf("Errcode:%d Internal parameters are not exist, please re-calibration first!\n", flag);
#endifreturn flag;}fs_1["board_width"] >> w;fs_1["board_height"] >> h;fs_1["square_size"] >> squareSize;}flag = 1;return flag;
}void writeRes(char* path, vector<Point2f> point0, vector<Point2f> point1, vector<Point2f> point2, vector<Point2f> point3, vector<Point2f> point4, vector<Point2f> point5, int flag_i)
{FILE* fp = fopen(path, "a+");if (flag_i == 0){fp = fopen(path, "w");}fprintf(fp, "pic%d \n",flag_i);for (int i = 0; i < point0.size(); i++){if (i==0){fprintf(fp, "pointbuf:\t\t\tpointbuf1:\t\t\tpointbuf2:\t\t\tdelta_1less0:\t\tdelta_2less0\t\tdelta_2less1_abs: \n", flag_i);}fprintf(fp, "%.2f\t%.2f\t\t", point0[i].x, point0[i].y);fprintf(fp, "%.2f\t%.2f\t\t", point1[i].x, point1[i].y);fprintf(fp, "%.2f\t%.2f\t\t", point2[i].x, point2[i].y);fprintf(fp, "%.2f\t%.2f\t\t", point3[i].x, point3[i].y);fprintf(fp, "%.2f\t%.2f\t\t", point4[i].x, point4[i].y);fprintf(fp, "%.2f\t%.2f\t\t\n", point5[i].x, point5[i].y);}fclose(fp);
}
????????測試結果如圖,測試了5張圖片的校正效果,現貼出第一張結果(其他結果類似),其他結果txt在文章最后附下載鏈接。
(五)結論
????????從測試結果分析,角點優化對兩種畸變校正效果影響較大,角點提取+角點優化的方案兩種畸變校正效果相當,校正效果差的絕對值基本在0.1pixel以內,如結果版本1、版本3;而只提取角點未進行亞像素優化的校正效果,二者效果差的絕對值普遍偏大,甚至有些點已經超過2pixel,如版本2、版本4。而在采用角點優化的方案下,文中findChessboardCorners()設置的兩個參數方案對校正效果影響不大。
????????因此得出結論,在進行棋盤格角點提取后,應對棋盤格角點進行再次優化,即用cornerSubPix()函數再次進行亞像素提取。然后再根據應用需求選擇畸變校正的方法,remap()適用于整圖校正,undistortPoints()適用于對圖片中某些點進行畸變校正。
三.結語
????????以上為本人測試結果,能力有限,難免出錯,歡迎指正?,F將程序的代碼、所用圖片、內參結果、測試結果等下載鏈接附上,歡迎有興趣的人自行修改測試,如有問題,在博客下方留言討論~
????????覺得博客及程序寫的不錯的,歡迎點贊打賞哦,讓更多的人看見!
完整程序及圖片資源下載地址
總結
以上是生活随笔為你收集整理的OpenCV 畸变校正函数undistortPoints()与remap()详解及校正效果对比的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法的时间与空间复杂度详解
- 下一篇: windows/linux计算文件 MD