opencvsharp图片中标尺自动识别

1.首先找白色的区域作为ROI区域
2.在ROI区域找最长的横线
需要用到paddleocr第三方库
image

点击查看代码
using System;
using OpenCvSharp;
using System.Linq;
using OpenCvSharp.Extensions;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;
class Program
{
    static void Main(string[] args)
    {
        //string imagePath = "D:\\pic\\jinqiaolvye\\5-晶粒度100\\image_250708_001.JPG"; // 建议使用PNG等无损格式以保证255纯白的准确性
        string imagePath = "D:\\pic\\jinqiaolvye\\13-晶粒度-100\\平面\\09-4293.jpg"; // 建议使用PNG等无损格式以保证255纯白的准确性
        double umperPixel = rulerRecognition(imagePath);
        Console.WriteLine(umperPixel);
    }
    /// <summary>
    /// 标尺自动识别 适用于白底黑子的标尺
    /// </summary>
    /// <param name="imagePath"></param>
    /// <returns></returns>
    static double rulerRecognition(string imagePath)
    {
        double umperPixel;
        try
        {
            // 输入图像路径
            //string imagePath = "D:\\pic\\jinqiaolvye\\1-第二相尺寸大小500X\\001.jpg"; // 建议使用PNG等无损格式以保证255纯白的准确性
            
            // 读取图像(以灰度模式读取,便于处理单通道像素值)
            Mat srcGray = Cv2.ImRead(imagePath, ImreadModes.Grayscale);
            if (srcGray.Empty())
            {
                Console.WriteLine("无法读取图像,请检查文件路径!");
                
            }

            // 创建掩码:只保留像素值精确为255的白色区域
            Mat whiteMask = new Mat();
            // 阈值处理:像素值等于255的设为255(白色),其他设为0(黑色)
            Cv2.Threshold(srcGray, whiteMask, 254, 255, ThresholdTypes.Binary);
            // 注:使用254作为阈值,配合Binary模式,实现像素值>=254的都被保留,实际等价于筛选255

            // 形态学操作:去除小噪点并连接可能断裂的区域
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3));
            // 先腐蚀再膨胀,去除细小白色噪点
            Cv2.MorphologyEx(whiteMask, whiteMask, MorphTypes.Open, kernel, iterations: 1);
            // 膨胀后腐蚀,连接断裂的白色区域
            Cv2.MorphologyEx(whiteMask, whiteMask, MorphTypes.Close, kernel, iterations: 2);

            // 寻找所有白色区域的轮廓
            Point[][] contours;
            HierarchyIndex[] hierarchy;
            Cv2.FindContours(whiteMask, out contours, out hierarchy,
                           RetrievalModes.External, ContourApproximationModes.ApproxSimple);

            if (contours.Length == 0)
            {
                Console.WriteLine("未找到像素值为255的白色区域!");
              
            }

            // 筛选出面积最大的轮廓
            var contourAreas = contours.Select(c => Cv2.ContourArea(c)).ToArray();
            int maxAreaIdx = contourAreas.Select((val, idx) => new { val, idx })
                                        .OrderByDescending(x => x.val)
                                        .First().idx;

            // 获取最大白色区域的边界矩形作为ROI
            Rect roiRect = Cv2.BoundingRect(contours[maxAreaIdx]);

            // 绘制ROI标记(在彩色图上显示)
            Mat srcColor = Cv2.ImRead(imagePath, ImreadModes.Color);
            Cv2.Rectangle(srcColor, roiRect, new Scalar(0, 0, 255), 2); // 红色矩形标记
            Cv2.PutText(srcColor, "Largest White ROI",
                       new Point(roiRect.X, roiRect.Y - 10),
                       HersheyFonts.HersheySimplex, 0.5, new Scalar(0, 0, 255), 2);

            // 提取ROI区域
            Mat roiArea = new Mat(srcGray, roiRect);



            //Cv2.ImShow("原始灰度图", srcGray);
            //Cv2.ImShow("255白色区域掩码", whiteMask);
            //Cv2.ImShow("带ROI标记的图像", srcColor);
            //Cv2.ImShow("提取的ROI区域", roiArea);

            // 保存结果
            string roiSavePath = "E:\\VSWorkSpace\\ConsoleApp1\\ConsoleApp1\\bin\\Debug\\debug_images\\autoRoi\\extracted_roi.png";
            Cv2.ImWrite("E:\\VSWorkSpace\\ConsoleApp1\\ConsoleApp1\\bin\\Debug\\debug_images\\autoRoi\\white_mask.png", whiteMask);
            Cv2.ImWrite("E:\\VSWorkSpace\\ConsoleApp1\\ConsoleApp1\\bin\\Debug\\debug_images\\autoRoi\\result_with_roi.png", srcColor);
            Cv2.ImWrite("E:\\VSWorkSpace\\ConsoleApp1\\ConsoleApp1\\bin\\Debug\\debug_images\\autoRoi\\extracted_roi.png", roiArea);
            PaddleOCRSharp.OCRResult ocrResult = new PaddleOCRSharp.PaddleOCREngine().DetectText(roiArea.ToBitmap());

