Emgu3.0 相机标定原理以及Emgu实现源码

      网上关于相机标定的实现都是基于Emgu 3.0之前的版本,我没有找到Emgu 3.0版本的实现方法,因此我特地花了两天将Emgu 3.0的实现方法贴了上来,前人栽树后人乘凉,也希望让后来者更快的熟悉Emgu 3.0版本中相机标定函数的使用方法。代码中我添加了大量的注释,方便阅读。由于时间比较仓促,代码写得并不是很规范,如果您在阅读过程中有任何的疑问欢迎私信我一起讨论,共同进步。如果这篇文章帮到了您,记得给我点个赞!哈哈哈!

 我所采用的是张正友相机标定法:

相机标定的目的获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵),内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。

相机标定的输入标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。

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

这三个基础的问题就决定了使用Opencv实现张正友法标定相机的标定流程、标定结果评价以及使用标定结果矫正原始图像的完整流程

1. 准备标定图片

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

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

4. 在棋盘标定图上绘制找到的内角点(非必须,仅为了显示)

5. 相机标定

6. 对标定结果进行评价

7. 查看标定效果——利用标定结果对棋盘图进行矫正


由于代码中添加了大量的注释,我就不详细解释了,跟着注释一起看,很快就能理解。

源码:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using System.IO;
using System.Runtime.InteropServices;
using Emgu.CV.Util;

