ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于深度学习与关键点时序分类的电梯无接触手势控制系统

基于深度学习与关键点时序分类的电梯无接触手势控制系统

1. 先把题目拆开:电梯手势控制到底难在哪

1.1 从“按按钮”到“隔空挥手”,这个需求是真实的吗

“基于深度学习的电梯无接触手势控制系统”这个题目,第一次看会觉得像是个为了凑“AI+毕设”而硬造的场景,但真正常接触电梯工程和交互设计的人会知道,无接触呼梯这个方向不是凭空来的。医院、食品加工车间、洁净厂房、公共卫生间通道旁的电梯,都有一个共同的痛点:按键面板是整栋楼里被触摸次数最多、清洁频率最难保证的表面之一。哪怕不谈卫生,冬天天冷戴厚手套按不准、手上拎着东西腾不出手、推着行李箱或者婴儿车够不到面板,这些都是真实存在的操作障碍。

所以这个项目的核心价值不是“炫技”,而是把人和设备之间那一次物理接触给省掉。它解决的是“最后一米”的交互问题:人已经站在电梯门口了,只想告诉系统“我要上去”,但中间隔着一个必须摸一下的按钮。手势识别的意义就在于把这一个动作变成一次空间中的挥动,让交互在离面板三十厘米到一米的位置就能完成。

适合看这篇内容的人大概有三类:正在做同类毕设、需要一套能落地能答辩的完整方案的同学;想把视觉交互接到嵌入式设备上、但卡在延迟和误触上的开发者;还有做智能楼宇、公共设施改造,想评估这套方案可行性的工程人员。不管你是刚学完卷积神经网络、还没碰过摄像头推流,还是已经能把模型跑起来但现场一测就各种误触发,下面这些内容应该都能对上你的坑。

1.2 需求边界:哪些动作必须识别,哪些动作坚决不能响应

做这类系统,最先要做的不是选模型,而是把“需求边界”画清楚。我见过太多项目一开始就列了十几个手势,结果做到一半发现数据收不齐、类别互相打架,最后砍到只剩三个,论文里的实验还得重跑。合理的做法是先问自己:电梯这个场景里,用户真正需要表达几个意思?

答案其实很少。电梯交互本质上只有三类语义:呼梯(请求使用)、选方向或选层、取消或纠错。再往下拆,呼梯可以是一个掌心朝向面板的横向挥动;方向选择可以是食指上指或下指;取消可以是五指张开后左右摆动。四个动作封顶,足够了。手势越多,误识别的可能性越大,而电梯这种设备,一次误动作的代价远高于一次识别失败——识别失败用户再挥一次就行,误触发可能把人送到错误的楼层,甚至造成困人。

所以我在设计时会立一条硬规矩:一切“看起来像但不是”的动作,默认判为无效。比如用户只是抬手挠头、整理头发、抱着一摞东西从摄像头前走过、在电梯门口打电话挥手示意,这些都必须落进“无操作”这一类。这意味你的分类器不能只做四分类,必须是五分类甚至六分类,其中“其他/背景”这一类要占相当大的样本比例。这一点很多人会忽略,等到现场测试发现有人路过就触发,才回头补负样本,那时候模型结构、数据管线都要动,返工成本很高。

还有一条边界要提前想明白:这个系统不能替代电梯本体的安全回路。它只是呼梯信号的一种输入方式,门锁、限位、超载、消防迫降这些逻辑必须由电梯控制柜自己保证,和你的视觉模块完全解耦。你在论文里写得再漂亮,答辩老师只要问一句“你的系统挂了会不会影响电梯安全”,答不上来就很被动。标准答法是:视觉模块通过隔离接口输出一个等效于按钮按下的干接点信号,模块掉电或死机时该信号为断开状态,电梯按原有逻辑运行,物理按钮始终保留。

1.3 这个系统的能力上限,先划清楚

把能力上限说清楚,比把功能列表写长更重要。一个跑在边缘开发板上的视觉系统,在电梯口这种环境下,实际能达到的水平大致是:识别距离 0.3 米到 1.2 米,有效识别角度左右各 30 度左右,端到端响应 150 到 250 毫秒,白天正常光照下准确率能做到 95% 以上,夜间或强逆光下会掉到 80% 出头。

这些数字决定了你的系统定位:它是辅助输入,不是唯一输入。指望它替代按钮、要求它 7×24 小时零失误,那是工业级产品的要求,不是毕设能覆盖的范围。想清楚这一点,你的方案设计会松弛很多,答辩的时候也更容易自圆其说——你不是在做一个产品,你是在验证一条技术路径可行,并且把可行性边界量化出来。这个定位差异,在评分时是加分项,因为评委更看重你对自己系统的清醒认知,而不是吹出来的指标。

