Loading...
时易うさぎのBlog
闲聊工作经验

16路工业相机同时运行是一种什么体验

一次多相机检测系统线程架构的踩坑记录

两年前开始接触工业视觉项目时,我对多相机系统的理解其实很简单:

一个相机采图,一个线程处理;如果一个相机能跑,那么换成多个相机无非就是复制几份代码。

直到真正把多路工业相机放到生产环境里运行之后,才发现事情远没有想象中简单。

工业现场真正困难的地方,往往不是"能不能采到图片",而是:

  • 图片能不能保持正确顺序;

  • 长时间运行会不会积压;

  • 某一路异常会不会影响其他相机;

  • 线程模型是否合理;

  • 内存是否会持续增长。

本文记录一次多相机检测系统开发过程中遇到的一些问题,以及后来调整线程架构的一些经验。


一、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。

它确实解决了一部分任务调度问题。

项目中也有使用线程池处理相机相关任务。

但是后来发现:

线程并不是越多越好。

如果设计成:

最后可能变成:

几十甚至上百个线程

大量时间消耗在线程切换,而不是实际检测。

工业视觉系统需要的是稳定的数据流,而不是尽可能多的线程。


四、最终方案:生产者消费者模型

后来调整成更适合工业场景的结构:

核心思想:

相机负责生产

相机回调只做:

  • 获取图片;

  • 保存必要信息;

  • 放入对应队列。

不要在回调里面执行耗时算法。


检测线程负责消费

检测线程:

  • 从队列取图片;

  • 调用算法;

  • 保存结果;

  • 释放资源。

这样采集速度和检测速度解耦。

即使算法偶尔变慢,也只是队列暂时积压,而不会直接影响相机采集。


五、实际项目中的队列设计

项目中每个相机都有自己的图片队列。

例如:

不同检测状态的图片进入不同队列。

检测线程根据状态读取对应队列。

这样做的好处:

  1. 不同状态互相隔离;

  2. 图片顺序更容易保证;

  3. 某一路处理异常时不会影响所有任务。


六、图片对象设计:不要让GC管理工业图像

工业视觉中,一个常见问题就是内存。

如果每张图片都创建 Bitmap:

长期运行会产生大量GC压力。

项目中图片对象没有直接保存 Bitmap,而是保存原始Buffer:

class CameraImg
{
    public IntPtr ImageBuffer;

    public int Width;

    public int Height;

    public int FrameLen;
}

图片数据直接使用非托管内存保存。

这样可以减少托管堆压力。

但是需要注意:

非托管内存不会自动释放。

所以必须明确生命周期。

例如:

否则运行时间越长,内存占用越高。


七、另一个问题:图片显示和图片检测不是一回事

工业现场经常需要实时显示图片。

但是:

实时显示 ≠ 实时检测。

如果每张图片都刷新UI:

很容易导致:

  • UI卡顿;

  • 消息队列堆积;

  • 影响检测。

所以显示通常需要:

  • 降低刷新频率;

  • 单独处理;

  • 不影响检测主流程。

检测优先级应该高于显示。


八、异常处理比正常流程更重要

工业软件和普通软件最大的区别:

普通软件:

崩溃以后重新打开。

工业软件:

崩溃可能导致生产停线。

所以需要考虑异常情况。

相机掉线

不能简单退出程序。

应该:


队列堆积

如果算法速度突然下降:

不能无限缓存图片。

需要考虑:

  • 丢弃无效图片;

  • 清理旧数据;

  • 保证最新检测周期。


算法异常

第三方算法库出现异常时:

不能让整个采集系统一起退出。

需要:

  • 捕获异常;

  • 记录日志;

  • 保存现场信息。


九、总结

这篇文章并不是一个标准答案。

不同项目的相机数量、触发方式、算法耗时都不同。

但是在多相机工业视觉系统中,有几个经验比较重要:

  1. 不要直接把每张图片丢给Task.Run;

  2. 不要认为线程越多性能越好;

  3. 采集和检测应该通过队列解耦;

  4. 工业图像需要明确的内存生命周期;

  5. 稳定运行比单次性能更重要。

多相机系统真正复杂的地方,并不是连接更多相机,而是让大量数据长期、有序、可靠地流动起来。

分享到

评论