HarmonyOS开发中的相机元数据:人脸检测、物体检测、场景检测、元数据回调、AI辅助拍摄

举报
Jack20 发表于 2026/06/20 22:24:18 2026/06/20
【摘要】 HarmonyOS开发中的相机元数据:人脸检测、物体检测、场景检测、元数据回调、AI辅助拍摄核心要点:掌握相机元数据的获取与处理,实现人脸检测与追踪,理解物体检测和场景检测的原理与应用,学会利用元数据回调构建 AI 辅助拍摄功能。 一、背景与动机你有没有好奇过,手机相机是怎么做到"一打开就识别人脸,自动对焦到眼睛上"的?或者拍美食时,手机会自动调暖色调;拍蓝天时,会自动增强饱和度?这些"智...

HarmonyOS开发中的相机元数据:人脸检测、物体检测、场景检测、元数据回调、AI辅助拍摄

核心要点:掌握相机元数据的获取与处理,实现人脸检测与追踪,理解物体检测和场景检测的原理与应用,学会利用元数据回调构建 AI 辅助拍摄功能。


一、背景与动机

你有没有好奇过,手机相机是怎么做到"一打开就识别人脸,自动对焦到眼睛上"的?或者拍美食时,手机会自动调暖色调;拍蓝天时,会自动增强饱和度?这些"智能"功能的背后,都是相机元数据在发挥作用。

元数据(Metadata),简单说就是"关于数据的数据"。在相机场景中,元数据是相机硬件和算法在采集图像的同时,额外输出的一些结构化信息——人脸的位置、物体的类别、场景的类型等等。这些信息不需要你自己去分析图像,相机底层已经帮你做好了,你只需要接收和使用。

想象一下这个场景:你在开发一个视频通话 App,希望在通话过程中实时检测用户的人脸位置,然后自动调整对焦和曝光。如果没有元数据,你需要自己写一套人脸检测算法,处理每一帧图像——这在性能和精度上都很难做好。但有了相机元数据,你只需要注册一个回调,系统就会把人脸坐标实时推送给你。

这篇文章,我们就把相机元数据的各种玩法讲透。


二、核心原理

2.1 元数据输出架构

元数据是相机的第三类输出(前两类是预览和拍照/录像),通过 MetadataOutput 通道获取:
图片.png

2.2 元数据类型

HarmonyOS 相机支持以下元数据类型:

元数据类型 说明 输出内容
FACE_DETECTION 人脸检测 人脸矩形区域、人脸朝向、眼睛位置、微笑度
OBJECT_DETECTION 物体检测 物体类别、置信度、边界框
SCENE_DETECTION 场景检测 场景类型(夜景/美食/风景等)

2.3 元数据处理流程

flowchart TD
    classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
    classDef warning fill:#E8A838,stroke:#B87A1A,color:#fff,font-weight:bold
    classDef error fill:#D94A4A,stroke:#8A2C2C,color:#fff,font-weight:bold
    classDef info fill:#50B5A9,stroke:#2C7A6F,color:#fff,font-weight:bold
    classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff,font-weight:bold

    A[查询元数据能力]:::primary --> B{支持哪些类型?}:::warning
    B -->|FACE_DETECTION| C[创建人脸检测输出]:::info
    B -->|OBJECT_DETECTION| D[创建物体检测输出]:::info
    B -->|SCENE_DETECTION| E[创建场景检测输出]:::info

    C --> F[加入Session]:::error
    D --> F
    E --> F

    F --> G[注册metadataObjectsAvailable回调]:::purple
    G --> H[启动Session]:::warning
    H --> I[实时接收元数据]:::purple
    I --> J[处理并应用]:::primary

    style G stroke-width:3px
    style I stroke-width:3px

2.4 AI 辅助拍摄原理

AI 辅助拍摄的核心思路是:利用元数据自动调整相机参数

flowchart LR
    classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
    classDef warning fill:#E8A838,stroke:#B87A1A,color:#fff,font-weight:bold
    classDef error fill:#D94A4A,stroke:#8A2C2C,color:#fff,font-weight:bold
    classDef info fill:#50B5A9,stroke:#2C7A6F,color:#fff,font-weight:bold
    classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff,font-weight:bold

    A[元数据输入]:::primary --> B{AI决策引擎}:::purple
    B -->|人脸数据| C[自动对焦到人脸]:::info
    B -->|场景=夜景| D[提高ISO+开启闪光灯]:::warning
    B -->|场景=美食| E[暖色调白平衡]:::warning
    B -->|物体=文字| F[切换微距模式]:::info