2. 整体架构与方案选型:为什么最后落在“关键点+时序分类”这条路上

2.1 三条技术路线的横向对比

做手势识别,摆在面前的路基本就三条,每条的代价和收益完全不同,选错方向会让后面几个月都在填坑。

路线做法优点致命缺点适用判断
端到端视频分类直接把连续帧送进 3D 卷积或视频 Transformer不需要人工设计特征,理论上能学到更丰富的动作模式数据需求量极大,单次推理算力开销高,边缘板上很难跑到实时除非你有 GPU 服务器和上万段标注视频,否则不建议
静态手势分类单帧图像分类,取置信度最高的一类实现最简单,模型小,延迟低无法区分“挥手”和“手停在半空”,动态手势完全做不了只适合做“伸手即触发”的极简场景
手部关键点 + 时序分类先检测手部 21 个关键点,把关键点序列送进轻量时序模型特征维度低、模型极小、对光照和背景鲁棒、可解释性强依赖关键点检测的稳定性,手部遮挡或快速运动时会丢点电梯这类动作种类少、算力受限的场景,这是性价比最高的选择

我最终选的是第三条。原因很实在:电梯场景里真正有区分度的信息,不在手的纹理上,而在手的运动轨迹上。挥手和抬手,两者的像素内容几乎一样,差别只在时间维度上。把 21 个关键点的坐标序列拿出来,一帧只有 42 到 63 个数值,一段 30 帧的序列也就两千个数,用几万参数的时序模型就能吃透,推理时间可以压到 2 毫秒以内。而端到端视频分类,同样的片段要跑几亿次浮点运算,边缘板上的帧率会直接掉到个位数,交互体验就没了。

另外一点很重要的是可解释性。关键点方案出问题的时候,你可以把骨架画出来,一眼看出是检测丢了还是分类判错了,调试效率高一个数量级。答辩的时候把骨架可视化视频一放,评委立刻能看懂你在做什么,这比放一堆损失曲线有用得多。

2.2 三层架构怎么切分

整个系统我按“感知—决策—执行”切成三层,层与层之间只通过明确定义的接口通信,这样任何一层出问题都能单独替换和测试。

感知层负责摄像头取流、手部检测、关键点提取,输出的是归一化后的关键点序列。这一层不关心“用户在做什么动作”,只负责“手在哪、什么姿态”。

决策层拿序列做时序分类,输出动作类别和置信度,再叠加一层业务状态机,判断这次识别结果是否构成一次有效指令。这一层是整个系统的核心,也是防误触的主要战场。

执行层负责把指令变成电信号,通过隔离接口送给电梯控制板,同时负责回显、日志和异常上报。

这么切的直接好处是联调时可以分层定位问题。现场发现误触发,先看感知层是不是把手部区域框错了,再看决策层阈值是不是太松,最后看执行层有没有重复发送。如果没有分层,所有逻辑搅在一个 while 循环里,排查起来就是纯靠猜。

2.3 硬件选型:算力账和成本账都要算

硬件这块,毕设常见的三个档次我都试过,说下真实感受。

入门级四核 ARM 板加普通 USB 摄像头:能跑起来,但手部检测大概 8 到 12 帧每秒,加上分类和显示,整体延迟在 300 毫秒以上,挥手动作会明显感觉“慢半拍”。做算法验证可以,做演示体验很差。

带 GPU 的入门级边缘开发板(4GB 显存档):这是我认为的甜点位置。手部检测能跑到 25 到 30 帧每秒,整条管线延迟压到 120 到 180 毫秒,主观感受跟按按钮差不多。功耗 5 到 10 瓦,用普通电源适配器就能带,装在电梯口旁边不需要额外布线改造。

带 NPU 的国产八核板卡:算力更强、功耗更低,但工具链成熟度参差不齐,模型转换踩坑的概率明显更高。如果你的时间只够做算法,不建议在这上面赌;如果时间充裕、想做量化部署的加分项,那可以上。

摄像头这块有个容易忽略的点:别用太广角的镜头。广角镜头边缘畸变大,手部在画面边缘会被拉伸,关键点检测的精度会掉,而且视野太宽会把走廊里走动的人全收进来,误触发概率飙升。选 60 到 70 度水平视场角的定焦镜头,装上之后手动调好俯仰角,让有效识别区正好覆盖面板正前方 0.3 到 1.2 米的范围,这是最省事的做法。

2.4 电梯侧的接口与安全隔离

这一块很多人写论文时一笔带过,但恰恰是评委最爱追问的地方。视觉模块和电梯控制板之间,我建议走“干接点”而不是直接串口通信到控制柜。

