OpenCvSharp 棋盘格标定助手

4 篇文章 0 订阅
3 篇文章 1 订阅
  • 使用的是VS调用OpenCvSharp资源库进行一个Winform操作界面编写,网上找了很多开源的程序,发现根本用不了的,用的时候还需要你配置各种电脑系统变量,显得好麻烦。现在弄了个简单的标定助手,可以完美运行,带有棋盘格图像生成工具,操作简单,源码也不复杂。
    这里是
using OpenCvSharp;//需要引用OpenCvSharp
using OpenCvSharp.Extensions;
using Size = OpenCvSharp.Size;

        public Mat ChessBoardMat;
        /// <summary>
        /// 生成棋盘格图像
        /// </summary>
        /// <param name="BoardSize">输入棋盘格角点数量的大小</param>
        /// <param name="ImagePixel">输入棋盘格的像素大小</param>
        /// <returns></returns>
		public Bitmap GenChessBoard(OpenCvSharp.Size BoardSize, OpenCvSharp.Size ImagePixel)
        {
            int perBoardPixel = ImagePixel.Height / BoardSize.Height;
            int basisHeight = (ImagePixel.Height - perBoardPixel * BoardSize.Height) / 2;
            int basisWidth = (ImagePixel.Width - perBoardPixel * BoardSize.Width) / 2;
            if (basisHeight < 0 || basisWidth < 0)
            {
                return null;
            }
            ChessBoardMat = new Mat(ImagePixel, MatType.CV_8UC1, Scalar.All(255));
            int flag;
            for (int j = 0; j < BoardSize.Height; j++)
            {
                for (int i = 0; i < BoardSize.Width; i++)
                {
                    flag = (i + j) % 2;
                    if (flag == 0)
                    {
                        for (int n = j * perBoardPixel; n < (j + 1) * perBoardPixel; n++)
                            for (int m = i * perBoardPixel; m < (i + 1) * perBoardPixel; m++)
                                ChessBoardMat.At<byte>(n + basisHeight, m + basisWidth) = 0;
                    }
                }
            }
            return BitmapConverter.ToBitmap(ChessBoardMat);//返回棋盘格图像结果
            //Cv2.ImWrite("chessBoard1.bmp", image);//这里是保存生成的图像,可以不使用
        }

结果如下:
棋盘格生成工具点击“导入图像”后,选择采集的棋盘格图像所在文件夹,结果如下:
棋盘格标定助手 导入图像

可以在图像列表看到导入结果,在参数设置那里设置正确的棋盘格角点数量后,在图像列表双击图像路径,可以实现棋盘格图像角点的提取显示:
棋盘格标定助手 图像角点显示
可以提取角点之后就可以标定啦!
棋盘格标定助手  标定结果标定完成后,可以进行畸变矫正,这个是进行畸变矫正的结果,在这里并没有写保存矫正结果的代码。
所以只是看看就好

然后所有结果都出来了,并且自动保存结果在图像文件里内!
标定结果数据查看

这里是核心源码,至于Winform的操作代码,这里就不放了。感兴趣的朋友可以去下载看看

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using Size = OpenCvSharp.Size;

