海尔630冰箱的标定和畸变校正。上篇文章中直接使用OpenCV的例程进行畸变校正的效果不太理想。使用以下方法(张正友)效果更好。



标定代码:

/************************************************************************
运行环境:VS2013+OpenCV 2.4.13
运行结果:检测拍摄的棋盘畸变图像,获取摄像头的畸变信息
*************************************************************************/



#include <opencv2\opencv.hpp>
#include <fstream>
#include <iostream>
using namespace std;
using namespace cv;

char dir[64];
char fileNames[64];


char chess_boardImage_path[] = "E:\\hanxiaoxuan\\distort\\";
char chess_boardDetect_path[] = "E:\\hanxiaoxuan\\distort_detect\\";
char chess_boardCorner_path[] = "E:\\hanxiaoxuan\\distort_corner\\";
char calibrationResult[] = "E:\\hanxiaoxuan\\calibration_result.txt";
char datFileName[] = "E:\\hanxiaoxuan\\camParam.dat";


int main()
{
	string *imageList = new string[100]; 
	string *chess_boardList = new string[100];  

	ofstream fout(calibrationResult);  //保存标定结果的文件

	// 利用dir命令将当前目录下的.jpg文件名写入names.txt
	sprintf(dir, "%s%s%s%s%s%s", "dir ", chess_boardImage_path, "*.jpg", " /a /b >", chess_boardImage_path, "names.txt");
	system(dir);
	char name[64] = "";
	// 打开文件读取其中的文件名
	sprintf(fileNames, "%s%s", chess_boardImage_path, "names.txt");
	FILE* fp = fopen(fileNames, "r");

	if (NULL == fp)
		printf("error,cannot open the name list");

	// 获得文件数量
	int line = 0;
	while (fgets(name, 64, fp) != NULL)
	{
		char subname[64];
		sscanf(name, "%[^\n]%s", subname);
		string image_name;
		stringstream stream;
		stream << subname;
		image_name = stream.str();
		imageList[line] = image_name.substr(0, image_name.length() - 4);
		line++;
	}


	//读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化
	cout << "开始提取角点………………" << endl;
	int image_count = line;                    //图像数量
	Size board_size = Size(9, 6);            //棋盘上每行、列的内角点数
	vector<Point2f> corners;                  //缓存每幅图像上检测到的角点
	vector<vector<Point2f>>  corners_Seq;    //保存检测到的所有角点
	vector<Mat>  image_Seq;
	int successImageNum = 0;				//成功提取角点的棋盘图数量


	int count = 0;

	for (int i = 0; i != image_count; i++)
	{
		cout << "Frame #" << i + 1 << "..." << endl;
		string imageFileName;
		imageFileName = imageList[i];     //图像的文件名
		imageFileName += ".jpg";       //图像的文件名.jpg
		cv::Mat image = imread(chess_boardImage_path + imageFileName);

		//提取角点
		cv::Mat imageGray;
		cvtColor(image, imageGray, CV_RGB2GRAY);
		bool patternfound = findChessboardCorners(image, board_size, corners, CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE +
			CALIB_CB_FAST_CHECK);

		if (!patternfound)
		{
			cout << "can not find chessboard corners!\n";
			continue;
			exit(1);
		}
		else
		{
			//亚像素精确化
			cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
			//绘制检测到的角点并保存
			Mat imageTemp = image.clone();
			for (int j = 0; j < corners.size(); j++)
			{
				circle(imageTemp, corners[j], 10, Scalar(0, 0, 255), 2, 8, 0);
			}
			string imageFileName;
			imageFileName = imageList[i];
			imageFileName += "_corner.jpg";
			imwrite(chess_boardCorner_path + imageFileName, imageTemp);
			cout << "Frame corner#" << i + 1 << "...end" << endl;

			imwrite(chess_boardDetect_path + imageFileName, image);
			chess_boardList[successImageNum] = imageList[i];
			count = count + corners.size();
			successImageNum = successImageNum + 1;
			corners_Seq.push_back(corners);
		}
		image_Seq.push_back(image);
	}

	cout << "角点提取完成!\n";

	//摄像机标定
	cout << "开始标定………………" << endl;
	Size square_size = Size(30, 30);
	vector<vector<Point3f>>  object_Points;        //保存标定板上角点的三维坐标

	Mat image_points = Mat(1, count, CV_32FC2, Scalar::all(0));  //保存提取的所有角点
	vector<int>  point_counts;
	//初始化标定板上角点的三维坐标
	for (int t = 0; t < successImageNum; t++)
	{
		vector<Point3f> tempPointSet;
		for (int i = 0; i < board_size.height; i++)
		{
			for (int j = 0; j < board_size.width; j++)
			{
				//假设标定板放在世界坐标系中z=0的平面上
				Point3f tempPoint;
				tempPoint.x = i*square_size.width;
				tempPoint.y = j*square_size.height;
				tempPoint.z = 0;
				tempPointSet.push_back(tempPoint);
			}
		}
		object_Points.push_back(tempPointSet);
	}
	for (int i = 0; i < successImageNum; i++)
	{
		point_counts.push_back(board_size.width*board_size.height);
	}
	//开始标定
	Size image_size = image_Seq[0].size();
	cv::Matx33d intrinsic_matrix;    //摄像机内参数矩阵
	cv::Vec4d distortion_coeffs;     //摄像机的4个畸变系数:k1,k2,k3,k4
	std::vector<cv::Vec3d> rotation_vectors;                           //每幅图像的旋转向量
	std::vector<cv::Vec3d> translation_vectors;                        //每幅图像的平移向量
	int flags = 0;
	flags |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC;
	flags |= cv::fisheye::CALIB_CHECK_COND;
	flags |= cv::fisheye::CALIB_FIX_SKEW;
	fisheye::calibrate(object_Points, corners_Seq, image_size, intrinsic_matrix, distortion_coeffs, rotation_vectors, translation_vectors, flags, cv::TermCriteria(3, 20, 1e-6));
	cout << "标定完成!\n";

	FILE *camParam = fopen(datFileName, "wb");
	if (camParam == NULL) {
		std::cout << "can not create data file: " << datFileName << " !!!" << std::endl;
		return false;
	}
	fwrite(&intrinsic_matrix, sizeof(cv::Matx33d), 1, camParam);
	fwrite(&distortion_coeffs, sizeof(cv::Vec4d), 1, camParam);
	fwrite(&image_size, sizeof(Size), 1, camParam);
	fclose(camParam);

	//对标定结果进行评价
	cout << "开始评价标定结果………………" << endl;
	double total_err = 0.0;                   //所有图像的平均误差的总和
	double err = 0.0;                        //每幅图像的平均误差
	vector<Point2f>  image_points2;             //保存重新计算得到的投影点

	cout << "每幅图像的标定误差:" << endl;
	cout << "每幅图像的标定误差:" << endl << endl;
	for (int i = 0; i < successImageNum; i++)
	{
		vector<Point3f> tempPointSet = object_Points[i];
		//通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点
		fisheye::projectPoints(tempPointSet, image_points2, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs);
		//计算新的投影点和旧的投影点之间的误差
		vector<Point2f> tempImagePoint = corners_Seq[i];
		Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2);
		Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);
		for (size_t i = 0; i != tempImagePoint.size(); i++)
		{
			image_points2Mat.at<Vec2f>(0, i) = Vec2f(image_points2[i].x, image_points2[i].y);
			tempImagePointMat.at<Vec2f>(0, i) = Vec2f(tempImagePoint[i].x, tempImagePoint[i].y);
		}
		err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
		total_err += err /= point_counts[i];
		cout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
		fout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
	}
	cout << "总体平均误差:" << total_err / image_count << "像素" << endl;
	fout << "总体平均误差:" << total_err / image_count << "像素" << endl << endl;
	cout << "评价完成!" << endl;

	//保存标定结果
	cout << "开始保存标定结果………………" << endl;
	Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); //保存每幅图像的旋转矩阵

	fout << "相机内参数矩阵:" << endl;
	fout << intrinsic_matrix << endl;
	fout << "畸变系数:\n";
	fout << distortion_coeffs << endl;
	for (int i = 0; i < successImageNum; i++)
	{
		fout << "第" << i + 1 << "幅图像的旋转向量:" << endl;
		fout << rotation_vectors[i] << endl;

		//将旋转向量转换为相对应的旋转矩阵
		Rodrigues(rotation_vectors[i], rotation_matrix);
		fout << "第" << i + 1 << "幅图像的旋转矩阵:" << endl;
		fout << rotation_matrix << endl;
		fout << "第" << i + 1 << "幅图像的平移向量:" << endl;
		fout << translation_vectors[i] << endl;
	}
	cout << "完成保存" << endl;
	fout << endl;

	//显示标定结果
	Mat mapx = Mat(image_size, CV_32FC1);
	Mat mapy = Mat(image_size, CV_32FC1);
	Mat R = Mat::eye(3, 3, CV_32F);
	cout << "保存矫正图像" << endl;
	for (int i = 0; i != successImageNum; i++)
	{
		cout << "Frame #" << i + 1 << "..." << endl;
		Mat newCameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));
		fisheye::initUndistortRectifyMap(intrinsic_matrix, distortion_coeffs, R, intrinsic_matrix, image_size, CV_32FC1, mapx, mapy);
		Mat t = image_Seq[i].clone();
		cv::remap(image_Seq[i], t, mapx, mapy, INTER_LINEAR);
		string imageFileName;
		imageFileName = chess_boardList[i];
		imageFileName += "_d.jpg";
		imwrite(chess_boardCorner_path + imageFileName, t);
	}
	cout << "保存结束" << endl;

	delete [] imageList;
	delete [] chess_boardList;

	return 0;
}



