OpenCV 畸变校正函数undistortPoints()与remap()详解及校正效果对比

转载请注明出处。
文章地址: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. 读取相机内参及畸变系数
  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
(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在文章最后附下载链接。
OpenCV 畸变校正函数undistortPoints()与remap()详解及校正效果对比_第1张图片
OpenCV 畸变校正函数undistortPoints()与remap()详解及校正效果对比_第2张图片
OpenCV 畸变校正函数undistortPoints()与remap()详解及校正效果对比_第3张图片
OpenCV 畸变校正函数undistortPoints()与remap()详解及校正效果对比_第4张图片

(五)结论

        从测试结果分析,角点优化对两种畸变校正效果影响较大,角点提取+角点优化的方案两种畸变校正效果相当,校正效果差的绝对值基本在0.1pixel以内,如结果版本1、版本3;而只提取角点未进行亚像素优化的校正效果,二者效果差的绝对值普遍偏大,甚至有些点已经超过2pixel,如版本2、版本4。而在采用角点优化的方案下,文中findChessboardCorners()设置的两个参数方案对校正效果影响不大。
        因此得出结论,在进行棋盘格角点提取后,应对棋盘格角点进行再次优化,即用cornerSubPix()函数再次进行亚像素提取。然后再根据应用需求选择畸变校正的方法,remap()适用于整图校正,undistortPoints()适用于对图片中某些点进行畸变校正。

三.结语

        以上为本人测试结果,能力有限,难免出错,欢迎指正。现将程序的代码、所用图片、内参结果、测试结果等下载链接附上,欢迎有兴趣的人自行修改测试,如有问题,在博客下方留言讨论~
        觉得博客及程序写的不错的,欢迎点赞打赏哦,让更多的人看见!
完整程序及图片资源下载地址

你可能感兴趣的:(图像处理,opencv,CV畸变校正函数校正效果对比,undistortPoints,CV两种畸变校正函数效果对比,undistort与remap,OpenCV畸变校正)