最近,在动手开发一个有无的视觉检测系统,纯手搓,记录一下自己的开发历程,希望对其他有相似的项目需求的小伙伴有帮助。

项目需求

为避免在生产的过程中漏工序,我们要在生产结束时,拍张产品照片,检测螺母有无缺少。

项目方案

使用C# 的opencv库,实现检测应该一个照片,操作下面流程

1. 图像二值化

2. 对二值化的图像就行阈值分析

3. 筛选满足条件的区域(面积、长、宽) 渲染图片中螺母的位置,如果用到roi,详细说说怎么实现的

图像二值化是什么?最简单的图像分割方法是二值化(Binarization)图像二值化( Image Binarization)就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。二值图像每个像素只有两种取值:要么纯黑,要么纯白

进行二值化的方式?其中最常用的就是采用阈值法(Thresholding)进行二值化,(根据阈值选取方式的不同,可以分为全局阈值局部阈值【又称为自适应阈值 

步骤概述

  1. 图像二值化:将图像转换为灰度,然后应用二值化。
  2. 阈值分析:使用二值化后的图像进行轮廓检测。
  3. 筛选区域:根据面积、长宽等条件筛选轮廓。
  4. 标记螺母位置:在原图上标记筛选后的区域。

实现代码

首先,确保你已经安装了OpenCvSharp库,可以通过NuGet包管理器安装。

using System;
using System.Collections.Generic;
using OpenCvSharp;

class Program
{
    static void Main()
    {
        // 读取图像
        Mat src = Cv2.ImRead("path_to_your_image.jpg", ImreadModes.Color);

        // 1. 图像二值化
        Mat gray = new Mat();
        Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
        
        Mat binary = new Mat();
        Cv2.Threshold(gray, binary, 100, 255, ThresholdTypes.Binary);

        // 2. 阈值分析(轮廓检测)
        List<RotatedRect> boundingRects = new List<RotatedRect>();
        Cv2.FindContours(binary, out var contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple);

        // 3. 筛选满足条件的区域
        foreach (var contour in contours)
        {
            var rect = Cv2.MinAreaRect(contour);
            var area = rect.Size.Width * rect.Size.Height;

            // 条件筛选:面积、长宽比等
            if (area > 100 && rect.Size.Width > 10 && rect.Size.Height > 10)
            {
                boundingRects.Add(rect);
            }
        }

        // 4. 渲染螺母的位置
        foreach (var rect in boundingRects)
        {
            Point2f[] vertices = rect.Points();
            for (int i = 0; i < 4; i++)
            {
                Cv2.Line(src, vertices[i], vertices[(i + 1) % 4], new Scalar(0, 255, 0), 2);
            }
        }

        // 保存结果
        Cv2.ImWrite("output.jpg", src);
    }
}

详细说明

  • 图像二值化:使用Cv2.CvtColor将图像转换为灰度,然后通过Cv2.Threshold进行二值化。
  • 轮廓检测Cv2.FindContours用于检测二值图像中的轮廓。
  • 区域筛选:通过Cv2.MinAreaRect获取每个轮廓的最小外接矩形,并根据面积和尺寸进行筛选。
  • 标记螺母位置:使用Cv2.Line在原图上绘制矩形的边。

关于ROI(Region of Interest)

如果你只想处理图像的某一部分,可以定义一个ROI。ROI可以通过Mat的子矩阵操作实现:

// 定义ROI区域
Rect roi = new Rect(x, y, width, height);

// 提取ROI
Mat roiMat = new Mat(binary, roi);

// 对ROI进行处理,例如再次进行轮廓检测

在这个例子中,xywidthheight定义了ROI的位置和大小。你可以在这个子矩阵上应用相同的处理步骤。

通过这种方式,你可以有效地检测和标记图像中的螺母位置。

界面展示

在 C# 中,如果你想将 OpenCV 的 Mat 对象显示在 PictureBox 控件上,你需要将 Mat 转换为 Bitmap,然后将 Bitmap 赋值给 PictureBox.Image

步骤

  1. 将 Mat 转换为 Bitmap
    OpenCvSharp 提供了 OpenCvSharp.Extensions.BitmapConverter.ToBitmap() 方法,可以直接将 Mat 转换为 Bitmap

  2. 将 Bitmap 赋值给 PictureBox
    使用 pictureBox.Image = bitmap; 来显示图像。

using OpenCvSharp;
using OpenCvSharp.Extensions; // 用于 Mat 和 Bitmap 的转换
using System.Drawing;
using System.Windows.Forms;

public class ImageDisplayExample
{
    public void DisplayImageInPictureBox(PictureBox pictureBox, string imagePath)
    {
        // 1. 读取图像
        Mat src = Cv2.ImRead(imagePath, ImreadModes.Color);

        // 2. 转换为灰度图(可选)
        Mat gray = new Mat();
        Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

        // 3. 将 Mat 转换为 Bitmap
        Bitmap bitmap = BitmapConverter.ToBitmap(gray); // 如果是彩色图,可以直接用 src

        // 4. 显示在 PictureBox 上
        pictureBox.Image?.Dispose(); // 释放旧的 Bitmap 资源(避免内存泄漏)
        pictureBox.Image = bitmap;
    }
}

详细说明

  1. Cv2.ImRead 读取图像
    • 使用 ImreadModes.Color 读取彩色图像,或者 ImreadModes.Grayscale 直接读取灰度图。
  2. Cv2.CvtColor 转换为灰度(可选)
    • 如果需要显示灰度图,可以使用 Cv2.CvtColor 转换。
    • 如果直接显示彩色图,可以跳过这一步。
  3. BitmapConverter.ToBitmap 转换
    • OpenCvSharp 提供了 BitmapConverter 类,可以方便地将 Mat 转换为 Bitmap
    • 如果是彩色图,可以直接转换 src,如果是灰度图,转换 gray
  4. PictureBox.Image 赋值
    • 在赋值前,最好先调用 pictureBox.Image?.Dispose() 释放旧的 Bitmap,避免内存泄漏。
    • 然后将新的 Bitmap 赋值给 PictureBox.Image

完整示例(WinForms 应用)

using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.Drawing;
using System.Windows.Forms;

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        // 示例:点击按钮加载并显示图像
        Button btnLoad = new Button { Text = "加载图像" };
        btnLoad.Click += (s, e) => 
        {
            DisplayImageInPictureBox(pictureBox1, "your_image_path.jpg");
        };
        this.Controls.Add(btnLoad);
    }

    private void DisplayImageInPictureBox(PictureBox pictureBox, string imagePath)
    {
        Mat src = Cv2.ImRead(imagePath, ImreadModes.Color);
        Mat gray = new Mat();
        Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

        Bitmap bitmap = BitmapConverter.ToBitmap(gray); // 或者直接用 src 显示彩色图

        pictureBox.Image?.Dispose();
        pictureBox.Image = bitmap;
    }
}

