🤖 C# + 机器视觉 + AI:从工业相机取图到 YOLO 目标检测的完整工控解决方案

📝 前言

随着工业 4.0 和智能制造的浪潮,机器视觉与 AI 的结合已成为工控领域的热门技术。传统的视觉方案往往依赖规则算法,难以应对复杂场景;而引入深度学习模型(如 YOLO)后,我们可以实现高精度的目标检测、缺陷分类等智能应用。

本文将带你从零开始,在 C#(WinForms) 中实现一个完整的工控视觉系统:

  • 通过 海康工业相机 SDK 实时取图
  • 调用 Python + YOLOv8 进行目标检测
  • 在上位机界面实时显示检测结果
  • 可选:通过 Modbus TCP 与 PLC 通信,根据检测结果控制设备

这是一个典型的“工控 + AI”落地案例,代码可直接用于项目原型开发,希望能为你的技术栈注入新的灵感。


🛠 环境准备

硬件

  • 海康威视工业相机(网口或 USB3.0)
  • 工控机 / PC(推荐带独立显卡,加速推理)
  • 可选:PLC(如西门子 S7-1200)用于联动控制

软件与库

组件 说明
Visual Studio 2022 C# 开发环境
.NET 6.0 / .NET Framework 4.8 目标框架
MVS(机器视觉软件) 海康相机驱动及 SDK
Python 3.9+ 深度学习推理环境
ultralytics YOLOv8 推理库
OpenCV-Python 图像处理
NModbus4 C# Modbus 通信库(可选)

安装命令:

pip install ultralytics opencv-python numpy

🏗 系统架构

┌─────────────────────────────────────────────────────────┐
│                     C# 上位机 (WinForms)                 │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │
│  │ 相机控制模块 │  │ 图像显示模块 │  │ 结果解析模块 │    │
│  │ (海康SDK)   │  │ (PictureBox)│  │ (JSON)      │    │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘    │
│         │                │                │            │
│         ▼                ▼                ▼            │
│  ┌─────────────────────────────────────────────────┐  │
│  │              Python 推理引擎 (进程调用)          │  │
│  │  ┌─────────────────────────────────────────┐   │  │
│  │  │ YOLOv8 模型 (ultralytics)              │   │  │
│  │  └─────────────────────────────────────────┘   │  │
│  └─────────────────────────────────────────────────┘  │
│                         │                              │
│                         ▼                              │
│  ┌─────────────────────────────────────────────────┐  │
│  │           PLC (Modbus TCP / 串口)               │  │
│  │         接收结果,执行动作(报警/分拣)          │  │
│  └─────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

🔌 第一步:海康相机实时取图

参考上一篇文章《C# 调用相机终极指南》,我们封装一个 HikCamera 类,实现相机初始化、开始采集和图像回调。

关键代码片段

using MvCameraControl;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;

public class HikCamera
{
    private MyCamera _camera;
    private bool _isGrabbing;
    public event Action<Bitmap> ImageCaptured;

    public bool Open()
    {
        // 枚举设备、创建、打开(具体代码见上篇文章)
        // ...
        _camera.MV_CC_RegisterImageCallBack_NET(ImageCallback, IntPtr.Zero);
        _camera.MV_CC_StartGrabbing_NET();
        _isGrabbing = true;
        return true;
    }

    private void ImageCallback(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo, IntPtr pUser)
    {
        if (!_isGrabbing) return;
        // 将原始数据转为 Bitmap
        Bitmap bitmap = ConvertToBitmap(pData, pFrameInfo);
        ImageCaptured?.Invoke(bitmap);
    }

