棋盘格标定

棋盘格标定程序及源码

  • **相机标定的目的:**获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的旋转和平移矩阵),

    内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。

  • **相机标定的输入:**标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。对于每张拍摄的棋盘图片,检测图片中所有棋盘格的特征点(内角点,也就是黑白棋盘交叉点)。我们定义打印的棋盘图纸位于世界坐标系zw=0的平面上,世界坐标系的原点位于棋盘图纸的固定一角。像素坐标系原点位于图片左上角。

  • **相机标定的输出:**摄像机的内参、外参系数。

  • 标定方法: 单目相机标定,张氏标定法

  • 标定图片: opencv的安装路径下opencv\sources\samples\data

  • Progress:

    step1. 准备标定图片

    step2. 对每一张标定图片,提取角点信息

    step3. 对每一张标定图片进一步提取亚像素角点信息

    step4. 在期盼标定图上绘制找到的内角点(仅用作显示)

    step5. 进行标定,并对标定结果进行评价

    step6. 对比标定前后的图片

    其中main()函数中 string infilename 为filenameLeft.txt文档所在路径,该文本文件存储的是每一张图片的路径(注意文档中的空白行)

    string outfilename为保存标定结果的文本文档路径

/*************************************************************************************
************************************************************************************/
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <fstream>
#include <vector>

using namespace cv;
using namespace std;

