1. 从“看见”到“看懂”一块板子的视觉能力分水岭很多人第一次接触计算机视觉脑子里浮现的画面是摄像头拍一张照然后程序告诉你“这是一只猫”。这个理解不算错但它只停留在“看见”的层面。真正让一块嵌入式板子变得有用的是它能不能在拍完照之后自己判断出“这只猫正在往桌上跳可能会碰倒杯子”然后决定要不要发出警报。前者是感知后者是理解。这两者之间的差距就是我在实际项目里踩过最多坑的地方。我手里这块板子算力不算强内存也紧巴巴的但它带了一个摄像头接口和一颗还算能用的神经网络加速单元。一开始我的想法很简单跑个分类模型识别几个物体任务就算完成了。结果真跑起来才发现问题根本不在模型能不能识别而在于识别出来的东西怎么用。比如摄像头拍到一个人分类模型输出“人”这个标签置信度0.92。然后呢板子不知道这个人是站在门口还是躺在路中间不知道他是在正常走路还是突然摔倒。标签本身没有空间信息也没有时序信息板子只是“看见”了一个人但完全没有“看懂”这个人在干什么。这个认知转变花了我不少时间。后来我把整个流程拆成了三层第一层是像素到特征第二层是特征到目标第三层是目标到事件。第一层靠卷积网络第二层靠检测或分割第三层靠逻辑判断和状态机。很多教程只讲到第二层就停了但真正让板子“看懂”的恰恰是第三层。这一层没有现成的模型可以调得自己根据场景写规则、设阈值、做状态跟踪。听起来很土但实测下来在算力有限的板子上这种“模型加规则”的混合方案比端到端的大模型稳得多。这篇文章我想把这三层拆开讲清楚重点放在第三层怎么设计、第二层怎么选型、第一层怎么在板子上跑得动。如果你手里也有一块带摄像头的开发板想让它从“能拍照”变成“能判断”下面的内容应该能帮你省下不少试错的时间。2. 板子上的第一层像素到特征算力都花在哪了2.1 为什么第一层最容易把板子跑死像素到特征这一步说白了就是把一张图变成一堆数字。一张640乘480的彩色图原始像素是92万个数值。如果直接拿这些数值去做判断计算量会大到板子根本扛不住。所以第一步一定是降维用卷积操作把空间信息压缩成特征图。但卷积本身也是计算而且是最耗算力的部分。我实测过在一块算力大概0.5TOPS的板子上跑一个标准的轻量卷积网络单帧推理时间在80到120毫秒之间。这意味着帧率只有8到12帧勉强能看但稍微复杂一点的场景就会卡顿。这里有个很多人忽略的点板子的算力瓶颈往往不在卷积乘加本身而在内存带宽。卷积层之间的特征图需要反复读写如果内存带宽不够算力单元就会空转等数据。我试过把特征图从float32量化成int8推理时间直接降了将近一半不是因为计算变快了而是因为数据搬运量少了。所以如果你发现板子跑模型很慢先别急着换模型看看能不能做量化。2.2 轻量骨干网络的选择逻辑在板子上选骨干网络核心原则只有一个参数量和计算量要匹配板子的内存和算力。我对比过几种常见的轻量结构下面这张表是我在实际项目中测出来的大致数据板子算力按0.5TOPS算输入分辨率统一到224乘224。网络结构参数量单帧推理时间内存占用适合场景标准卷积堆叠约4.2M约150ms约18MB不推荐太慢深度可分离卷积约1.1M约65ms约7MB通用分类带残差连接的轻量结构约1.8M约85ms约11MB需要稍高精度分组卷积加通道重排约0.9M约55ms约6MB极致速度优先从表里能看出来深度可分离卷积和分组卷积是板子上的主力。标准卷积堆叠虽然精度高一点但推理时间直接翻倍在实时场景里基本不可用。我一般会先用分组卷积加通道重排的结构跑通流程如果精度不够再换成带残差连接的版本同时把输入分辨率从224降到192用分辨率换速度。还有一个细节很多轻量网络在训练时用的是大分辨率部署时直接缩小输入精度会掉得厉害。我的做法是在训练阶段就加入多尺度增强让网络见过不同分辨率的输入这样部署时缩小分辨率精度下降会小很多。这个技巧在文档里很少提但实测能挽回三到五个百分点的精度。2.3 量化不是万能药但不用量化万万不能量化这件事我踩过的坑比成功的次数多。第一次做int8量化精度直接从92%掉到78%原因是我把激活值的量化范围设得太窄很多中间特征被截断了。后来学乖了量化之前先跑一遍校准集统计每一层激活值的实际分布再根据分布去定缩放因子。校准集不用多一两百张覆盖典型场景的图就够了但一定要有代表性。如果校准集里全是白天光照充足的图部署到夜间场景量化后的模型基本就废了。另一个坑是量化后的算子兼容性。不是所有卷积层都能顺利转成int8有些板子的加速单元只支持特定尺寸的卷积核。我遇到过3乘3的卷积能加速5乘5的就回退到CPU速度反而更慢。所以量化之前一定要查板子的算子支持列表把不支持的层替换掉或者调整结构。这个工作很琐碎但省不掉。3. 第二层从特征到目标检测和分割怎么选3.1 检测框和分割掩码的取舍特征出来之后下一步是定位目标。常见的有两种输出检测框和分割掩码。检测框就是一个矩形告诉你目标大概在哪分割掩码是像素级的告诉你哪些像素属于目标。在板子上这两者的计算代价差很多。检测框的后处理主要是非极大值抑制计算量不大分割掩码需要逐像素分类后处理更重而且输出分辨率高内存占用也大。我一般会先问一个问题这个场景真的需要像素级精度吗如果只是判断有没有人、人在哪个区域检测框足够了。如果是要测量物体的面积、形状或者做精细的抓取才需要分割。我做过一个案例用检测框判断传送带上有没有异物准确率已经到96%以上换成分割之后准确率只提升了不到一个百分点但推理时间增加了40%。这种投入产出比就不划算。3.2 锚框设计的实战经验检测模型通常需要锚框来匹配目标。锚框的尺寸和比例如果和实际目标差太多召回率会很难看。我见过有人直接套用公开数据集的锚框参数结果在自己的场景里小目标全漏检。正确的做法是统计自己数据集里所有目标框的宽高分布用聚类算法算出几个代表性的尺寸。这个统计不用很精确但一定要做。我一般会统计目标框的宽高比然后按比例分成三到五组每组取中位数作为锚框尺寸。比如我的场景里目标大多是细长条宽高比集中在1比3到1比5之间那锚框就设成窄高的形状而不是正方形。改完锚框之后召回率从71%提到了89%效果非常直接。还有一个细节是锚框的步长。步长太大小目标匹配不上步长太小计算量爆炸。在板子上我一般会把最小步长设在8或16再小就吃不消了。如果小目标很多宁可提高输入分辨率也不要减小步长因为分辨率提高是均匀增加计算步长减小是指数级增加候选框数量。3.3 非极大值抑制的阈值怎么调非极大值抑制是检测后处理里最影响最终结果的一步。阈值设高了重叠的目标会被合并成一个设低了同一个目标会出现多个框。默认的0.5在大多数场景下能用但有两个情况需要调整。第一种是密集场景。比如货架上并排摆了很多相似的物品阈值0.5会把相邻物品的框互相抑制掉。这时候要把阈值提高到0.6甚至0.7让重叠的框保留下来。第二种是遮挡场景。目标被部分遮挡时模型可能会输出多个置信度不高的框阈值太低会导致重复检测。这时候反而要降低阈值到0.4左右同时提高置信度门槛把低质量的框先滤掉。我通常会在验证集上画一条阈值和F1分数的曲线找峰值对应的阈值。这个曲线跑一次只要几分钟但能避免很多拍脑袋的调参。4. 第三层从目标到事件规则引擎才是“看懂”的关键4.1 为什么纯模型方案在板子上走不通很多人想用端到端的大模型直接输出事件描述比如“有人摔倒了”或者“车辆逆行了”。这个思路在服务器上可行在板子上基本不现实。原因有两个一是算力不够大模型的参数量和计算量远超板子的承载能力二是数据不够事件级别的标注数据比目标级别的标注数据难获取得多没有足够的数据模型根本学不会。我试过用视频分类模型直接判断动作输入是连续16帧输出是动作类别。在板子上跑一帧要300多毫秒而且准确率只有七成左右误报率很高。后来换成“检测加规则”的方案检测部分每帧只要60毫秒规则部分几乎不耗算力整体准确率反而提到了九成以上。这个对比让我彻底放弃了在板子上跑端到端事件模型的念头。4.2 状态机设计让板子记住上一帧发生了什么规则引擎的核心是状态机。板子不能只看当前帧它需要记住目标在过去几帧里的位置和状态才能判断事件有没有发生。最简单的状态机是“进入、停留、离开”三态。比如检测到人进入画面状态从空闲变成进入人在画面里持续存在状态变成停留人消失状态回到空闲。如果停留时间超过阈值就触发“长时间停留”事件。这个状态机看起来简单但实际写起来有很多细节。比如目标ID的分配如果每帧都重新检测同一个人的ID会跳变状态机就乱了。我一般会用简单的距离匹配来做ID关联当前帧的检测框和上一帧的检测框做IoU计算IoU最高的那个继承上一帧的ID。如果IoU都低于阈值就分配新ID。这个方法在目标移动不快的时候很稳移动快了会断轨但板子场景里通常够用。还有一个细节是状态超时。如果目标被遮挡了几帧检测不到状态机不能立刻判定目标离开否则会频繁触发进入和离开事件。我一般会设一个容忍帧数比如连续5帧检测不到才判定离开。这个数字根据帧率和场景来定帧率低就设小一点帧率高就设大一点。4.3 事件触发的防抖和去重状态机跑起来之后事件触发会非常频繁。比如一个人站在画面里每一帧都满足“停留”条件如果不做处理会连续触发几十次事件。我的做法是给每个事件加一个冷却时间同一个事件在冷却时间内只触发一次。冷却时间根据事件类型来定比如“进入”事件的冷却时间可以短一点一两秒“摔倒”事件的冷却时间要长一点十秒以上避免同一次摔倒被反复报警。另一个问题是误触发。检测模型偶尔会漏检或误检导致状态机产生假事件。我一般会在事件触发前加一个确认逻辑连续N帧都满足触发条件才真正触发事件。N取3到5比较合适太小了防不住抖动太大了会延迟响应。这个确认逻辑和冷却时间配合使用能把误报率压到很低。5. 把三层串起来一个完整的板端视觉流水线5.1 数据流的组织方式三层串起来之后数据流是这样的摄像头采集图像第一层做预处理和特征提取第二层做目标检测第三层做状态跟踪和事件判断。每一层的输出都是下一层的输入但中间需要一些缓存和队列来平滑处理速度的波动。比如第一层推理偶尔会慢一帧如果直接丢给第二层第二层就会等整个流水线就卡住了。我一般会在层与层之间加一个长度为2到3的缓冲队列允许短暂的速度不匹配。内存管理也很关键。板子的内存有限如果每一层都申请新的内存很快就会耗尽。我的做法是预分配一块内存池每一层的输入输出都从池子里取用完就还回去。这样虽然写起来麻烦一点但能避免内存碎片和泄漏。实测下来用内存池之后连续跑几个小时都不会出现内存增长。5.2 时间戳和帧同步三层流水线里每一帧都要带时间戳。第一层处理完打一个时间戳第二层处理完再打一个第三层根据时间戳来判断事件的时序关系。如果没有时间戳状态机就不知道两帧之间隔了多久冷却时间和超时判断都会失准。帧同步是另一个容易忽略的问题。如果摄像头采集是30帧但第一层只能处理10帧那就有20帧被丢掉了。丢帧本身没问题但丢帧之后时间戳会跳变状态机需要能处理这种跳变。我的做法是在丢帧的时候记录丢了多少帧状态机根据实际时间差来更新状态而不是根据帧数。这样即使帧率波动事件判断的逻辑也不会乱。5.3 资源占用的实测数据下面这张表是我在一块典型板子上跑完整流水线的资源占用情况输入分辨率640乘480检测模型用轻量结构状态机跟踪最多10个目标。模块CPU占用内存占用单帧耗时图像采集和预处理约8%约2MB约5ms第一层特征提取约45%约12MB约60ms第二层目标检测约30%约8MB约40ms第三层状态跟踪约5%约1MB约2ms合计约88%约23MB约107ms从表里能看出来第一层和第二层吃掉了绝大部分资源第三层几乎不占什么。这也印证了我之前的判断板子上的算力应该优先花在感知上理解部分用规则来实现性价比最高。如果反过来把算力花在端到端的事件模型上资源根本不够用。6. 实战中容易翻车的几个地方6.1 光照变化导致的特征漂移板子部署到真实场景里光照变化是最常见的干扰。白天训练好的模型到了晚上或者阴天精度会明显下降。我试过在训练数据里加入亮度、对比度、色温的随机扰动让模型见过各种光照条件。这个做法能提升一些鲁棒性但不能完全解决问题。更有效的办法是在预处理阶段做自适应直方图均衡把图像的亮度分布拉平减少光照差异对特征的影响。还有一个坑是红外补光。有些场景晚上需要红外灯但红外图像和可见光图像的分布差异很大同一个模型在两种图像上的表现完全不同。如果场景需要红外训练数据里一定要包含红外图像不能只用可见光数据训练然后指望模型能泛化。6.2 目标尺度变化带来的漏检目标离摄像头近的时候很大远的时候很小尺度变化范围可能超过十倍。单一尺度的检测模型很难同时覆盖大目标和小目标。我一般会用特征金字塔结构在不同尺度的特征图上分别做检测。但特征金字塔会增加计算量在板子上要控制层数一般三层就够了再多算力吃不消。另一个办法是调整摄像头的安装位置和角度让目标在画面里的尺度变化尽量小。比如把摄像头装低一点减少俯视角度目标在远近处的尺度差异会小很多。这个属于物理层面的优化不耗算力但效果很好。我在一个项目里把摄像头从三米高降到一米五小目标的召回率直接提了十五个百分点。6.3 状态机在目标交叉时的ID跳变多目标跟踪时两个目标交叉走过ID很容易互换。一旦ID互换状态机就会把两个目标的历史轨迹搞混事件判断全乱。我试过用外观特征来做ID匹配但板子上跑外观特征提取太耗算力。后来用了一个折中方案在目标交叉时暂停状态更新等目标分开后再根据位置和运动方向重新关联。这个方案不完美但比ID直接互换要好得多。还有一个更简单的办法如果场景里目标不多可以限制同时跟踪的目标数量比如最多跟踪5个。超过5个就只保留置信度最高的5个其他的忽略。这样虽然会丢一些目标但能保证跟踪的稳定性。在板子场景里这个取舍通常是值得的。6.4 事件阈值设得太死导致漏报事件触发的阈值如果设得太严格会漏掉很多真实事件。比如摔倒检测如果要求人体框的宽高比在连续10帧内都超过某个值才触发那快速摔倒可能只持续五六帧就漏掉了。我的经验是阈值要留有余量宁可误报多一点也不要漏报。误报可以靠冷却时间和确认逻辑来压制漏报是没法补救的。具体做法是先用宽松的阈值跑一遍测试集统计误报和漏报的比例然后逐步收紧阈值直到误报降到可接受的范围。这个过程需要反复迭代没有一步到位的办法。我一般会留出至少两天时间专门做阈值调优这个时间不能省。7. 板子视觉方案的边界与扩展思路7.1 什么场景适合板端方案板端视觉方案最适合的是固定场景、有限目标、实时性要求高的任务。比如门口的人体检测、传送带的异物识别、停车位的占用判断。这些场景的共同特点是背景相对固定目标类型不多事件定义清晰。板子算力有限但胜在低功耗、低成本、低延迟适合大规模部署。不适合的场景也很明显开放环境、任意目标、复杂事件。比如让板子理解“一个人在超市里拿了商品又放回去”这种事件涉及多目标交互和长时序推理板子根本做不了。这种场景还是得把数据传到服务器或者用更高算力的设备来处理。7.2 多板协同的扩展方式如果单块板子的算力不够可以考虑多板协同。比如一块板子专门做检测另一块板子专门做跟踪和事件判断两块板子之间通过网络或者串口通信。这样每块板子的负载都降低了整体处理能力提升了。但多板协同也带来了新的问题通信延迟、时钟同步、数据一致性。我试过两块板子通过串口通信延迟在10毫秒左右对大多数场景够用但对高帧率场景还是有点吃力。另一种扩展方式是边缘加云端的混合架构。板子做第一层和第二层把检测结果传到云端做第三层的事件判断。这样板子的算力压力最小但依赖网络连接延迟也不可控。适合对实时性要求不高的场景。7.3 模型更新的工程化问题板子部署之后模型可能需要更新。如果每次更新都要拆机、接线、重新烧录维护成本会很高。我一般会在设计阶段就留好远程更新的通道比如通过无线网络下载新的模型文件然后重启推理进程加载新模型。这个功能看起来简单但实际做的时候要注意模型文件的校验和回滚机制。如果新模型有问题要能自动回滚到旧版本否则板子就变砖了。还有一个细节是模型版本和规则版本的匹配。检测模型更新之后输出的目标类别或数量可能变了状态机的规则也要跟着改。我一般会把模型文件和规则配置打包在一起作为一个整体版本发布避免版本不匹配导致的问题。8. 我个人在实际操作中的几点体会板子上的计算机视觉说到底是一个在资源约束下做取舍的工程问题。算力、内存、功耗、延迟、精度这几个指标不可能同时最优必须根据场景排优先级。我的习惯是先确定延迟上限比如要求100毫秒内出结果然后在这个约束下尽量提精度。如果精度不够再考虑放宽延迟或者换更强的板子。另一个体会是规则引擎虽然土但在板子上真的管用。很多人觉得规则不够“智能”想用模型替代结果往往是算力不够、数据不够、效果还不如规则。我的建议是先把规则方案跑通把基线建立起来然后再看有没有必要用模型去替换其中的某些环节。大多数情况下规则加轻量模型的混合方案就是板端视觉的最优解。最后说一个容易被忽略的点日志和可视化。板子部署之后出了问题怎么排查如果没有日志只能靠猜。我一般会在板子上留一个轻量的日志模块记录每一帧的检测结果、状态变化、事件触发。日志不用存太久滚动覆盖就行但一定要有。可视化方面如果板子有显示接口可以输出带检测框和状态标注的画面调试的时候非常直观。这些工程细节不涉及算法但决定了方案能不能真正落地。