/*
2018_04_08_ZhaoXu
相机标定的目的: 获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵
                 内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像

相机标定的输入: 标定图像上所有内角点的图像坐标,
                 标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上

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

namespace EmguCalibrateCamera
{
	public partial class Form1 : Form
	{
		public Form1()
		{

			InitializeComponent();

		}
		private void Form1_Load(object sender, EventArgs e)
		{

		}
		//Emgu中不可以直接利用at对矩阵数据的像素进行操作,自己写个转换方法
		public static double[] GetDoubleArray( Mat mat)
		{
			double[] temp = new double[mat.Height * mat.Width];
			Marshal.Copy(mat.DataPointer, temp, 0, mat.Height * mat.Width);
			return temp;
		}
		public static void SetArray( Mat mat, double[] data)
		{
			Marshal.Copy(data, 0, mat.DataPointer, mat.Height * mat.Width);
		}
		//相机标定
		private void CalibraCamera(object sender, EventArgs e)
		{
			//图像标定
			StreamReader sin = new StreamReader("calibdata.txt");
			//读取每一副图像,从中提出角点,然后对角点进行亚像素精确化
			Console.WriteLine("开始提取角点");
			int image_count = 0;//图像数量
			Size image_size = new Size();//图像尺寸
			int width = 4;
			int height = 6;
			Size board_size = new Size(4, 6);//标定版上每行每列的角点数目
			int CornerNum = board_size.Width * board_size.Height;//每张图片上的角点总数
			int nImage = 14;
			VectorOfPointF Npointsl = new VectorOfPointF();

			string filename;
			int count = 0;//用于存储角点个数
			Console.WriteLine("count = " + count);
			MCvPoint3D32f[][] object_points = new MCvPoint3D32f[nImage][];//保存标定板上角点的三维坐标
			PointF[][] corner_count = new PointF[nImage][];
			while ((filename = sin.ReadLine()) != null)
			{
				image_count++;
				//用于观察检验输出
				Console.WriteLine("image_count = " + image_count);
				//输出检验
				//打开获取到的图像
				Image<Bgr, byte> imageInput = new Image<Bgr, byte>(new Bitmap(Image.FromFile(filename)));
				pictureBox1.Image = imageInput.ToBitmap();
				if (image_count == 1)//读入第一张图片时获取图像宽高信息
				{
					Console.WriteLine("<---成功读取第一张图片--->");
					image_size.Width = imageInput.Cols;
					image_size.Height = imageInput.Rows;
					Console.WriteLine("image_size.Width  = " + image_size.Width);
					Console.WriteLine("image_size.Hright = " + image_size.Height);
				}
				//提取角点
				Mat view_gray = new Mat();
				CvInvoke.CvtColor(imageInput, view_gray, ColorConversion.Rgb2Gray);
				//提取内角点(内角点与标定板的边缘不接触)
				//对每一张标定图片,提取角点信息
				/*
				第一个参数Image,传入拍摄的棋盘图Mat图像,必须是8位的灰度或者彩色图像;
                                第二个参数patternSize,每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向;
                                第三个参数corners,用于存储检测到的内角点图像坐标位置,一般用元素是VectorOfPoint类型表示
                                第四个参数flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。 
				*/
				CvInvoke.FindChessboardCorners(view_gray, board_size, Npointsl, CalibCbType.AdaptiveThresh);
				corner_count[image_count - 1] = new PointF[24];	
				for (int i = 0; i < 24; i++)
				{
					corner_count[image_count - 1][i] = Npointsl.ToArray()[i];
				}
				//为了提高标定精度,需要在初步提取的角点信息上进一步提取亚像素信息,降低相机标定偏差
				//亚像素精确化FindCornerSubPix()
				/*
                                第一个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是PointF[][]向量来表示
                                第二个参数winSize,大小为搜索窗口的一半;
                                第三个参数zeroZone,死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为(-1,-1)时表示没有死区;
                                第四个参数criteria,定义求角点的迭代过程的终止条件,可以为迭代次数和角点精度两者的组合;
				*/
				view_gray.ToImage<Gray, byte>().FindCornerSubPix(corner_count, new Size(11, 11), new Size(-1, -1), new MCvTermCriteria(30, 0.1));
				//在图像上显示角点位置,在图片中标记角点
				Console.WriteLine("第" + image_count + "个图片已经被标记角点");
				//DrawChessboardCorners用于绘制被成功标定的角点
				/*
				第一个参数image,8位灰度或者彩色图像;
                                第二个参数patternSize,每张标定棋盘上内角点的行列数;
                                第三个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据
                                第四个参数patternWasFound,标志位,用来指示定义的棋盘内角点是否被完整的探测到,true表示别完整的探测到,函数会用直线依次连接所有的内角点,作为一个整体,false表示有未被探测到的内角点,这时候函数会以(红色)圆圈标记处检测到的内角点; 
				*/
				CvInvoke.DrawChessboardCorners(view_gray, board_size, Npointsl, true);//非必需,仅用做测试
				pictureBox2.Image = view_gray.ToImage<Bgr, byte>().ToBitmap();
				count = image_count;
				CvInvoke.WaitKey(500);//暂停0.5秒*/
			}
			Console.WriteLine("角点提取完成!!!");
			//摄像机标定
			Console.WriteLine("开始标定");
			//摄像机内参数矩阵
			Mat cameraMatrix = new Mat(3, 3, DepthType.Cv32F, 1);
			//畸变矩阵
			//摄像机的5个畸变系数:k1,k2,p1,p2,k3
			Mat distCoeffs = new Mat(1, 5, DepthType.Cv32F, 1);
			//旋转矩阵R
			Mat[] rotateMat = new Mat[nImage];
			for(int i=0;i<nImage;i++)
			{
				rotateMat[i] = new Mat(3, 3, DepthType.Cv32F, 1);
			}
			//平移矩阵T
			Mat[] transMat = new  Mat[nImage];
			for (int i = 0; i < nImage; i++)
			{
				transMat[i] = new Mat(3, 1, DepthType.Cv32F, 1);
			}
			//初始化标定板上角点的三维坐标
			List<MCvPoint3D32f> object_list = new List<MCvPoint3D32f>();
			for (int k = 0; k < nImage; k++)
			{
				object_list.Clear();
				for (int i = 0; i < height; i++)
				{
					for (int j = 0; j < width; j++)
					{
						object_list.Add(new MCvPoint3D32f(j , i , 0f));
					}
				}
				object_points[k] = object_list.ToArray();
			}
			//相机标定
			//获取到棋盘标定图的内角点图像坐标之后,使用CalibrateCamera函数进行相机标定,计算相机内参和外参矩阵
			/*
			第一个参数objectPoints,为世界坐标系中的三维点。在使用时,应该输入一个三维坐标点的向量的向量MCvPoint3D32f[][],即需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标。
                        第二个参数imagePoints,为每一个内角点对应的图像坐标点。和objectPoints一样,应该输入PointF[][]类型变量;
                        第三个参数imageSize,为图像的像素尺寸大小,在计算相机的内参和畸变矩阵时需要使用到该参数;
                        第四个参数cameraMatrix为相机的内参矩阵。输入一个Mat cameraMatrix即可,如Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
                        第五个参数distCoeffs为畸变矩阵。输入一个Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))即可 
			第六个参数CalibType相机标定类型
			第七个参数criteria是最优迭代终止条件设定
			第八个参数out Mat[]类型的旋转矩阵
			第九个参数out Mat[]类型的平移矩阵
			*/
			//在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化
			//标定的结果是生成相机的内参矩阵cameraMatrix、相机的5个畸变系数distCoeffs
			//另外每张图像都会生成属于自己的平移向量和旋转向量
			CvInvoke.CalibrateCamera(object_points, corner_count, image_size, cameraMatrix,
				  distCoeffs, CalibType.RationalModel, new MCvTermCriteria(30,0.1),  out rotateMat, out transMat);
			Console.WriteLine("标定完成");							 
			/*标定评价略*/
			//利用标定结果对图像进行畸变校正
			//mapx和mapy为输出的x/y坐标重映射参数
			/*
			Mat mapx = new Mat(image_size, DepthType.Cv32F, 1);
			Mat mapy = new Mat(image_size, DepthType.Cv32F, 1);
			//可选输入,是第一和第二相机坐标之间的旋转矩阵
			Mat R = new Mat(3, 3, DepthType.Cv32F, 1);
			//输出校正之后的3x3摄像机矩阵
			Mat newCameraMatrix = new Mat(3, 3, DepthType.Cv32F, 1);
			*/
			Console.WriteLine("保存矫正图像");

			StreamReader sin_test = new StreamReader("calibdata1.txt");
			string filename_test;
			for (int i = 0; i < nImage; i++)
			{
				//InitUndistortRectifyMap用来计算畸变映射
				//CvInvoke.InitUndistortRectifyMap(cameraMatrix, distCoeffs, R, newCameraMatrix, image_size, DepthType.Cv32F, mapx, mapy);
				if((filename_test = sin_test.ReadLine()) != null)
				{
					Image<Bgr, byte> imageSource = new Image<Bgr, byte>(new Bitmap(Image.FromFile(filename_test)));
					Image<Bgr, byte> newimage = imageSource.Clone();
					CvInvoke.Undistort(imageSource,newimage,cameraMatrix,distCoeffs);
					//Remap把求得的映射应用到图像上
					//CvInvoke.Remap(imageSource, newimage, mapx, mapy, Inter.Linear, BorderType.Constant, new MCvScalar(0));
					pictureBox3.Image = imageSource.ToBitmap();								
					pictureBox4.Image = newimage.ToBitmap();					
					CvInvoke.WaitKey(500);
				}
			}
			Console.WriteLine("标定结束!");
		}
		private void button1_Click_1(object sender, EventArgs e)
		{
			CalibraCamera(sender,e);
		}
	}
}

整个工程文件见我的Github(完整工程,包含图片以及txt文件):

https://github.com/yuntianqinghe/CalibrateCamera


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值