扫脸 SDK 做完后,怎么让同事真正接得上

0 阅读11分钟

前面几篇文章,一篇写怎么先稳住原来的扫脸功能,一篇写哪些代码该放进 SDK、哪些代码该留在 App,一篇写 SDK 接入以后主工程里的旧代码怎么删。
这一篇写最后一个很容易被低估的问题:SDK 已经做完以后,怎么让同事真正接得上。

扫脸 SDK 在主工程里跑通以后,我又回头看了一遍 SDK 使用文档和 Demo 工程代码。

这时候发现,SDK 能跑,只说明我自己接通了。

但下一个接入它的人,不会从我这几周的改动记录开始看,他打开的只有两样东西:

  1. SDK 使用文档。
  2. Demo 工程代码。

如果这两样东西讲不清楚,同事对接就会出问题。

他可以通过 Pod 集成SDK,也能创建几个 SDK 对象,但真正开始做完整扫脸功能时,还是会问:

  1. 相机权限什么时候申请?
  2. 每一帧相机画面怎么送进人脸识别?
  3. 人脸识别结果出来以后,怎么判断当前能不能测?
  4. 红绿蓝三路信号从哪里来?
  5. 什么时候调用最终结果计算?
  6. 出错了先看 SDK,还是先看自己项目里的页面代码?

这些问题如果全靠接入同事自己猜,那 SDK 就没有交付完整。

所以扫脸 SDK 做到最后,我没有只停在“代码已经打 tag、Pod 已经能装上”,我又做了一轮使用文档和 Demo 的整理。

一、Pod install能成功,只是第一步

SDK 交付时,最容易让人误判的一件事是:只要 Pod 能装上,SDK 就算可以用了。

如果是很简单的工具 SDK,这个判断可能没问题,但扫脸 SDK 不是一个普通的工具函数,它不是传几个参数进去,马上就返回一个健康结果。

一个完整的扫脸过程,至少要经过这些步骤:

  1. 打开前置相机。
  2. 持续拿到前置相机拍到的画面。
  3. 从这些画面里识别人脸。
  4. 判断脸的位置、距离、晃动、遮挡是否满足测量条件。
  5. 从额头、脸颊这些区域里取红绿蓝变化。
  6. 积累足够多的红绿蓝信号。
  7. 最后再计算健康结果。

所以 SDK 使用文档里只有这段是不够的(示例):

pod 'ZZHFaceScanSDK',
    :git => 'https://xxx.xxx.xx/xxx/ZZHFaceScanSDK.git',
    :tag => '0.1.8'

这段只能说明同事能把 SDK 集成到项目里,但它还没有回答更关键的问题:装进去以后,完整扫脸功能怎么跑起来?

这也是我后来重新整理 SDK 使用文档的原因,使用文档不能只告诉别人“怎么安装”,还要告诉别人“安装后先做什么、再做什么、哪些事情 SDK 做、哪些事情自己的 App 还要做”。

二、SDK 使用文档要先讲清楚接入顺序

我这次整理 SDK 使用文档时,最先处理的不是把每个接口都列一遍,而是把几个关键对象的使用顺序写清楚。

因为只解释单个接口,同事还是会遇到一个问题:这些接口放在一起时,到底应该先调用谁、再调用谁。

扫脸 SDK 更容易出现这个问题,因为它里面有好几个看起来都很重要的对象:

  1. FaceScanCaptureService
  2. FaceMeshTracker
  3. FaceScanQualityEvaluator
  4. FaceScanMeasurementCoordinator
  5. ZZHFaceScanSDK.EstimationEngine

如果使用文档只是分别介绍它们,同事知道每个东西大概是干什么的,但不一定知道它们怎么连起来。

所以我把 SDK 使用文档改成先讲完整接入顺序:

  1. 页面进入后,先准备人脸识别。
  2. 再请求相机权限。
  3. 权限通过后,启动前置相机。
  4. 每来一帧前置相机拍到的画面,就把这帧交给人脸识别。
  5. 识别到脸以后,先判断当前脸的位置和状态适不适合测量。
  6. 适合测量以后,再开始积累红绿蓝信号。
  7. 信号够了以后,再调用结果计算。

