/// <summary>
 /// 1cm×1cm小方块标定工具类(OpenCvSharp4,标定函数参数为图片路径)
 /// </summary>
 public class SquareCalibrationTool
 {
     // 常量定义:可根据实际场景调整
     private const int ThresholdValue = 127; // 二值化阈值
     private const int CannyThresh1 = 50; // Canny低阈值
     private const int CannyThresh2 = 150; // Canny高阈值
     private const double SquareActualSizeCm = 1.0; // 标定物实际尺寸:1cm×1cm
     private const double alpha=1; //对比度
     private const double beta=5; //亮度
     private const int MorphKernelSize = 3; // 形态学核大小
     private const int GaussianKernelSize = 5; // 高斯核大小

     /// <summary>
     /// 标定1cm×1cm正方形,计算像素与厘米的比例(逐个显示轮廓+颜色标注)
     /// </summary>
     /// <param name="imagePath">输入图像路径</param>
     /// <param name="outDir">过程图片保存目录</param>
     /// <param name="calibratedImage">输出的标定结果图</param>
     /// <param name="pixelToCmRatio">输出的像素转厘米比例</param>
     /// <returns>是否标定成功</returns>
     public static bool CalibrateSquare(string imagePath, string outDir, out Mat calibratedImage, out double dpi)
     {
         // 初始化输出参数
         calibratedImage = null;
         dpi = 0;
         Point[] squareContour = null;

         // 声明所有需要的Mat对象(扁平化管理)
         Mat inputImage = null;
         Mat grayImage = null;
         Mat blurredImage = null;
         Mat binaryImage = null;
         Mat morphImage = null;
         Mat edgeImage = null;
         Mat edgeImageClone = null;

         try
         {
             #region 1. 基础校验与文件处理
             if (!File.Exists(imagePath))
             {
                 Console.WriteLine($"错误:原始图片不存在,路径:{imagePath}");
                 return false;
             }

             if (!Directory.Exists(outDir))
             {
                 Directory.CreateDirectory(outDir);
                 Console.WriteLine($"已创建保存目录:{outDir}");
             }

             inputImage = Cv2.ImRead(imagePath, ImreadModes.Color);
             if (inputImage.Empty())
             {
                 Console.WriteLine($"错误:无法读取图片(格式不支持或文件损坏),路径:{imagePath}");
                 return false;
             }
             Console.WriteLine($"成功读取图片:{imagePath}");
             SaveProcessImage(inputImage, Path.Combine(outDir, "0_原图.jpg"));
             #endregion

             #region 2. 图像预处理(平铺步骤)
             // 步骤1:转灰度图
             grayImage = new Mat();
             Cv2.CvtColor(inputImage, grayImage, ColorConversionCodes.BGR2GRAY);
             Cv2.ConvertScaleAbs(grayImage, grayImage, alpha, beta);
             SaveProcessImage(grayImage, Path.Combine(outDir, "1_灰度图.jpg"));

             // 步骤2:高斯降噪
             blurredImage = new Mat();
             Cv2.GaussianBlur(grayImage, blurredImage, new Size(GaussianKernelSize, GaussianKernelSize), 1.5);
             SaveProcessImage(blurredImage, Path.Combine(outDir, "2_高斯降噪.jpg"));

             // 步骤3:二值化(反相)
             binaryImage = new Mat();
             Cv2.Threshold(blurredImage, binaryImage, ThresholdValue, 255, ThresholdTypes.BinaryInv);
             SaveProcessImage(binaryImage, Path.Combine(outDir, "3_二值化.jpg"));

             // 步骤4:形态学去噪
             morphImage = new Mat();
             Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(MorphKernelSize, MorphKernelSize));
             Cv2.MorphologyEx(binaryImage, morphImage, MorphTypes.Open, kernel, iterations: 1);
             Cv2.MorphologyEx(morphImage, morphImage, MorphTypes.Close, kernel, iterations: 1);
             SaveProcessImage(morphImage, Path.Combine(outDir, "4_形态学去噪.jpg"));

             // 步骤5:Canny边缘检测
             edgeImage = new Mat();
             Cv2.Canny(morphImage, edgeImage, CannyThresh1, CannyThresh2);
             SaveProcessImage(edgeImage, Path.Combine(outDir, "5_边沿检测.jpg"));
             #endregion

             #region 3. 轮廓检测
             edgeImageClone = edgeImage.Clone();
             Point[][] contours;
             HierarchyIndex[] hierarchy;
             Cv2.FindContours(
                 edgeImageClone,
                 out contours,
                 out hierarchy,
                 RetrievalModes.External,
                 ContourApproximationModes.ApproxSimple,
                 new Point(0, 0)
             );
             #endregion

             #region 4. 筛选正方形轮廓
             // 第一步:找到面积最大的轮廓
             double maxArea = 0;
             Point [] maxAreaContour = null;
             foreach (var contour in contours) 
             {
                 double area = Cv2.ContourArea(contour);
                 if (area > maxArea)
                 {
                     maxArea = area;
                     maxAreaContour = contour;
                 }
             }
             // 第二步:对最大面积轮廓做形状筛选
             if (maxAreaContour != null)
             {
                 calibratedImage = inputImage.Clone();
                 RotatedRect rotatedRect = Cv2.MinAreaRect(maxAreaContour);
                 float pixelWidth = rotatedRect.Size.Width;
                 float pixelHeight = rotatedRect.Size.Height;
                 float avgPixelSize = (pixelWidth + pixelHeight) / 2f;

                 // ===================== 通用配置:修改此处即可切换标定尺寸 =====================
                 float dpiCoefficient = 2.54f / (float)SquareActualSizeCm;
                 dpi = avgPixelSize * dpiCoefficient;
                 // ==========================================================================

                 Point2f[] vertices = Cv2.BoxPoints(rotatedRect);
                 Point[] points = Array.ConvertAll(vertices, p => new Point((int)p.X, (int)p.Y));
                 Cv2.Polylines(calibratedImage, new[] { points }, true, Scalar.Red, 2);

                 // 标注位置与样式
                 string dpiText = $"DPI:{dpi:F2}";
                 Size textSize = Cv2.GetTextSize(dpiText, HersheyFonts.HersheySimplex, 0.6, 2, out int baseline);
                 Point textPos = new Point((int)rotatedRect.Center.X- textSize.Width/2, rotatedRect.Center.Y);
                 Cv2.PutText(calibratedImage, dpiText, textPos, HersheyFonts.HersheySimplex, 0.6, Scalar.Red, 2);
                 SaveProcessImage(calibratedImage, Path.Combine(outDir, "6_标定结果图.jpg"));
             }
             #endregion
             return true;
         }
         catch (Exception ex)
         {
             Console.WriteLine($"标定失败:{ex.Message}");
             return false;
         }
         finally
         {
             // 释放所有Mat资源
             inputImage?.Release();
             grayImage?.Release();
             blurredImage?.Release();
             binaryImage?.Release();
             morphImage?.Release();
             edgeImage?.Release();
             edgeImageClone?.Release();
         }
     }

     #region 工具方法:保存图像(带异常处理)
     /// <summary>
     /// 保存OpenCvSharp的Mat图像到指定路径
     /// </summary>
     /// <param name="image">要保存的图像</param>
     /// <param name="fileName">文件名(按步骤命名)</param>
     private static void SaveProcessImage(Mat image, string filePath)
     {
         try
         {
             Cv2.ImWrite(filePath, image);
             Console.WriteLine($"过程图片已保存:{filePath}");
         }
         catch (Exception ex)
         {
             Console.WriteLine($"图片保存失败【{filePath}】:{ex.Message}");
         }
     }
     #endregion
 }

Logo

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

更多推荐