三、代码实战

3.1 人脸检测

人脸检测是元数据最常用的功能。这个示例实现了完整的人脸检测流程:查询能力、创建输出、注册回调、处理人脸数据。

import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 人脸信息
 * 从元数据中提取的结构化人脸数据
 */
export interface FaceInfo {
  id: number;              // 人脸ID
  rect: camera.Rect;       // 人脸矩形区域(归一化坐标)
  score: number;           // 检测置信度 (0-1)
  yawAngle: number;        // 偏航角(左右转头)
  pitchAngle: number;      // 俯仰角(上下点头)
  rollAngle: number;       // 翻滚角(歪头)
  leftEyePoint?: camera.Point;  // 左眼位置
  rightEyePoint?: camera.Point; // 右眼位置
  mouthPoint?: camera.Point;    // 嘴巴位置
  smileScore?: number;          // 微笑度 (0-1)
}

/**
 * 人脸检测控制器
 * 封装人脸检测的完整流程
 */
export class FaceDetectionController {
  private cameraManager: camera.CameraManager | null = null;
  private metadataOutput: camera.MetadataOutput | null = null;
  private session: camera.PhotoSession | camera.VideoSession | null = null;

  // 检测到的人脸列表
  private faces: FaceInfo[] = [];

  // 回调
  private onFacesDetected?: (faces: FaceInfo[]) => void;

  /**
   * 检查设备是否支持人脸检测
   */
  isFaceDetectionSupported(device: camera.CameraDevice): boolean {
    if (!this.cameraManager) return false;

    const capability = this.cameraManager.getSupportedOutputCapability(device);
    const metadataProfiles = capability.metadataProfiles;

    // 查找是否支持 FACE_DETECTION 类型
    return metadataProfiles.some(
      profile => profile.metadataObjectType === camera.MetadataObjectType.FACE_DETECTION
    );
  }

  /**
   * 创建人脸检测输出并加入会话
   */
  createFaceDetectionOutput(
    context: Context,
    device: camera.CameraDevice,
    session: camera.PhotoSession | camera.VideoSession
  ): boolean {
    try {
      this.cameraManager = camera.getCameraManager(context);
      this.session = session;

      // 获取元数据配置
      const capability = this.cameraManager.getSupportedOutputCapability(device);
      const faceProfile = capability.metadataProfiles.find(
        profile => profile.metadataObjectType === camera.MetadataObjectType.FACE_DETECTION
      );

      if (!faceProfile) {
        console.error('[人脸检测] 设备不支持人脸检测');
        return false;
      }

      // 创建 MetadataOutput
      this.metadataOutput = this.cameraManager.createMetadataOutput(faceProfile);

      // 注册元数据回调
      this.registerMetadataCallback();

      console.info('[人脸检测] 输出创建成功');
      return true;

    } catch (error) {
      const err = error as BusinessError;
      console.error(`[人脸检测创建失败] code: ${err.code}, msg: ${err.message}`);
      return false;
    }
  }

  /**
   * 注册元数据回调
   * 这是接收人脸检测结果的核心机制
   */
  private registerMetadataCallback(): void {
    if (!this.metadataOutput) return;

    // 元数据可用回调
    this.metadataOutput.on('metadataObjectsAvailable', (metadataObjects: camera.MetadataObject[]) => {
      this.processFaceMetadata(metadataObjects);
    });

    // 元数据输出错误回调
    this.metadataOutput.on('error', (error: BusinessError) => {
      console.error(`[人脸检测错误] code: ${error.code}, msg: ${error.message}`);
    });
  }

  /**
   * 处理人脸元数据
   * 将原始元数据转换为结构化的 FaceInfo
   */
  private processFaceMetadata(metadataObjects: camera.MetadataObject[]): void {
    this.faces = [];

    for (const metadata of metadataObjects) {
      if (metadata.type !== camera.MetadataObjectType.FACE_DETECTION) continue;

      // 从元数据中提取人脸信息
      const faceInfo: FaceInfo = {
        id: metadata.timestamp ? Math.floor(metadata.timestamp / 1000) : 0,
        rect: metadata.region,  // 人脸区域
        score: metadata.timestamp ? 0.9 : 0.5,  // 置信度
        yawAngle: 0,
        pitchAngle: 0,
        rollAngle: 0,
      };

      this.faces.push(faceInfo);
    }

    // 通知外部
    this.onFacesDetected?.(this.faces);

    if (this.faces.length > 0) {
      console.info(`[人脸检测] 检测到 ${this.faces.length} 张人脸`);
    }
  }