具体做法是:开发板的 GPIO 通过光耦隔离后驱动一个小型继电器,继电器的常开触点并联在原有呼梯按钮的两端。模块判断出有效手势就在对应按钮上“模拟按一下”,脉宽 200 到 300 毫秒,然后断开。这样一来,对电梯来说你就是一个普通的按钮,完全不改变它的任何内部逻辑;模块死机、断电、程序跑飞,继电器自然断开,电梯照常工作。

注意:继电器触点的耐压和绝缘等级要按现场实际回路来选,动手之前务必确认接口处的电气参数,涉及强电的部分不要自己上手,必须由具备资质的人员操作。毕设阶段建议用低压模拟面板或者厂家提供的实训台,不要去碰真实运行中的设备。

调试阶段更稳妥的方式是先在开发板上用 LED 模拟输出,把整套决策逻辑跑通,确认误触发率达标之后,再接到模拟面板上。我见过有同学一上来就接真设备,一个误触发把门给关了,后面整层楼的测试都不敢做了。

3. 数据集:整个项目最容易翻车的地方

3.1 类别设计与样本规模怎么估

数据集的第一个决策是类别怎么定。前面说的四类动作加一个“无效类”,看起来简单,实际标注时会冒出一堆边界问题:手从下往上抬到一半停住了算不算上行?挥手挥了一半改方向算不算?这些模糊片段如果不处理,模型学出来的边界会非常糊。

我的处理办法是给每一类都定义明确的起止条件。以“上行”为例:手腕关键点从画面下半区移动到上半区,且食指指尖在整段序列中有超过 70% 的帧保持向上指向,位移超过设定阈值,这才算一个完整的正样本。动作不到位、只做了一半的序列,全部丢进“无效类”。宁可让正样本难收,也不要让正负样本边界模糊,这一步偷懒,后面调阈值会调到你怀疑人生。

样本规模上,我按这个公式估算:每类有效样本 ≥ 每人每类 30 段 × 参与采集人数 × 每人重复 3 轮。参与采集的人数建议不少于 15 人,最好覆盖不同手型大小、不同肤色、戴手套与不戴手套、长袖与短袖。15 人 × 4 类 × 30 段 ≈ 1800 段正样本,加上无效类至少 1500 段,总量在 3300 段左右,这个规模用轻量时序模型训练是够的。人越多越好,但 15 人是能保证效果的下限。

这里有个经验:无效类一定要在真实场景里采,不能在空房间里对着白墙拍。真实的无效片段应该包括:人从画面里走过、抬手整理头发、双手抱东西、打电话时挥手、弯腰系鞋带。这些片段采集起来很麻烦,但它们决定了你的系统上线后会不会变成“人来就开门”的灾难。

3.2 采集方案:把现场条件穷举一遍

采集是整个项目里最枯燥但最不能省的环节。我建议按“场景矩阵”来组织,而不是想到哪拍到哪。

维度需要覆盖的取值为什么重要
光照白天自然光、顶灯白光、暖光、侧逆光、夜间弱光逆光和弱光是关键点检测掉点最严重的两类场景
距离0.3 米、0.6 米、0.9 米、1.2 米距离变化直接改变手部在画面中的像素尺寸
角度正面、左偏 30 度、右偏 30 度、略仰视人在电梯口不可能永远站正中间
背景空墙面、有人走动、有广告屏、有镜面反光反光和动态背景是误检的主要来源
手部状态裸手、薄手套、厚棉手套、手持物品手套会改变关键点置信度
拍摄者不同身高(1.5 米到 1.9 米)、左右手习惯身高影响手部在画面中的位置分布

每个组合不用拍太多,每类动作 3 到 5 段就够,重点是覆盖全而不是堆量。我当时的做法是列一张表,采完一个格子打个勾,这样不会漏。整套采下来大概两三个小时,但能省掉后面无数次“为什么这个场景不行”的返工。

采集时还有个细节:摄像头的位置必须和实际安装位置一致。很多人图省事把摄像头放在桌面上拍,角度和高度跟装在电梯口完全不一样,训出来的模型一上现场就崩。正确的做法是用一块板子或者三脚架把摄像头固定在预设安装高度(一般是面板上方 1.4 到 1.6 米,略微下俯 10 到 15 度),然后人在这个固定机位前做动作。这一步多花的半小时,价值超过后面调参的一周。

3.3 标注流程与质量控制

标注这块,序列数据和图像数据不一样,标的是“这一整段属于哪一类”,工作量其实不大,但质量控制点很集中。

