当前位置: 首页 > article >正文

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区域内面积最大的连通域。


http://www.kler.cn/a/406317.html

相关文章:

  • 速通前端篇 —— CSS
  • uni-app 修改复选框checkbox选中后背景和字体颜色
  • python之开发笔记
  • 学习日志015--python单链表
  • 网络安全应急响应及其发展方向
  • 渗透测试---shell(5)字符串运算符与逻辑运算符
  • 力扣 LeetCode 501. 二叉搜索树中的众数(Day10:二叉树)
  • 【vim】vim怎么从指定行到指定行的行首添加内容
  • 真题-桂城2018年六年级
  • OpenCV与AI深度学习|16个含源码和数据集的计算机视觉实战项目(建议收藏!)
  • HarmonyOS . 沉浸状态栏使用
  • Elasticsearch Windows版的安装及启动
  • 14:00面试,14:08就出来了,问的问题有点变态。。。
  • Unreal从入门到精通之如何绘制用于VR的3DUI交互的手柄射线
  • 基于干扰观测器的 PD 控制
  • 高性能存储SIG月度动态:重构和优化fuse,推动containerd社区支持erofs
  • 大模型基本能力评测---知识利用
  • Linux2.6内核进程调度队列
  • Windows 驱动开发中 ExAcquireResourceExclusiveLite 和其他锁的区别:
  • Windows中指定路径安装DockerDesktop
  • 死锁相关习题 10道 附详解
  • Day 18
  • 04高可用高并发(D1_高并发 - D1_缓存)
  • ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
  • 设计模式之 状态模式
  • WonderJourney 部署踩坑笔记 2024 ok