一次多相机检测系统线程架构的踩坑记录
两年前开始接触工业视觉项目时,我对多相机系统的理解其实很简单:
一个相机采图,一个线程处理;如果一个相机能跑,那么换成多个相机无非就是复制几份代码。
直到真正把多路工业相机放到生产环境里运行之后,才发现事情远没有想象中简单。
工业现场真正困难的地方,往往不是"能不能采到图片",而是:
图片能不能保持正确顺序;
长时间运行会不会积压;
某一路异常会不会影响其他相机;
线程模型是否合理;
内存是否会持续增长。
本文记录一次多相机检测系统开发过程中遇到的一些问题,以及后来调整线程架构的一些经验。
一、16路相机到底意味着多少数据?
项目中的相机使用的是工业面阵相机,主要参数:
分辨率:1280×1024
Pixel Format:Mono8
触发方式:机台触发
帧率:根据机台速度变化,一般20~30fps
单张图片大小:
1280 × 1024 × 1 byte ≈ 1.25MB
如果按照30fps计算:
1.25MB × 30 ≈ 37.5MB/s
单个相机的数据量其实并不夸张。
但是如果同时运行16路:
37.5MB/s × 16 ≈ 600MB/s
当然,实际检测过程中图片通常会经过ROI裁剪,例如:
1280 × 100
数据量会下降很多。
所以多相机系统最大的压力并不只是图片大小,而是:
大量图片如何有序地从采集端流向检测端。
二、最开始的方案:收到图片直接 Task.Run
最开始的想法非常直接:
相机收到一张图片:
看起来非常符合异步编程的思想。
但是实际运行后,很快发现问题。
问题1:线程池被大量任务占用
工业相机的回调是持续发生的。
如果检测速度稍微跟不上采集速度:
任务数量会不断增长。
.NET 自带线程池会开始频繁调度线程。
最终表现:
CPU占用升高;
任务执行顺序不可控;
图片处理顺序发生变化;
出现丢图或者图片状态错乱。
尤其工业检测中,图片通常不是独立的。
一张图片对应:
当前工件;
当前触发周期;
当前检测状态。
顺序错误可能比单纯丢图片更严重。
这让我意识到:
Task适合处理短生命周期任务,但不适合作为持续图像流的调度方式。
三、第二个误区:盲目增加线程池
后来又尝试引入线程池管理。
例如 SmartThreadPool。
它确实解决了一部分任务调度问题。
项目中也有使用线程池处理相机相关任务。
但是后来发现:
线程并不是越多越好。
如果设计成:
最后可能变成:
几十甚至上百个线程
大量时间消耗在线程切换,而不是实际检测。
工业视觉系统需要的是稳定的数据流,而不是尽可能多的线程。
四、最终方案:生产者消费者模型
后来调整成更适合工业场景的结构:
核心思想:
相机负责生产
相机回调只做:
获取图片;
保存必要信息;
放入对应队列。
不要在回调里面执行耗时算法。
检测线程负责消费
检测线程:
从队列取图片;
调用算法;
保存结果;
释放资源。
这样采集速度和检测速度解耦。
即使算法偶尔变慢,也只是队列暂时积压,而不会直接影响相机采集。
五、实际项目中的队列设计
项目中每个相机都有自己的图片队列。
例如:
不同检测状态的图片进入不同队列。
检测线程根据状态读取对应队列。
这样做的好处:
不同状态互相隔离;
图片顺序更容易保证;
某一路处理异常时不会影响所有任务。
六、图片对象设计:不要让GC管理工业图像
工业视觉中,一个常见问题就是内存。
如果每张图片都创建 Bitmap:
长期运行会产生大量GC压力。
项目中图片对象没有直接保存 Bitmap,而是保存原始Buffer:
class CameraImg
{
public IntPtr ImageBuffer;
public int Width;
public int Height;
public int FrameLen;
}
图片数据直接使用非托管内存保存。
这样可以减少托管堆压力。
但是需要注意:
非托管内存不会自动释放。
所以必须明确生命周期。
例如:
否则运行时间越长,内存占用越高。
七、另一个问题:图片显示和图片检测不是一回事
工业现场经常需要实时显示图片。
但是:
实时显示 ≠ 实时检测。
如果每张图片都刷新UI:
很容易导致:
UI卡顿;
消息队列堆积;
影响检测。
所以显示通常需要:
降低刷新频率;
单独处理;
不影响检测主流程。
检测优先级应该高于显示。
八、异常处理比正常流程更重要
工业软件和普通软件最大的区别:
普通软件:
崩溃以后重新打开。
工业软件:
崩溃可能导致生产停线。
所以需要考虑异常情况。
相机掉线
不能简单退出程序。
应该:
队列堆积
如果算法速度突然下降:
不能无限缓存图片。
需要考虑:
丢弃无效图片;
清理旧数据;
保证最新检测周期。
算法异常
第三方算法库出现异常时:
不能让整个采集系统一起退出。
需要:
捕获异常;
记录日志;
保存现场信息。
九、总结
这篇文章并不是一个标准答案。
不同项目的相机数量、触发方式、算法耗时都不同。
但是在多相机工业视觉系统中,有几个经验比较重要:
不要直接把每张图片丢给Task.Run;
不要认为线程越多性能越好;
采集和检测应该通过队列解耦;
工业图像需要明确的内存生命周期;
稳定运行比单次性能更重要。
多相机系统真正复杂的地方,并不是连接更多相机,而是让大量数据长期、有序、可靠地流动起来。
评论