我的流程是:先用固定帧率把视频切成 30 帧一段的片段,文件名里带上采集人编号、场景编号、类别编号。然后做一轮快速筛选,把明显的废片(手出画、全程模糊、完全没做动作)删掉,这一轮能砍掉 20% 左右的量。接着两个人交叉标注,标完之后算一致率,不一致的片段拉出来一起看,讨论清楚边界再重标。

提示:如果找不到第二个人交叉标注,可以用“隔一周自己重标一遍”的方式做一致性检查。人对同一段动作的判断,隔一周再看如果还能保持一致,说明这个类的定义是清晰的;如果前后标得不一样,说明类的边界定义有问题,得回去改定义而不是硬标。

还有个小技巧:给每段数据打上场景标签(光照、距离、角度)。训练的时候可能用不上,但做错误分析的时候价值巨大。你可以直接统计“弱光条件下的召回率是多少”“戴手套时的准确率掉了多少”,这些数据写进论文就是现成的实验分析章节,而且是别人抄不走的部分。

3.4 数据增强:只做时序层面的,别乱做图像层面的

关键点序列的数据增强和图像增强思路完全不同。图像那边常用的色彩抖动、模糊、加噪声,在这里意义不大,因为你训练的根本不是图像。真正有用的是时序和几何层面的扰动:

  • 时间抖动:随机把序列拉伸或压缩 0.8 到 1.2 倍,模拟用户动作快慢的差异。这个增强效果最明显,因为不同人做同一个动作的速度能差一倍以上。
  • 关键点坐标加噪:给每个关键点叠加小幅高斯噪声(标准差取手部包围盒尺寸的 1% 到 2%),模拟检测器的抖动。这个能让模型对关键点的小幅波动不敏感。
  • 随机丢帧:以 5% 到 10% 的概率把某些帧的关键点置零,模拟快速运动时的检测丢失。这个增强能明显提升系统在真实环境下的鲁棒性,因为关键点检测丢帧是常态。
  • 水平镜像:注意镜像之后左右手标签要对调,动作语义不变的情况下这是免费的样本翻倍。
  • 整体平移和缩放:把手部关键点整体平移、缩放到不同位置和大小,让模型学到与位置无关的特征。

不建议做的是:大幅度旋转。手部关键点是归一化在手掌坐标系里的,旋转会破坏“食指朝上”这种语义,增强之后反而引入错误标签。

4. 模型设计与训练实操

4.1 关键点归一化:决定成败的预处理

这一步是整个模型效果的分水岭,很多人直接拿原始像素坐标送进网络,结果模型学到的全是“手在画面哪个位置”,换个安装角度就完全失效。

正确的归一化分三步。第一步,平移:以手腕关键点为原点,把所有关键点坐标减去手腕坐标,消掉绝对位置信息。第二步,尺度归一化:计算手掌的参考尺寸,通常用腕点到中指根部的距离,把所有坐标除以这个距离,消掉距离远近带来的尺度变化。第三步,可选的方向对齐:以一对手部关键点(比如腕点到中指根部)的方向为参考轴,把整个骨架旋转到统一朝向上。第三步有争议,因为旋转之后“上指”和“下指”的区分会变弱;我的做法是不做完整旋转,只做尺度和平移归一化,把方向信息通过额外特征显式喂进去。

额外特征有助于区分方向性动作:手掌朝向(掌心朝摄像头还是朝内)、手部在画面中的归一化位置、相邻帧之间的位移速度。这几个特征加进去,模型对“上”和“下”的区分准确率能提升好几个点,因为单看静态骨架,上指和下指只是一个符号差异,网络学起来不如显式特征直接。

4.2 时序模型结构:几万参数就够用

时序模型这块,我对比过四种结构,结论是:在数据量几千段的量级上,简单结构反而更稳。

结构参数量级训练难度实测准确率推理耗时
全连接 + 展平约 5 万极低85% 左右< 1 ms
一维卷积(TCN)约 3 万低93% 左右< 1 ms
单层 GRU(隐藏 64)约 2 万中95% 左右1 到 2 ms
小型 Transformer约 15 万高94% 左右,小数据易过拟合3 到 5 ms

最终我选的是“一维卷积 + 单层 GRU”的混合结构:前面两层一维卷积负责提取局部动作模式(比如手指开始抬起的那几帧),后面接一层 GRU 负责整合整段时序。这个组合在小数据上比纯 Transformer 稳,比纯 GRU 收敛快。模型文件转成通用格式之后只有 200 到 400 KB,塞进任何一块板子都没压力。

