C# OpenCvSharp Demo - 棋盘格相机标定
目录
效果
项目
代码
下载
效果
项目
代码
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Windows.Forms;
namespace OpenCvSharp_Demo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
string fileFilter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png";
string startupPath;
string image_path;
Stopwatch stopwatch = new Stopwatch();
Mat image;
Mat result_image;
//棋盘格的宽度和高度
int BoardSize_Width = 9;
int BoardSize_Height = 6;
OpenCvSharp.Size BoardSize;
//每个方格的宽度
private int SquareSize = 50;
private int winSize = 11;
StringBuilder sb=new StringBuilder();
private void Form1_Load(object sender, EventArgs e)
{
startupPath = System.Windows.Forms.Application.StartupPath;
BoardSize = new OpenCvSharp.Size(BoardSize_Width, BoardSize_Height);
image_path = "1.jpg";
pictureBox1.Image = new Bitmap(image_path);
image = new Mat(image_path);
}
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = fileFilter;
if (ofd.ShowDialog() != DialogResult.OK) return;
pictureBox1.Image = null;
pictureBox2.Image = null;
textBox1.Text = "";
image_path = ofd.FileName;
pictureBox1.Image = new Bitmap(image_path);
image = new Mat(image_path);
}
private void button2_Click(object sender, EventArgs e)
{
stopwatch.Restart();
sb.Clear();
result_image = image.Clone();
// 存储每个图像的棋盘角点
List<Point2f[]> imagesPoints = new List<Point2f[]>();
// 相机内参矩阵和畸变系数
Mat cameraMatrix = new Mat(), distCoeffs = new Mat();
// 图像的尺寸
OpenCvSharp.Size imageSize = new OpenCvSharp.Size();
bool found = false;
// 读取图像
Mat view = new Mat(image_path);
Mat p = null;
if (!view.Empty())
{
imageSize = view.Size();
Point2f[] pointBuf;
// 查找棋盘角点
found = Cv2.FindChessboardCorners(view, BoardSize, out pointBuf, ChessboardFlags.AdaptiveThresh | ChessboardFlags.NormalizeImage);
if (found)
{
// 灰度化
Mat viewGray = new Mat();
Cv2.CvtColor(view, viewGray, ColorConversionCodes.BGR2GRAY);
// 亚像素精确化
Cv2.CornerSubPix(viewGray, pointBuf, new OpenCvSharp.Size(winSize, winSize), new OpenCvSharp.Size(-1, -1), new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.Count, 30, 0.0001));
// 存储角点坐标
imagesPoints.Add(pointBuf);
p = Mat.FromArray<Point2f>(pointBuf);
// 在图像上绘制角点
Cv2.DrawChessboardCorners(view, BoardSize, pointBuf, found);
Mat temp = view.Clone();
Cv2.ImShow("Image View", view);
}
}
Mat[] rvecs = new Mat[0];
Mat[] tvecs = new Mat[0];
// 运行相机标定
RunCalibration(1, imageSize, out cameraMatrix, out distCoeffs, new Mat[] { p }, out rvecs, out tvecs, out double totalAvgErr);
// 相机矩阵、畸变系数和平均误差
sb.AppendLine(string.Format("相机矩阵:\n{0}", Cv2.Format(cameraMatrix) + "\n"));
sb.AppendLine(string.Format("畸变系数:\n{0}", Cv2.Format(distCoeffs) + "\n"));
sb.AppendLine(string.Format("平均误差:\n{0}", totalAvgErr + "\n"));
// 畸变校正
Mat map1 = new Mat();
Mat map2 = new Mat();
Mat newCameraMatrix = Cv2.GetOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, out Rect roi);
Cv2.InitUndistortRectifyMap(cameraMatrix, distCoeffs, new Mat(), newCameraMatrix, imageSize, MatType.CV_16SC2, map1, map2);
// 显示校正后的图像
Mat temp2 = Cv2.ImRead(image_path, ImreadModes.Color);
Mat rview = new Mat();
// 校正
Cv2.Remap(temp2, rview, map1, map2, InterpolationFlags.Linear);
double costTime = stopwatch.Elapsed.TotalMilliseconds;
sb.AppendLine( $"\r\n耗时:{costTime:F2}ms");
textBox1.Text = sb.ToString();
pictureBox2.Image = new Bitmap(rview.ToMemoryStream());
}
// 运行相机标定
private void RunCalibration(int imagesCount, OpenCvSharp.Size imageSize, out Mat cameraMatrix, out Mat distCoeffs, Mat[] imagePoints, out Mat[] rvecs, out Mat[] tvecs, out double totalAvgErr)
{
// 初始化相机矩阵和畸变系数
cameraMatrix = Mat.Eye(new OpenCvSharp.Size(3, 3), MatType.CV_64F);
distCoeffs = Mat.Zeros(new OpenCvSharp.Size(8, 1), MatType.CV_64F);
// 计算棋盘角点的世界坐标
Mat[] objectPoints = CalcBoardCornerPositions(BoardSize, SquareSize, imagesCount);
// 进行相机标定
double rms = Cv2.CalibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, out rvecs, out tvecs, CalibrationFlags.None);
// 检查相机矩阵和畸变系数的范围
bool ok = Cv2.CheckRange(InputArray.Create(cameraMatrix)) && Cv2.CheckRange(InputArray.Create(distCoeffs));
// 计算重投影误差
totalAvgErr = ComputeReprojectionErrors(objectPoints, imagePoints, rvecs, tvecs, cameraMatrix, distCoeffs);
}
// 计算棋盘角点的世界坐标
private Mat[] CalcBoardCornerPositions(OpenCvSharp.Size BoardSize, float SquareSize, int imagesCount)
{
Mat[] corners = new Mat[imagesCount];
// 遍历每张图片
for (int k = 0; k < imagesCount; k++)
{
Point3f[] p = new Point3f[BoardSize.Height * BoardSize.Width];
for (int i = 0; i < BoardSize.Height; i++)
{
for (int j = 0; j < BoardSize.Width; j++)
{
// 计算每个格子的三维坐标并储存在一维数组 p 中
p[i * BoardSize.Width + j] = new Point3f(j * SquareSize, i * SquareSize, 0);
}
}
// 将三维坐标转换成 Mat 类型并存储再 corners 数组中
corners[k] = Mat.FromArray<Point3f>(p);
}
return corners;
}
// 计算重投影误差
private double ComputeReprojectionErrors(Mat[] objectPoints, Mat[] imagePoints, Mat[] rvecs, Mat[] tvecs, Mat cameraMatrix, Mat distCoeffs)
{
Mat imagePoints2 = new Mat();
int totalPoints = 0;
double totalErr = 0, err;
for (int i = 0; i < objectPoints.Length; ++i)
{
Cv2.ProjectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);
err = Cv2.Norm(imagePoints[i], imagePoints2, NormTypes.L2);
int n = objectPoints[i].Width * objectPoints[i].Height;
totalErr += err * err;
totalPoints += n;
}
return Math.Sqrt(totalErr / totalPoints);
}
private void button3_Click(object sender, EventArgs e)
{
if (pictureBox2.Image == null)
{
return;
}
Bitmap output = new Bitmap(pictureBox2.Image);
var sdf = new SaveFileDialog();
sdf.Title = "保存";
sdf.Filter = "Images (*.jpg)|*.jpg|Images (*.png)|*.png|Images (*.bmp)|*.bmp|Images (*.emf)|*.emf|Images (*.exif)|*.exif|Images (*.gif)|*.gif|Images (*.ico)|*.ico|Images (*.tiff)|*.tiff|Images (*.wmf)|*.wmf";
if (sdf.ShowDialog() == DialogResult.OK)
{
switch (sdf.FilterIndex)
{
case 1:
{
output.Save(sdf.FileName, ImageFormat.Jpeg);
break;
}
case 2:
{
output.Save(sdf.FileName, ImageFormat.Png);
break;
}
case 3:
{
output.Save(sdf.FileName, ImageFormat.Bmp);
break;
}
case 4:
{
output.Save(sdf.FileName, ImageFormat.Emf);
break;
}
case 5:
{
output.Save(sdf.FileName, ImageFormat.Exif);
break;
}
case 6:
{
output.Save(sdf.FileName, ImageFormat.Gif);
break;
}
case 7:
{
output.Save(sdf.FileName, ImageFormat.Icon);
break;
}
case 8:
{
output.Save(sdf.FileName, ImageFormat.Tiff);
break;
}
case 9:
{
output.Save(sdf.FileName, ImageFormat.Wmf);
break;
}
}
MessageBox.Show("保存成功,位置:" + sdf.FileName);
}
}
}
}
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Windows.Forms;namespace OpenCvSharp_Demo
{public partial class Form1 : Form{public Form1(){InitializeComponent();}string fileFilter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png";string startupPath;string image_path;Stopwatch stopwatch = new Stopwatch();Mat image;Mat result_image;//棋盘格的宽度和高度int BoardSize_Width = 9;int BoardSize_Height = 6;OpenCvSharp.Size BoardSize;//每个方格的宽度private int SquareSize = 50;private int winSize = 11;StringBuilder sb=new StringBuilder();private void Form1_Load(object sender, EventArgs e){startupPath = System.Windows.Forms.Application.StartupPath;BoardSize = new OpenCvSharp.Size(BoardSize_Width, BoardSize_Height);image_path = "1.jpg";pictureBox1.Image = new Bitmap(image_path);image = new Mat(image_path);}private void button1_Click(object sender, EventArgs e){OpenFileDialog ofd = new OpenFileDialog();ofd.Filter = fileFilter;if (ofd.ShowDialog() != DialogResult.OK) return;pictureBox1.Image = null;pictureBox2.Image = null;textBox1.Text = "";image_path = ofd.FileName;pictureBox1.Image = new Bitmap(image_path);image = new Mat(image_path);}private void button2_Click(object sender, EventArgs e){stopwatch.Restart();sb.Clear();result_image = image.Clone();// 存储每个图像的棋盘角点List<Point2f[]> imagesPoints = new List<Point2f[]>();// 相机内参矩阵和畸变系数Mat cameraMatrix = new Mat(), distCoeffs = new Mat();// 图像的尺寸OpenCvSharp.Size imageSize = new OpenCvSharp.Size();bool found = false;// 读取图像Mat view = new Mat(image_path);Mat p = null;if (!view.Empty()){imageSize = view.Size();Point2f[] pointBuf;// 查找棋盘角点found = Cv2.FindChessboardCorners(view, BoardSize, out pointBuf, ChessboardFlags.AdaptiveThresh | ChessboardFlags.NormalizeImage);if (found){// 灰度化Mat viewGray = new Mat();Cv2.CvtColor(view, viewGray, ColorConversionCodes.BGR2GRAY);// 亚像素精确化Cv2.CornerSubPix(viewGray, pointBuf, new OpenCvSharp.Size(winSize, winSize), new OpenCvSharp.Size(-1, -1), new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.Count, 30, 0.0001));// 存储角点坐标imagesPoints.Add(pointBuf);p = Mat.FromArray<Point2f>(pointBuf);// 在图像上绘制角点Cv2.DrawChessboardCorners(view, BoardSize, pointBuf, found);Mat temp = view.Clone();Cv2.ImShow("Image View", view);}}Mat[] rvecs = new Mat[0];Mat[] tvecs = new Mat[0];// 运行相机标定RunCalibration(1, imageSize, out cameraMatrix, out distCoeffs, new Mat[] { p }, out rvecs, out tvecs, out double totalAvgErr);// 相机矩阵、畸变系数和平均误差sb.AppendLine(string.Format("相机矩阵:\n{0}", Cv2.Format(cameraMatrix) + "\n"));sb.AppendLine(string.Format("畸变系数:\n{0}", Cv2.Format(distCoeffs) + "\n"));sb.AppendLine(string.Format("平均误差:\n{0}", totalAvgErr + "\n"));// 畸变校正Mat map1 = new Mat();Mat map2 = new Mat();Mat newCameraMatrix = Cv2.GetOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, out Rect roi);Cv2.InitUndistortRectifyMap(cameraMatrix, distCoeffs, new Mat(), newCameraMatrix, imageSize, MatType.CV_16SC2, map1, map2);// 显示校正后的图像Mat temp2 = Cv2.ImRead(image_path, ImreadModes.Color);Mat rview = new Mat();// 校正Cv2.Remap(temp2, rview, map1, map2, InterpolationFlags.Linear);double costTime = stopwatch.Elapsed.TotalMilliseconds;sb.AppendLine( $"\r\n耗时:{costTime:F2}ms");textBox1.Text = sb.ToString();pictureBox2.Image = new Bitmap(rview.ToMemoryStream());}// 运行相机标定private void RunCalibration(int imagesCount, OpenCvSharp.Size imageSize, out Mat cameraMatrix, out Mat distCoeffs, Mat[] imagePoints, out Mat[] rvecs, out Mat[] tvecs, out double totalAvgErr){// 初始化相机矩阵和畸变系数cameraMatrix = Mat.Eye(new OpenCvSharp.Size(3, 3), MatType.CV_64F);distCoeffs = Mat.Zeros(new OpenCvSharp.Size(8, 1), MatType.CV_64F);// 计算棋盘角点的世界坐标Mat[] objectPoints = CalcBoardCornerPositions(BoardSize, SquareSize, imagesCount);// 进行相机标定double rms = Cv2.CalibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, out rvecs, out tvecs, CalibrationFlags.None);// 检查相机矩阵和畸变系数的范围bool ok = Cv2.CheckRange(InputArray.Create(cameraMatrix)) && Cv2.CheckRange(InputArray.Create(distCoeffs));// 计算重投影误差totalAvgErr = ComputeReprojectionErrors(objectPoints, imagePoints, rvecs, tvecs, cameraMatrix, distCoeffs);}// 计算棋盘角点的世界坐标private Mat[] CalcBoardCornerPositions(OpenCvSharp.Size BoardSize, float SquareSize, int imagesCount){Mat[] corners = new Mat[imagesCount];// 遍历每张图片for (int k = 0; k < imagesCount; k++){Point3f[] p = new Point3f[BoardSize.Height * BoardSize.Width];for (int i = 0; i < BoardSize.Height; i++){for (int j = 0; j < BoardSize.Width; j++){// 计算每个格子的三维坐标并储存在一维数组 p 中p[i * BoardSize.Width + j] = new Point3f(j * SquareSize, i * SquareSize, 0);}}// 将三维坐标转换成 Mat 类型并存储再 corners 数组中corners[k] = Mat.FromArray<Point3f>(p);}return corners;}// 计算重投影误差private double ComputeReprojectionErrors(Mat[] objectPoints, Mat[] imagePoints, Mat[] rvecs, Mat[] tvecs, Mat cameraMatrix, Mat distCoeffs){Mat imagePoints2 = new Mat();int totalPoints = 0;double totalErr = 0, err;for (int i = 0; i < objectPoints.Length; ++i){Cv2.ProjectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);err = Cv2.Norm(imagePoints[i], imagePoints2, NormTypes.L2);int n = objectPoints[i].Width * objectPoints[i].Height;totalErr += err * err;totalPoints += n;}return Math.Sqrt(totalErr / totalPoints);}private void button3_Click(object sender, EventArgs e){if (pictureBox2.Image == null){return;}Bitmap output = new Bitmap(pictureBox2.Image);var sdf = new SaveFileDialog();sdf.Title = "保存";sdf.Filter = "Images (*.jpg)|*.jpg|Images (*.png)|*.png|Images (*.bmp)|*.bmp|Images (*.emf)|*.emf|Images (*.exif)|*.exif|Images (*.gif)|*.gif|Images (*.ico)|*.ico|Images (*.tiff)|*.tiff|Images (*.wmf)|*.wmf";if (sdf.ShowDialog() == DialogResult.OK){switch (sdf.FilterIndex){case 1:{output.Save(sdf.FileName, ImageFormat.Jpeg);break;}case 2:{output.Save(sdf.FileName, ImageFormat.Png);break;}case 3:{output.Save(sdf.FileName, ImageFormat.Bmp);break;}case 4:{output.Save(sdf.FileName, ImageFormat.Emf);break;}case 5:{output.Save(sdf.FileName, ImageFormat.Exif);break;}case 6:{output.Save(sdf.FileName, ImageFormat.Gif);break;}case 7:{output.Save(sdf.FileName, ImageFormat.Icon);break;}case 8:{output.Save(sdf.FileName, ImageFormat.Tiff);break;}case 9:{output.Save(sdf.FileName, ImageFormat.Wmf);break;}}MessageBox.Show("保存成功,位置:" + sdf.FileName);}}}
}
下载
源码下载