转载请注明出处。
文章地址:https://blog.csdn.net/duiwangxiaomi/article/details/110506764?spm=1001.2014.3001.5501
前面写过一篇博客–“疑问:undistortPoints()与remap()畸变校正后,结果相差很大”,博客中对比了OpenCV中自带畸变校正函数undistortPoints()与remap()的结果,但二者校正效果相差很大。不久前,有伙伴在博客下留言,自己也对比了二者校正效果,差异不大。因此,本人也根据博友的链接重新验证了一下,发现二者校正效果相当,今天根据实验结果重新写篇博客。在此,非常感谢在博客下留言的伙伴!!!先将本人的前一篇博客和这位博友的博客链接贴一下:
1.疑问:undistortPoints()与remap()畸变校正后,结果相差很大
2.OpenCV 不同畸变校正函数的使用说明
主要测试OpenCV两个畸变校正函数undistortPoints()、remap()校正效果,共测试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
(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
#include
#include
#include
#include
using namespace std;
using namespace cv;
#define CamIntrinRes "./CamIntrinCalibRes.yml"
#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<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_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 (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(4, 0))*/ )) //k3可以为0
{
flag = -2;
#if _debug_printDebug
printf("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_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<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()适用于对图片中某些点进行畸变校正。
以上为本人测试结果,能力有限,难免出错,欢迎指正。现将程序的代码、所用图片、内参结果、测试结果等下载链接附上,欢迎有兴趣的人自行修改测试,如有问题,在博客下方留言讨论~
觉得博客及程序写的不错的,欢迎点赞打赏哦,让更多的人看见!
完整程序及图片资源下载地址