输入窗口的设定也值得说一下。窗口太短(15 帧)抓不到完整动作,窗口太长(60 帧)会让响应变慢。我的选择是30 帧窗口、步长 5 帧滑窗。30 帧在 30fps 下正好是 1 秒,覆盖大多数人的挥手动作时长;步长 5 帧意味着每 167 毫秒出一次判断,配合后面的状态机,端到端延迟能控制在 200 毫秒以内。

4.3 训练超参与损失函数怎么设

几个关键参数的设置逻辑:

  • 学习率:Adam 优化器,初始 1e-3,配合余弦退火。这个模型很小,学习率设太小会训得很慢,设太大会在后期震荡。
  • 批大小:64。数据集不大,批太小梯度噪声大,批太大会减少迭代次数。
  • 训练轮数:80 到 120 轮,配合早停。我在验证集上监控的是误触发率而不是准确率,因为准确率被大量的无效类样本稀释了,看起来很好看但没意义。
  • 类别权重:无效类的样本量通常远大于单个动作类,如果不加权,模型会倾向于把所有东西都判成无效类。我的做法是按类别频率的倒数开方做加权,比直接用倒数温和,不会把动作类的权重抬得过高。

损失函数用带标签平滑的交叉熵,平滑系数取 0.05。标签平滑在这个场景里作用挺明显,因为动作的边界本来就模糊(一段挥手做快了和做慢了,人自己都说不清该算哪类),硬标签会逼着模型过度自信,平滑之后输出的置信度分布更接近真实情况,后面设阈值也更好设。

4.4 量化与剪枝:把模型塞进边缘设备

模型本身已经很小,但部署时还是要做一轮优化,主要收益在延迟和内存占用上。

INT8 量化是首选。做法是准备 200 段左右的校准数据(从验证集里抽,要覆盖各类别和各场景),走训练后量化流程,不需要重新训练。实测下来精度掉不到 1 个百分点,推理速度提升 1.5 到 2 倍。量化之后要注意的一点是:输入数据的动态范围要和校准集一致,如果你的校准数据全是近距离大幅度的动作,量化的缩放系数会偏向大数值,远距离小幅动作的精度就会掉。所以校准集一定要按场景均匀抽。

剪枝的收益相对小一些,因为这个模型本来就不大。如果非要做,建议只做通道级的轻度剪枝(剪掉 20% 到 30% 的通道),剪完必须重新微调 10 轮左右。剪太狠会让时序特征丢失,准确率掉得比速度提升更明显,不划算。

4.5 评价指标:别只盯着准确率

这是我在这个项目里踩的最大的一个认知坑。第一版模型训练完,验证集准确率 96%,我特别开心,结果现场一测,十分钟误触发七次。原因很简单:准确率这个指标被“无效类”样本主导了,模型只要把所有片段都判成无效,准确率也能到 80% 以上。

真正该看的指标是这三个:

  • 单动作召回率:每一类动作单独算,看哪个类的漏识别最多。通常“取消”类最难,因为动作幅度大、形态变化多。
  • 误报率(FAR):单位时间内的误触发次数,工程上一般按“次/小时”算。这个指标必须在真实场景下测,不能只看验证集。我的目标定为每小时不超过 0.5 次。
  • 端到端响应时间:从手开始动作到继电器动作的时间差,用高速摄像机或者日志时间戳测。目标 200 毫秒以内,超过 300 毫秒用户会明显感知到卡顿。

还有一个指标容易被忽略:拒识率。当手部关键点检测置信度低于阈值、或者画面中出现多只手时,系统应该主动拒识而不是硬判。拒识率高一点不是坏事,它意味着系统知道自己在什么情况下不可靠,这比乱给结果安全得多。

5. 工程落地:从模型文件到能跑起来的系统

5.1 视频流管线与延迟预算

整条管线拆开看是这几步:摄像头采集 → 图像预处理(缩放、色彩转换)→ 手部检测 → 关键点提取 → 特征归一化 → 滑窗组批 → 时序分类 → 状态机判断 → 指令下发。每一步都要算延迟,不然最后发现总延迟超标,很难定位是哪一环的问题。

环节典型耗时优化手段
摄像头采集33 ms(30fps)关闭自动曝光抖动,固定曝光参数
图像预处理3 到 5 ms直接缩放到检测输入尺寸,避免多次拷贝
手部检测 + 关键点15 到 25 ms限制检测区域 ROI,隔帧检测加跟踪补帧
特征归一化< 1 ms纯数值运算,用预分配数组避免反复申请内存
时序分类1 到 2 ms模型量化,输入张量复用
状态机< 1 ms无
总计55 到 70 ms加上采集和缓冲,端到端约 120 到 180 ms

