我一开始以为,前置摄像头测心率这件事,主要风险应该在算法。
因为后摄那条链路已经从"按帧数数峰 "改成了"按真实时间戳算 RR 间隔",心率、HRV、Stress 都来自同一轮 PPG 信号。照这个理解,把采集入口从后摄换到前摄,再把屏幕亮度拉满,应该就能继续验证。
真正测完以后,结果没有这么简单。
同一套前摄测量方案,在 iPhone 16 Pro 上连续 30 次都能形成完整结果,心率和 Apple Watch 很接近;但换到 iPhone XR,30 次尝试里只有 18 次形成完整的 HR、HRV、Stress 结果,剩下 12 次记录为测量失败。
更微妙的是,XR 并不是每次都给出一个离谱心率。它有完整结果的 18 次里,心率和 Apple Watch 的平均绝对误差只有 2.33 bpm。单看这些成功样本,HR 数字并不难看。
但从产品角度看,它仍然不稳。
因为一个测量功能不能只看"成功样本里 HR 是否接近"。它还要看这台设备能不能稳定完成测量,HRV 和 Stress 会不会跟着前面的心跳信号一起变差,以及失败和成功之间有没有清晰边界。
这次真正让我重新判断的是:前摄测心率不能只理解成"继续用原来的算法,只是换了一个采集入口"。换到前摄以后,光源模型变了,设备能力变了,算法拿到的输入信号也变了。
前摄测心率,先变的是光源
手机摄像头测心率,本质上是在采集手指区域的 PPG 信号。
简单说,手指盖住摄像头后,光线穿过手指,血容量随心跳产生周期性变化,摄像头看到的颜色和亮度也会出现微小波动。算法再从这些波动里找出可信的心跳时间点,计算相邻心跳之间的 RR 间隔,最后得到心率。
后摄测量通常有一个很重要的条件:闪光灯。
后摄旁边有 torch,可以提供比较强、比较稳定、方向明确的光源,手指盖住摄像头和闪光灯,摄像头采到的 PPG 信号一般更容易稳定。
前摄不一样,前摄没有 torch,只能靠屏幕当光源,测量时可以把屏幕亮度拉到最高,但屏幕补光和后摄闪光灯不是一回事:
- 屏幕光更散,不像闪光灯那样集中;
- 手指到屏幕和前摄的距离、角度更敏感;
- 环境光、屏幕实际亮度、系统亮度策略都会影响补光;
- 老设备的屏幕亮度、前摄传感器、曝光和帧率稳定性都可能弱一些。
所以,同一套算法换到前摄以后,输入条件已经变了。
如果只从代码角度看,会觉得算法没变,但从信号角度看,算法拿到的波形可能已经完全不是同一个质量等级。
两台设备测出来的差异
这次我用两台设备做了同样的 30 次前摄测量:
- iPhone 16 Pro;
- iPhone XR;
- 测量时都使用前摄;
- 屏幕亮度都拉满;
- Apple Watch 作为同时间参考;
- 每次记录 App HR、Apple Watch HR、App HRV、App Stress 和是否成功。
先看最直接的数据。
| 指标 | iPhone 16 Pro | iPhone XR |
|---|---|---|
| 总尝试次数 | 30 次 | 30 次 |
| 有完整 HR/HRV/Stress 结果 | 30 次 | 18 次 |
| 记录为测量失败 | 0 次 | 12 次 |
| HR 平均绝对误差 | 1.83 bpm | 2.33 bpm(仅统计 18 次完整结果) |
| HR 绝对误差中位数 | 1 bpm | 2 bpm(仅统计 18 次完整结果) |
| HR 最大绝对误差 | 11 bpm | 7 bpm(仅统计 18 次完整结果) |
| App HRV 均值 | 33.57 ms | 66.83 ms(仅统计 18 次完整结果) |
| App Stress 均值 | 35.43 | 20.33(仅统计 18 次完整结果) |
如果只看 HR 成功样本,XR 这组 30 次里的心率并没有明显失准。18 次完整结果里,所有 HR 误差都在 10 bpm 以内。
但问题在另一个地方,iPhone 16 Pro 是 30 次全部成功,结果比较连续;iPhone XR 是 30 次里 12 次失败,失败比例达到 40%。
这说明 XR 前摄在这套采集条件下,稳定形成完整结果的能力明显弱很多。
而且,XR 成功样本的 HRV 和 Stress 分布也明显不同:
- 16 Pro 的 HRV 均值约 33.57 ms;
- XR 的 HRV 均值约 66.83 ms;
- 16 Pro 的 Stress 均值约 35.43;
- XR 的 Stress 均值约 20.33。
这不是说 XR 的 HRV 一定"不可能这么高",单次 HRV 本来就会受状态影响,但在同一测试者、同一天、同一类测量方式下,XR 的 HRV 整体明显偏高,Stress 整体明显偏低,就需要回头看前面识别出来的心跳时间点是否稳定。
因为 HRV 和 Stress 都是用同一轮 RR 间隔继续算出来的,前面的心跳时间点一旦不稳,后面算出来的 HRV 和 Stress 也会跟着变化。
HRV 和 Stress 为什么会跟着变
这套新算法里,三项结果来自同一条链路:
text
前摄画面里的颜色变化
-> PPG 波形
-> 可信心跳时间点
-> RR 间隔
-> HR / HRV / Stress
HR 是由 RR 间隔换算出来的。RR 越短,心率越高;RR 越长,心率越低。
HRV 看的是 RR 间隔之间的变化。也就是说,它不是直接看"心率数字",而是看相邻心跳间隔有没有细微变化。
Stress 又是基于 HRV 做的一个 0 到 100 的压力值映射。通常 HRV 越高,Stress 越低。
这条关系决定了一件事:如果前面识别出来的 RR 间隔不够可信,HRV 和 Stress 也会被带偏。
举个更直白的例子。
如果前摄信号比较弱,算法没有稳定抓到真实心跳时间点,某些 RR 间隔就可能被拉长、变散,HRV 可能跟着变高,Stress 可能跟着变低。这个时候,表面上看是"压力值偏低"或者"HRV 偏高",实际问题可能不是 Stress 公式单独坏了,而是前面的心跳信号已经不够稳。
这也是为什么我没有选择只调 Stress 的显示范围。
把 Stress 的上下限收紧,最多能让页面数字看起来没那么突兀,但不能让这次测量真的变可靠。只要 RR 质量没有解决,HRV 和 Stress 的底层依据还是不稳。
能测完,不代表这次结果就可信
这类测量功能里,失败本身不一定是坏事。
如果信号不够好,算法告诉用户"这次没测出来",产品体验当然要处理,但至少它没有把不可信的数据当成健康结果展示出去。
更需要警惕的是另一种情况:信号质量已经不够,但页面仍然进入成功结果。
因为用户看到的是一个完整结果页,有心率、有 HRV、有压力值。他不会知道这次 RR 证据到底够不够,也不会知道前摄信号是不是偏弱。
这次 XR 的 30 次表格里,问题更多表现为失败率高。对这类测量功能来说,这已经足够说明老机型前摄不是"偶尔数字差一点",而是采集条件本身不够稳。失败率高会影响可用性;如果为了提高成功率继续放宽质量判断,又可能把低质量结果放进成功页。
如果继续让这类设备走前摄,算法就必须给它更严格的质量判断:
- 结果不够稳时宁可失败;
- HRV 异常偏高时不能只看 HR 是否接近;
- Stress 过低时要回查 RR 和 HRV;
- 不能让成功率看起来很高,但里面混着低质量结果。
这条路能走,但成本会明显变高。
为什么最后不是继续调一个全局阈值
遇到 XR 这种结果,最容易想到的修法是调阈值。
比如:
- 把前摄的信号质量门槛调高;
- 把 HRV 上限压低;
- 把 Stress 下限抬高;
- 对旧机型单独加更严格的成功条件。
这些都不是完全不行,但它们有一个共同风险:很容易影响已经表现稳定的新机型。
iPhone 16 Pro 这组数据里,30 次全部形成完整结果,HR 平均绝对误差 1.83 bpm,28 次误差不超过 3 bpm。它的问题不是"大面积不稳",而是只有一两个边界样本需要继续观察。
如果为了 XR 把整套前摄阈值都收紧,新机型可能也会被影响到。原本能稳定完成的测量,可能开始出现更多失败;原本可接受的 HRV/Stress,也可能因为统一收紧规则而变得不稳定。
所以这次更合理的判断不是"继续调一个所有设备共用的前摄阈值",而是先承认设备条件不同。
新机型屏幕更亮,前摄和系统处理能力更好,前摄方案可以保留;老机型前摄补光和采样条件不够稳定,就走更成熟的后摄路径。
这不是算法偷懒,而是产品策略要匹配硬件现实。
新机前摄,老机后摄
最后形成的策略很直接:
- 前摄链路保留;
- 后摄链路也保留;
- 新款、高端、前摄表现稳定的 iPhone 走前摄;
- 指定老机型走后摄;
- 不用老机型的问题去改坏新机型已经验证过的稳定性。
这个选择背后的逻辑是:
-
前摄对新机型是有价值的。 iPhone 16 Pro 的 30 次数据说明,这条链路不是"实验玩具",它可以稳定产出接近 Apple Watch 的 HR。
-
后摄对老机型仍然更稳。
老机型前摄的补光条件和采样稳定性差一些,强行让它走前摄,会把问题推给更严格的成功判断。
-
HRV 和 Stress 依赖 HR/RR 质量。
如果心跳信号不稳,HRV 和 Stress 自然不可靠。产品不能只看 HR 接近,还要看三项结果是不是来自可信的一轮测量。
-
设备分流比全局阈值更可控。 新机型继续走已验证前摄,老机型走后摄,两个路径互不拖累。
很多工程问题最后都会走到这一步:不是"有没有一个完美参数让所有设备都一样",而是要不要按设备能力做不同选择。
失败后重测也是一类真实问题
这次还有一个和算法公式无关、但和真实体验很相关的问题:测量失败以后,用户把手指重新放回前摄,不能自然开始下一轮测量。
这个问题看起来像"前摄测量失败",但根因不是 HR、HRV、Stress 公式。
更准确地说,它是测量会话状态没有重置干净:
text
本轮测量超时失败
-> 页面圆环回退
-> 用户重新遮挡
-> 业务层没有明确开启一轮新测量
-> 还停留在上一轮失败状态附近
这种问题在相机测量类功能里很常见。算法算得再清楚,如果业务层没有把"失败、移开、重新遮挡、重新开始"这条路径处理好,用户看到的仍然是功能不可用。
所以后面修复这个问题时,我没有动 HR/HRV/Stress 算法,只是在业务状态里补了失败后重新开始的边界:失败以后,等用户确实移开手指,再重新遮挡时开启新一轮测量。
这类修复和算法优化要分开看。前者解决的是测量流程,后者解决的是结果可信度。
这次最大的收获
如果只看这次前摄验证,我觉得最值得带走的不是某个具体阈值,而是几个判断顺序。
第一,相机 PPG 的稳定性不只由算法决定。
同一套算法,后摄有闪光灯,前摄靠屏幕;新机型屏幕亮、采样稳,老机型屏幕和前摄能力弱一些。算法看到的输入信号不一样,结果自然可能不一样。
第二,"能出结果"不等于"结果可信"。
测量功能不能只统计成功率,也不能只看成功样本里的平均误差。要同时看失败率、异常样本、HRV/Stress 分布,以及质量日志能不能解释这次为什么成功。
第三,HRV 和 Stress 的问题经常要回到 RR。
压力值看起来低,HRV 看起来高,不一定是显示公式单独错了。先看心跳间隔有没有被稳定识别出来,很多问题会更清楚。
第四,设备分流不是妥协,而是工程选择。
如果一个方案在新机型上稳定,在老机型上不稳,继续强行调一个全局参数,可能会让两边的体验都变差。让新机走前摄,老机走后摄,;两种设备各走更适合自己的测量方式。
这次我真正改掉的认知是:前摄心率测量不是"算法已经好了,换个摄像头继续测"。它更像是重新换了一套采集条件。采集条件一变,算法、硬件、质量判断和产品策略都要重新看一遍。
这也是移动端相机类功能最容易被低估的地方。屏幕上最后只有一个数字,但这个数字背后,站着光源、镜头、帧率、手指覆盖、RR 选择、失败状态和设备分流,任何一环不稳,最后都可能变成用户看到的"不准"。