手把手教你:用C# WinForm结合YOLOv8实现摄像头目标检测
最近后台总有粉丝私信我,说想做一个“看得见、能操作”的实时目标检测工具,既要用C# WinForm做可视化界面(毕竟工业场景里上位机还是C#的天下),又要结合当下最火的YOLOv8保证检测精度和速度。其实这个需求看似复杂,本质上就是把YOLOv8的推理能力和C#的界面开发能力结合起来,再加上摄像头的视频流处理就行。我前阵子刚好给工厂做过一个类似的小项目,用来检测生产线上的零件是否到位,今天就把整个
最近后台总有粉丝私信我,说想做一个“看得见、能操作”的实时目标检测工具,既要用C# WinForm做可视化界面(毕竟工业场景里上位机还是C#的天下),又要结合当下最火的YOLOv8保证检测精度和速度。其实这个需求看似复杂,本质上就是把YOLOv8的推理能力和C#的界面开发能力结合起来,再加上摄像头的视频流处理就行。
我前阵子刚好给工厂做过一个类似的小项目,用来检测生产线上的零件是否到位,今天就把整个过程手把手拆解出来,从环境配置到代码实现,再到踩坑优化,全给大家讲透。哪怕你是刚接触C#和YOLO的新手,跟着步骤走也能做出成品。
一、先搞清楚:我们要用到哪些工具和技术?
在开始之前,先梳理一下核心依赖,避免大家走弯路。其实整个流程的核心就是:
“YOLOv11模型转ONNX格式 + C# WinForm调用ONNX模型 + AForge/OpenCVSharp处理视频流”
推荐技术栈(2026年新手最友好组合)
| 用途 | 推荐工具/库 | 为什么选它(新手视角) | 替代方案(次选) |
|---|---|---|---|
| 开发环境 | Visual Studio 2022(社区版免费) | 界面拖拽 + 调试最友好 | VS Code(需要自己配环境) |
| .NET版本 | .NET 9.0(或 .NET 8.0) | 性能最好、跨平台支持最完善 | .NET Framework 4.8(老项目) |
| YOLO模型推理 | YoloSharp 或 YoloDotNet(NuGet) | 最简单,几行代码就能跑 | Microsoft.ML.OnnxRuntime(更底层) |
| 模型格式 | ONNX(yolov11n.onnx) | C#最友好,不依赖Python环境 | TensorFlow .NET(已过时) |
| 视频采集 | AForge.Video.DirectShow | 支持大多数USB工业相机/普通摄像头,开箱即用 | OpenCVSharp4(功能更强但稍复杂) |
| 图像绘制 | SkiaSharp.Views.WinForms | 画框速度快、效果好、跨平台 | System.Drawing(老项目常用) |
| PLC/报警联动 | NModbus4(可选) | Modbus TCP/RTU最常用,几行代码就能写线圈 | Snap7(西门子S7专用) |
二、环境准备(30分钟内必须搞定)
1. 安装 Visual Studio 2022 社区版
- 官网下载:https://visualstudio.microsoft.com/zh-hans/downloads/
- 安装时至少勾选:
- .NET 桌面开发
- 使用 C# 的 .NET 跨平台开发(可选,但推荐)
2. 创建项目
- 打开VS → 创建新项目 → 搜索 “Windows 窗体应用” → 选 .NET 9.0(或 .NET 8.0)
- 项目名称随意,例如:YoloWinFormDemo
3. 安装必须的 NuGet 包
右键项目 → 管理NuGet程序包 → 浏览 → 搜索并安装:
Install-Package YoloSharp # YOLO推理(最简单)
Install-Package SkiaSharp.Views.WinForms # 高效画框
Install-Package AForge.Video.DirectShow # 摄像头采集
Install-Package System.Drawing.Common # .NET 8+ 必须
# 可选(需要PLC联动时安装)
Install-Package NModbus4
4. 获取 YOLOv11 模型(最关键一步)
推荐新手直接下载这个(416×416,轻量、速度快、精度够用):
- 下载链接:https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov11n.onnx
(大小约6MB,下载后重命名为yolov11n.onnx)
操作步骤:
- 在项目根目录新建文件夹
models - 把下载的模型拖进去
- 右键模型文件 → 属性 → 复制到输出目录 = 始终复制
三、完整代码实现(单文件可运行版)
新建项目后,直接把下面全部代码粘贴到 Form1.cs,覆盖原有内容即可。
using System;
using System.Collections.Concurrent;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using AForge.Video;
using AForge.Video.DirectShow;
using SkiaSharp;
using YoloSharp;
namespace YoloWinFormDemo
{
public partial class Form1 : Form
{
// ==================== 控件 ====================
private PictureBox picPreview;
private Label lblStatus;
private Button btnStart, btnStop;
// ==================== YOLO & 采集 ====================
private YoloPredictor yolo;
private VideoCaptureDevice videoSource;
private readonly ConcurrentQueue<Bitmap> frameQueue = new();
private volatile bool isProcessing = false;
private CancellationTokenSource cts = new();
// FPS 计算用
private int frameCount = 0;
private DateTime lastFpsTime = DateTime.Now;
// Letterbox 反算参数
private float scale = 1f;
private int padX, padY;
public Form1()
{
InitializeComponent();
SetupUI();
this.Load += Form1_Load;
this.FormClosing += Form1_FormClosing;
}
private void SetupUI()
{
this.Text = "C# YOLO 实时目标检测(新手友好版)";
this.Size = new Size(1000, 700);
this.StartPosition = FormStartPosition.CenterScreen;
picPreview = new PictureBox { Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.Zoom };
lblStatus = new Label { Text = "状态:未启动 | FPS: --", Dock = DockStyle.Bottom, Height = 40, Font = new Font("微软雅黑", 12), BackColor = Color.Black, ForeColor = Color.Lime };
btnStart = new Button { Text = "启动检测", Width = 140, Height = 45 };
btnStop = new Button { Text = "停止", Width = 140, Height = 45 };
btnStart.Click += BtnStart_Click;
btnStop.Click += BtnStop_Click;
var panel = new FlowLayoutPanel { Dock = DockStyle.Bottom, Height = 60, BackColor = Color.FromArgb(30, 30, 30) };
panel.Controls.Add(btnStart);
panel.Controls.Add(btnStop);
panel.Controls.Add(lblStatus);
this.Controls.Add(picPreview);
this.Controls.Add(panel);
}
private void Form1_Load(object sender, EventArgs e)
{
// 加载模型(只需一次)
try
{
yolo = new YoloPredictor("models/yolov11n.onnx", YoloTask.Detect);
lblStatus.Text = "模型加载成功 | 等待启动摄像头...";
}
catch (Exception ex)
{
lblStatus.Text = "模型加载失败!请检查 models/yolov11n.onnx 是否存在";
MessageBox.Show(ex.Message);
}
// 启动后台处理线程
_ = Task.Run(ProcessFramesAsync, cts.Token);
}
private void BtnStart_Click(object sender, EventArgs e)
{
var devices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (devices.Count == 0)
{
MessageBox.Show("未找到摄像头!请检查设备管理器");
return;
}
videoSource = new VideoCaptureDevice(devices[0].MonikerString);
videoSource.NewFrame += Video_NewFrame;
videoSource.Start();
btnStart.Enabled = false;
btnStop.Enabled = true;
lblStatus.Text = "状态:运行中 | FPS: 计算中...";
}
private void BtnStop_Click(object sender, EventArgs e)
{
videoSource?.SignalToStop();
btnStart.Enabled = true;
btnStop.Enabled = false;
lblStatus.Text = "状态:已停止";
}
// ==================== 采集最新帧(只保留最新一帧,防堆积) ====================
private void Video_NewFrame(object sender, NewFrameEventArgs e)
{
// 优化:丢弃旧帧,只保留最新一帧
while (frameQueue.Count > 1)
{
frameQueue.TryDequeue(out var old);
old?.Dispose();
}
frameQueue.Enqueue((Bitmap)e.Frame.Clone());
}
// ==================== 后台推理线程(防UI卡顿) ====================
private async Task ProcessFramesAsync()
{
while (!cts.IsCancellationRequested)
{
if (frameQueue.TryDequeue(out var frame) && !isProcessing)
{
isProcessing = true;
try
{
await Task.Run(() => DetectAndDraw(frame));
}
finally
{
frame?.Dispose();
isProcessing = false;
}
}
else
{
await Task.Delay(5);
}
}
}
// ==================== 核心:预处理 + 推理 + 坐标反算 + 绘图 ====================
private void DetectAndDraw(Bitmap original)
{
int originalWidth = original.Width;
int originalHeight = original.Height;
// 步骤1:Letterbox 预处理(带黑边缩放,保持比例)
var processed = Letterbox(original, 416, 416);
// 步骤2:YOLO推理(后台执行)
var results = yolo.Detect(processed);
// 步骤3:坐标反算(从416x416映射回原始图像)
var finalResults = new System.Collections.Generic.List<YoloResult>();
foreach (var r in results)
{
if (r.Confidence < 0.5f) continue;
float x = (r.BoundingBox.X - padX) / scale;
float y = (r.BoundingBox.Y - padY) / scale;
float w = r.BoundingBox.Width / scale;
float h = r.BoundingBox.Height / scale;
// 限制坐标不超出原始图像边界
x = Math.Max(0, Math.Min(x, originalWidth));
y = Math.Max(0, Math.Min(y, originalHeight));
w = Math.Min(w, originalWidth - x);
h = Math.Min(h, originalHeight - y);
finalResults.Add(new YoloResult
{
Label = r.Label,
Confidence = r.Confidence,
BoundingBox = new SKRect(x, y, x + w, y + h)
});
}
// 步骤4:绘制结果到原始图像(而不是416x416的processed)
using var skOriginal = SKBitmap.FromImage(SKImage.FromBitmap(original));
using var canvas = new SKCanvas(skOriginal);
foreach (var r in finalResults)
{
var rect = r.BoundingBox;
using var paint = new SKPaint { Style = SKPaintStyle.Stroke, Color = SKColors.Red, StrokeWidth = 4 };
canvas.DrawRect(rect, paint);
canvas.DrawText($"{r.Label.Name} {r.Confidence:P0}", rect.X, rect.Y - 5,
new SKPaint { Color = SKColors.Yellow, TextSize = 20 });
}
// 步骤5:更新UI(主线程安全)
this.Invoke((MethodInvoker)delegate
{
picPreview.Image?.Dispose();
picPreview.Image = skOriginal.ToBitmap();
// FPS 计算
frameCount++;
if ((DateTime.Now - lastFpsTime).TotalSeconds >= 1)
{
lblStatus.Text = $"状态:运行中 | FPS: {frameCount}";
frameCount = 0;
lastFpsTime = DateTime.Now;
}
});
}
// ==================== Letterbox + 记录缩放参数(用于坐标反算) ====================
private float scale = 1f;
private int padX, padY;
private SKBitmap Letterbox(Bitmap src, int targetW, int targetH)
{
float ratio = Math.Min((float)targetW / src.Width, (float)targetH / src.Height);
int newW = (int)(src.Width * ratio);
int newH = (int)(src.Height * ratio);
scale = ratio;
padX = (targetW - newW) / 2;
padY = (targetH - newH) / 2;
var skSrc = SKBitmap.FromImage(SKImage.FromBitmap(src));
var dst = new SKBitmap(targetW, targetH);
using var canvas = new SKCanvas(dst);
canvas.Clear(SKColors.Black); // 黑边填充(YOLO官方推荐)
canvas.DrawBitmap(skSrc, new SKRect(padX, padY, padX + newW, padY + newH));
skSrc.Dispose();
return dst;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
cts.Cancel();
videoSource?.SignalToStop();
yolo?.Dispose();
base.OnFormClosing(e);
}
}
// 简单结果包装类
public class YoloResult
{
public YoloLabel Label { get; set; }
public float Confidence { get; set; }
public SKRect BoundingBox { get; set; }
}
}
三、最常见报错 & 一键解决口诀
| 报错关键词 | 原因 | 解决口诀(记住就行) |
|---|---|---|
| Could not load model / file not found | onnx文件没复制到输出目录 | 右键模型文件 → 属性 → 始终复制 |
| Out of memory / 内存不足 | Bitmap没释放 | 所有Bitmap/SKBitmap必须用using块 |
| UI无响应 / 卡死 | 推理放在主线程 | 必须用 Task.Run + Invoke 更新UI |
| 检测框位置完全错 | 没做坐标反算 | 必须记录 Letterbox 的 scale/padX/padY |
| FPS很低(<10) | 模型太大 / 分辨率太高 | 用 yolov11n + 416×416 输入 |
| 摄像头打不开 | 被其他软件占用 | 关闭QQ/微信视频、OBS等软件 |
四、快速上手 & 下一步建议
- 新建WinForms项目(.NET 9)
- 安装上面4个NuGet包
- 下载 yolov11n.onnx(416×416)放
models文件夹 → 属性 → 始终复制 - 替换 Form1.cs 为上面全部代码
- F5运行 → 插摄像头 → 点击“启动检测”
下一步进阶(建议顺序):
- 加PLC报警:检测到异常 → 写Modbus线圈
- 加语音播报:用NAudio或Windows Speech
- 加异常截图保存 + Excel报表
- 加GPU加速:安装 Microsoft.ML.OnnxRuntime.DirectML
点赞+收藏,这可能是你今年最适合新手的C# YOLO上位机教程!
更多推荐


所有评论(0)