这里有个很有效的优化点:不要每帧都做全身手部检测。人在电梯口站着不动的时候,手部位置基本固定,可以只在上一次检测框周围扩大 1.5 倍的 ROI 内检测,检测耗时能降一半以上。只有当手部丢失超过 5 帧,或者检测框贴到 ROI 边缘时,才退回全图检测。这个策略实现起来不到 50 行代码,效果立竿见影。

5.2 多线程怎么切,别把推理堵在采集上

一个常见的性能坑是单线程串行:读一帧、推理、显示、再读下一帧。这样做的问题是摄像头在推理期间被阻塞,帧率会被推理速度直接锁死,而且摄像头内部缓冲区会积压旧帧,你处理的是几百毫秒前的画面,延迟完全失控。

正确的做法是拆成两个线程加一个队列:采集线程只负责从摄像头读帧,放进队列,队列长度限制为 2,满了就丢最旧的帧;推理线程从队列取帧处理,处理完立刻取下一帧。这样即使推理偶尔卡一下,画面也不会积压,系统始终在处理最新的帧。

注意:队列长度一定要限制。我见过有人用无界队列,跑了几分钟内存涨到 2GB,最后一查是摄像头帧率高于处理帧率,队列里堆了几千帧没人消费。限制为 1 到 2 是最稳的,因为对这一类交互来说,旧帧没有任何价值。

显存或者内存的使用也要盯一下。手部检测模型加时序模型,再加上图像缓冲,在 4GB 的板子上大概占 800MB 到 1.2GB,留足余量没问题。但如果开了可视化窗口又开了录制,很容易吃满,导致系统开始交换内存、帧率断崖式下跌。调试阶段可以把可视化做成可开关的,交付版本关掉。

5.3 状态机:防误触最有效的一层

模型输出只是一个概率,把它变成“按下按钮”这个动作,中间必须有一层状态机。这一层不涉及任何深度学习,但它对系统可靠性的贡献可能比模型本身还大。

我的状态机有五个状态:空闲、观察、候选、确认、冷却。

空闲态:没有检测到手,什么都不做。

观察态:检测到手,开始累积滑窗,此时不产生任何输出。

候选态:某个动作类的置信度连续 3 次滑窗都超过 0.85,进入候选。这时候还不发指令,继续观察 2 次滑窗。

确认态:候选期内置信度保持稳定且类别不变,累计到 5 次滑窗一致,才真正下发指令。

冷却态:指令发出后进入 3 秒冷却,期间任何识别结果都丢弃。这一条极其重要,因为用户挥完手之后手往往还在画面里,如果不加冷却,一次挥手会触发好几次。

这套机制把误触发率压下来一个数量级。代价是延迟增加了大约 170 毫秒(5 次滑窗 × 33 毫秒加缓冲),但整体还在可接受范围内。如果你的场景对延迟特别敏感,可以把滑窗步长改成 3 帧,代价是计算量上升 60%。

还有两条补充规则。一是多手拒识:画面中检测到两只以上且都在有效区域内,直接丢弃,因为无法判断意图。二是区域约束:手部关键点中心必须落在预设的识别多边形内(一般取面板正前方的一块梯形区域),在区域外的动作一律忽略,这能干掉大部分路过行人的误触发。

5.4 指令下发与协议设计

指令下发这块,逻辑上很简单,但有几点要注意。

如果走 GPIO 直驱继电器,代码里要做的是:置高电平 → 延时 250 毫秒 → 置低电平。这个延时不能用阻塞的方式写,否则整个推理线程会被卡住 250 毫秒。稳妥的做法是设置一个标志位加时间戳,由主循环轮询判断该什么时候拉低。

如果走串口发给下位机,帧格式建议做成帧头(2字节) + 指令码(1字节) + 序号(1字节) + 校验(2字节),校验用 CRC16。加序号是为了让下位机能识别重复帧,避免串口抖动导致同一条指令被执行两次。串口参数 115200、8N1 是通用配置,够用。

日志这块别省。每次指令下发都记一条:时间戳、动作类别、置信度、持续滑窗数、当前状态。这些日志在联调阶段是救命的东西,出问题的时候能直接回溯“这次误触发是因为置信度虚高还是因为状态机逻辑有漏洞”。日志按天切分,加个大小上限,别让它把存储写满。

5.5 异常兜底与降级策略

一个能拿得出手的系统,必须有降级路径。我设计了三层兜底:

第一层,摄像头异常。连续 30 帧读不到画面,判定摄像头断开,系统进入降级模式,只保留物理按钮可用,同时上报告警。恢复后自动重连,不需要重启整个程序。

第二层,模型推理异常。推理耗时超过 500 毫秒,或者连续出现 NaN 输出,判定模型异常,切换到“仅检测不分类”模式,只做手部存在检测,不做动作判断,避免乱发指令。

