C#项目实战——工业视觉缺陷有无检测系统(一)
本文介绍了使用C#和OpenCV开发螺母有无检测系统的过程。通过图像二值化、阈值分析和区域筛选三个步骤实现检测功能:首先将图像转为灰度并进行二值化处理,然后检测轮廓,最后根据面积和尺寸筛选出螺母区域并标记。文中详细说明了ROI(感兴趣区域)的实现方法,并提供了将OpenCV的Mat对象显示在C# PictureBox控件上的完整代码示例。该系统可有效检测产品照片中的螺母缺失情况,适用于工业生产中的
最近,在动手开发一个有无的视觉检测系统,纯手搓,记录一下自己的开发历程,希望对其他有相似的项目需求的小伙伴有帮助。
项目需求
为避免在生产的过程中漏工序,我们要在生产结束时,拍张产品照片,检测螺母有无缺少。

项目方案
使用C# 的opencv库,实现检测应该一个照片,操作下面流程
1. 图像二值化
2. 对二值化的图像就行阈值分析
3. 筛选满足条件的区域(面积、长、宽) 渲染图片中螺母的位置,如果用到roi,详细说说怎么实现的
图像二值化是什么?最简单的图像分割方法是二值化(Binarization)。图像二值化( Image Binarization)就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。二值图像每个像素只有两种取值:要么纯黑,要么纯白。
进行二值化的方式?其中最常用的就是采用阈值法(Thresholding)进行二值化,(根据阈值选取方式的不同,可以分为全局阈值和局部阈值【又称为自适应阈值 】)
步骤概述
- 图像二值化:将图像转换为灰度,然后应用二值化。
- 阈值分析:使用二值化后的图像进行轮廓检测。
- 筛选区域:根据面积、长宽等条件筛选轮廓。
- 标记螺母位置:在原图上标记筛选后的区域。
实现代码
首先,确保你已经安装了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进行处理,例如再次进行轮廓检测
在这个例子中,x、y、width和height定义了ROI的位置和大小。你可以在这个子矩阵上应用相同的处理步骤。
通过这种方式,你可以有效地检测和标记图像中的螺母位置。
界面展示
在 C# 中,如果你想将 OpenCV 的 Mat 对象显示在 PictureBox 控件上,你需要将 Mat 转换为 Bitmap,然后将 Bitmap 赋值给 PictureBox.Image。
步骤
-
将
Mat转换为Bitmap
OpenCvSharp 提供了OpenCvSharp.Extensions.BitmapConverter.ToBitmap()方法,可以直接将Mat转换为Bitmap。 -
将
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;
}
}
详细说明
Cv2.ImRead读取图像- 使用
ImreadModes.Color读取彩色图像,或者ImreadModes.Grayscale直接读取灰度图。
- 使用
Cv2.CvtColor转换为灰度(可选)- 如果需要显示灰度图,可以使用
Cv2.CvtColor转换。 - 如果直接显示彩色图,可以跳过这一步。
- 如果需要显示灰度图,可以使用
BitmapConverter.ToBitmap转换- OpenCvSharp 提供了
BitmapConverter类,可以方便地将Mat转换为Bitmap。 - 如果是彩色图,可以直接转换
src,如果是灰度图,转换gray。
- OpenCvSharp 提供了
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;
}
}
整体流程总结
- 程序启动:创建
MainForm窗口,调用InitializeComponent()初始化设计器中的控件(如pictureBox1)。 - 添加按钮:手动在窗体上添加“加载图像”按钮,绑定点击事件。
- 点击按钮:
- 调用
DisplayImageInPictureBox方法,传入pictureBox1(显示控件)和图像路径。 - 读取指定路径的彩色图像。
- 将彩色图转换为灰度图。
- 将灰度图从
Mat转换为Bitmap(适配 Windows Forms 显示)。 - 释放
pictureBox之前可能显示的旧图像,避免内存泄漏。 - 将新转换的
Bitmap显示在pictureBox1中。
- 调用
注意事项与补充
-
路径问题:
- 示例中的
"your_image_path.jpg"是占位符,实际使用时必须替换为真实存在的图像路径(如@"D:\images\test.jpg")。 - 若路径不存在,
Cv2.ImRead会返回空 Mat,后续操作可能报错。
- 示例中的
-
图像显示模式:
- 当前代码显示的是 灰度图(经过
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.CV和Emgu.CV.Bitmap命名空间 - 在进行新图像显示前,检查
PictureBox.Image是否已有旧图像 - 如果有旧图像,应先调用
PictureBox.Image.Dispose()方法释放资源 - 然后再将新转换的
Bitmap赋给PictureBox.Image - 频繁的图像显示操作会产生多个
Bitmap对象 - 未及时释放的
Bitmap会导致内存泄漏 - 建议在窗体关闭时统一释放所有图像资源
- 实时视频处理应用的预览窗口
- 图像处理软件的中间结果显示
- 计算机视觉算法的可视化调试界面
更多推荐


所有评论(0)