namespace CvCalibrate
{
    public class Calib_Class
    {
        public Mat ChessBoardMat;
        #region 初始化变量
        /*内外参数*/
        Mat cameraMatrix = new Mat(3, 3, MatType.CV_64FC1, Scalar.All(0)); /* 摄像机内参数矩阵 */
        List<int> point_counts = new List<int>();  // 每幅图像中角点的数量
        Mat distCoeffs = new Mat(1, 12, MatType.CV_64FC1, Scalar.All(0)); /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */  /*加上薄棱镜畸变模型,启用畸变系数S1、S2、S3和S4*/
        List<Mat> TranslationMats = new List<Mat>();  /* 每幅图像的旋转向量 */
        List<Mat> RotationMats = new List<Mat>(); /* 每幅图像的平移向量 */
        /*Mat数据类型转换double的结果*/
        public double[,] CameraParameterArray = new double[3, 3];//摄像机内参数矩阵
        public double[] DistCoeffsArray = new double[12];//摄像机的5个畸变系数:k1,k2,p1,p2,k3 /*加上薄棱镜畸变模型,启用畸变系数S1、S2、S3和S4*/
        public Vec3d[] Rotation; //存放所有图像的3*1旋转向量,每一副图像的旋转向量为一个mat
        public Vec3d[] Translation;//存放所有图像的3*1平移向量,每一副图像的平移向量为一个mat
        public double MeanError = 0.0; /* 所有图像的平均误差的总和 */
        public List<double> TotalError = new List<double>(); /* 每幅图像的平均误差 */
        public Size BoardImageSize = new Size();
        #endregion
        /// <summary>
        /// 角点提取,返回Bitmap数据
        /// </summary>
        /// <param name="imageInput">图像输入</param>
        /// <param name="BoardSize">棋盘格角点数量大小</param>
        /// <returns></returns>
        public Bitmap FindChessboardCorners(Image imageInput, OpenCvSharp.Size BoardSize)
        {
            try
            {
                Point2f[] image_points_buf;  /* 缓存每幅图像上检测到的角点 */
                List<Point2f[]> image_points_seq = new List<Point2f[]>(); /* 保存检测到的所有角点 */
                Mat image = BitmapConverter.ToMat(new Bitmap(imageInput));
                /* 提取角点 */
                Cv2.FindChessboardCorners(image, BoardSize, out image_points_buf);
                Mat view_gray = new Mat();
                Cv2.CvtColor(image, view_gray, ColorConversionCodes.RGB2GRAY);
                /* 亚像素精确化 */
                Point2f[] SubPix_points = Cv2.CornerSubPix(view_gray, image_points_buf, new Size(3, 3), new Size(-1, -1), TermCriteria.Both(30, 0.1));
                image_points_seq.Add(SubPix_points);  //保存亚像素角点
                /* 在图像上显示角点位置 */
                Cv2.DrawChessboardCorners(view_gray, BoardSize, image_points_buf, true); //用于在图片中标记角点
                return BitmapConverter.ToBitmap(view_gray);
            }
            catch (Exception)
            {
                return new Bitmap(imageInput);
            }

        }
        public double Fovx/*沿水平传感器轴的视野*/, Fovy/*沿竖直传感器轴的视野*/, FocalLength/*透镜焦距mm*/, AspectRatio/*fy/fx*/;
        public Point2d Principal/*主点坐标mm*/;
        /// <summary>
        /// 运行标定
        /// </summary>
        /// <param name="ChessBoardFiles">棋盘格图像文件路径</param>
        /// <param name="BoardNum">棋盘格角点数量大小</param>
        /// <param name="ErrorMessage">返回判断的消息</param>
        /// <returns></returns>
        public bool Calibrate(List<string> ChessBoardFiles, Size BoardNum, string ErrorMessage)
        {
            int image_count = 0;  /* 图像数量 */
            Size image_size = new Size();  /* 图像的尺寸 */
            if (ChessBoardFiles.Count() > 0)
            {

                #region 角点提取                 
                Point2f[] image_points_buf;  /* 缓存每幅图像上检测到的角点 */
                List<Point2f[]> image_points_seq = new List<Point2f[]>(); /* 保存检测到的所有角点 */
                /*读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化*/
                for (int i = 0; i < ChessBoardFiles.Count(); i++)
                {
                    try
                    {
                        Mat imageInput = Cv2.ImRead(ChessBoardFiles[i]);
                        image_count++;
                        if (image_count == 1)  //读入第一张图片时获取图像宽高信息
                        {
                            BoardImageSize.Width = image_size.Width = imageInput.Cols;
                            BoardImageSize.Height = image_size.Height = imageInput.Rows;
                        }
                        image_points_buf = FindChessboardCorners(imageInput, BoardNum, ErrorMessage);
                        if (image_points_buf.Count() > 0)
                        {
                            image_points_seq.Add(image_points_buf);
                        }
                        else
                        {
                            ErrorMessage += "图 " + i + ", ";
                        }

                    }
                    catch (Exception Err)
                    {
                        ErrorMessage = " 角点提取失败!" + "\n\r" + Err.Message;
                        return false;
                    }
                }
                #endregion

                #region 标定板初始化
                //棋盘三维信息
                // 初始化标定板上角点的三维坐标
                //生成一个标准的标定板角点坐标集合
                Size square_size = new Size(20, 20);  //初始化标定板上每个棋盘格的大小 
                List<List<Point3f>> object_points = new List<List<Point3f>>(); // 保存标定板上角点的三维坐标
                if (BoardNum.Width > 0)
                {
                    try
                    {
                        for (int t = 0; t < image_count; t++)
                        {
                            List<Point3f> tempPointSet = new List<Point3f>();
                            for (int i = 0; i < BoardNum.Height; i++)
                            {
                                for (int j = 0; j < BoardNum.Width; j++)
                                {
                                    Point3f realPoint;
                                    // 假设标定板放在世界坐标系中z=0的平面上
                                    realPoint.X = i * square_size.Width;
                                    realPoint.Y = j * square_size.Height;
                                    realPoint.Z = 0;
                                    tempPointSet.Add(realPoint);
                                }
                            }
                            object_points.Add(tempPointSet);
                            // 初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板 
                            point_counts.Add(BoardNum.Width * BoardNum.Height);
                        }
                    }
                    catch (Exception Err)
                    {
                        ErrorMessage = "初始化棋盘格失败!" + "\n\r" + Err.Message;
                    }
                }
                else
                {
                    ErrorMessage = "棋盘格大小设置错误!";
                    return false;
                }

                #endregion

                #region 开始标定
                try
                {
                    /*薄棱镜畸变模型,启用畸变系数S1、S2、S3和S4*/
                    /*迭代标准*/
                    TermCriteria criteria = new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 30, 0.01);
                    Cv2.CalibrateCamera(object_points, image_points_seq, image_size, CameraParameterArray, DistCoeffsArray, out Rotation, out Translation, CalibrationFlags.ThinPrismModel, criteria);

                }
                catch (Exception Err)
                {
                    ErrorMessage = "标定失败!" + "\n\r" + Err.Message;
                    return false;
                }
                #endregion

                #region 数据类型转换
                for (int r = 0; r < CameraParameterArray.GetLength(0); r++)
                {
                    for (int c = 0; c < CameraParameterArray.GetLength(1); c++)
                    {
                        cameraMatrix.At<double>(r, c) = CameraParameterArray[r, c];
                    }
                }

                for (int r = 0; r < DistCoeffsArray.Length; r++)
                {
                    distCoeffs.At<double>(0, r) = DistCoeffsArray[r];
                }
                for (int r = 0; r < Rotation.Length; r++)
                {
                    Mat TempRotation = new Mat(3, 1, MatType.CV_64FC1);

                    TempRotation.At<double>(0, 0) = Rotation[r].Item0;
                    TempRotation.At<double>(1, 0) = Rotation[r].Item1;
                    TempRotation.At<double>(2, 0) = Rotation[r].Item2;
                    RotationMats.Add(TempRotation);

                    Mat TempTrans = new Mat(3, 1, MatType.CV_64FC1);

                    TempTrans.At<double>(0, 0) = Translation[r].Item0;
                    TempTrans.At<double>(1, 0) = Translation[r].Item1;
                    TempTrans.At<double>(2, 0) = Translation[r].Item2;
                    TranslationMats.Add(TempTrans);
                }
                #endregion

                #region 重新投影计算,误差计算
                double total_err = 0.0;            // 所有图像的平均误差的总和 
                double Error;                      //每幅图像的平均误差 

                for (int i = 0; i < image_count; i++)
                {
                    try
                    {
                        List<Point3f> tempPointSet = object_points[i];
                        /* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
                        Mat NewProjectPoints = new Mat();/* 保存重新计算得到的投影点 */
                        Mat jacobian = new Mat();//雅可比矩阵
                        Cv2.ProjectPoints(InputArray.Create<Point3f>(tempPointSet), RotationMats[i], TranslationMats[i], cameraMatrix, distCoeffs, NewProjectPoints, jacobian, 0);
                        List<Point2f> NewProjectPointsToPoint2f = new List<Point2f>(); /* 保存重新计算得到的投影点,格式转换 */
                        {
                            string[] PointsArray = Cv2.Format(NewProjectPoints, FormatType.CSV).Split('\n');//分割出来最后一个回车符号占了一个空间
                            for (int r = 0; r < PointsArray.Length - 1; r++)
                            {
                                Point2f Temp = new Point2f();//临时缓存
                                string[] PointTemp = PointsArray[r].Split(',');
                                Temp.X = float.Parse(PointTemp[0]);
                                Temp.Y = float.Parse(PointTemp[1]);
                                NewProjectPointsToPoint2f.Add(Temp);
                            }
                        }

                        /* 计算新的投影点和旧的投影点之间的误差*/
                        Point2f[] OldProjectPoints = image_points_seq[i];
                        Mat tempImagePointMat = new Mat(1, OldProjectPoints.Length, MatType.CV_32FC2);
                        Mat image_points2Mat = new Mat(1, NewProjectPoints.Rows, MatType.CV_32FC2);
                        for (int j = 0; j < OldProjectPoints.Count(); j++)
                        {
                            image_points2Mat.At<Vec2f>(0, j) = new Vec2f(NewProjectPointsToPoint2f[j].X, NewProjectPointsToPoint2f[j].Y);
                            tempImagePointMat.At<Vec2f>(0, j) = new Vec2f(OldProjectPoints[j].X, OldProjectPoints[j].Y);
                        }
                        Error = Cv2.Norm(image_points2Mat, tempImagePointMat, NormTypes.L2);//当前图像的投影误差
                        total_err += Error * Error;
                        TotalError.Add(Error);
                        MeanError += Error /= point_counts[i];//总的平均误差
                        double 重投影误差3 = (total_err / image_count);
                    }
                    catch (Exception Err)
                    {
                        ErrorMessage = "误差计算失败!" + "\n\r" + Err.Message;
                        return false;
                    }
                }
                #endregion

                #region 旋转矩阵计算
                List<Mat> AllRotation_Matrix = new List<Mat>(); /* 保存每幅图像的旋转矩阵 */
                for (int i = 0; i < image_count; i++)
                {
                    try
                    {
                        Mat rotation_matrix = new Mat(3, 3, MatType.CV_64FC1, Scalar.All(0));
                        /* 将旋转向量转换为相对应的旋转矩阵 */
                        /*Rodrigues()可以将旋转向量转化为旋转矩阵,也可以将旋转矩阵转化为旋转向量。旋转向量指定了旋转轴,同时它的模长也指定了旋转角度。*/
                        Cv2.Rodrigues(TranslationMats[i], rotation_matrix);
                        AllRotation_Matrix.Add(rotation_matrix);
                    }
                    catch (Exception Err)
                    {
                        ErrorMessage = "旋转矩阵计算失败!" + "\n\r" + Err.Message;
                        return false;
                    }

                }
                #endregion
                //Mat Rvec = new Mat();
                //Mat Tvec = new Mat();
                //Cv2.SolvePnP(InputArray.Create<Point3f>(object_points[0]), InputArray.Create<Point2f>(image_points_seq[0]), cameraMatrix, distCoeffs, Rvec, Tvec);
                //double[] R = MatToDouble(Rvec);
                //double[] T = MatToDouble(Tvec);
                Cv2.CalibrationMatrixValues(cameraMatrix, BoardImageSize, 5.3, 7.2, out Fovx, out Fovy, out FocalLength, out Principal, out AspectRatio);

            }
            else
            {
                ErrorMessage = "输入图像路径为空!";
                return false;
            }
            return true;
        }