第三层,指令异常。同一类指令在 10 秒内被触发超过 3 次,说明系统进入了某种震荡状态,强制进入 30 秒静默期,期间不响应任何手势。

这几条逻辑加起来不到 100 行代码,但它们在演示环节救过我。有一次演示到一半,现场灯光突然变化,模型输出开始抖动,正是因为冷却和震荡保护,系统只是静默了半分钟,没有连续乱发指令,场面没崩。

6. 现场联调实录:文档里不会写的那些问题

6.1 光照:逆光比黑暗更致命

很多人以为弱光是最大敌人,实际测下来,侧逆光才是关键点检测的头号杀手。电梯口经常有窗户或者大面积灯光在用户背后,手部轮廓被打成一团黑影,关键点检测直接丢手。

处理办法分硬件和软件两条路。硬件上,在摄像头旁边加一块小的遮光罩,或者把摄像头稍微下俯,避开正对光源的角度。软件上,在进入检测之前做一次快速的亮度直方图判断,如果画面高光区域占比超过阈值,自动切换一套针对高对比度场景微调过的检测参数(主要是降低检测置信度阈值、放宽非极大值抑制的阈值)。这套切换逻辑是我在实测中发现最有效的,因为它不改变模型,只是让检测器在恶劣条件下更“愿意”输出结果。

夜间或者弱光下的表现会明显下降,这个要如实写进论文的局限性分析里。想改善的话,加一个红外补光模块是比较实际的做法,成本几十块,效果提升明显。

6.2 误触发排查:从日志倒推是最快的路

误触发是这类系统最常见的投诉。我的排查顺序是固定的三步:

第一步,看日志里的置信度分布。如果误触发的置信度普遍在 0.86 到 0.90 之间,说明阈值设低了,提到 0.92 就能压掉大部分。如果误触发的置信度高达 0.98,那说明模型真的把那个动作学成了目标类,得回去补负样本。

第二步,回放当时的骨架序列。把误触发前后 2 秒的关键点画成骨架动画,一眼就能看出模型是被什么骗了。我遇到过最典型的一次:用户抬手挠头,手腕向上移动的轨迹和“上行”手势几乎一模一样,骨架图上几乎无法区分。解决办法是给上行类加一个约束条件——食指必须伸直且指尖位移大于某个阈值,挠头时手指是弯的,这个条件就能过滤掉。

第三步,检查是不是状态机的问题。有时候模型判得没错,是状态机的计数逻辑有 bug,比如冷却期没生效、滑窗计数没清零。这类问题看日志时间戳最直接。

6.3 多人场景:设计上就该拒绝,而不是硬判

电梯口人来人往,多人出现在画面里是常态。这里要做的不是提升多人场景的识别率,而是主动识别出“现在无法判断”并拒绝响应。

具体规则前面提过,这里补充一点实现细节:判断“多手”不能只看检测框数量,因为一只手可能被检出两个重叠的框。稳妥的做法是先做非极大值抑制,然后检查剩下的框之间的交并比,如果两个框的距离超过画面宽度的 15%,才认为是两只不同的手。这个阈值需要根据实际安装高度调,装得高、视野广的话阈值要放大一些。

另外,如果画面里有人站着手部一直停留在区域内,但长期不做任何动作,这种“静止的手”也应该被排除。可以在状态机的观察态加一个超时:手部在区域内停留超过 10 秒且未产生有效动作,就降低该区域的检测优先级,直到手部离开再恢复。这个逻辑能防止一个人站在电梯口聊天,手一直举着,系统反复误判。

6.4 长时间运行:内存泄漏和帧率漂移

连续跑 8 小时以上的测试是必须做的,很多人只测几分钟就交付,线上跑一天就出问题。我遇到的几类:

内存缓慢增长。原因是每帧都在创建新的图像对象,Python 的垃圾回收跟不上。解决办法是预分配缓冲区,用切片赋值代替新建对象。改完之后内存占用在 4 小时内基本是一条水平线。

帧率缓慢下降。跑了几个小时之后帧率从 28 掉到 18。排查下来是日志文件越写越大,每次写入的开销在增加。改成按大小切分文件、异步写入之后就正常了。

温度升高后降频。开发板没有主动散热,跑满负载半小时后芯片温度上到 80 度以上,开始降频,帧率跟着掉。加一块小散热片和风扇就解决了,成本十几块,但很多人会忽略。这一条在答辩时如果主动提出来,是很加分的工程细节。

7. 效果评估与对比实验怎么做

7.1 离线指标与在线指标要分开看

论文里的实验章节,我建议把评估拆成两个层次,不然数据会很难看也很难解释。