void main(char *args)
{
	//保存文件名称
	std::vector<std::string>  filenames;

	//需要更改的参数
	//左相机标定,指定左相机图片路径,以及标定结果保存文件
	string infilename = "C:/Users/TXL/Desktop/work/0805minDistanceMeasure/cameraCalibration/filenameLeft.txt";        //存放图片路径的txt文件
	string outfilename = "C:/Users/TXL/Desktop/work/0805minDistanceMeasure/cameraCalibration/left_caliberation_result.txt";

	//标定所用图片文件的路径,每一行保存一个标定图片的路径  ifstream 是从硬盘读到内存
	ifstream fin(infilename);
	//保存标定的结果  ofstream 是从内存写到硬盘
	ofstream fout(outfilename);

	/*
	1.读取毎一幅图像,从中提取出角点,然后对角点进行亚像素精确化、获取每个角点在像素坐标系中的坐标
	像素坐标系(u,v)的原点位于图像的左上角
	*/
	std::cout << "开始提取角点......" << std::endl;;
	//图像数量
	int imageCount = 0;
	//图像尺寸
	cv::Size imageSize;
	//修改参数//标定板上每行每列的角点数
	cv::Size boardSize = cv::Size(9, 6);
	//缓存每幅图像上检测到的角点
	std::vector<Point2f>  imagePointsBuf;
	//保存检测到的所有角点
	std::vector<std::vector<Point2f>> imagePointsSeq;
	char filename[100];
	if (fin.is_open())
	{
		//读取完毕?
		while (!fin.eof())//判断文件是否为空,或者是读到了文件末尾
		{
			//一次读取一行
			fin.getline(filename, sizeof(filename) / sizeof(char));
			//保存文件名
			filenames.push_back(filename);//将图片的每一行路径存到filenames向量里
			//读取图片
			cv::Mat imageInput = cv::imread(filename);
			//读入第一张图片时获取图宽高信息
			if (imageCount == 0)
			{
				imageSize.width = imageInput.cols;
				imageSize.height = imageInput.rows;
				std::cout << "imageSize.width = " << imageSize.width << std::endl;
				std::cout << "imageSize.height = " << imageSize.height << std::endl;
			}

			std::cout << "imageCount = " << imageCount << std::endl;
			imageCount++;

			//提取每一张图片的角点
			if (cv::findChessboardCorners(imageInput, boardSize, imagePointsBuf) == 0)
			{
				//找不到角点
				std::cout << "Can not find chessboard corners!" << std::endl;
				exit(1);
			}
			else
			{
				cv::Mat viewGray;
				//转换为灰度图片
				cv::cvtColor(imageInput, viewGray, cv::COLOR_BGR2GRAY);
				//亚像素精确化   对粗提取的角点进行精确化
				cv::find4QuadCornerSubpix(viewGray, imagePointsBuf, cv::Size(5, 5));//参数2,初始的角点向量,同时作为亚像素角点坐标位置输出
				//保存亚像素点
				imagePointsSeq.push_back(imagePointsBuf);//将每一张图片的亚像素角点向量存到imagePointsSeq向量序列中
				//在图像上显示角点位置
				cv::drawChessboardCorners(viewGray, boardSize, imagePointsBuf, true);
				//显示图片
				cv::imshow("Camera Calibration", viewGray);
				//cv::imwrite("test.jpg", viewGray);
				//等待0.5s
				cv::waitKey(500);
			}
		}

		//计算每张图片上的角点数 54
		int cornerNum = boardSize.width * boardSize.height;

		//角点总数
		int total = imagePointsSeq.size()*cornerNum;
		std::cout << "total = " << total << std::endl;

		for (int i = 0; i < total; i++)
		{
			int num = i / cornerNum;//mun = 所有图片总角点数/每张图片的角点数,获得图片的张数
			int p = i % cornerNum;//p = 所有图片总角点个数%每张图片角点个数,用于检测一张图片中的角点是否输出完毕
			//cornerNum是每幅图片的角点个数,此判断语句是为了输出,便于调试
			if (p == 0)
			{
				//新的一张图片
				std::cout << "\n第 " << num + 1 << "张图片的数据 -->: " << std::endl;
			}
			//输出所有的角点
			std::cout.setf(ios::right);//设置对齐方式
			std::cout.width(1);//设置输出宽度(间隔宽度1字节)
			std::cout.flags(ios::fixed);
			std::cout.precision(6);//设置输出精度
			std::cout<< p + 1 << ":(" << imagePointsSeq[num][p].x << ","
							   <<imagePointsSeq[num][p].y << ")\t";
			if ((p + 1) % 3 == 0)
			{
				std::cout << std::endl;//每行输出三个坐标
			}
		}

		std::cout << "角点提取完成!" << std::endl;

		/*
		2.摄像机标定 世界坐标系原点位于标定板左上角(第一个方格的左上角)
		*/
		std::cout << "开始标定" << std::endl;
		//棋盘三维信息,设置棋盘在世界坐标系的坐标
		//实际测量得到标定板上每个棋盘格的大小
		cv::Size squareSize = cv::Size(26, 26);//表示一个格子大小为26mm*26mm
		//毎幅图片角点数量
		std::vector<int> pointCounts;
		//保存标定板上角点的三维坐标
		std::vector<std::vector<cv::Point3f>> objectPoints;//每张图片存进一个vector中
		//摄像机内参数矩阵 M=[fx γ u0,0 fy v0,0 0 1]
		cv::Mat cameraMatrix = cv::Mat(3, 3, CV_64F, Scalar::all(0));
		//摄像机的5个畸变系数k1,k2,p1,p2,k3
		cv::Mat distCoeffs = cv::Mat(1, 5, CV_64F, Scalar::all(0));
		//每幅图片的旋转向量
		std::vector<cv::Mat> tvecsMat;
		//每幅图片的平移向量
		std::vector<cv::Mat> rvecsMat;

		//初始化标定板上角点的三维坐标
		int i, j, t;
		for (t = 0; t < imageCount; t++)//imageCount为图片数量
		{
			std::vector<cv::Point3f> tempPointSet;//标定板的三维坐标,左上角第一个内角点坐标为(0,0,0)
			//行数
			for (i = 0; i < boardSize.height; i++)//boardSize.height角点行数
			{
				//列数
				for (j = 0; j < boardSize.width; j++)//boardSize.width角点列数
				{
					cv::Point3f realPoint;
					//假设标定板放在世界坐标系中z=0的平面上。
					realPoint.x = i * squareSize.width;
					realPoint.y = j * squareSize.height;
					realPoint.z = 0;
					tempPointSet.push_back(realPoint);
				}
			}
			//标定板每次的三维原点可以不同,但是这里我们都是以标定板的左上角第一个内角点为3D原点
			objectPoints.push_back(tempPointSet);
		}

		//初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板
		for (i = 0; i < imageCount; i++)
		{
			pointCounts.push_back(boardSize.width*boardSize.height);
		}
		//开始标定param1:3D点 param2:2D点 param3:图像的像素尺寸大小 param4:相机内参数矩阵 param5:畸变矩阵
		//param6:旋转向量 param7:平移向量 param8: 标定时采用的算法,有默认值
		cv::calibrateCamera(objectPoints, imagePointsSeq, imageSize, cameraMatrix, distCoeffs, rvecsMat, tvecsMat);
		std::cout << "标定完成" << std::endl;
		
		/*
		对标定结果进行评价的方法是通过得到的相机内外参数,对空间的三维点进行重新投影计算,得到空间三维点
		在图像上新的投影点坐标,计算投影坐标和亚像素角点坐标之间的偏差,偏差越小,标定的结果越好
		对空间三维点进行反向投影的函数是projectPoints()
		*/
		//对标定结果进行评价
		std::cout << "开始评价标定结果......" << std::endl;
		//所有图像的平均误差的总和
		double totalErr = 0.0;
		//每幅图像的平均误差
		double err = 0.0;
		//保存重新计算得到的投影点
		std::vector<cv::Point2f> imagePoints2;
		std::cout << "每幅图像的标定误差:" << std::endl;
		fout << "每幅图像的标定误差:" << std::endl;
		for (i = 0; i < imageCount; i++)
		{
			std::vector<cv::Point3f> tempPointSet = objectPoints[i];
			//通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点imagePoints2(在像素坐标系下的点坐标)
			cv::projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, imagePoints2);
			//计算新的投影点和旧的投影点之间的误差
			std::vector<cv::Point2f> tempImagePoint = imagePointsSeq[i];
			cv::Mat tempImagePointMat = cv::Mat(1, tempImagePoint.size(), CV_32FC2);
			cv::Mat imagePoints2Mat = cv::Mat(1, imagePoints2.size(), CV_32FC2);
			for (int j = 0; j < tempImagePoint.size(); j++)
			{
				imagePoints2Mat.at<cv::Vec2f>(0, j) = cv::Vec2f(imagePoints2[j].x, imagePoints2[j].y);
				tempImagePointMat.at<cv::Vec2f>(0, j) = cv::Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
			}
			//Calculates an absolute difference norm or a relative difference norm.
			err = cv::norm(imagePoints2Mat, tempImagePointMat, NORM_L2);
			totalErr += err /= pointCounts[i];//totalErr = totalErr+err; err = err/pointCounts[i];
			std::cout << "  第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
			fout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;

		}
		//每张图像的平均总误差
		std::cout << "  总体平均误差:" << totalErr / imageCount << "像素" << std::endl;
		fout << "总体平均误差:" << totalErr / imageCount << "像素" << std::endl;
		std::cout << "评价完成!" << std::endl;
		//保存标定结果
		std::cout << "开始保存标定结果....." << std::endl;
		//保存每张图像的旋转矩阵
		cv::Mat rotationMatrix = cv::Mat(3, 3, CV_32FC1, Scalar::all(0));
		fout << "相机内参数矩阵:" << std::endl;
		fout << cameraMatrix << std::endl << std::endl;
		fout << "畸变系数:" << std::endl;
		fout << distCoeffs << std::endl << std::endl;

		for (int i = 0; i < imageCount; i++)
		{
			fout << "第" << i + 1 << "幅图像的旋转向量:" << std::endl;
			fout << tvecsMat[i] << std::endl;
			//将旋转向量转换为相对应的旋转矩阵
			cv::Rodrigues(tvecsMat[i], rotationMatrix);
			fout << "第" << i + 1 << "幅图像的旋转矩阵:" << std::endl;
			fout << rotationMatrix << std::endl;
			fout << "第" << i + 1 << "幅图像的平移向量:" << std::endl;
			fout << rvecsMat[i] << std::endl;
		}
		std::cout << "保存完成" << std::endl;

		/************************************************************************
		显示定标结果
		*************************************************************************/
		cv::Mat mapx = cv::Mat(imageSize, CV_32FC1);
		cv::Mat mapy = cv::Mat(imageSize, CV_32FC1);
		cv::Mat R = cv::Mat::eye(3, 3, CV_32F);
		std::cout << "显示矫正图像" << endl;
		for (int i = 0; i != imageCount; i++)
		{
			std::cout << "Frame #" << i + 1 << "..." << endl;
			//计算图片畸变矫正的映射矩阵mapx、mapy(不进行立体校正、立体校正需要使用双摄)
			cv::initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, imageSize, CV_32FC1, mapx, mapy);
			//读取一张图片
			cv::Mat imageSource = imread(filenames[i]);
			cv::Mat newimage = imageSource.clone();
			//另一种不需要转换矩阵的方式
			//undistort(imageSource,newimage,cameraMatrix,distCoeffs);
			//进行校正
			cv::remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
			cv::imshow("原始图像", imageSource);
			cv::imshow("矫正后图像", newimage);
			cv::waitKey(500);
		}

		//释放资源
		fin.close();
		fout.close();
		system("pause");
	}
}

//Reference https://www.cnblogs.com/zyly/p/9366080.html

百度云链接
链接:https://pan.baidu.com/s/10fMazQohubCB8ZPG91JiPA
提取码:qdhw

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值