代码里大概是这样的顺序:

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 时,我没有把它做成一个只显示调用结果的最小页面,原因是扫脸功能最容易接错的地方,不在结果页,也不在入口页,而是在扫脸页。

Snipaste_2026-04-28_03-33-06.png

扫脸页里有很多实时变化:

  1. 相机预览。
  2. 人脸框和引导区域。
  3. 未识别到脸、太近、太远、晃动、遮挡这些提示。
  4. 校准状态。
  5. 倒计时。
  6. 测量中的波形和实时BMP。
  7. 测量完成后跳结果页。

如果 Demo 只是做一个按钮,点击后打印一段假结果,它对接入同事帮助不大。

因为同事真正要解决的是:我的页面怎么把 SDK 的相机、人脸识别、测量状态和最终结果串起来。

所以最后 Demo 的取舍是:

  1. 入口页可以简单。
  2. 结果页可以简单。
  3. 扫脸页必须尽量接近真实项目。

这样同事看 Demo 时,至少能知道完整扫脸过程在页面上应该怎么走。

他不一定要照搬 Demo 的 UI,但他可以沿着 Demo 找到关键位置:

  1. 哪个文件负责把前置相机拍到的画面送进人脸识别。
  2. 哪个文件负责判断当前能不能扫脸测量。
  3. 哪个文件负责整理红绿蓝信号。
  4. 哪个文件负责实时波形和实时BMP。
  5. 哪个文件负责测量完成后跳结果页。

这比只给一个最小调用片段有用得多。

四、最容易卡住的是红绿蓝信号

扫脸 SDK 使用文档里,最需要讲清楚的一件事,是红绿蓝信号从哪里来。

因为最终结果计算需要这样的输入:

let request = ZZHFaceScanSDK.EstimationRequest(
    redSignal: red,
    greenSignal: green,
    blueSignal: blue,
    sampleRate: 30,
    userProfile: profile,
    measurementContext: context
)

这段代码看起来很简单,但问题是:redSignalgreenSignalblueSignal 不是相机直接给你的。

相机给的是一帧一帧画面,人脸识别会告诉你额头、脸颊这些区域在哪里,接入层还要从这些区域里,把每一帧的红绿蓝平均值取出来,再按时间顺序存成三组数组。

也就是说,这里真正发生的是:

  1. 相机持续输出画面。
  2. 人脸识别找到可用的人脸区域。
  3. 从这些区域里取颜色变化。
  4. 把很多帧相机拍到的画面的颜色变化存起来。
  5. 最后把这一整段数据交给 SDK 计算。

如果文档只写最终怎么调用计算接口,同事可能会以为 SDK 装好后就能直接算结果。

但实际上,算结果之前,必须先拿到红绿蓝三路信号。

所以 Demo 里必须有一个清楚的参考文件,专门告诉他这件事。

在当前 Demo 里,这个角色是 DemoFaceScanSignalBuffer.swift

我希望接入同事看到这个文件时,能理解它不是业务页面,也不是结果展示。

它做的事情很明确:

  1. 先缓存相机原始画面。
  2. 等人脸识别结果回来。
  3. 根据可用的人脸区域取红绿蓝变化。
  4. 把每一帧的结果存起来。
  5. 最后整理成结果计算需要的三路信号。

这块讲清楚以后,SDK 使用文档才不会只停在“传入 red、green、blue”这种表面说明上。

五、SDK 使用文档和 Demo 要分工

整理到后面,我对 SDK 使用文档和 Demo 的分工也更明确了。

SDK 使用文档负责讲清楚这些事情:

  1. 怎么安装 SDK。
  2. 项目要配哪些权限。
  3. SDK 负责什么。
  4. App 自己还要负责什么。
  5. 完整扫脸应该按什么顺序接。
  6. 常见错误先查哪里。