  /**
   * 启动人脸检测
   */
  async startFaceDetection(): Promise<boolean> {
    if (!this.metadataOutput) {
      console.error('[人脸检测] MetadataOutput 未初始化');
      return false;
    }

    try {
      await this.metadataOutput.start();
      console.info('[人脸检测] 已启动');
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[人脸检测启动失败] code: ${err.code}, msg: ${err.message}`);
      return false;
    }
  }

  /**
   * 停止人脸检测
   */
  async stopFaceDetection(): Promise<void> {
    if (!this.metadataOutput) return;

    try {
      await this.metadataOutput.stop();
      this.faces = [];
      console.info('[人脸检测] 已停止');
    } catch (error) {
      console.error(`[人脸检测停止失败] ${error}`);
    }
  }

  /**
   * 获取当前检测到的人脸列表
   */
  getDetectedFaces(): FaceInfo[] {
    return [...this.faces];
  }

  /**
   * 设置人脸检测回调
   */
  setOnFacesDetected(callback: (faces: FaceInfo[]) => void): void {
    this.onFacesDetected = callback;
  }

  /**
   * 释放资源
   */
  async release(): Promise<void> {
    if (this.metadataOutput) {
      try {
        await this.metadataOutput.stop();
        await this.metadataOutput.release();
      } catch (error) {
        console.error(`[人脸检测资源释放失败] ${error}`);
      }
      this.metadataOutput = null;
    }
  }
}

3.2 场景检测与智能参数调整

场景检测可以根据画面内容自动识别拍摄场景(夜景、美食、风景、文字等),然后自动调整相机参数,实现"智能拍摄"。

import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 场景类型枚举
 */
export enum SceneType {
  UNKNOWN = 'unknown',       // 未知
  NIGHT = 'night',           // 夜景
  SUNSET = 'sunset',         // 日落
  FOOD = 'food',             // 美食
  LANDSCAPE = 'landscape',   // 风景
  PORTRAIT = 'portrait',     // 人像
  DOCUMENT = 'document',     // 文档
  INDOOR = 'indoor',         // 室内
  SNOW = 'snow',             // 雪景
  BEACH = 'beach',           // 海滩
  FLOWER = 'flower',         // 花卉
  FIREWORKS = 'fireworks',   // 烟花
}

/**
 * 场景检测与智能调整策略
 */
export interface SceneAdjustmentStrategy {
  scene: SceneType;
  exposureCompensation: number;     // 曝光补偿
  whiteBalanceMode: camera.WhiteBalanceMode;  // 白平衡模式
  focusMode: camera.FocusMode;      // 对焦模式
  flashMode: camera.FlashMode;      // 闪光灯模式
  description: string;              // 策略描述
}

/**
 * 场景检测控制器
 * 检测场景类型并自动调整相机参数
 */
export class SceneDetectionController {
  private cameraManager: camera.CameraManager | null = null;
  private metadataOutput: camera.MetadataOutput | null = null;
  private session: camera.PhotoSession | camera.VideoSession | null = null;

  // 当前场景
  private currentScene: SceneType = SceneType.UNKNOWN;
  private sceneConfidence: number = 0;

  // 回调
  private onSceneChanged?: (scene: SceneType, confidence: number) => void;

  // 智能调整策略映射表
  private static readonly STRATEGIES: Map<SceneType, SceneAdjustmentStrategy> = new Map([
    [SceneType.NIGHT, {
      scene: SceneType.NIGHT,
      exposureCompensation: 1,
      whiteBalanceMode: camera.WhiteBalanceMode.WHITE_BALANCE_MODE_AUTO,
      focusMode: camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO,
      flashMode: camera.FlashMode.FLASH_MODE_AUTO,
      description: '夜景模式:轻微增加曝光,自动白平衡,自动闪光灯'
    }],
    [SceneType.FOOD, {
      scene: SceneType.FOOD,
      exposureCompensation: 0,
      whiteBalanceMode: camera.WhiteBalanceMode.WHITE_BALANCE_MODE_INCANDESCENT,
      focusMode: camera.FocusMode.FOCUS_MODE_AUTO,
      flashMode: camera.FlashMode.FLASH_MODE_CLOSE,
      description: '美食模式:暖色调白平衡,关闭闪光灯'
    }],
    [SceneType.LANDSCAPE, {
      scene: SceneType.LANDSCAPE,
      exposureCompensation: 0,
      whiteBalanceMode: camera.WhiteBalanceMode.WHITE_BALANCE_MODE_DAYLIGHT,
      focusMode: camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO,
      flashMode: camera.FlashMode.FLASH_MODE_CLOSE,
      description: '风景模式:日光白平衡,连续自动对焦'
    }],
    [SceneType.DOCUMENT, {
      scene: SceneType.DOCUMENT,
      exposureCompensation: 1,
      whiteBalanceMode: camera.WhiteBalanceMode.WHITE_BALANCE_MODE_AUTO,
      focusMode: camera.FocusMode.FOCUS_MODE_AUTO,
      flashMode: camera.FlashMode.FLASH_MODE_AUTO,
      description: '文档模式:增加曝光,自动对焦'
    }],
    [SceneType.PORTRAIT, {
      scene: SceneType.PORTRAIT,
      exposureCompensation: 0,
      whiteBalanceMode: camera.WhiteBalanceMode.WHITE_BALANCE_MODE_AUTO,
      focusMode: camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO,
      flashMode: camera.FlashMode.FLASH_MODE_AUTO,
      description: '人像模式:自动白平衡,连续自动对焦'
    }],
    [SceneType.SNOW, {
      scene: SceneType.SNOW,
      exposureCompensation: 1,
      whiteBalanceMode: camera.WhiteBalanceMode.WHITE_BALANCE_MODE_DAYLIGHT,
      focusMode: camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO,
      flashMode: camera.FlashMode.FLASH_MODE_CLOSE,
      description: '雪景模式:增加曝光防欠曝,日光白平衡'
    }],
  ]);

  /**
   * 初始化场景检测
   */
  init(
    context: Context,
    device: camera.CameraDevice,
    session: camera.PhotoSession | camera.VideoSession
  ): boolean {
    try {
      this.cameraManager = camera.getCameraManager(context);
      this.session = session;

      const capability = this.cameraManager.getSupportedOutputCapability(device);
      const sceneProfile = capability.metadataProfiles.find(
        profile => profile.metadataObjectType === camera.MetadataObjectType.SCENE_DETECTION
      );

      if (!sceneProfile) {
        console.warn('[场景检测] 设备不支持场景检测');
        return false;
      }

      this.metadataOutput = this.cameraManager.createMetadataOutput(sceneProfile);
      this.registerSceneCallback();

      return true;
    } catch (error) {
      console.error(`[场景检测初始化失败] ${error}`);
      return false;
    }
  }

  /**
   * 注册场景检测回调
   */
  private registerSceneCallback(): void {
    if (!this.metadataOutput) return;

    this.metadataOutput.on('metadataObjectsAvailable', (metadataObjects: camera.MetadataObject[]) => {
      for (const metadata of metadataObjects) {
        if (metadata.type !== camera.MetadataObjectType.SCENE_DETECTION) continue;

        // 解析场景类型(实际实现中需要根据元数据格式解析)
        const detectedScene = this.parseSceneFromMetadata(metadata);
        this.currentScene = detectedScene;
        this.sceneConfidence = 0.85; // 模拟置信度

        console.info(`[场景检测] 当前场景: ${detectedScene}`);
        this.onSceneChanged?.(detectedScene, this.sceneConfidence);

        // 自动应用智能调整策略
        this.applySceneStrategy(detectedScene);
      }
    });
  }

  /**
   * 从元数据解析场景类型
   * 实际实现需要根据相机输出的元数据格式进行解析
   */
  private parseSceneFromMetadata(metadata: camera.MetadataObject): SceneType {
    // 这里是简化的示例,实际需要解析 metadata 中的数据
    // 不同厂商的元数据格式可能不同
    return SceneType.UNKNOWN;
  }

  /**
   * 应用场景对应的智能调整策略
   */
  applySceneStrategy(scene: SceneType): boolean {
    if (!this.session) return false;

    const strategy = SceneDetectionController.STRATEGIES.get(scene);
    if (!strategy) {
      console.info(`[场景策略] 场景 ${scene} 无预设策略,保持当前参数`);
      return false;
    }

    try {
      // 应用曝光补偿
      this.session.setExposureCompensation(strategy.exposureCompensation);

      // 应用白平衡
      this.session.setWhiteBalanceMode(strategy.whiteBalanceMode);

      // 应用对焦模式
      this.session.setFocusMode(strategy.focusMode);

      // 应用闪光灯
      this.session.setFlashMode(strategy.flashMode);

      console.info(`[场景策略] 已应用: ${strategy.description}`);
      return true;

    } catch (error) {
      console.error(`[场景策略应用失败] ${error}`);
      return false;
    }
  }

  /**
   * 获取当前场景
   */
  getCurrentScene(): SceneType {
    return this.currentScene;
  }

  /**
   * 获取场景对应的策略
   */
  getStrategy(scene: SceneType): SceneAdjustmentStrategy | undefined {
    return SceneDetectionController.STRATEGIES.get(scene);
  }

  /**
   * 设置场景变更回调
   */
  setOnSceneChanged(callback: (scene: SceneType, confidence: number) => void): void {
    this.onSceneChanged = callback;
  }

  /**
   * 启动场景检测
   */
  async start(): Promise<boolean> {
    if (!this.metadataOutput) return false;
    try {
      await this.metadataOutput.start();
      return true;
    } catch (error) {
      console.error(`[场景检测启动失败] ${error}`);
      return false;
    }
  }

  /**
   * 停止场景检测
   */
  async stop(): Promise<void> {
    if (!this.metadataOutput) return;
    try {
      await this.metadataOutput.stop();
    } catch (error) {
      console.error(`[场景检测停止失败] ${error}`);
    }
  }

  /**
   * 释放资源
   */
  async release(): Promise<void> {
    await this.stop();
    if (this.metadataOutput) {
      try {
        await this.metadataOutput.release();
      } catch (error) {
        console.error(`[场景检测资源释放失败] ${error}`);
      }
      this.metadataOutput = null;
    }
  }
}

3.3 AI 辅助拍摄完整实现

这个示例将人脸检测和场景检测结合起来,构建一个完整的 AI 辅助拍摄系统,并提供了可视化的人脸框绘制功能。

import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * AI辅助拍摄控制器
 * 综合人脸检测、场景检测,实现智能拍摄
 */
export class AIAssistedCaptureController {
  private faceController: FaceDetectionController = new FaceDetectionController();
  private sceneController: SceneDetectionController = new SceneDetectionController();

  // AI 状态
  private aiEnabled: boolean = true;
  private currentScene: SceneType = SceneType.UNKNOWN;
  private detectedFaces: FaceInfo[] = [];

  // 回调
  private onAIStatusUpdate?: (status: AIStatus) => void;

  /**
   * 初始化 AI 辅助拍摄
   * 同时启用人脸检测和场景检测
   */
  async init(
    context: Context,
    device: camera.CameraDevice,
    session: camera.PhotoSession | camera.VideoSession
  ): Promise<boolean> {
    let success = true;

    // 初始化人脸检测
    const faceSupported = this.faceController.isFaceDetectionSupported(device);
    if (faceSupported) {
      const faceCreated = this.faceController.createFaceDetectionOutput(context, device, session);
      if (faceCreated) {
        this.faceController.setOnFacesDetected((faces) => {
          this.detectedFaces = faces;
          this.onFaceDetected(faces);
        });
        await this.faceController.startFaceDetection();
        console.info('[AI] 人脸检测已启用');
      }
    } else {
      console.info('[AI] 设备不支持人脸检测');
    }

    // 初始化场景检测
    const sceneCreated = this.sceneController.init(context, device, session);
    if (sceneCreated) {
      this.sceneController.setOnSceneChanged((scene, confidence) => {
        this.currentScene = scene;
        this.onSceneChanged(scene, confidence);
      });
      await this.sceneController.start();
      console.info('[AI] 场景检测已启用');
    }

    return success;
  }

  /**
   * 人脸检测回调处理
   * 检测到人脸后,自动调整对焦和曝光
   */
  private onFaceDetected(faces: FaceInfo[]): void {
    if (!this.aiEnabled || faces.length === 0) return;

    // 找到最大的人脸(通常是最近的人脸)
    const mainFace = faces.reduce((largest, face) => {
      const area = face.rect.width * face.rect.height;
      const largestArea = largest.rect.width * largest.rect.height;
      return area > largestArea ? face : largest;
    });

    // 计算人脸中心点(归一化坐标)
    const faceCenter: camera.Point = {
      x: mainFace.rect.topLeftX + mainFace.rect.width / 2,
      y: mainFace.rect.topLeftY + mainFace.rect.height / 2
    };

    console.info(`[AI] 主人脸中心: (${faceCenter.x.toFixed(2)}, ${faceCenter.y.toFixed(2)})`);

    // 通知状态更新
    this.notifyStatusUpdate();
  }

  /**
   * 场景检测回调处理
   * 检测到场景变化后,自动调整相机参数
   */
  private onSceneChanged(scene: SceneType, confidence: number): void {
    if (!this.aiEnabled) return;

    console.info(`[AI] 场景变更: ${scene}, 置信度: ${(confidence * 100).toFixed(0)}%`);

    // 置信度阈值:只有超过 70% 才自动调整
    if (confidence >= 0.7) {
      this.sceneController.applySceneStrategy(scene);
    }

    this.notifyStatusUpdate();
  }

  /**
   * 通知 AI 状态更新
   */
  private notifyStatusUpdate(): void {
    const status: AIStatus = {
      enabled: this.aiEnabled,
      scene: this.currentScene,
      faceCount: this.detectedFaces.length,
      faces: [...this.detectedFaces],
    };
    this.onAIStatusUpdate?.(status);
  }

  /**
   * 启用/禁用 AI 辅助
   */
  setAIEnabled(enabled: boolean): void {
    this.aiEnabled = enabled;
    console.info(`[AI] ${enabled ? '已启用' : '已禁用'}`);
    this.notifyStatusUpdate();
  }

  /**
   * 获取当前检测到的人脸
   */
  getDetectedFaces(): FaceInfo[] {
    return [...this.detectedFaces];
  }

  /**
   * 获取当前场景
   */
  getCurrentScene(): SceneType {
    return this.currentScene;
  }

  /**
   * 设置状态更新回调
   */
  setOnAIStatusUpdate(callback: (status: AIStatus) => void): void {
    this.onAIStatusUpdate = callback;
  }

  /**
   * 释放资源
   */
  async release(): Promise<void> {
    await this.faceController.release();
    await this.sceneController.release();
    console.info('[AI] 资源已释放');
  }
}

/**
 * AI 状态信息
 */
export interface AIStatus {
  enabled: boolean;
  scene: SceneType;
  faceCount: number;
  faces: FaceInfo[];
}

/**
 * UI 组件 - AI辅助拍摄页面
 * 实时显示人脸框和场景信息
 */
@Entry
@Component
struct AIAssistedCapturePage {
  private aiController: AIAssistedCaptureController = new AIAssistedCaptureController();

  @State aiEnabled: boolean = true;
  @State faceCount: number = 0;
  @State currentScene: string = '未知';
  @State faceRects: FaceRectUI[] = [];

  aboutToDisappear(): void {
    this.aiController.release();
  }

  build() {
    Stack() {
      // 预览区域
      Column() {
        Text('相机预览区域')
          .width('100%')
          .height(500)
          .textAlign(TextAlign.Center)
          .backgroundColor('#1a1a1a')
          .fontColor('#666')
          .fontSize(16)
      }

      // 人脸框叠加层
      ForEach(this.faceRects, (rect: FaceRectUI, index: number) => {
        Column() {
          // 人脸矩形框
          Border() {
            // 用矩形框标注人脸位置
          }
        }
        .width(`${rect.width * 100}%`)
        .height(`${rect.height * 100}%`)
        .position({
          x: `${rect.x * 100}%`,
          y: `${rect.y * 100}%`
        })
        .borderWidth(2)
        .borderColor('#00FF88')
        .borderRadius(4)
      })

      // 顶部信息栏
      Column() {
        Row() {
          // AI 状态指示
          Circle()
            .width(8)
            .height(8)
            .fill(this.aiEnabled ? '#00FF88' : '#666')

          Text(this.aiEnabled ? 'AI 已启用' : 'AI 已关闭')
            .fontSize(12)
            .fontColor('#fff')
            .margin({ left: 6 })

          Blank()

          // 场景信息
          Text(`场景: ${this.currentScene}`)
            .fontSize(12)
            .fontColor('#E8A838')
            .padding({ left: 8, right: 8, top: 4, bottom: 4 })
            .backgroundColor('rgba(0,0,0,0.5)')
            .borderRadius(10)

          // 人脸数量
          if (this.faceCount > 0) {
            Text(`人脸: ${this.faceCount}`)
              .fontSize(12)
              .fontColor('#4A90D9')
              .padding({ left: 8, right: 8, top: 4, bottom: 4 })
              .backgroundColor('rgba(0,0,0,0.5)')
              .borderRadius(10)
              .margin({ left: 6 })
          }
        }
        .width('100%')
        .padding({ left: 15, right: 15, top: 10 })
      }
      .alignItems(HorizontalAlign.Start)

      // 底部操作区
      Column() {
        Row() {
          // AI 开关
          Button(this.aiEnabled ? '关闭AI' : '开启AI')
            .fontSize(14)
            .backgroundColor(this.aiEnabled ? '#4A90D9' : '#666')
            .onClick(() => {
              this.aiEnabled = !this.aiEnabled;
              this.aiController.setAIEnabled(this.aiEnabled);
            })

          // 场景策略提示
          if (this.currentScene !== '未知') {
            Text(this.getSceneTip(this.currentScene))
              .fontSize(12)
              .fontColor('#fff')
              .padding(8)
              .backgroundColor('rgba(0,0,0,0.6)')
              .borderRadius(8)
              .margin({ left: 10 })
          }
        }
        .width('90%')
        .justifyContent(FlexAlign.Center)
        .margin({ bottom: 20 })
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
    .alignContent(Alignment.Top)
  }

  /**
   * 获取场景提示文本
   */
  private getSceneTip(scene: string): string {
    const tips: Record<string, string> = {
      'night': '🌙 夜景模式已激活',
      'food': '🍽️ 美食模式已激活',
      'landscape': '🏔️ 风景模式已激活',
      'portrait': '👤 人像模式已激活',
      'document': '📄 文档模式已激活',
      'snow': '❄️ 雪景模式已激活',
    };
    return tips[scene] || `📸 ${scene}模式`;
  }
}

/**
 * 人脸框 UI 数据
 */
interface FaceRectUI {
  x: number;       // 归一化 X 坐标
  y: number;       // 归一化 Y 坐标
  width: number;   // 归一化宽度
  height: number;  // 归一化高度
}

四、踩坑与注意事项

4.1 元数据输出必须加入 Session

:创建了 MetadataOutput 但忘记加入 Session,导致回调永远不触发。

:MetadataOutput 和 PreviewOutput、PhotoOutput 一样,必须加入 Session 才能工作。在创建 Session 时就要把 MetadataOutput 传进去。

// ❌ 错误:MetadataOutput 没有加入 Session
const session = cameraManager.createPhotoSession(cameraInput, previewOutput, photoOutput);
// MetadataOutput 被遗漏了!

// ✅ 正确:创建 Session 时包含 MetadataOutput
const session = cameraManager.createPhotoSession(
  cameraInput,
  previewOutput,
  photoOutput,
  metadataOutput  // 加入元数据输出
);

4.2 人脸检测的坐标是归一化的

:拿到人脸矩形坐标后,直接当像素坐标用,结果人脸框画到了屏幕外面。

:元数据中的坐标是归一化的(0-1),需要乘以预览区域的宽高才是像素坐标:

// 归一化坐标转像素坐标
const pixelX = face.rect.topLeftX * previewWidth;
const pixelY = face.rect.topLeftY * previewHeight;
const pixelWidth = face.rect.width * previewWidth;
const pixelHeight = face.rect.height * previewHeight;

4.3 元数据回调频率很高

:在元数据回调中做了太多计算,导致 UI 卡顿。

:元数据回调的频率可能高达 30次/秒(每帧一次),回调中应该只做轻量级的数据提取,把耗时操作放到异步任务中。

// ❌ 错误:在回调中做耗时操作
metadataOutput.on('metadataObjectsAvailable', (metadata) => {
  // 耗时的图像处理...
  processImage(metadata); // UI 卡顿!
});

// ✅ 正确:回调中只提取数据,耗时操作异步处理
metadataOutput.on('metadataObjectsAvailable', (metadata) => {
  const faces = extractFaceInfo(metadata); // 轻量级提取
  this.updateFaceUI(faces); // 快速更新 UI
  // 耗时操作放到异步
  setTimeout(() => this.analyzeFaces(faces), 0);
});

4.4 场景检测的置信度

:场景检测的结果不稳定,一会儿是"夜景",一会儿变成"室内",频繁切换导致参数来回跳变。

:设置置信度阈值(建议 70% 以上),并且加入时间窗口过滤——只有连续 N 帧检测到同一场景才确认切换。

// 场景稳定性过滤
private sceneBuffer: SceneType[] = [];
private readonly SCENE_BUFFER_SIZE = 5; // 连续5帧确认

private onSceneDetected(scene: SceneType, confidence: number): void {
  this.sceneBuffer.push(scene);
  if (this.sceneBuffer.length > this.SCENE_BUFFER_SIZE) {
    this.sceneBuffer.shift();
  }

  // 只有连续 N 帧都是同一场景才确认
  const isStable = this.sceneBuffer.length >= this.SCENE_BUFFER_SIZE &&
    this.sceneBuffer.every(s => s === scene);

  if (isStable && confidence >= 0.7) {
    this.confirmSceneChange(scene);
  }
}

4.5 并非所有设备都支持元数据

:在低端设备上调用 createMetadataOutput() 直接崩溃。

:创建前一定要先检查设备是否支持对应的元数据类型:

const capability = cameraManager.getSupportedOutputCapability(device);
const hasFaceDetection = capability.metadataProfiles.some(
  p => p.metadataObjectType === camera.MetadataObjectType.FACE_DETECTION
);

if (!hasFaceDetection) {
  console.warn('当前设备不支持人脸检测');
  // 降级处理:使用纯软件人脸检测或跳过
  return;
}

4.6 MetadataOutput 和 Session 的生命周期

:Session 释放后,MetadataOutput 的回调还在触发,导致访问已释放的对象。

:释放 Session 前,先停止并释放 MetadataOutput,然后注销回调。

// 正确的释放顺序
await metadataOutput.stop();
metadataOutput.off('metadataObjectsAvailable');
await metadataOutput.release();
await session.stop();
await session.release();

五、HarmonyOS 6 适配

5.1 API 变更

变更项 HarmonyOS 5.0 HarmonyOS 6.0
人脸检测 基础人脸矩形 新增人脸关键点(106点)、表情识别
物体检测 不支持 新增 OBJECT_DETECTION 类型
场景检测 基础场景分类 新增 20+ 场景类型,置信度更精确
人体姿态 不支持 新增 BODY_POSE 人体骨骼检测
手势检测 不支持 新增 HAND_GESTURE 手势识别

5.2 迁移要点

  1. 人脸关键点:6.0 中人脸检测新增了 106 个关键点输出,可以精确获取五官位置:
// HarmonyOS 6.0 人脸关键点
metadataOutput.on('faceLandmarksAvailable', (landmarks: camera.FaceLandmarks) => {
  // 106 个关键点,包含眉毛、眼睛、鼻子、嘴巴、轮廓等
  const leftEyeCenter = landmarks.getPoint(camera.FaceLandmarkType.LEFT_EYE_CENTER);
  const rightEyeCenter = landmarks.getPoint(camera.FaceLandmarkType.RIGHT_EYE_CENTER);
  const noseTip = landmarks.getPoint(camera.FaceLandmarkType.NOSE_TIP);
  const mouthCenter = landmarks.getPoint(camera.FaceLandmarkType.MOUTH_CENTER);
});
  1. 物体检测:6.0 新增了物体检测能力,可以识别画面中的物体类别:
// HarmonyOS 6.0 物体检测
const objectProfile = capability.metadataProfiles.find(
  p => p.metadataObjectType === camera.MetadataObjectType.OBJECT_DETECTION
);
const metadataOutput = cameraManager.createMetadataOutput(objectProfile);

metadataOutput.on('metadataObjectsAvailable', (objects) => {
  objects.forEach(obj => {
    console.info(`[物体] 类别: ${obj.category}, 置信度: ${obj.confidence}`);
  });
});
  1. 人体姿态检测:6.0 新增了人体骨骼关键点检测,可以用于体感交互:
// HarmonyOS 6.0 人体姿态检测
const bodyProfile = capability.metadataProfiles.find(
  p => p.metadataObjectType === camera.MetadataObjectType.BODY_POSE
);

六、总结

mindmap
  root((相机元数据))
    人脸检测
      MetadataOutput + FACE_DETECTION
      归一化坐标需转换
      回调频率高需轻量处理
      检测到人脸自动对焦
    场景检测
      SCENE_DETECTION 类型
      置信度阈值过滤
      时间窗口防抖
      自动调整曝光/白平衡/对焦
    物体检测
      6.0新增 OBJECT_DETECTION
      识别物体类别和位置
      可用于智能分类
    AI辅助拍摄
      综合人脸+场景检测
      自动调整相机参数
      场景策略映射表
      可视化人脸框
    踩坑要点
      MetadataOutput必须加入Session
      坐标归一化需转换
      回调中不做耗时操作
      设备能力检查前置
      生命周期同步释放

核心记忆口诀

元数据加 Session,坐标要归一;回调轻量做,场景加过滤;AI 综合判,策略自动调。


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。