        private delegate Point2f[] FindCornersDelegate(Mat Src, Size BoardNum, string ErrorMessage);

        /// <summary>
        /// 角点提取
        /// </summary>
        /// <param name="Src">图源</param>
        /// <param name="BoardNum">棋盘格角点数量大小</param>
        /// <param name="ErrorMessage">返回判断的消息</param>
        /// <returns></returns>
        private static Point2f[] FindChessboardCorners(Mat Src, Size BoardNum, string ErrorMessage)
        {
            Point2f[] image_points_buf = new Point2f[BoardNum.Height * BoardNum.Width];  /* 缓存每幅图像上检测到的角点 */
            Point2f[] SubPix_points = new Point2f[BoardNum.Height * BoardNum.Width];
            try
            {
                /* 提取角点 */
                if (!Cv2.FindChessboardCorners(Src, BoardNum, out image_points_buf))
                {
                    //粗提取角点,若无法提取则返回失败,反之进行精细化提取
                    ErrorMessage = " 角点提取失败!";
                }
                else
                {
                    Mat view_gray = new Mat();
                    Cv2.CvtColor(Src, view_gray, ColorConversionCodes.RGB2GRAY);
                    SubPix_points = Cv2.CornerSubPix(view_gray, image_points_buf, new Size(3, 3), new Size(-1, -1), TermCriteria.Both(30, 0.1));//对粗提取的角点进行精确化
                    //Cv2.Find4QuadCornerSubpix(view_gray, image_points_buf, new Size(5, 5)); //对粗提取的角点进行精确化
                }
            }
            catch (Exception Err)
            {
                ErrorMessage = Err.Message;
            }
            return SubPix_points;
        }

