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

需求和效果预览

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

解决分析

设置左右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_8UC1
                labels_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区域内面积最大的连通域。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/921116.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ZYNQ-7020嵌入式系统学习笔记(1)——使用ARM核配置UART发送Helloworld

本工程实现调用ZYNQ-7000的内部ARM处理器&#xff0c;通过UART给电脑发送字符串。 硬件&#xff1a;正点原子领航者-7020 开发平台&#xff1a;Vivado 2018、 SDK 1 Vivado部分操作 1.1 新建工程 设置工程名&#xff0c;选择芯片型号。 1.2 添加和配置PS IP 点击IP INTEGR…

冲破AI 浪潮冲击下的 迷茫与焦虑

在这个科技日新月异的时代&#xff0c;人工智能如汹涌浪潮般席卷而来&#xff0c;不断改变我们的生活。你是否对 AI 充满好奇&#xff0c;却不知它将如何改变你的工作与生活&#xff1f;又是否会在 AI 浪潮的冲击下陷入迷茫与焦虑&#xff1f;《AI 时代&#xff1a;弯道超车新思…

【FRP 内网穿透 从0到1 那些注意事项】

【摘要】 最近跟第三方团队调试问题&#xff0c;遇到一个比较烦的操作。就是&#xff0c;你必须要发个版到公网环境&#xff0c;他们才能链接到你的接口地址&#xff0c;才能进行调试。按理说&#xff0c;也没啥&#xff0c;就是费点时间。但是&#xff0c;在调试的时候&#…

基于SpringBoot+RabbitMQ完成应⽤通信

前言&#xff1a; 经过上面俩章学习&#xff0c;我们已经知道Rabbit的使用方式RabbitMQ 七种工作模式介绍_rabbitmq 工作模式-CSDN博客 RabbitMQ的工作队列在Spring Boot中实现&#xff08;详解常⽤的⼯作模式&#xff09;-CSDN博客作为⼀个消息队列,RabbitMQ也可以⽤作应⽤程…

进度条程序

目录 1.回车与换行 2.缓冲区 强制刷新&#xff1a;fflush 策略 3.倒计时程序 4.进度条 4.1先做一下基本的准备工作 4.2现在我们正式来实现 进度: 比率: 旋转光标 表明动态变化: 4.3如果我们要完成一个下载任务 4.3.1实现: 4.3.2光标显示: 4.3.2.1证明一下&#…

软件测试——自动化测试常见函数

在上一篇文章软件测试——自动化测试概念篇-CSDN博客中&#xff0c;给大家演示了一下自动化程序&#xff0c;而本篇文章会带大家详细学习selenium库。 selenium库是python官方的库&#xff0c;里面包含了很多操控浏览器的函数。 本节重点 元素定位操作测试对象窗口等待导航弹…

STM32F103C8T6实时时钟RTC

目录 前言 一、RTC基本硬件结构 二、Unix时间戳 2.1 unix时间戳定义 2.2 时间戳与日历日期时间的转换 2.3 指针函数使用注意事项 ​三、RTC和BKP硬件结构 四、驱动代码解析 前言 STM32F103C8T6外部低速时钟LSE&#xff08;一般为32.768KHz&#xff09;用的引脚是PC14和PC…

AI社媒引流工具:解锁智能化营销的新未来

在数字化浪潮的推动下&#xff0c;社交媒体成为品牌营销的主战场。然而&#xff0c;面对海量的用户数据和日益复杂的运营需求&#xff0c;传统营销方法显得力不从心。AI社媒引流王应运而生&#xff0c;帮助企业在多平台中精准触达目标用户&#xff0c;提升营销效率和效果。 1.…

知识中台:提升企业知识管理的智能化水平

在数字化转型的浪潮中&#xff0c;企业知识管理的智能化水平成为提升竞争力的关键。HelpLook知识中台通过集成先进的AI技术&#xff0c;为企业提供了一个智能化的知识管理平台。 一、知识管理智能化的重要性 智能化的知识管理不仅能够提高信息检索的效率&#xff0c;还能通过…

