opencvsharp图片中标尺自动识别
1.首先找白色的区域作为ROI区域
2.在ROI区域找最长的横线
需要用到paddleocr第三方库
点击查看代码
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);
}
}