摄像头模型返回一个手势标签时,桌面程序还不能立刻执行动作。模型描述的是某一帧画面,业务事件表达的是用户完成了一次有意操作。要把前者变成后者,需要一条可取消、可去重、可复位的确认链路。
flowchart TD
A[摄像头帧] --> B[手部检测]
B --> C[手势分类]
C --> D[标签归一化]
D --> E{置信度通过}
E -- 否 --> F[继续采样]
E -- 是 --> G[候选手势]
G --> H{连续结果稳定}
H -- 否 --> F
H -- 是 --> I[生成一次事件]
I --> J[策略层复核]
I --> K[等待释放]
K --> L[重新布防]分类、确认和策略决策应当分层。分类器不关心后续动作,确认器不直接执行系统操作,策略层也不重新实现一套视觉判断。
struct Candidate
{
std::string label;
uint64_t startedAtMs = 0;
uint64_t lastResultAtMs = 0;
int acceptedSamples = 0;
};
bool UpdateCandidate(
Candidate& candidate,
const GestureSample& sample,
const ConfirmationPolicy& policy)
{
if (candidate.label != sample.label)
{
candidate = Candidate{
.label = sample.label,
.startedAtMs = sample.timestampMs,
.lastResultAtMs = sample.timestampMs,
.acceptedSamples = 1
};
return false;
}
if (sample.timestampMs == candidate.lastResultAtMs)
{
return false;
}
candidate.lastResultAtMs = sample.timestampMs;
++candidate.acceptedSamples;
return policy.IsConfirmed(candidate);
}状态同时记录标签、开始时间和最后结果时间。标签切换时重新开始,同一结果不重复累计,确认窗口超时后退出候选。具体阈值和时间由多设备测试确定,不应散落在多个组件里。
用户保持手势时,模型会持续返回同一个标签。确认成功后应解除布防,只生成一次事件。
if (armed_ && confirmed)
{
Report(BuildGestureEvent(candidate));
armed_ = false;
}
if (!armed_ && releaseState_.Observe(noAcceptedGesture, nowMs))
{
candidate = {};
armed_ = true;
}释放状态也需要连续观察。单帧低置信度可能来自遮挡或曝光变化,不能直接当作用户已经放下手。
struct GestureEvent
{
std::string eventId;
std::string sessionId;
uint64_t sequence = 0;
std::string gestureType;
int stableSamples = 0;
float confidence = 0.0F;
};事件 ID 用于幂等,序列号拒绝旧消息,会话 ID 区分识别进程重启。接收端还要核对功能开关、配置手势和当前策略,不能因为消息来自本机就默认可信。
有人突然靠近电脑、用户不方便明显操作键盘鼠标时,手势可以作为主动触发入口。我参与开发的 超级看门狗 SuperWatchDog 将手势与离席检测分为两种语义:手势代表用户仍在电脑前发出的指令,离席检测负责已经离开后的补充判断。
摄像头画面在本机处理,不保存或上传照片、视频。光照、遮挡、角度和设备质量会影响结果,因此预览状态和实际位置测试也是完整方案的一部分。
模型能力决定能识别什么,确认管线决定识别结果能不能成为可靠的桌面事件。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。