Unreal5从入门到精通之EnhancedInput增强输入系统详解

前言 从Unreal5开始,老版的输入系统,正式替换为EnhancedInput增强型输入系统,他们之间有什么区别呢? 如果有使用过Unity的同学,大概也知道,Unity也在2020版本之后逐渐把输入系统也升级成了新版输入系统,为什么Unreal和Unity都热衷于升级输入系统呢?这之间又有什么联系…

C语言数据结构与算法--简单实现队列的入队和出队

&#xff08;一&#xff09;队列的基本概念 和栈相反&#xff0c;队列(Queue)是一种先进先出&#xff08;First In First Out&#xff09;的线性表。只 允许在表的一端进行插入&#xff0c;而在另一端删除元素&#xff0c;如日常生活中的排队现象。队列中 允许插入的一端叫队尾…

docker搭建私有仓库,实现镜像的推送和拉取

1.拉取docker仓库镜像 docker pull registry 2.启动registry容器 docker run -d registry 3.查看当前仓库中存在的镜像&#xff08;一&#xff09; curl -XGET http://192.168.111.162: 5000/v2/_catalog 192.168.111.162 部署docker仓库宿主机的ip 5000 部署docker仓库映射到宿…

算法学习笔记(九):网格图DFS、图论算法DFS、动态规划DP、贪心

一.网格图DFS 适用于需要计算连通块个数、大小的题目 1.岛屿数量 给你一个由 1(陆地) 和 0&#xff08;水&#xff09;组成的二维网格&#xff0c;请你计算网格中岛屿的数量 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和\或竖直方向上相邻的陆地连接形成 此外&…

Cmakelist.txt之Linux-redis配置

1.cmakelist.txt cmake_minimum_required(VERSION 3.16) ​ project(redis_linux_test LANGUAGES C) ​ ​ ​ add_executable(redis_linux_test main.c) ​ # 设置hiredis库的头文件路径和库文件路径 set(Hiredis_INCLUDE_DIR /usr/local/include/hiredis) set(Hiredis_LIBRA…

【Node.js】Node.js 和浏览器之间的差异

Node.js 是一个强大的运行时环境&#xff0c;它在现代 JavaScript 开发中扮演着重要角色。然而&#xff0c;许多开发者在使用 Node.js 时常常会感到困惑&#xff0c;尤其是与浏览器环境的对比。本文将深入探讨 Node.js 和浏览器之间的差异&#xff0c;帮助你全面理解两者的设计…

【物联网原理与应用】实验二:红外传感实验

目录 一、实验目的 二、实验原理 三、实验内容及步骤 四、实验结果 五、核心代码 一、实验目的 学习试验模块上线路的连接操作理解掌握红外传感器的工作原理实现对红外传感器数据的接收和处理 二、实验原理 1、将红外辐射能转换成电能的光敏元件称为红外传感器&#…

PAL(Program-Aided Language Model)

PAL&#xff08;Program-Aided Language Model&#xff09;是一种结合生成式语言模型&#xff08;如 GPT&#xff09;和程序执行能力的技术框架。它的核心思想是通过让语言模型生成代码或程序来解决复杂任务&#xff0c;程序执行的结果反过来增强语言模型的输出准确性和逻辑性。…

java基础概念36:正则表达式1

一、正则表达式的作用 作用一&#xff1a;校验字符串是否满足规则&#xff1b;作用二&#xff1a;在一段文本中查找满足要求的内容。——爬虫 二、正则表达式 2-1、字符类 示例&#xff1a; public static void main(String[] args) {System.out.println("a".matc…

VsCode 插件推荐(个人常用)

VsCode 插件推荐&#xff08;个人常用&#xff09;

工业储能柜的大小该如何选择,工商储能系统设备哪家好?

在能源转型和可持续发展的大潮中&#xff0c;工商业储能系统因其提升清洁能源利用率、降低电能损耗、实现“双碳”目标等优势而备受青睐。它们不仅增强了电力系统的可靠性和灵活性&#xff0c;还帮助企业降低成本、提高经济效益。储能系统通过负荷管理适应电价波动&#xff0c;…