OpenCV畸变校正函数undistortPoints()与remap()详解及校正效果对⽐⼀.概述
前⾯写过⼀篇博客–“疑问:undistortPoints()与remap()畸变校正后,结果相差很⼤”,博客中对⽐了OpenCV中⾃带畸变校正函数undistortPoints()与remap()的结果,但⼆者校正效果相差很⼤。不久前,有伙伴在博客下留⾔,⾃⼰也对⽐了⼆者校正效果,差异不⼤。因此,本⼈也根据博友的链接重新验证了⼀下,发现⼆者校正效果相当,今天根据实验结果重新写篇博客。在此,⾮常感谢在博客下留⾔的伙伴先将本⼈的前⼀篇博客和这位博友的博客链接贴⼀下:
⼆.测试思路
主要测试OpenCV两个畸变校正函数undistortPoints()、remap()校正效果,共测试5张棋盘格分别分布在图像边缘、中间等不同位置的图⽚。
(⼀)程序流程
1. 读取相机内参及畸变系数
2. 计算畸变映射,函数initUndistortRectifyMap()
3. 读图并提取⾓点,提取⾓点函数findChessboardCorners()
4. ⾓点优化,优化函数cornerSubPix()
5. 畸变校正⽅法(1),remap(),校正后⽤findChessboardCorners()提取校正后图像⾓点并优化
6. 畸变校正⽅法(2),undistortPoints结合步骤3中提取的⾓点进⾏校正
7. 保存步骤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)fprintf作用
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 "./l"
#define _debug_printDebug 1
void 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<Poi nt2f> 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::CAL IB_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::CAL IB_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, 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_printDebug
printf("Errcode:%d Failed to open the 'CamIntrinRes' file, please check!\n", flag);
#endif
return flag;
}
fs_1["camera_matrix"]>> cam_intrin;
fs_1["distortion_coefficients"]>> distcoeffs;
//传参给界⾯
if(ls&&ws &&
((!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;
flag =-2;
#if _debug_printDebug
printf("Errcode:%d Internal parameters are not accurate, please re-calibration first!\n", flag);
#endif
return flag;
}
else if((!ls)||(!ws))
{
flag =-3;
#if _debug_printDebug
printf("Errcode:%d Internal parameters are not exist, please re-calibration first!\n", flag);
#endif
return 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<Poi nt2f> 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在⽂章最后附下载链接。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。