        #region 类型转换
        static double[] MatToDouble(Mat InMat)
        {
            if (InMat.Rows >= 1 && InMat.Cols == 1)
            {
                double[] OutDouble = new double[InMat.Rows];
                for (int i = 0; i < InMat.Rows; i++)
                {
                    OutDouble[i] = InMat.At<double>(i);
                }
                return OutDouble;
            }
            else if (InMat.Rows == 1 && InMat.Cols >= 1)
            {
                double[] OutDouble = new double[InMat.Cols];
                for (int i = 0; i < InMat.Cols; i++)
                {
                    OutDouble[i] = InMat.At<double>(i);
                }
                return OutDouble;
            }
            else
            {
                return null;
            }
        }
        static double[,] MatToDouble2D(Mat InMat)
        {
            if (InMat.Rows >= 1 && InMat.Cols >= 1)
            {
                double[,] OutDouble = new double[InMat.Rows, InMat.Cols];
                for (int i = 0; i < InMat.Rows; i++)
                {
                    for (int j = 0; j < InMat.Cols; j++)
                    {
                        OutDouble[i, j] = InMat.At<double>(i, j);
                    }
                }
                return OutDouble;
            }
            else
            {
                return null;
            }
        }
        static Mat DoubleToMat(double[] InDouble)
        {
            Mat OutMat = new Mat(1, InDouble.Length, MatType.CV_64FC1);
            for (int r = 0; r < InDouble.Length; r++)
            {
                OutMat.At<double>(0, r) = InDouble[r];
            }
            return OutMat;
        }
        static Mat Double2DToMat(double[,] InDouble)
        {
            Mat OutMat = new Mat(InDouble.GetLength(0), InDouble.GetLength(1), MatType.CV_64FC1);
            for (int r = 0; r < InDouble.GetLength(0); r++)
            {
                for (int c = 0; c < InDouble.GetLength(1); c++)
                {
                    OutMat.At<double>(r, c) = InDouble[r, c];
                }

            }
            return OutMat;
        }
        #endregion



       
    }
}

