Skip to content

小阳心健康测量 iOS SDK

简介

小阳心健康测量 SDK 是一套基于摄像头视频流的人脸生理信号测量方案,通过实时分析面部特征及视频信号,可生成健康测量报告。

SDK 采用二进制 XCFramework 形式发布,通过 CocoaPods 集成,内部已集成:

  • MediaPipe Face Landmarker
  • Protobuf 3.29.6
  • 模型资源文件(measurementSDK.bundle
  • 测量通信模块(SignalR)

业务方仅需接入摄像头数据并实现回调接口即可完成测量。


系统要求

项目要求
iOS14.0+
Xcode15.0+
Swift5.8+(Swift 宿主可选)
架构arm64 真机 / arm64 + x86_64 模拟器

集成方式

1. 配置 Podfile

ruby
platform :ios, '14.0'

target 'YourApp' do
  use_frameworks!

  pod 'MeasurementSDK',
      :git => 'https://github.com/xiaoyang-tech/iOS-Specs.git',
      :tag => 'v1.0.0'
end

Pod 会自动安装 MediaPipeTasksVisionProtobuf 及 SDK 资源,业务方无需手动添加。

2. 执行安装

bash
pod install

3. 权限配置

Info.plist 中添加相机权限说明:

xml
<key>NSCameraUsageDescription</key>
<string>需要使用摄像头进行健康测量</string>

4. 导入 SDK

Objective-C

在需要使用 SDK 的 .m 文件中:

objc
#import <MeasurementSDK/MeasurementSDK.h>

Swift

请勿使用 import MeasurementSDK

Swift 宿主通过 Bridging Header 引入 OC 公开 API。在 App 的 Bridging Header(如 YourApp-Bridging-Header.h)中添加:

objc
#import "MeasurementSDK-SwiftBridge.h"

MeasurementSDK-SwiftBridge.h 由 Pod 随 Support/ 目录下发,CocoaPods 安装后位于 Pods/MeasurementSDK/Support/
若 Xcode 提示找不到头文件,可在 Target → Build Settings → Header Search Paths 中添加:

$(PODS_ROOT)/MeasurementSDK/Support

Bridging Header 已包含以下公开类型,Swift 中可直接使用:

  • XYMeasurementManager / XYMeasurementConfig / XYMeasurementDelegate
  • XYFaceDetector / XYLogConfig
  • Report / HrReport / MeasurementCategory

SDK 初始化

创建配置对象

Swift

swift
let config = XYMeasurementConfig(appId: "YOUR_APP_ID", sdkKey: "YOUR_SDK_KEY")
config.measurementDuration = 30
config.measurementCategoryValues = [NSNumber(value: MeasurementCategory_All.rawValue)]
config.enableLog = true

Objective-C

objc
XYMeasurementConfig *config = [[XYMeasurementConfig alloc] initWithAppId:@"YOUR_APP_ID"
                                                                  sdkKey:@"YOUR_SDK_KEY"];
config.measurementDuration = 30;
config.measurementCategoryValues = @[@(MeasurementCategory_All)];
config.enableLog = YES;

初始化 SDK 实例

Swift

swift
let measureSDK = XYMeasurementManager()
measureSDK.delegate = self
measureSDK.setConfig(config)

Objective-C

objc
XYMeasurementManager *measureSDK = [[XYMeasurementManager alloc] init];
measureSDK.delegate = self;
[measureSDK setConfig:config];

XYMeasurementConfig 配置项

属性类型说明
appIdString平台分配的 APP_ID(必填)
sdkKeyString平台分配的 SDK_KEY(必填)
measurementDurationInt测量总时长(秒),默认 30
minChunkTimeSpanInt阶段性 partial 报告最小间隔(秒),默认 6
measurementUrlString?私有化测量服务地址
authUrlString?私有化认证地址
measurementCategoryValues[NSNumber]测量指标 rawValue 数组
enableLogBool是否打印 SDK 日志,默认 YES

测量类型枚举(MeasurementCategory)

objc
typedef GPB_ENUM(MeasurementCategory) {
  MeasurementCategory_None = 0,
  MeasurementCategory_HeartRate = 1,        // 心率房颤
  MeasurementCategory_BloodPressure = 2,    // 血压
  MeasurementCategory_BloodOxygen = 4,      // 血氧
  MeasurementCategory_Risk = 8,             // 健康风险
  MeasurementCategory_Aggressivity = 16,    // 攻击性
  MeasurementCategory_Anxiety = 32,         // 焦虑度
  MeasurementCategory_Depression = 64,      // 压抑度
  MeasurementCategory_Vitality = 128,       // 活力度
  MeasurementCategory_Positivity = 256,     // 积极性
  MeasurementCategory_Suppression = 512,    // 抑郁度
  MeasurementCategory_Fatigue = 1024,       // 疲劳度
  MeasurementCategory_Physiology = 15,      // 生理测量组合
  MeasurementCategory_Emotion = 1713,       // 情绪测量组合
  MeasurementCategory_All = 1727,           // 全部指标
};

配置示例

Swift

swift
let config = XYMeasurementConfig(appId: "YOUR_APP_ID", sdkKey: "YOUR_SDK_KEY")
config.measurementDuration = 15
config.measurementCategoryValues = [NSNumber(value: MeasurementCategory_All.rawValue)]
measureSDK.setConfig(config)

Objective-C

objc
XYMeasurementConfig *config = [[XYMeasurementConfig alloc] initWithAppId:@"YOUR_APP_ID"
                                                                  sdkKey:@"YOUR_SDK_KEY"];
config.measurementDuration = 15;
config.measurementCategoryValues = @[@(MeasurementCategory_All)];
[measureSDK setConfig:config];

测量流程

推荐接入顺序:

setConfig → 人脸引导(XYFaceDetector)→ beginFaceGuidance(可选)→ start → 持续送帧 → stop
  1. setConfig: 配置鉴权参数,SDK 建立 SignalR 连接
  2. 人脸引导: 校验人脸位置与姿态 手机是否垂直
  3. start: 开始正式测量
  4. enqueueSampleBuffer: 每帧相机数据送入 SDK
  5. stop / interrupt: 结束或中断测量

核心功能调用

开始测量

Swift

swift
measureSDK.beginFaceGuidance()  // 可选:开启引导模式
measureSDK.start()

Objective-C

objc
[measureSDK beginFaceGuidance];  // 可选
[measureSDK start];

视频流输入

相机输出格式须为 NV12kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)。