整体流程总结

  1. 程序启动:创建 MainForm 窗口,调用 InitializeComponent() 初始化设计器中的控件(如 pictureBox1)。
  2. 添加按钮:手动在窗体上添加“加载图像”按钮,绑定点击事件。
  3. 点击按钮
    • 调用 DisplayImageInPictureBox 方法,传入 pictureBox1(显示控件)和图像路径。
    • 读取指定路径的彩色图像。
    • 将彩色图转换为灰度图。
    • 将灰度图从 Mat 转换为 Bitmap(适配 Windows Forms 显示)。
    • 释放 pictureBox 之前可能显示的旧图像,避免内存泄漏。
    • 将新转换的 Bitmap 显示在 pictureBox1 中。

注意事项与补充

  1. 路径问题

    • 示例中的 "your_image_path.jpg" 是占位符,实际使用时必须替换为真实存在的图像路径(如 @"D:\images\test.jpg")。
    • 若路径不存在,Cv2.ImRead 会返回空 Mat,后续操作可能报错。
  2. 图像显示模式

    • 当前代码显示的是 灰度图(经过 BGR2GRAY 转换)。如果想显示原始彩色图,只需修改这行:
      Bitmap bitmap = BitmapConverter.ToBitmap(src); // 直接用彩色 src 转换
      

    • 资源释放

      • Mat 对象:OpenCvSharp 的 Mat 有托管和非托管资源,一般无需手动释放(.NET 垃圾回收会处理),但如果处理大量图像,可通过 src.Dispose() 手动释放,避免内存溢出。
      • Bitmap 对象:必须手动释放,否则会导致内存泄漏(如代码中的 pictureBox.Image?.Dispose())。
    • PictureBox 的设置

      • 在窗体设计器中,需将 pictureBox1 的 SizeMode 设置为 StretchImage(可选),确保图像自适应控件大小,否则可能显示不全。
    • 异常处理

      实际开发中,应添加异常捕获(如文件不存在、路径无效等),避免程序崩溃。例如:
      try
      {
          Mat src = Cv2.ImRead(imagePath, ImreadModes.Color);
          if (src.Empty())
          {
              MessageBox.Show("图像加载失败,请检查路径!");
              return;
          }
          // ... 后续处理
      }
      catch (Exception ex)
      {
          MessageBox.Show($"错误:{ex.Message}");
      }
      

总结

示例代码:

// 转换并显示图像
if (pictureBox1.Image != null)
{
    pictureBox1.Image.Dispose();
}
pictureBox1.Image = BitmapConverter.ToBitmap(matImage);

内存管理注意事项:

典型应用场景:

  • 使用 BitmapConverter.ToBitmap() 方法可以轻松地将 OpenCV 的 Mat 对象转换为 .NET Framework 的 Bitmap 对象。这个转换过程会保留图像的所有像素数据,包括色彩空间和分辨率信息。转换完成后,直接将生成的 Bitmap 对象赋值给 PictureBox 控件的 Image 属性即可在 Windows Forms 应用程序中显示图像。

    在实际应用中,建议按照以下步骤操作:

  • 首先确保已安装并引用了 Emgu.CVEmgu.CV.Bitmap 命名空间
  • 在进行新图像显示前,检查 PictureBox.Image 是否已有旧图像
  • 如果有旧图像,应先调用 PictureBox.Image.Dispose() 方法释放资源
  • 然后再将新转换的 Bitmap 赋给 PictureBox.Image
  • 频繁的图像显示操作会产生多个 Bitmap 对象
  • 未及时释放的 Bitmap 会导致内存泄漏
  • 建议在窗体关闭时统一释放所有图像资源
  • 实时视频处理应用的预览窗口
  • 图像处理软件的中间结果显示
  • 计算机视觉算法的可视化调试界面

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