前面几篇文章,一篇写怎么先稳住原来的扫脸功能,一篇写哪些代码该放进 SDK、哪些代码该留在 App,一篇写 SDK 接入以后主工程里的旧代码怎么删。
这一篇写最后一个很容易被低估的问题:SDK 已经做完以后,怎么让同事真正接得上。
扫脸 SDK 在主工程里跑通以后,我又回头看了一遍 SDK 使用文档和 Demo 工程代码。
这时候发现,SDK 能跑,只说明我自己接通了。
但下一个接入它的人,不会从我这几周的改动记录开始看,他打开的只有两样东西:
- SDK 使用文档。
- Demo 工程代码。
如果这两样东西讲不清楚,同事对接就会出问题。
他可以通过 Pod 集成SDK,也能创建几个 SDK 对象,但真正开始做完整扫脸功能时,还是会问:
- 相机权限什么时候申请?
- 每一帧相机画面怎么送进人脸识别?
- 人脸识别结果出来以后,怎么判断当前能不能测?
- 红绿蓝三路信号从哪里来?
- 什么时候调用最终结果计算?
- 出错了先看 SDK,还是先看自己项目里的页面代码?
这些问题如果全靠接入同事自己猜,那 SDK 就没有交付完整。
所以扫脸 SDK 做到最后,我没有只停在“代码已经打 tag、Pod 已经能装上”,我又做了一轮使用文档和 Demo 的整理。
一、Pod install能成功,只是第一步
SDK 交付时,最容易让人误判的一件事是:只要 Pod 能装上,SDK 就算可以用了。
如果是很简单的工具 SDK,这个判断可能没问题,但扫脸 SDK 不是一个普通的工具函数,它不是传几个参数进去,马上就返回一个健康结果。
一个完整的扫脸过程,至少要经过这些步骤:
- 打开前置相机。
- 持续拿到前置相机拍到的画面。
- 从这些画面里识别人脸。
- 判断脸的位置、距离、晃动、遮挡是否满足测量条件。
- 从额头、脸颊这些区域里取红绿蓝变化。
- 积累足够多的红绿蓝信号。
- 最后再计算健康结果。
所以 SDK 使用文档里只有这段是不够的(示例):
pod 'ZZHFaceScanSDK',
:git => 'https://xxx.xxx.xx/xxx/ZZHFaceScanSDK.git',
:tag => '0.1.8'
这段只能说明同事能把 SDK 集成到项目里,但它还没有回答更关键的问题:装进去以后,完整扫脸功能怎么跑起来?
这也是我后来重新整理 SDK 使用文档的原因,使用文档不能只告诉别人“怎么安装”,还要告诉别人“安装后先做什么、再做什么、哪些事情 SDK 做、哪些事情自己的 App 还要做”。
二、SDK 使用文档要先讲清楚接入顺序
我这次整理 SDK 使用文档时,最先处理的不是把每个接口都列一遍,而是把几个关键对象的使用顺序写清楚。
因为只解释单个接口,同事还是会遇到一个问题:这些接口放在一起时,到底应该先调用谁、再调用谁。
扫脸 SDK 更容易出现这个问题,因为它里面有好几个看起来都很重要的对象:
FaceScanCaptureServiceFaceMeshTrackerFaceScanQualityEvaluatorFaceScanMeasurementCoordinatorZZHFaceScanSDK.EstimationEngine
如果使用文档只是分别介绍它们,同事知道每个东西大概是干什么的,但不一定知道它们怎么连起来。
所以我把 SDK 使用文档改成先讲完整接入顺序:
- 页面进入后,先准备人脸识别。
- 再请求相机权限。
- 权限通过后,启动前置相机。
- 每来一帧前置相机拍到的画面,就把这帧交给人脸识别。
- 识别到脸以后,先判断当前脸的位置和状态适不适合测量。
- 适合测量以后,再开始积累红绿蓝信号。
- 信号够了以后,再调用结果计算。
代码里大概是这样的顺序:
func prepare() {
faceMeshTracker.configureIfNeeded()
captureService.requestAccessIfNeeded()
}
func captureService(
_ service: FaceScanCaptureService,
didUpdateAuthorization state: FaceScanCameraAuthorizationState
) {
guard state == .authorized else { return }
service.configureSessionIfNeeded()
service.startRunning()
}
func captureService(_ service: FaceScanCaptureService, didOutput frame: FaceScanCaptureFrame) {
faceMeshTracker.process(
sampleBuffer: frame.sampleBuffer,
orientation: .portraitMirrored,
timestampInMilliseconds: frame.timestampInMilliseconds
)
}
这段代码不是为了展示“SDK 有哪些 API”。
它解决的是另一个问题:同事第一次接入时,应该先把哪几步串起来。
如果这个顺序不写清楚,同事很容易一上来就看结果计算接口,然后发现自己根本没有红绿蓝信号。
三、Demo 不能只是最小示例
做 SDK Demo 时,我没有把它做成一个只显示调用结果的最小页面,原因是扫脸功能最容易接错的地方,不在结果页,也不在入口页,而是在扫脸页。
扫脸页里有很多实时变化:
- 相机预览。
- 人脸框和引导区域。
- 未识别到脸、太近、太远、晃动、遮挡这些提示。
- 校准状态。
- 倒计时。
- 测量中的波形和实时BMP。
- 测量完成后跳结果页。
如果 Demo 只是做一个按钮,点击后打印一段假结果,它对接入同事帮助不大。
因为同事真正要解决的是:我的页面怎么把 SDK 的相机、人脸识别、测量状态和最终结果串起来。
所以最后 Demo 的取舍是:
- 入口页可以简单。
- 结果页可以简单。
- 扫脸页必须尽量接近真实项目。
这样同事看 Demo 时,至少能知道完整扫脸过程在页面上应该怎么走。
他不一定要照搬 Demo 的 UI,但他可以沿着 Demo 找到关键位置:
- 哪个文件负责把前置相机拍到的画面送进人脸识别。
- 哪个文件负责判断当前能不能扫脸测量。
- 哪个文件负责整理红绿蓝信号。
- 哪个文件负责实时波形和实时BMP。
- 哪个文件负责测量完成后跳结果页。
这比只给一个最小调用片段有用得多。
四、最容易卡住的是红绿蓝信号
扫脸 SDK 使用文档里,最需要讲清楚的一件事,是红绿蓝信号从哪里来。
因为最终结果计算需要这样的输入:
let request = ZZHFaceScanSDK.EstimationRequest(
redSignal: red,
greenSignal: green,
blueSignal: blue,
sampleRate: 30,
userProfile: profile,
measurementContext: context
)
这段代码看起来很简单,但问题是:redSignal、greenSignal、blueSignal 不是相机直接给你的。
相机给的是一帧一帧画面,人脸识别会告诉你额头、脸颊这些区域在哪里,接入层还要从这些区域里,把每一帧的红绿蓝平均值取出来,再按时间顺序存成三组数组。
也就是说,这里真正发生的是:
- 相机持续输出画面。
- 人脸识别找到可用的人脸区域。
- 从这些区域里取颜色变化。
- 把很多帧相机拍到的画面的颜色变化存起来。
- 最后把这一整段数据交给 SDK 计算。
如果文档只写最终怎么调用计算接口,同事可能会以为 SDK 装好后就能直接算结果。
但实际上,算结果之前,必须先拿到红绿蓝三路信号。
所以 Demo 里必须有一个清楚的参考文件,专门告诉他这件事。
在当前 Demo 里,这个角色是 DemoFaceScanSignalBuffer.swift。
我希望接入同事看到这个文件时,能理解它不是业务页面,也不是结果展示。
它做的事情很明确:
- 先缓存相机原始画面。
- 等人脸识别结果回来。
- 根据可用的人脸区域取红绿蓝变化。
- 把每一帧的结果存起来。
- 最后整理成结果计算需要的三路信号。
这块讲清楚以后,SDK 使用文档才不会只停在“传入 red、green、blue”这种表面说明上。
五、SDK 使用文档和 Demo 要分工
整理到后面,我对 SDK 使用文档和 Demo 的分工也更明确了。
SDK 使用文档负责讲清楚这些事情:
- 怎么安装 SDK。
- 项目要配哪些权限。
- SDK 负责什么。
- App 自己还要负责什么。
- 完整扫脸应该按什么顺序接。
- 常见错误先查哪里。
Demo 负责讲清楚这些事情:
- 一个真实扫脸页怎么启动相机。
- 前置相机拍到的画面怎么送进人脸识别。
- 测量状态怎么变化。
- 红绿蓝信号怎么整理。
- 实时波形和实时BMP怎么展示。
- 测量完成后怎么进入结果页。
这两者不能互相替代。
如果只有 SDK 使用文档,没有 Demo,同事可以知道接口,但很难知道完整页面怎么接。
如果只有 Demo,没有 SDK 使用文档,同事可以看代码,但很容易在一堆页面和组件里找不到重点。
所以更合理的做法是:SDK 使用文档先告诉同事接入顺序,Demo 再给他一个真的能跑起来的参考工程。
六、Demo 里也要告诉同事先看哪里
Demo 工程一旦做得接近真实项目,就会有另一个问题:文件会变多。
入口页、扫描页、结果页、相机预览、人脸引导、波形、动画、状态卡、结果展示,全都放在一个工程里。
如果不告诉同事先看哪里,他打开 Demo 以后还是会迷路。
所以我在 SDK 使用文档里明确写了几个优先入口。
如果同事只想理解完整扫脸过程,先看:
DemoFaceScanSessionController.swiftDemoFaceScanSignalBuffer.swiftDemoFaceScanLiveSupport.swiftDemoFaceScanViewController.swift
这几个文件对应的不是“页面长什么样”,而是“完整扫脸怎么跑起来”。
其中:
DemoFaceScanSessionController.swift负责把相机、人脸识别、测量判断和结果计算串起来。DemoFaceScanSignalBuffer.swift负责整理红绿蓝信号。DemoFaceScanLiveSupport.swift负责实时波形和实时BMP。DemoFaceScanViewController.swift负责把这些状态展示到扫脸页上。
入口页和结果页当然也有价值,但它们不是第一优先级。
因为同事最容易接错的,不是资料输入页怎么画,也不是结果页卡片怎么排版,真正容易出问题的是扫脸页里的实时过程。
七、常见问题也要写得像排查入口
常见问题这一节,最怕写成一句模糊提醒。
比如只写“人脸识别失败时,请检查配置”,同事看完还是不知道该检查什么。
所以常见问题要尽量写成排查入口。
比如相机打不开,就先看:
Info.plist有没有配置相机权限。- 有没有真的调用权限申请。
- 权限通过后有没有启动相机会话。
- delegate 有没有接上。
比如人脸识别不起,就先看:
- SDK 是否完整集成。
- 人脸识别模型资源有没有重复或缺失。
FaceMeshTracker有没有先准备。- 每一帧相机画面有没有真的送进去。
比如最终结果算不出来,就先看:
- 红绿蓝三路信号长度是否一致。
- 样本点是否太少。
- 采样率是否过低。
- 是否真的等到测量完成后才调用结果计算。
这种写法比单纯写“检查参数是否正确”更有用。
因为它能把同事从“我不知道哪里错了”带到“我先按这几个点查一遍”。
八、这次留下来的经验
扫脸 SDK 做到最后,我对“交付”这件事的理解变得更具体:
- 代码能跑,只是我自己完成了开发;
- 主工程接入成功,只是证明这个 SDK 在当前项目里可用。
但如果要让同事接得上,还要继续补两件事:
- SDK 使用文档要让人少猜。
- Demo 要让人看到完整扫脸过程怎么跑。
尤其是扫脸这种实时功能,不能只告诉别人“调用结果计算接口”。
因为最终结果前面还有相机、人脸识别、测量判断、红绿蓝信号这些步骤。
这些步骤不讲清楚,同事就算把 SDK 装进项目,也还是不知道下一步该怎么接。
所以这次扫脸 SDK 到最后真正完成的,不只是一个 SDK 仓库,更重要的是,它有了一份能指导接入的 SDK 使用文档,也有了一个能跑完整扫脸过程的 Demo。 这样同事接入时,才不是从一堆 API 里猜,而是能沿着一条清楚的顺序往下做。