public class CameraAutoCalibrator
 {
     #region 可配置参数【根据你的硬件修改,必改项】
     public OpenCvSharp.Size ChessboardSize = new OpenCvSharp.Size(9, 6);  // 棋盘格内角点行列数 (宽x高)
     public float ChessboardSquareSize = 20.0f;    // 单个棋盘格物理边长,单位:毫米mm
     public bool IsDistortionFreeLens = true;      // true=工业无畸变镜头/远心镜头,false=普通畸变镜头
     public double Pixel2MmScale { get; private set; } = 0; // 核心转换系数:mm/像素
     #endregion

     #region 标定核心参数
     public Mat CameraMatrix { get; private set; } = new Mat(3, 3, MatType.CV_64FC1);
     public Mat DistortionCoeffs { get; private set; } = new Mat();
     public bool IsCalibrated { get; private set; } = false;
     public int WidthOffsetPixel { get; private set; }= 0;
     public int HighOffsetPixel { get; private set; } = 0;  
     #endregion

     #region 你要求的DPI属性【精准换算,无单位误差】
     public double ImageDpi
     {
         get
         {
             if (Pixel2MmScale <= 0) return 0;
             return Math.Round(25.4 / Pixel2MmScale, 2); // DPI=25.4mm ÷ 每像素毫米数
         }
     }
     #endregion

     /// <summary>
     /// ✅ 全自动标定主函数 - 无任何编译报错
     /// </summary>
     public double AutoCalibrate(List<string> calibImagePaths)
     {
         List<Mat> objPointMats = new List<Mat>();
         List<Mat> imgPointMats = new List<Mat>();
         OpenCvSharp.Size imageSize = new OpenCvSharp.Size();
         int cornerCount = ChessboardSize.Width * ChessboardSize.Height;

         // 生成棋盘格3D世界坐标Mat (CV_32FC3 XYZ)
         Mat singleObjMat = new Mat(cornerCount, 1, MatType.CV_32FC3);
         float[] objData = new float[cornerCount * 3];
         int dataIdx = 0;
         for (int i = 0; i < ChessboardSize.Height; i++)
         {
             for (int j = 0; j < ChessboardSize.Width; j++)
             {
                 objData[dataIdx++] = j * ChessboardSquareSize;
                 objData[dataIdx++] = i * ChessboardSquareSize;
                 objData[dataIdx++] = 0.0f;
             }
         }
         // ✅ 适配你的SetArray<T>(params T[] data) 无行列参数
         singleObjMat.SetArray<float>(objData);

         // 遍历标定图,角点检测+亚像素优化
         foreach (var imgPath in calibImagePaths)
         {
             if (!File.Exists(imgPath)) continue;
             using (Mat grayImg = Cv2.ImRead(imgPath, ImreadModes.Grayscale))
             {
                 if (grayImg.Empty()) continue;
                 imageSize = grayImg.Size();

                 bool findCorners = Cv2.FindChessboardCorners(grayImg, ChessboardSize, out Point2f[] corners,
                     ChessboardFlags.AdaptiveThresh | ChessboardFlags.NormalizeImage | ChessboardFlags.FastCheck);

                 if (findCorners)
                 {
                     // ✅ 你的3参数版亚像素函数 原生调用,无任何修改
                     Cv2.Find4QuadCornerSubpix(grayImg, corners, new OpenCvSharp.Size(5, 5));

                     // 生成2D像素坐标Mat (CV_32FC2 XY)
                     Mat singleImgMat = new Mat(cornerCount, 1, MatType.CV_32FC2);
                     float[] imgData = new float[cornerCount * 2];
                     dataIdx = 0;
                     foreach (var corner in corners)
                     {
                         imgData[dataIdx++] = corner.X;
                         imgData[dataIdx++] = corner.Y;
                     }
                     singleImgMat.SetArray<float>(imgData);

                     objPointMats.Add(singleObjMat.Clone());
                     imgPointMats.Add(singleImgMat);
                 }
             }
         }

         if (objPointMats.Count < 3)
             throw new Exception("标定失败:有效棋盘格图像不足3张,工业建议10-20张不同位姿");

         // ✅ 完美匹配你的CalibrateCamera重载原型
         Mat[] rvecs;
         Mat[] tvecs;
         double reprojError = Cv2.CalibrateCamera(
             objectPoints: objPointMats,
             imagePoints: imgPointMats,
             imageSize: imageSize,
             cameraMatrix: CameraMatrix,
             distCoeffs: DistortionCoeffs,
             rvecs: out rvecs,
             tvecs: out tvecs,
             flags: CalibrationFlags.None,
             criteria: new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 30, 0.001)
         );

         // 计算核心转换系数
         CalculatePixel2MmScale();

         // 无畸变镜头:清空畸变系数,跳过矫正流程,提升效率
         if (IsDistortionFreeLens)
             DistortionCoeffs = new Mat();

         IsCalibrated = true;
         return Math.Round(reprojError, 3);
     }

     /// <summary>
     /// ✅ 核心修改:完美适配你的GetOptimalNewCameraMatrix完整7参数原型
     /// 畸变矫正函数 - 无畸变镜头直接返回原图,无性能损耗
     /// </summary>
     public Mat CorrectDistortion(Mat srcImage)
     {
         // 未标定 或 无畸变镜头,直接返回原图克隆,避免引用冲突
         if (!IsCalibrated || IsDistortionFreeLens)
             return srcImage.Clone();

         Mat dstImage = new Mat();
         Rect validPixROI; // 新增:你的原型必填的有效像素区域输出参数
         // ✅ 精准匹配你的GetOptimalNewCameraMatrix全部参数,无任何缺失
         Mat optimalCamMat = Cv2.GetOptimalNewCameraMatrix(
             cameraMatrix: CameraMatrix,
             distCoeffs: DistortionCoeffs,
             imageSize: srcImage.Size(),
             alpha: 0, // alpha=0 裁剪畸变黑边,输出无黑边的有效图像,工业首选
             newImgSize: srcImage.Size(), // 矫正后图像尺寸与原图一致
             validPixROI: out validPixROI,
             centerPrincipalPoint: false
         );
         // 执行畸变矫正
         Cv2.Undistort(srcImage, dstImage, CameraMatrix, DistortionCoeffs, optimalCamMat);

         return dstImage;
     }

     /// <summary>
     /// 计算像素→毫米 核心转换系数
     /// </summary>
     private void CalculatePixel2MmScale()
     {
         double fx = CameraMatrix.At<double>(0, 0);
         double fy = CameraMatrix.At<double>(1, 1);
         Pixel2MmScale = ChessboardSquareSize / ((fx + fy) / 2 * 0.001);
         Pixel2MmScale = Math.Round(Pixel2MmScale, 6);
     }

     /// <summary>
     /// 像素长度 → 实际物理尺寸 (mm) 工业测量核心方法
     /// </summary>
     public double PixelToMm(double pixelLength)
     {
         if (!IsCalibrated) throw new Exception("请先完成相机标定,再进行尺寸测量!");
         return Math.Round(pixelLength * Pixel2MmScale, 3);
     }

     /// <summary>
     /// 实际物理尺寸 (mm) → 像素长度
     /// </summary>
     public double MmToPixel(double mmLength)
     {
         if (!IsCalibrated) throw new Exception("请先完成相机标定,再进行尺寸转换!");
         return Math.Round(mmLength / Pixel2MmScale, 0);
     }

     /// <summary>
     /// 保存标定参数到XML文件,开机直接加载无需重复标定【工业项目必用】
     /// </summary>
     public void SaveCalibParams(string savePath)
     {
         if (!IsCalibrated) return;
         using (FileStorage fs = new FileStorage(savePath, Modes.Write))
         {
             fs.Write("CameraMatrix", CameraMatrix);
             fs.Write("DistortionCoeffs", DistortionCoeffs);
             fs.Write("Pixel2MmScale", Pixel2MmScale);
             fs.Write("ImageDpi", ImageDpi);
             fs.Write("IsDistortionFreeLens", IsDistortionFreeLens?1:0);
             fs.Write("WidthOffsetPixel", WidthOffsetPixel);
             fs.Write("HighOffsetPixel", HighOffsetPixel);
         }
     }

     /// <summary>
     /// 加载本地标定参数【工业项目必用】
     /// </summary>
     public void LoadCalibParams(string loadPath)
     {
         // 校验文件是否存在,不存在直接返回
         if (!File.Exists(loadPath)) return;

         // ✅ 修正点1:OpenCvSharp4 读取模式的正确写法,替代原FileStorageMode.Read
         using (FileStorage fs = new FileStorage(loadPath, Modes.Read))
         {
             // 读取相机内参矩阵、畸变系数矩阵 写法不变,本身无错误
             fs["CameraMatrix"].ReadMat(CameraMatrix);
             fs["DistortionCoeffs"].ReadMat(DistortionCoeffs);

             // 读取浮点型系数 写法不变,无错误
             Pixel2MmScale = fs["Pixel2MmScale"].ReadDouble();

             // ✅ 修正点2:读取布尔值的正确替代方案(核心解决ReadBool()报错)
             IsDistortionFreeLens = fs["IsDistortionFreeLens"].ReadInt() == 1;

             // 标定完成标记
             IsCalibrated = true;

             WidthOffsetPixel = fs["WidthOffsetPixel"].ReadInt();
             HighOffsetPixel = fs["HighOffsetPixel"].ReadInt();
         }
     }
 }

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