Swift

swift
measureSDK.enqueueSampleBuffer(
    sampleBuffer,
    orientation: orientation,
    guideRect: guideRect
)

Objective-C

objc
[measureSDK enqueueSampleBuffer:sampleBuffer
                    orientation:orientation
                      guideRect:guideRect];
参数类型说明
sampleBufferCMSampleBuffer摄像头采集的视频帧(NV12)
orientationUIImageOrientation视频方向
guideRectCGRect测量引导框(预览视图坐标系)
previewSizeCGSize可选,预览区域尺寸,须与 guideRect 同一坐标系

完整示例(Swift)

swift
func captureOutput(
    _ output: AVCaptureOutput,
    didOutput sampleBuffer: CMSampleBuffer,
    from connection: AVCaptureConnection
) {
    let orientation: UIImage.Orientation = .leftMirrored
    measureSDK.enqueueSampleBuffer(
        sampleBuffer,
        orientation: orientation,
        guideRect: scanningFrame
    )
}

停止测量

Swift

swift
measureSDK.stop()       // 正常结束,等待完整报告
measureSDK.interrupt()  // 立即中断(用户取消 / 异常恢复)

Objective-C

objc
[measureSDK stop];
[measureSDK interrupt];

回调协议(XYMeasurementDelegate)

所有回调方法均为 @optional,按需实现即可。

Swift

swift
extension ViewController: XYMeasurementDelegate {

    func onStarted(withMeasurementId measurementId: String) {
        print("测量任务 ID:\(measurementId)")
    }

    func onStateUpdate(withCode code: Int, message: String) {
        tipsLabel.text = message
    }

    func onInterrupted(withReason reason: String) {
        print("中断原因:\(reason)")
    }

    func onCollected() {
        print("采集完成,等待报告")
    }

    func onChunkReportGenerated(withMeasurementId measurementId: String, hrReport: HrReport) {
        let hr = Int(hrReport.data_p.hrBpm)
        heartRateLabel.text = "\(hr)"
    }

    func onWholeReportGenerated(withMeasurementId measurementId: String, report: Report) {
        print("测量完成")
    }

    func onCrashed(withCode code: Int, message: String) {
        print("异常:\(code) - \(message)")
    }
}

Objective-C

objc
@interface ViewController () <XYMeasurementDelegate>
@end

@implementation ViewController

- (void)onStartedWithMeasurementId:(NSString *)measurementId {
    NSLog(@"测量任务 ID:%@", measurementId);
}

- (void)onStateUpdateWithCode:(NSInteger)code message:(NSString *)message {
    self.tipsLabel.text = message;
}