    private Bitmap ConvertToBitmap(IntPtr pData, MyCamera.MV_FRAME_OUT_INFO_EX info)
    {
        // 假设相机输出 BGR8 格式
        int width = (int)info.nWidth;
        int height = (int)info.nHeight;
        Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(
            new Rectangle(0, 0, width, height),
            System.Drawing.Imaging.ImageLockMode.WriteOnly,
            bmp.PixelFormat);
        int stride = bmpData.Stride;
        int bytesPerPixel = 3;
        for (int y = 0; y < height; y++)
        {
            IntPtr srcLine = IntPtr.Add(pData, y * info.nFrameLen / height);
            IntPtr dstLine = IntPtr.Add(bmpData.Scan0, y * stride);
            CopyMemory(dstLine, srcLine, width * bytesPerPixel);
        }
        bmp.UnlockBits(bmpData);
        return bmp;
    }

    [DllImport("kernel32.dll")]
    private static extern void CopyMemory(IntPtr dest, IntPtr src, int length);
}

在 WinForms 中,订阅 ImageCaptured 事件,将图片显示到 PictureBox 上。


🐍 第二步:Python YOLO 推理脚本

我们编写一个脚本 yolo_detect.py,接收 base64 编码的图像,返回检测结果的 JSON。

import sys
import json
import base64
import cv2
import numpy as np
from ultralytics import YOLO

# 加载模型(首次运行会自动下载)
model = YOLO('yolov8n.pt')  # 可换成自己训练的模型

def detect_objects(image_base64):
    # 解码图像
    img_bytes = base64.b64decode(image_base64)
    nparr = np.frombuffer(img_bytes, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

    # 推理
    results = model(img, conf=0.5)  # 置信度阈值

    detections = []
    for r in results:
        boxes = r.boxes
        if boxes is None:
            continue
        for box in boxes:
            x1, y1, x2, y2 = box.xyxy[0].tolist()
            conf = box.conf[0].item()
            cls = int(box.cls[0].item())
            detections.append({
                "class": cls,
                "class_name": model.names[cls],
                "confidence": conf,
                "bbox": [x1, y1, x2, y2]
            })
    return json.dumps(detections)

if __name__ == "__main__":
    input_data = sys.stdin.read().strip()
    if input_data:
        output = detect_objects(input_data)
        sys.stdout.write(output)
    else:
        sys.stderr.write("No image data")

提示:如果推理速度慢,可以启用 GPU(需安装 CUDA 和 torch with CUDA)。


🔄 第三步:C# 调用 Python 脚本

我们复用上篇文章的 PythonHelper 类,将相机捕获的 Bitmap 转为 base64,传给 Python,解析返回的 JSON。

在 WinForms 中集成

public partial class FormMain : Form
{
    private HikCamera _camera;
    private PythonHelper _pythonHelper;
    private System.Windows.Forms.Timer _inferenceTimer;  // 控制推理频率
    private Bitmap _currentFrame;

    public FormMain()
    {
        InitializeComponent();
        _pythonHelper = new PythonHelper("python", @"D:\Projects\yolo_detect.py");
        _camera = new HikCamera();
        _camera.ImageCaptured += OnImageCaptured;
        
        // 定时器:每隔 100ms 取最新帧进行推理
        _inferenceTimer = new System.Windows.Forms.Timer();
        _inferenceTimer.Interval = 100;
        _inferenceTimer.Tick += async (s, e) => await DetectOnFrame();
        _inferenceTimer.Start();
    }

    private void OnImageCaptured(Bitmap bitmap)
    {
        // 显示实时画面
        pictureBox1.Image = bitmap;
        // 保存最新帧,供推理使用
        _currentFrame = new Bitmap(bitmap);
    }

    private async Task DetectOnFrame()
    {
        if (_currentFrame == null) return;

        // 转为 base64
        string base64 = ImageToBase64(_currentFrame);
        try
        {
            string jsonResult = await _pythonHelper.RunScriptAsync(base64);
            var detections = JsonConvert.DeserializeObject<List<Detection>>(jsonResult);
            DrawDetections(detections);
        }
        catch (Exception ex)
        {
            // 记录错误,不弹窗以免影响实时性
            Console.WriteLine($"推理错误: {ex.Message}");
        }
    }

    private string ImageToBase64(Image img)
    {
        using (var ms = new MemoryStream())
        {
            img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
            return Convert.ToBase64String(ms.ToArray());
        }
    }

    private void DrawDetections(List<Detection> detections)
    {
        if (pictureBox1.Image == null) return;

        // 在原图上绘制检测框
        Bitmap bmp = new Bitmap(pictureBox1.Image);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            foreach (var det in detections)
            {
                float x1 = (float)det.bbox[0];
                float y1 = (float)det.bbox[1];
                float x2 = (float)det.bbox[2];
                float y2 = (float)det.bbox[3];
                using (Pen pen = new Pen(Color.Red, 2))
                {
                    g.DrawRectangle(pen, x1, y1, x2 - x1, y2 - y1);
                }
                using (Font font = new Font("Arial", 12))
                using (SolidBrush brush = new SolidBrush(Color.Red))
                {
                    string text = $"{det.class_name} {det.confidence:P0}";
                    g.DrawString(text, font, brush, x1, y1 - 15);
                }
            }
        }
        pictureBox1.Image = bmp;
    }

    private class Detection
    {
        public int @class { get; set; }
        public string class_name { get; set; }
        public double confidence { get; set; }
        public double[] bbox { get; set; }
    }
}

