【C#】以 BlockingCollection 为核心的多相机 YOLO 检测任务处理框架
以 BlockingCollection 为核心的多相机 YOLO 检测任务处理框架
在工业视觉应用中,我们经常会遇到 一台电脑同时接入多台相机 的场景。比如 10 台相机同时拍摄生产线上的产品,通过硬件 IO 触发采图,然后进行 YOLO 缺陷检测。
如果不合理设计,容易出现以下问题:
- 回调线程阻塞导致相机触发丢帧
- 多线程抢占 YOLO 对象导致检测结果错乱
- 图像内存泄露或 UI 更新异常
本文将以 BlockingCollection 为核心,介绍一个线程安全、支持多相机任务队列的 YOLO 检测框架。
1. 为什么选择 BlockingCollection
BlockingCollection<T>
是 .NET 提供的一个 线程安全的生产者-消费者队列,非常适合处理相机采图任务。
优点:
- 线程安全:多个线程同时 Add/Take 没问题
- 阻塞消费:队列为空时,Take 或 GetConsumingEnumerable 会自动等待
- 容量限制:防止队列无限增长
- 优雅结束:调用
CompleteAdding()
后,消费者循环可以自动退出
Tip:BlockingCollection 内部默认基于
ConcurrentQueue<T>
,也可以替换为ConcurrentStack<T>
或ConcurrentBag<T>
。
2. 相比 ConcurrentQueue / ConcurrentBag 的优势
特性 | ConcurrentQueue | ConcurrentBag | BlockingCollection |
---|---|---|---|
顺序 | FIFO | 无序 | FIFO / 自定义 |
阻塞 | ❌ | ❌ | ✅ |
容量限制 | ❌ | ❌ | ✅ |
支持生产者-消费者模式 | ❌ | ❌ | ✅ 内置 |
可结束队列/循环 | ❌ | ❌ | ✅ CompleteAdding |
总结:BlockingCollection 本质上是在 ConcurrentQueue/Bag 基础上加了阻塞、容量、结束控制,更适合多线程任务队列场景。
3. 多相机任务处理思路
针对 10 台相机 IO 触发的场景,设计思路如下:
-
Update 回调
- 回调线程只做一件事:将
CameraInfo
入队 - 避免在回调中执行耗时的检测任务,防止阻塞触发
- 回调线程只做一件事:将
-
后台 WorkerLoop
- 从
BlockingCollection
队列中取任务 - 调用
Detection()
进行 YOLO 处理
- 从
-
Detection 处理逻辑
- 图像预处理(Blob、裁剪、摆正等)
- YOLO 推理(检测或分割)
- UI 显示(通过 Dispatcher 确保线程安全)
- 任务完成后释放图像内存
-
停止 / 完整退出
- 调用
CompleteAdding()
告诉消费者:以后不再有新任务 - 消费者循环自动退出
- 调用
4. 核心代码示例
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;public class CameraProcessor
{private BlockingCollection<CameraInfo> taskQueue;private CancellationTokenSource cts;private Task workerTask;private readonly Dispatcher uiDispatcher;public CameraProcessor(Dispatcher dispatcher){uiDispatcher = dispatcher;StartWorker();}private void StartWorker(){taskQueue = new BlockingCollection<CameraInfo>();cts = new CancellationTokenSource();workerTask = Task.Run(() => WorkerLoop(), cts.Token);}public void Update(CameraInfo info) => taskQueue.Add(info);private void WorkerLoop(){foreach (var info in taskQueue.GetConsumingEnumerable(cts.Token)){try{var graphic = GetGraphicBySn(info.SerialNumber);if (graphic != null){var results = Detection(graphic, info.Image);// UI 更新uiDispatcher.Invoke(() =>{graphic.StatusText = "检测完成";graphic.LastResults = results;});}info.Image?.Dispose(); // 避免内存泄漏}catch (Exception ex){Debug.WriteLine($"检测异常: {ex}");}}}public void Stop(){taskQueue.CompleteAdding();cts.Cancel();workerTask.Wait();}public void Restart(){Stop();StartWorker();}private List<YOLOData> Detection(GraphicInfo graphic, HObject image){Stopwatch sw = new Stopwatch();List<YOLOData> data = new List<YOLOData>();// 1. 预处理sw.Restart();HObject imgProduct = PreprocessImage(graphic, image);sw.Stop();long preprocessTime = sw.ElapsedMilliseconds;// 2. YOLO 推理sw.Restart();//推理~~~~~sw.Stop();long detectTime = sw.ElapsedMilliseconds;return data;}private HObject PreprocessImage(GraphicInfo graphic, HObject image){HObject imgProduct = null;if (graphic.BlobConfig.BlobEnable)imageScriptTool.BlobProduct(image, graphic.BlobConfig, out imgProduct);if (graphic.ScriptConfig.Preprocessing)imageScriptTool.Preprocess(imgProduct ?? image, graphic.ScriptConfig.PreprocessFuncName, out imgProduct);if (imgProduct == null)imgProduct = image.Clone();if (graphic.ScriptConfig.定位摆正)imageScriptTool.PosJust(image, imgProduct, out imgProduct, out HTuple FixDeg, false);return imgProduct;}private GraphicInfo GetGraphicBySn(string sn){// TODO: 根据相机序列号找到对应的 GraphicInforeturn null;}
}
5. 使用建议
-
回调线程只入队
避免直接在 IO 回调里做检测,防止阻塞相机触发。 -
后台线程消费
WorkerLoop
永远循环消费任务,保证线程安全。 -
UI 更新
通过Dispatcher.Invoke
确保 WPF 控件安全访问。 -
停止 / 重启
- 程序退出时调用
Stop()
→ 完成队列 → 优雅退出 - 如果需要“暂停/恢复”,可用标志位控制,而不是
CompleteAdding()
再恢复。
- 程序退出时调用
-
多相机独立 YOLO 实例
每台相机维护独立的 YOLO 和预处理对象,避免线程竞争。
6. 总结
BlockingCollection<T>
是多线程生产者-消费者模式的核心利器,适合高并发图像处理场景- 将相机回调与耗时检测解耦,保证系统稳定
- 完整框架支持 多相机并发检测、UI 安全更新、暂停/停止控制