得到两组参数,分别是相机的内参矩阵和畸变系数。保存在camPara.dat供calibration代码校正使用。


校正代码:

/************************************************************************
运行环境:VS2013+OpenCV 2.4.13
运行结果:根据摄像头的畸变信息,进行相机标定
*************************************************************************/

#include <opencv2\opencv.hpp>
#include <fstream>
#include <iostream>
using namespace std;
using namespace cv;

char dir[64];
char test_fileNames[64];
char testFile_path[] = "E:\\hanxiaoxuan\\distort\\";
char outputFile_path[] = "E:\\hanxiaoxuan\\undistort\\";
char test_datFileName[] = "E:\\hanxiaoxuan\\camParam.dat";
//string test_imageList[100];

int main()
{
	string *test_imageList = new string[100];  

	// 利用dir命令将当前目录下的.jpg文件名写入test_names.txt
	sprintf(dir, "%s%s%s%s%s%s", "dir ", testFile_path, "*.jpg", " /a /b >", testFile_path, "test_names.txt");
	system(dir);
	char test_name[64] = "";
	// 打开文件读取其中的文件名
	sprintf(test_fileNames, "%s%s", testFile_path, "test_names.txt");
	FILE* fp_test = fopen(test_fileNames, "r");

	if (NULL == fp_test)
		printf("error,cannot open the name list");

	// 获得文件数量
	int line = 0;
	while (fgets(test_name, 64, fp_test) != NULL)
	{
		char subname[64];
		sscanf(test_name, "%[^\n]%s", subname);
		string image_name;
		stringstream stream;
		stream << subname;
		image_name = stream.str();
		test_imageList[line] = image_name.substr(0, image_name.length() - 4);
		line++;
	}

	string testName;

	//利用摄像机畸变参数对图片进行矫正
	cout << "保存矫正图像" << endl;
	for (int i = 0; i < line; i++)
	{
		cout << "Frame #" << i + 1 << "..." << endl;
		testName = test_imageList[i] + ".jpg";
		Mat testImage = imread(testFile_path + testName);
		cv::Matx33d test_intrinsic_matrix;
		cv::Vec4d test_distortion_coeffs;
		Size test_image_size = testImage.size();

		FILE *test_camParam = fopen(test_datFileName, "rb");
		if (test_camParam == NULL) {
			std::cout << "can not create data file: " << test_datFileName << " !!!" << std::endl;
			return false;
		}
		fread(&test_intrinsic_matrix, sizeof(cv::Matx33d), 1, test_camParam);
		fread(&test_distortion_coeffs, sizeof(cv::Vec4d), 1, test_camParam);
		fread(&test_image_size, sizeof(Size), 1, test_camParam);
		fclose(test_camParam);


		Mat test_mapx = Mat(test_image_size, CV_32FC1);
		Mat test_mapy = Mat(test_image_size, CV_32FC1);
		Mat test_R = Mat::eye(3, 3, CV_32F);

		fisheye::initUndistortRectifyMap(test_intrinsic_matrix, test_distortion_coeffs, test_R, test_intrinsic_matrix, test_image_size, CV_32FC1, test_mapx, test_mapy);
		Mat t = testImage.clone();
		cv::remap(testImage, t, test_mapx, test_mapy, INTER_LINEAR);
		imwrite(outputFile_path + testName, t);
	}

	cout << "标定结束" << endl;
	delete [] test_imageList;
	return 0;
}





校正效果如下:

原图1

python 畸变校正 opencv畸变校正_图像

原图校正后

python 畸变校正 opencv畸变校正_python 畸变校正_02

原图2

python 畸变校正 opencv畸变校正_标定_03

校正后

python 畸变校正 opencv畸变校正_opencv_04

校正效果基本令人满意。