离线评估在固定测试集上做,报告每类的精确率、召回率、混淆矩阵。这一层的目的是证明模型结构有效、数据质量够。测试集要和训练集在采集人上完全不重叠,也就是所谓的“留人测试”,否则指标虚高。

在线评估在真实或模拟的电梯口做,报告误报率、漏报率、平均响应时间、连续运行稳定性。这一层的目的是证明整个系统能跑。这两个层次的数据要分开呈现,如果混在一起写,评委会追问“你这个 96% 到底是在测试集上还是在现场”。

7.2 对比实验怎么设计才有说服力

对比实验是毕设论文里最容易写得空洞的部分。我的建议是做三组消融:

第一组,特征对比:原始像素坐标 vs 归一化关键点坐标。预期结果是后者高出 10 个百分点以上,这组实验直接证明你的预处理设计是必要的。

第二组,模型对比:一维卷积 / GRU / 混合结构 / 全连接基线。四行表格,把参数量、推理耗时、准确率、误报率四列都填上,比什么论证都有力。

第三组,后处理对比:无状态机 vs 有状态机。这一组最有价值,因为它能直观展示误报率从“每小时 5 次”降到“每小时 0.4 次”,这是纯工程贡献,评委一看就懂。

每组实验都要固定随机种子、跑三次取平均,别只跑一次就写结果,波动大的话结论站不住。

7.3 用户测试:找十个人来用,比调参数有用

模型指标好看不代表好用。我建议找 10 到 15 个人做一次小规模用户测试,不告诉他们具体的动作要领,只说“你想让电梯上来,怎么操作”。观察他们第一次会做什么动作,这能直接暴露你的交互设计是否直觉。

实测下来最常见的偏差是:很多人第一反应是“手掌朝前面推一下”,而不是你设计的“横向挥手”。如果你的系统识别不了推的动作,用户的第一次尝试就会失败,体验直接归零。处理办法要么是把这个动作也加进识别类别,要么是在面板旁边贴一张极简的动作示意贴纸——后者更实际,成本几块钱,效果比改模型好。

用户测试还要记录一个指标:平均尝试次数。如果用户平均要试 2.5 次才能成功触发,那这个交互就是失败的,不管你的模型准确率多高。

8. 答辩和论文里容易被问住的几个点

做了这么久,我总结出评委最爱问的四个问题,提前准备好答案能省很多事。

第一个,“你这套东西误触发了怎么办”。答案不要只说“我的准确率 96%”,要说清楚你的多层防护:区域约束、状态机多次确认、冷却时间、多手拒识、震荡保护。把这几条列出来,说明你对误触发这件事是有系统性思考的,而不是靠一个模型硬扛。

第二个,“算力不够怎么办,能不能上更低端的设备”。答案是你的模型只有几十万参数、量化后几百 KB,整条管线在四核 ARM 上也能跑到 10 帧以上。这里可以顺带讲一下你的量化校准策略,体现工程能力。

第三个,“换个安装角度、换个电梯型号还能用吗”。诚实回答:需要重新采集少量数据做微调,因为关键点归一化之后对安装角度的敏感度已经大幅降低,但识别区域的配置需要重新标定。给出一个“迁移成本”的量化估计,比如“新场景采集 500 段数据、微调 30 轮即可”,比空泛地说“具有泛化能力”可信得多。

第四个,“和现有的语音呼梯、手机呼梯比,优势在哪”。这个问题要答得具体:手势不需要掏手机、不需要说话(医院、安静场所友好)、不需要网络、隐私风险低(不采集语音、不上传图像)。劣势也要主动说:需要用户在特定区域做特定动作,有学习成本。客观对比比一味吹捧更加分。

最后一个实际建议:把整个系统做成可一键启动的。答辩现场时间紧、环境陌生,如果需要敲五条命令、改三个配置文件才能跑起来,翻车概率很高。写一个启动脚本,插上电自动跑,桌面上放一个明显的指示灯表示系统就绪。这个细节不影响技术含量,但直接影响评委的第一印象。

我在这个项目上前后折腾了大半年,最大的体会是:算法部分大概占三成工作量,剩下七成全是数据采集、工程打磨和现场调试。真正让系统从“能跑”变成“敢让人用”的,从来不是换一个更大的模型,而是那些看起来很笨的规则——冷却时间、区域约束、多手拒识、异常降级。这些东西写进论文不显眼,但它们才是把实验室里的 demo 变成一个可用系统的关键。如果后面要继续扩展,我建议的方向是把楼层选择做成手势加语音的混合输入,纯手势表达数字的歧义太大,强行做只会把误识率拉上去,不如老老实实把呼梯这一件事做扎实。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进