C#实现blob分析——分别基于OpenCvSharp和Emgu实现

server/2024/11/23 20:31:31/

需求和效果预览

对于下图,需要检测左右两侧是否断开:

解决分析

设置左右2个ROI区域,找到ROI内面积最大的连通域,通过面积阈值和连通域宽高比判定是否断开。

可能遇到的问题:部分区域反光严重,二值化阈值不容易写死,所以可以用动态阈值自动调整阈值。

实现

  • 基于OpenCvSharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using OpenCvSharp;namespace blob
{class Program{static void Main(string[] args){// 定义 二值化阈值 -- 使用动态阈值效果最佳int binary_threshold = 220;bool use_AdaptiveThreshold = true;// 定义 2个ROI区域int roi_w = 600, roi_h = 1570; // 左右两侧的ROI共用一个宽高int roi_x1_left = 220, roi_y1_left = 166; // 表示左上角的坐标int roi_x1_right = 900, roi_y1_right = 166;// 定义 blob的宽高比int hwRatio = 3;// 定义 blob面积上下限int minBlobArea = 140000;int maxBlobArea = 210000;string inputImage = "${input images path}";string saveResult = "${output images path}";foreach (string filePath in Directory.GetFiles(inputImage, "*.*", SearchOption.TopDirectoryOnly)){// 计算每张图片的计算时间double start = Cv2.GetTickCount() / Cv2.GetTickFrequency();// 加载图像Mat image = Cv2.ImRead(filePath, ImreadModes.Color);Mat image_raw = image.Clone(); // 用来可视化的// 图像预处理Cv2.CvtColor(image, image, ColorConversionCodes.BGR2GRAY);Size KernelBlur = new Size(3, 3);Cv2.GaussianBlur(image, image, KernelBlur, 0);// 二值化if (use_AdaptiveThreshold){// 动态阈值,通过计算像素点周围的k*k区域的加权平均,然后减去一个常数来得到自适应阈值; 11指窗口大小为11*11,2指减去的常数// k大一些效果更好,k=3的时候效果就不行,但k越大,速度越慢Cv2.AdaptiveThreshold(image, image, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, 11, 2);}else{// 硬二值化Cv2.Threshold(image, image, binary_threshold, 255, ThresholdTypes.Binary);}// 执行膨胀和腐蚀操作Mat KernelSize = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));Cv2.Erode(image, image, KernelSize, iterations:4);Cv2.Dilate(image, image, KernelSize, iterations:4);// 连通域分析Mat labels_32S = new Mat();Mat stats = new Mat();Mat centroids = new Mat();int num_labels = Cv2.ConnectedComponentsWithStats(image, labels_32S, stats, centroids); // 函数输出的labels_32S是MatType.CV_32S,32位系统可能导致内存分配失败Mat labels = new Mat(labels_32S.Size(), MatType.CV_8UC1); // 转为CV_8UC1labels_32S.ConvertTo(labels, MatType.CV_8UC1);// 提取 ROI 标签Mat roiLabelLeft = labels[new Rect(roi_x1_left, roi_y1_left, roi_w, roi_h)];Mat roiLabelRight = labels[new Rect(roi_x1_right, roi_y1_right, roi_w, roi_h)];// 初始化字典 -- maxAreaDict和maxAreaCoord的键都是"roi_left","roi_right",maxAreaDict的值是最大的blob面积,maxAreaCoord的值是最大面积对应的坐标Dictionary<string, int> maxAreaDict = new Dictionary<string, int>();Dictionary<string, List<int>> maxAreaCoord = new Dictionary<string, List<int>>();string[] roiNames = { "roi_left", "roi_right" };for (int i = 0; i < roiNames.Length; i++){string roiName = roiNames[i];Mat roiLabel = (i == 0) ? roiLabelLeft : roiLabelRight;int maxArea = 0;// ############### blob 分析 ###############// 创建与 roiLabel 相同大小的2个Mat// 注!new labelMask和labelValue必须在for外,不然会内存因不够而分配错误,而且运行很慢!Mat labelMask = new Mat(roiLabel.Size(), MatType.CV_8UC1);Mat labelValue = new Mat(roiLabel.Size(), MatType.CV_8UC1);for (int label = 1; label < num_labels; label++) // 从 1 开始,因为 0 是背景{labelValue.SetTo(new Scalar(label));Cv2.Compare(roiLabel, labelValue, labelMask, CmpType.EQ);// 计算面积int area = Cv2.CountNonZero(labelMask);// 获取坐标和宽高int x = (int)stats.At<int>(label, 0);int y = (int)stats.At<int>(label, 1);int width = (int)stats.At<int>(label, 2);int height = (int)stats.At<int>(label, 3);// 筛选连通域if (area > maxArea && (height / (double)width > hwRatio)) // 在满足宽高比的情况下,找到最大面积的连通域{var coor = new List<int> {x, y, width, height };maxAreaCoord[roiName] = coor;maxArea = area;}}maxAreaDict[roiName] = maxArea;}foreach (var blob in maxAreaCoord){string key = blob.Key;maxAreaDict.TryGetValue(key, out int max_area);if (max_area > minBlobArea && max_area < maxBlobArea) // 检查最大面积的连通域是否在设定的面积阈值内{List<int> coor_values = blob.Value;int x = coor_values[0];int y = coor_values[1];int width = coor_values[2];int height = coor_values[3];Rect rect = new Rect(x, y, width, height);Cv2.Rectangle(image_raw, rect, new Scalar(0, 255, 0), 4);string text = max_area.ToString();Point textLocation = new Point(rect.X, rect.Bottom + 60); // 显示在矩形框下面60个像素Cv2.PutText(image_raw, text, textLocation, HersheyFonts.HersheySimplex, fontScale:2.0, new Scalar(0, 255, 0), 3);} }double end = Cv2.GetTickCount() / Cv2.GetTickFrequency();string fileName = Path.GetFileName(filePath);string outputFilePath = Path.Combine(saveResult, fileName);// 保存处理后的图像Cv2.ImWrite(outputFilePath, image_raw);Console.WriteLine($"保存在: {outputFilePath}, 处理时间 = {end - start}");}}}
}

  • 基于Emgu
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;namespace blob
{class Program{static void Main(string[] args){int binary_threshold = 220;bool use_AdaptiveThreshold = true;int roi_w = 600, roi_h = 1570;int roi_x1_left = 220, roi_y1_left = 166;int roi_x1_right = 900, roi_y1_right = 166;int hwRatio = 3;int minBlobArea = 140000;int maxBlobArea = 210000;string inputImage = "${input images path}";string saveResult = "${output images path}";foreach (string filePath in Directory.GetFiles(inputImage, "*.*", SearchOption.TopDirectoryOnly)){Mat image = CvInvoke.Imread(filePath, ImreadModes.Color);Mat image_raw = image.Clone();// 定义核大小,统一用3*3的System.Drawing.Size KernelSize = new System.Drawing.Size(3, 3);CvInvoke.CvtColor(image, image, ColorConversion.Bgr2Gray);CvInvoke.GaussianBlur(image, image, KernelSize, 0);if (use_AdaptiveThreshold){CvInvoke.AdaptiveThreshold(image, image, 255, AdaptiveThresholdType.MeanC, ThresholdType.Binary, 5, 2);}else{CvInvoke.Threshold(image, image, binary_threshold, 255, ThresholdType.Binary);}Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, KernelSize, new System.Drawing.Point(-1, -1));CvInvoke.Erode(image, image, kernel, new System.Drawing.Point(-1, -1), 4, BorderType.Default, new MCvScalar(0));CvInvoke.Dilate(image, image, kernel, new System.Drawing.Point(-1, -1), 4, BorderType.Default, new MCvScalar(0));Mat labels = new Mat();Mat stats = new Mat();Mat centroids = new Mat();int num_labels = CvInvoke.ConnectedComponentsWithStats(image, labels, stats, centroids, LineType.EightConnected);Mat roiLabelLeft = new Mat(labels, new System.Drawing.Rectangle(roi_x1_left, roi_y1_left, roi_w, roi_h));Mat roiLabelRight = new Mat(labels, new System.Drawing.Rectangle(roi_x1_right, roi_y1_right, roi_w, roi_h));Dictionary<string, int> maxLabelDict = new Dictionary<string, int>();Dictionary<string, int> maxAreaDict = new Dictionary<string, int>();Dictionary<string, List<int>> maxAreaCoord = new Dictionary<string, List<int>>();string[] roiNames = { "roi_left", "roi_right" };for (int i = 0; i < roiNames.Length; i++){string roiName = roiNames[i];Mat roiLabel = (i == 0) ? roiLabelLeft : roiLabelRight;int maxArea = 0;int maxLabel = 0;int[] statsData = new int[stats.Rows * stats.Cols]; // 处理连通域分析结果--stats,后面容易数据处理stats.CopyTo(statsData);Mat labelMask = new Mat(roiLabel.Size, DepthType.Cv32S, 1);Mat labelValue = new Mat(roiLabel.Size, DepthType.Cv32S, 1);for (int label = 1; label < num_labels; label++) {labelValue.SetTo(new MCvScalar(label));CvInvoke.Compare(roiLabel, labelValue, labelMask, CmpType.Equal);int area = CvInvoke.CountNonZero(labelMask);int x = statsData[label * stats.Cols + 0];int y = statsData[label * stats.Cols + 1];int width = statsData[label * stats.Cols + 2];int height = statsData[label * stats.Cols + 3];if (area > maxArea && (height / (double)width > hwRatio)){var coor = new List<int> { x, y, width, height };maxAreaCoord[roiName] = coor;maxArea = area;maxLabel = label;}}maxLabelDict[roiName] = maxLabel;maxAreaDict[roiName] = maxArea;}foreach (var blob in maxAreaCoord){string key = blob.Key;maxAreaDict.TryGetValue(key, out int max_area);if (max_area > minBlobArea && max_area < maxBlobArea){List<int> coor_values = blob.Value;int x = coor_values[0];int y = coor_values[1];int width = coor_values[2];int height = coor_values[3];System.Drawing.Rectangle rect = new System.Drawing.Rectangle(x, y, width, height);CvInvoke.Rectangle(image_raw, rect, new MCvScalar(0, 255, 0), 4);string text = max_area.ToString();System.Drawing.Point textLocation = new System.Drawing.Point(rect.X, rect.Bottom + 60); CvInvoke.PutText(image_raw, text, textLocation, FontFace.HersheySimplex, 1.0, new MCvScalar(0, 255, 0), 2);}}string fileName = Path.GetFileName(filePath);string outputFilePath = Path.Combine(saveResult, fileName);CvInvoke.Imwrite(outputFilePath, image_raw);Console.WriteLine($"保存在: {outputFilePath}");//CvInvoke.Imshow("show", image_raw);//CvInvoke.WaitKey(0);//CvInvoke.DestroyAllWindows();}}}
}

处理结果

附录

blob可视化分析(代码暂未公开)

上图中,两个红色框框是设定的ROI区域,不同色块表示不同的连通域,右侧白框表示ROI区域内面积最大的连通域。


http://www.ppmy.cn/server/144343.html

相关文章

11.19 机器学习-梯度下降

# **正规方程求解的缺点** # 最小二乘法的缺点 # 之前利用正规方程求解的W是最优解的原因是MSE这个损失函数是凸函数。 # 但是&#xff0c;机器学习的损失函数并非都是凸函数&#xff0c;设置导数为0会得到很多个极值&#xff0c;不能确定唯一解,MSE还有一个问题,当数据量和…

前端速通(HTML)

1. HTML HTML基础&#xff1a; 什么是HTML&#xff1f; 超文本&#xff1a; "超文本"是指通过链接连接不同网页或资源的能力。HTML支持通过<a>标签创建超链接&#xff0c;方便用户从一个页面跳转到另一个页面。 标记语言&#xff1a; HTML使用一组预定义的标签…

失落的Apache JDBM(Java Database Management)

简介 Apache JDBM&#xff08;Java Database Management&#xff09;是一个轻量级的、基于 Java 的嵌入式数据库管理系统。它主要用于在 Java 应用程序中存储和管理数据。这个项目已经过时了&#xff0c;只是发表一下以示纪念&#xff0c;现在已经大多数被SQLite和Derby代替。…

基于 SpringBoot 的作业管理系统【附源码】

基于 SpringBoot 的作业管理系统 效果如下&#xff1a; 系统注册页面 学生管理页面 作业管理页面 作业提交页面 系统管理员主页面 研究背景 随着社会的快速发展&#xff0c;信息技术的广泛应用已经渗透到各个行业。在教育领域&#xff0c;课程作业管理是学校教学活动中的重要…

简单的使用Ngrok使用https

1、ngrok 使用谷歌邮箱 https://dashboard.ngrok.com/ 2、使用ngrok docker化部署 最快 https://dashboard.ngrok.com/get-started/setup/docker 本地网络不行无法下载&#xff0c;使用其他工具下载 然后保存 docker save -o ngrok.tar ngrok/ngrok3、静态域名 docker ru…

持续集成与持续部署:CI/CD简介

一、概念及含义 CI/CD 是一种在软件开发和交付过程中广泛应用的实践方法&#xff0c;它由持续集成&#xff08;Continuous Integration&#xff0c;简称 CI&#xff09;和持续交付 / 持续部署&#xff08;Continuous Delivery/Continuous Deployment&#xff0c;简称 CD&#…

鸿蒙动画开发07——粒子动画

1、概 述 粒子动画是在一定范围内随机生成的大量粒子产生运动而组成的动画。 动画元素是一个个粒子&#xff0c;这些粒子可以是圆点、图片。我们可以通过对粒子在颜色、透明度、大小、速度、加速度、自旋角度等维度变化做动画&#xff0c;来营造一种氛围感&#xff0c;比如下…

分布式锁RedissonClient应用

文章目录 一、RedissonClient 的由来二、RedissonClient 的优势三、RedissonClient 的应用场景四、实际应用4.1引入依赖4.2代码示例 一、RedissonClient 的由来 在分布式系统中&#xff0c;为了保证多个节点或进程对共享资源的并发访问的正确性和一致性&#xff0c;需要一种有效…