- (void)onInterruptedWithReason:(NSString *)reason {
    NSLog(@"中断原因:%@", reason);
}

- (void)onCollected {
    NSLog(@"采集完成,等待报告");
}

- (void)onChunkReportGeneratedWithMeasurementId:(NSString *)measurementId
                                       hrReport:(HrReport *)hrReport {
    self.heartRateLabel.text = [NSString stringWithFormat:@"%d", hrReport.data_p.hrBpm];
}

- (void)onWholeReportGeneratedWithMeasurementId:(NSString *)measurementId
                                         report:(Report *)report {
    NSLog(@"测量完成");
}

- (void)onCrashedWithCode:(NSInteger)code message:(NSString *)message {
    NSLog(@"异常:%ld - %@", (long)code, message);
}

@end

回调说明

回调触发时机
onStarted测量成功启动,返回服务端 measurementId
onStateUpdate状态或人脸引导提示更新(如「请正视屏幕」「请靠近一些」)
onInterrupted测量被中断(人脸丢失、姿态异常、用户取消等)
onCollected视频帧采集完成,SDK 等待完整报告
onChunkReportGenerated阶段性心率报告(partial),可实时更新 UI
onWholeReportGenerated完整健康报告生成
onCrashed不可恢复错误(鉴权失败、网络异常等)

完整接入示例

Swift

swift
import UIKit
import AVFoundation

class ViewController: UIViewController {

    let measureSDK = XYMeasurementManager()
    let scanningFrame = CGRect(x: 40, y: 120, width: 295, height: 400)

    override func viewDidLoad() {
        super.viewDidLoad()
        measureSDK.delegate = self

        let config = XYMeasurementConfig(appId: "YOUR_APP_ID", sdkKey: "YOUR_SDK_KEY")
        config.measurementCategoryValues = [NSNumber(value: MeasurementCategory_All.rawValue)]
        measureSDK.setConfig(config)
    }

    func startMeasure() {
        measureSDK.start()
    }

    func captureOutput(
        _ output: AVCaptureOutput,
        didOutput sampleBuffer: CMSampleBuffer,
        from connection: AVCaptureConnection
    ) {
        measureSDK.enqueueSampleBuffer(
            sampleBuffer,
            orientation: .leftMirrored,
            guideRect: scanningFrame
        )
    }
}

extension ViewController: XYMeasurementDelegate {
    func onStarted(withMeasurementId measurementId: String) { }
    func onStateUpdate(withCode code: Int, message: String) { }
    func onInterrupted(withReason reason: String) { }
    func onCollected() { }
    func onChunkReportGenerated(withMeasurementId measurementId: String, hrReport: HrReport) { }
    func onWholeReportGenerated(withMeasurementId measurementId: String, report: Report) { }
    func onCrashed(withCode code: Int, message: String) { }
}

Objective-C

objc
#import <MeasurementSDK/MeasurementSDK.h>

@interface ViewController () <XYMeasurementDelegate>
@property (nonatomic, strong) XYMeasurementManager *measureSDK;
@property (nonatomic, assign) CGRect scanningFrame;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.scanningFrame = CGRectMake(40, 120, 295, 400);
    self.measureSDK = [[XYMeasurementManager alloc] init];
    self.measureSDK.delegate = self;

    XYMeasurementConfig *config = [[XYMeasurementConfig alloc] initWithAppId:@"YOUR_APP_ID"
                                                                      sdkKey:@"YOUR_SDK_KEY"];
    config.measurementCategoryValues = @[@(MeasurementCategory_All)];
    [self.measureSDK setConfig:config];
}

- (void)startMeasure {
    [self.measureSDK start];
}

- (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection {
    [self.measureSDK enqueueSampleBuffer:sampleBuffer
                             orientation:UIImageOrientationLeftMirrored
                               guideRect:self.scanningFrame];
}

@end

注意事项

  1. Swift 宿主请勿 import MeasurementSDK,统一通过 Bridging Header 引入
  2. 面部需完整出现在引导框内,保持环境光线充足均匀
  3. 避免剧烈运动与头部晃动,不建议佩戴口罩测量
  4. 测量中不要切换前后台,必须持续传入视频帧
  5. 相机输出格式须为 NV12,引导框与 previewSize 须处于同一坐标系
  6. 正式测量前建议先通过 XYFaceDetector 完成人脸引导与二次确认

联系支持

如需技术支持、私有化部署或商务合作,请联系小阳心健康技术团队。

Released under the MIT License.