🏭 第四步:与 PLC 通信(可选)

根据检测结果,我们可以通过 Modbus TCP 控制 PLC 执行相应动作(如报警、分拣)。这里使用 NModbus4 库。

安装 NuGet:Install-Package NModbus4

示例:检测到“person”类且置信度>0.8时,写线圈

using Modbus.Device;
using System.Net.Sockets;

public class ModbusController
{
    private TcpClient _tcpClient;
    private ModbusIpMaster _master;

    public void Connect(string ip, int port)
    {
        _tcpClient = new TcpClient(ip, port);
        _master = ModbusIpMaster.CreateIp(_tcpClient);
        _master.Transport.ReadTimeout = 1000;
    }

    public void WriteCoil(int address, bool value)
    {
        _master.WriteSingleCoil(address, value);
    }
}

// 在检测结果回调中调用
if (detections.Any(d => d.class_name == "person" && d.confidence > 0.8))
{
    _modbus.WriteCoil(0, true);  // 触发报警
}
else
{
    _modbus.WriteCoil(0, false);
}

⚙️ 性能优化技巧

  • 多线程:相机回调线程只负责保存最新帧,推理在单独的 Task 中执行,避免阻塞。
  • 帧率控制:通过 Timer 控制推理频率,避免 GPU 过载。
  • 图像缩放:将相机图像缩放至 640x640 再送入 YOLO,可大幅提升推理速度。
  • 模型量化:使用 YOLOv8 的 int8 量化模型,进一步加速。
  • 进程复用:如果频繁调用,可考虑使用 Python.NET 或 HTTP 服务,避免每次启动进程的开销。

📚 参考文档与资源

  1. 海康机器视觉 SDKMVS 开发指南
  2. YOLOv8 官方文档Ultralytics YOLOv8
  3. C# Modbus 通信NModbus4 GitHub
  4. OpenCvSharp 图像处理OpenCvSharp 文档

🧩 总结与展望

本文提供了一个完整的“工控+AI”解决方案,涵盖了工业相机取图、YOLO 目标检测、上位机界面显示以及与 PLC 联动的全流程。你可以在此基础上:

  • 替换为自己训练的 YOLO 模型(缺陷检测、字符识别等)
  • 增加多相机支持
  • 集成数据库记录检测结果
  • 使用 Web 界面替代 WinForms,实现远程监控

机器视觉与 AI 的融合正在重塑工业自动化,希望这篇博客能为你打开一扇新的大门。

如果你觉得有用,欢迎点赞、收藏、评论!你的支持是我持续创作的动力!


Happy Coding! 🚀

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