付费资源,各取所需吧,花了时间和心思做出来的东西让我免费共享是不可能的。
项目资源下载
对了,下载的源码里面有畸变矫正功能的,但是参数那些暂时没设置正确,所以矫正出来的图像似乎没那么准确,有兴趣的朋友可以去研究下,其实所有的代码都是可以参考OpenCv的,只不过是放在了C#下,有些变量类型略有不同,具体怎么样看看它的类就知道了。

  • 6
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
棋盘镜头畸变是指相机采集图像时,由于棋盘的形状和镜头的特性,导致图像中的直线变形。Python中有一些库可以用来处理棋盘镜头畸变,如OpenCV。 在使用Python处理棋盘镜头畸变之前,首先需要获取相机的内参矩阵和畸变系数。可以使用一种称为相机标定的方法来获取这些参数。常见的相机标定方法是使用棋盘图像来计算内参矩阵和畸变系数。 一旦获取到了内参矩阵和畸变系数,就可以使用Python中的OpenCV库来进行棋盘镜头畸变的校正。下面是一个简单的Python代码示例: ```python import cv2 import numpy as np # 获取相机内参矩阵和畸变系数 camera_matrix = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) distortion_coeffs = np.array([k1, k2, p1, p2, k3]) # 读取图像 image = cv2.imread("image.jpg") # 进行棋盘镜头畸变校正 h, w = image.shape[:2] new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, distortion_coeffs, (w, h), 1, (w, h)) undistorted_image = cv2.undistort(image, camera_matrix, distortion_coeffs, None, new_camera_matrix) # 显示校正后的图像 cv2.imshow("Undistorted Image", undistorted_image) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在以上代码中,我们首先获取到相机的内参矩阵和畸变系数,然后使用`cv2.undistort()`函数对图像进行校正。最后,我们可以使用OpenCV的`imshow()`函数来显示校正后的图像。 通过以上的Python代码,我们可以很方便地进行棋盘镜头畸变的校正。校正后的图像会更加真实准确,有助于后续的图像处理和计算任务。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多巴胺耐受

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值