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);
}
}
}
}
下载
源码下载