用OpenCVSharp完成小孔镜头的内参标定和图像畸变校正
镜头的内参,外参的详细细节请自行百度。我这里直接上代码:
封装类名:class MyCameraCalibration
类内数据:
//图片格式后缀:jpg 或者bmp
public string Img_Extent_Name
{
set;
get;
}
//枚举
public enum Imgformat
{
Jpg = 0,
Bmp = 1
}
//棋盘格内角点数量,宽度方向,单位:个
public double Pattern_Size_W
{
set;
get;
}
//棋盘格内角点数量,高度方向,单位:个
public double Pattern_Size_H
{
set;
get;
}
//棋盘格,单个格子宽度,单位mm
public double Squart_Size_W
{
set;
get;
}
//棋盘格,单个格子高度,单位mm
public double Squart_Size_H
{
set;
get;
}
//实现图像校正后的视场缩放:1 不变,其他缩放视场
public double Alpha
{
set;
get;
}
//相机内参
double[,] camera_matrix;
//相机畸变参数
double[] dist_coeffs;
//n个棋盘物理点坐标
List<List<Point3f>> objectPlist;
//n个图像中找到的角点坐标
List<List<Point2f>> imagePlist;
//一个棋盘图片中对应的实际棋盘坐标
List<Point3f> one_img_Plist;
public Size Imgsize;
public int Imgsize_W
{
set;
get;
}
public int Imgsize_H
{
set;
get;
}
//图像校正的两个矩阵
public Mat MapX;
public Mat MapY;
//角点绘制图像保存文件夹
private string Corner_Draw_Path;
//相机内参和畸变数据保存文件夹
private string Camera_Data_Path;
//封装方法1:通过读取文件夹内的棋盘图片来完成相机内参和畸变参数的获取和保存
public int Calibrate_Chess_Images_Array(string imgfolder) { int result = 0; try { var allimages = MyFile.ListFilenames(imgfolder, Img_Extent_Name); //Get Related data first! for (int i = 0; i < allimages.Count; i++) { Mat chessimg_temp = Cv2.ImRead(allimages[i], ImreadModes.Color); Mat gray_temp = chessimg_temp.CvtColor(ColorConversionCodes.BGR2GRAY); Point2f[] corners = null; Point2f[] corners_subpix = null; if (Imgsize.Width == 0 || Imgsize.Height == 0) { Imgsize = chessimg_temp.Size(); Imgsize_W = Imgsize.Width; Imgsize_H = Imgsize.Height; WriteConfig(this); } //寻找角点 bool inner_result1 = Cv2.FindChessboardCornersSB(chessimg_temp, new Size(Pattern_Size_W, Pattern_Size_H), out corners, ChessboardFlags.None); if (inner_result1) { //加入成功找到 //亚像素 corners_subpix = Cv2.CornerSubPix(gray_temp, corners, new Size(11, 11), new Size(-1, -1), new TermCriteria(CriteriaType.Eps, 30, 0.1)); if (corners_subpix != null) { imagePlist.Add(corners_subpix.ToList()); objectPlist.Add(one_img_Plist.ToList()); //draw corners Cv2.DrawChessboardCorners(chessimg_temp, new Size(Pattern_Size_W, Pattern_Size_H), corners_subpix, true); //saveimg chessimg_temp.SaveImage(Corner_Draw_Path + i.ToString()+ ".jpg"); } } chessimg_temp.Dispose(); gray_temp.Dispose(); } Vec3d[] rvecs = null; Vec3d[] tvecs = null; var err= Cv2.CalibrateCamera(objectPlist,imagePlist, Imgsize, camera_matrix, dist_coeffs, out rvecs,out tvecs); //save FileStorage fs = new FileStorage(Camera_Data_Path+ "Intrinsics.xml", FileStorage.Mode.FormatYaml | FileStorage.Mode.Write); Cv2.Write(fs, "Intrinsics", Build_Camera_Matrix_from_data(camera_matrix)); fs.Dispose(); fs = new FileStorage(Camera_Data_Path + "Distortion.xml", FileStorage.Mode.FormatYaml | FileStorage.Mode.Write); Cv2.Write(fs, "Distortion", Build_Camera_Distort_from_data(dist_coeffs)); fs.Dispose(); } catch (Exception ex) { result = -1; } return result; }
封装方法2:主要完成畸变校正矩阵的计算
/// <summary> /// 计算默认的畸变映射矩阵 /// </summary> /// <returns></returns> public int Cal_Image_Map_Array() { int result = 0; try { // Mat R = Mat.Eye(3, 3, MatType.CV_32FC1); MapX = new Mat(Imgsize, MatType.CV_32FC1, Scalar.Black); MapY = new Mat(Imgsize, MatType.CV_32FC1, Scalar.Black); //read FileStorage fs = new FileStorage(Camera_Data_Path + "Intrinsics.xml", FileStorage.Mode.FormatYaml | FileStorage.Mode.Read); Mat camerra_matrix= Cv2.ReadMat(fs["Intrinsics"]); fs.Dispose(); fs = new FileStorage(Camera_Data_Path + "Distortion.xml", FileStorage.Mode.FormatYaml | FileStorage.Mode.Read); Mat camera_distortion = Cv2.ReadMat(fs["Distortion"]); fs.Dispose(); Rect validPixROI; Cv2.InitUndistortRectifyMap(camerra_matrix, camera_distortion, R, Cv2.GetOptimalNewCameraMatrix(camerra_matrix, camera_distortion, Imgsize, Alpha, Imgsize,out validPixROI) , Imgsize, MatType.CV_32FC1, MapX,MapY); R?.Dispose(); camerra_matrix?.Dispose(); camera_distortion?.Dispose(); } catch (Exception ex) { result = -1; } return result; }
其他相关封装方法:
/// <summary> /// Squart_Size_W,Squart_Size_H 棋盘格每个格子的实际尺寸 ,单位mm /// </summary> /// <returns></returns> private int Get_Chess_Images_ObjPoints() { int result = 0; try { for(int j=0;j<Pattern_Size_H;j++) { for (int i = 0; i < Pattern_Size_W; i++) { //假定Z=0 Point3f point3F = new Point3f(); point3F.X = i* (float)Squart_Size_W; point3F.Y = j* (float)Squart_Size_H; point3F.Z = 0; one_img_Plist.Add(point3F); } } } catch(Exception ex) { result = -1; } return result; } /// <summary> /// 将数组转换成矩阵来保存 /// </summary> /// <param name="inputdata"></param> /// <returns></returns> private Mat Build_Camera_Matrix_from_data(double[,] inputdata) { Mat Camera_Matrix =null; try { int row= inputdata.GetUpperBound(0); int col = inputdata.GetUpperBound(1); Camera_Matrix = new Mat(row+1, col+1, MatType.CV_32FC1, Scalar.Black); for(int j=0;j<=row;j++) { for(int i=0;i<=col;i++) { Camera_Matrix.Set<float>(j, i, (float)inputdata[j,i]); var test = Camera_Matrix.Get<float>(j, i); } } } catch(Exception ex) { Camera_Matrix?.Dispose(); Camera_Matrix = null; } return Camera_Matrix; } /// <summary> /// 将数组转换成矩阵来保存 /// </summary> /// <param name="inputdata"></param> /// <returns></returns> private Mat Build_Camera_Distort_from_data(double[] inputdata) { Mat Camera_Distort = new Mat(); try { int row = inputdata.GetUpperBound(0); // int col = inputdata.GetUpperBound(1); Camera_Distort = new Mat(row+1, 1, MatType.CV_32FC1, Scalar.Black); for (int j = 0; j <= row; j++) { Camera_Distort.Set<float>(j, 0, (float)inputdata[j]); } } catch (Exception ex) { Camera_Distort?.Dispose(); Camera_Distort = null; } return Camera_Distort; }
文件枚举方法:
public static List<string> ListFilenames(string path, string extName) { List<string> allfilename = new List<string>(); try { if (path == null || path == "") { return allfilename; } string[] dir = Directory.GetDirectories(path); //文件夹列表 DirectoryInfo fdir = new DirectoryInfo(path); FileInfo[] file = fdir.GetFiles(); if (file.Length != 0 || dir.Length != 0) //当前目录文件或文件夹不为空 { foreach (FileInfo f in file) //显示当前目录所有文件 { if (f.Extension.ToLower().IndexOf(extName.ToLower()) >= 0) { allfilename.Add(f.FullName); } } foreach (string d in dir) { getdir(d, extName);//递归 } } return allfilename; } catch (Exception ex) { return allfilename; throw ex; } }
/// <summary> /// 私有方法,递归获取指定类型文件,包含子文件夹 /// </summary> /// <param name="path"></param> /// <param name="extName"></param> private static void getdir(string path, string extName) { try { if(path==null|| path=="") { return; } string[] dir = Directory.GetDirectories(path); //文件夹列表 DirectoryInfo fdir = new DirectoryInfo(path); FileInfo[] file = fdir.GetFiles(); if (file.Length != 0 || dir.Length != 0) //当前目录文件或文件夹不为空 { foreach (FileInfo f in file) //显示当前目录所有文件 { if (f.Extension.ToLower().IndexOf(extName.ToLower()) >= 0) { List.Add(f); } } foreach (string d in dir) { getdir(d, extName);//递归 } } } catch (Exception ex) { throw ex; } }
部分参数需要初始化:
Imgsize = new Size(Imgsize_W, Imgsize_H); // objectPoints = new List<Mat>(); imagePoints = new List<Mat>(); //内参和畸变 cameraMatrix = new Mat(3, 3, MatType.CV_32FC1, Scalar.Black); distCoeffs = new Mat(5, 1, MatType.CV_32FC1, Scalar.Black); camera_matrix = new double[3,3]; dist_coeffs = new double[5];
使用前注意赋值给相关参数;
使用效果:
棋盘图片:如果需要可以联系我,太大了。
计算获取的参数:
内参:
%YAML:1.0
---
Distortion: !!opencv-matrix
rows: 5
cols: 1
dt: f
data: [ -1.07264303e-01, 7.62544945e-02, -1.15676608e-03,
1.04630087e-03, 4.89084125e-02 ]
畸变:
%YAML:1.0
---
Intrinsics: !!opencv-matrix
rows: 3
cols: 3
dt: f
data: [ 1.40335022e+03, 0., 1.21855811e+03, 0., 1.40350696e+03,
1.06063965e+03, 0., 0., 1. ]
棋盘角点图片:
畸变校正效果:畸变前
校正后:
仔细对比,还是有一定效果。
自动化比较忙。有问题可以留言。