            if (ocrResult != null)
            {
                Console.WriteLine(ocrResult.Text, "识别结果");
                //MessageBox.Show(ocrResult.Text, "识别结果");
            }
            //Console.WriteLine($"最大255白色区域ROI信息:");
            //Console.WriteLine($"位置:X={roiRect.X}, Y={roiRect.Y}");
            //Console.WriteLine($"大小:宽度={roiRect.Width}, 高度={roiRect.Height}");
            //Console.WriteLine($"面积:{contourAreas[maxAreaIdx]} 像素");
            Mat blackMask = new Mat();
            int blackThreshold = 150;
            Cv2.Threshold(roiArea, blackMask, blackThreshold, 255, ThresholdTypes.BinaryInv);
            // 形态学操作:使用水平结构元素增强水平线条
            Mat kernel01 = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 1)); // 水平结构
            Cv2.MorphologyEx(blackMask, blackMask, MorphTypes.Close, kernel01);

            int maxLength = 0;
            List<Point> longestLinePoints = new List<Point>();
            int minLineLength = 5;
            // 逐行扫描寻找最长连续黑色像素段(横线)
            for (int y = 0; y < blackMask.Rows; y++)
            {
                int currentLength = 0;
                List<Point> currentPoints = new List<Point>();

                for (int x = 0; x < blackMask.Cols; x++)
                {
                    // 获取当前像素值(黑色区域为255)
                    byte pixel = blackMask.At<byte>(y, x);

                    if (pixel == 255)
                    {
                        // 黑色像素,增加当前线段长度
                        currentLength++;
                        currentPoints.Add(new Point(x, y));
                    }
                    else
                    {
                        // 非黑色像素,检查当前线段是否为最长
                        if (currentLength >= minLineLength && currentLength > maxLength)
                        {
                            maxLength = currentLength;
                            longestLinePoints = new List<Point>(currentPoints);
                        }
                        currentLength = 0;
                        currentPoints.Clear();
                    }
                }

                // 检查行尾的线段
                if (currentLength >= minLineLength && currentLength > maxLength)
                {
                    maxLength = currentLength;
                    longestLinePoints = new List<Point>(currentPoints);
                }
            }

            // 在原图上标记最长横线(如果找到)
            Mat result = new Mat();
            if (roiArea.Channels() == 1)
                Cv2.CvtColor(roiArea, result, ColorConversionCodes.GRAY2BGR);
            else
                roiArea.CopyTo(result);

            if (longestLinePoints.Count > 0)
            {
                // 绘制最长横线
                Point start = longestLinePoints[0];
                Point end = longestLinePoints[longestLinePoints.Count - 1];
                Cv2.Line(result, start, end, new Scalar(0, 0, 255), 2);

                // 显示长度信息
                string lengthText = $"Length: {maxLength}px";
                Cv2.PutText(result, lengthText,
                           new Point(start.X, start.Y - 5),
                           HersheyFonts.HersheySimplex, 0.5, new Scalar(0, 255, 0), 2);
            }

            // 显示结果
            //Cv2.NamedWindow("ROI中的最长黑色横线", WindowTypes.Normal);
            Cv2.ImShow("ROI中的最长黑色横线", result);
            

            string resultNumber = RemoveLastTwoChars(ocrResult.Text);//画的数字
            Console.WriteLine($"原字符串: \"{ocrResult.Text}\" → 截取后: \"{resultNumber}\"");
            umperPixel = double.Parse(resultNumber) / maxLength;
            // 等待按键后关闭窗口
            Cv2.WaitKey(0);
            Cv2.DestroyAllWindows();
            return umperPixel;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理过程中发生错误:{ex.Message}");
            return 0;
        }
        
    }
    /// <summary>
    /// 去掉字符串后面两个字符
    /// </summary>
    /// <param name="str">输入字符串</param>
    /// <returns>去掉最后两个字符后的结果</returns>
    static string RemoveLastTwoChars(string str)
    {
        // 处理 null 或空串
        if (string.IsNullOrEmpty(str))
            return "";

        // 若长度≤2,去掉两个字符后为空
        if (str.Length <= 2)
            return "";

        // 截取从开头到“长度-2”的部分(去掉最后两个字符)
        return str.Substring(0, str.Length - 2);
    }
}

posted @ 2025-08-19 09:16  阳光天气  阅读(16)  评论(0)    收藏  举报