Demo 负责讲清楚这些事情:

  1. 一个真实扫脸页怎么启动相机。
  2. 前置相机拍到的画面怎么送进人脸识别。
  3. 测量状态怎么变化。
  4. 红绿蓝信号怎么整理。
  5. 实时波形和实时BMP怎么展示。
  6. 测量完成后怎么进入结果页。

这两者不能互相替代。

如果只有 SDK 使用文档,没有 Demo,同事可以知道接口,但很难知道完整页面怎么接。

如果只有 Demo,没有 SDK 使用文档,同事可以看代码,但很容易在一堆页面和组件里找不到重点。

所以更合理的做法是:SDK 使用文档先告诉同事接入顺序,Demo 再给他一个真的能跑起来的参考工程。

六、Demo 里也要告诉同事先看哪里

Demo 工程一旦做得接近真实项目,就会有另一个问题:文件会变多。

入口页、扫描页、结果页、相机预览、人脸引导、波形、动画、状态卡、结果展示,全都放在一个工程里。

如果不告诉同事先看哪里,他打开 Demo 以后还是会迷路。

所以我在 SDK 使用文档里明确写了几个优先入口。

如果同事只想理解完整扫脸过程,先看:

  1. DemoFaceScanSessionController.swift
  2. DemoFaceScanSignalBuffer.swift
  3. DemoFaceScanLiveSupport.swift
  4. DemoFaceScanViewController.swift

这几个文件对应的不是“页面长什么样”,而是“完整扫脸怎么跑起来”。

其中:

  1. DemoFaceScanSessionController.swift 负责把相机、人脸识别、测量判断和结果计算串起来。
  2. DemoFaceScanSignalBuffer.swift 负责整理红绿蓝信号。
  3. DemoFaceScanLiveSupport.swift 负责实时波形和实时BMP。
  4. DemoFaceScanViewController.swift 负责把这些状态展示到扫脸页上。

入口页和结果页当然也有价值,但它们不是第一优先级。

因为同事最容易接错的,不是资料输入页怎么画,也不是结果页卡片怎么排版,真正容易出问题的是扫脸页里的实时过程。

七、常见问题也要写得像排查入口

常见问题这一节,最怕写成一句模糊提醒。

比如只写“人脸识别失败时,请检查配置”,同事看完还是不知道该检查什么。

所以常见问题要尽量写成排查入口。

比如相机打不开,就先看:

  1. Info.plist 有没有配置相机权限。
  2. 有没有真的调用权限申请。
  3. 权限通过后有没有启动相机会话。
  4. delegate 有没有接上。

比如人脸识别不起,就先看:

  1. SDK 是否完整集成。
  2. 人脸识别模型资源有没有重复或缺失。
  3. FaceMeshTracker 有没有先准备。
  4. 每一帧相机画面有没有真的送进去。

比如最终结果算不出来,就先看:

  1. 红绿蓝三路信号长度是否一致。
  2. 样本点是否太少。
  3. 采样率是否过低。
  4. 是否真的等到测量完成后才调用结果计算。

这种写法比单纯写“检查参数是否正确”更有用。

因为它能把同事从“我不知道哪里错了”带到“我先按这几个点查一遍”。

八、这次留下来的经验

扫脸 SDK 做到最后,我对“交付”这件事的理解变得更具体:

  • 代码能跑,只是我自己完成了开发;
  • 主工程接入成功,只是证明这个 SDK 在当前项目里可用。

但如果要让同事接得上,还要继续补两件事:

  1. SDK 使用文档要让人少猜。
  2. Demo 要让人看到完整扫脸过程怎么跑。

尤其是扫脸这种实时功能,不能只告诉别人“调用结果计算接口”。

因为最终结果前面还有相机、人脸识别、测量判断、红绿蓝信号这些步骤。

这些步骤不讲清楚,同事就算把 SDK 装进项目,也还是不知道下一步该怎么接。

所以这次扫脸 SDK 到最后真正完成的,不只是一个 SDK 仓库,更重要的是,它有了一份能指导接入的 SDK 使用文档,也有了一个能跑完整扫脸过程的 Demo。 这样同事接入时,才不是从一堆 API 里猜,而是能沿着一条清楚的顺序往下做。