在移动应用开发中,抠图(即从图片中提取特定主体)是一个非常高频且具有挑战性的需求。在过去,这通常需要依赖服务端庞大的 AI 模型或集成复杂的第三方 SDK。

而在 HarmonyOS Next 中,华为通过 Core Vision Kit(基础视觉服务) 将这一能力下沉到了系统层面,提供了主体分割(Subject Segmentation) API。开发者只需几行代码,即可在端侧实现高精度、低延时的自动抠图功能。

本文将结合本项目中的**“卡通云合影”**功能,详细介绍如何在鸿蒙应用中集成和使用主体分割能力。

1. 场景介绍:卡通云合影

我们发现,虽然大家在群里都很活跃,但是在线下实际上可能都没有见过面,现代互联网的发展,让更多人之间的关系成为了“网游”。我们说线下好友聚会能有合照,那网友咋办?所有我们开发了一款App,叫“卡通云合影”(上架中)。在我们的“卡通云合影”应用中,有一个核心功能是**“抠图选人”**。

交互流程如下:

  1. 用户从相册选择一张包含人物的照片。
  2. App 自动识别照片中的人物,并将他们从背景中分离出来(抠图)。
  3. 用户点击选择需要保留的人像。
  4. 被选中的人像将被合成到卡通背景中。

为了实现第 2 步的“自动抠图”,我们正是使用了 HarmonyOS 的主体分割能力。

2. 核心 API 概览

主体分割能力主要由 @kit.CoreVisionKit 模块提供。

2.1 关键接口

  • subjectSegmentation.init(): 初始化分割服务。
  • subjectSegmentation.doSegmentation(visionInfo, config): 执行分割操作。
  • subjectSegmentation.release(): 释放资源(建议在不再使用时调用)。

2.2 关键参数

  • VisionInfo: 包含待处理的图片信息,主要是 pixelMap
  • SegmentationConfig: 配置分割行为。
    • maxCount: 最大分割主体数量。
    • enableSubjectDetails: 是否输出主体的详细信息(如位置框)。
    • enableSubjectForegroundImage: 关键参数,设置为 true 才会返回抠好的前景图(PixelMap)。

3. 实战代码解析

以下代码片段摘自本项目中的 entry/src/main/ets/pages/PhotoStep2Page.ets,展示了完整的调用流程。

3.1 引入模块

首先,我们需要引入 Core Vision Kit 和 Image Kit:

import { subjectSegmentation } from '@kit.CoreVisionKit';
import { image } from '@kit.ImageKit';

3.2 实现抠图方法

我们在页面组件中定义了一个 segmentPerson 方法,用于处理图片分割。

  /**
   * 执行人像分割
   * 该方法负责调用 Core Vision Kit 的主体分割能力,识别图片中的人像主体。
   * 
   * @param imageId 图片的唯一标识ID,用于在 selectedImages 数组中定位目标图片
   */
  private async segmentPerson(imageId: string): Promise<void> {
    // 1. 查找目标图片
    // 遍历 selectedImages 数组,找到 ID 匹配的图片对象索引
    const imageIndex = this.selectedImages.findIndex(img => img.id === imageId)
    // 如果未找到(例如图片已被删除),直接返回
    if (imageIndex === -1) return

    try {
      // 开启处理状态,UI 上会显示 Loading 动画
      this.isProcessing = true
      
      // 2. 初始化主体分割服务
      // 这是一个异步操作,用于加载底层 AI 模型和资源。
      // 建议放在 try-catch 中,以捕获初始化失败的情况(如设备不支持)。
      await subjectSegmentation.init()
      
      // 3. 构建 VisionInfo 对象
      // VisionInfo 是 AI 能力的输入结构体。
      // 这里将从相册选择并转换好的 PixelMap (this.selectedImages[imageIndex].originalImage) 传入。
      const visionInfo: subjectSegmentation.VisionInfo = {
        pixelMap: this.selectedImages[imageIndex].originalImage
      }

      // 4. 配置 SegmentationConfig
      // SegmentationConfig 用于控制分割行为。
      const config: subjectSegmentation.SegmentationConfig = {
        maxCount: 10, // 最大分割主体数量:限制最多识别 10 个人物,避免过多干扰项
        enableSubjectDetails: true, // 启用主体详情:设置为 true 后,返回结果包含位置信息等详细数据
        enableSubjectForegroundImage: true // 【关键参数】启用前景图输出:必须设置为 true,否则结果中不会包含抠好的人像 PixelMap
      }

      // 5. 调用核心接口 doSegmentation
      // 这是一个耗时操作,执行实际的 AI 推理和图像分割。
      const result: subjectSegmentation.SegmentationResult = 
        await subjectSegmentation.doSegmentation(visionInfo, config)

      // 6. 处理分割结果
      // 检查结果对象及其 subjectDetails 数组是否存在且非空
      if (result && result.subjectDetails && result.subjectDetails.length > 0) {
        const subjects: image.PixelMap[] = []
        
        // 遍历所有识别出的主体
        for (const d of result.subjectDetails) {
          // 如果该主体包含前景图(即抠好的透明背景图),则加入列表
          if (d.foregroundImage) {
            subjects.push(d.foregroundImage)
          }
        }

        // 7. 更新 UI 数据
        if (subjects.length > 0) {
          // 获取原始图片对象
          const base = this.selectedImages[imageIndex]
          // 创建 selectedImages 的副本,遵循不可变数据更新原则(尽管这里直接赋值给了状态变量)
          const updated = [...this.selectedImages]
          
          // 将识别出的第一个主体更新到原位置
          // 这里我们将原图对象更新为包含分割后图像(segmentedImage)的新对象
          updated[imageIndex] = {
            id: base.id,
            originalImage: base.originalImage,
            segmentedImage: subjects[0], // 设置第一个分割结果
            isSelected: false // 默认不选中
          }

          // 如果识别出多个主体(例如合影中有 2 个人),
          // 我们将剩余的主体作为新的可选项添加到列表中,方便用户单独选择。
          for (let i = 1; i < subjects.length; i++) {
            updated.push({
              id: `${base.id}_${i}`, // 生成新的唯一 ID
              originalImage: base.originalImage, // 共享原图引用
              segmentedImage: subjects[i], // 设置对应的分割结果
              isSelected: false
            })
          }
          
          // 更新状态变量,触发 UI 刷新
          this.selectedImages = updated
          hm.toast(`人像分割完成,共检测到 ${subjects.length} 个主体`)
        } else {
          // 虽然识别到了主体信息,但没有成功提取出前景图
          hm.toast('未检测到人像')
        }
      } else {
        // 未识别到任何主体
        hm.toast('未检测到人像')
      }
    } catch (error) {
      // 捕获并处理所有异常,如初始化失败、推理超时等
      console.error('人像分割失败:', error)
      hm.toast('人像分割失败')
    } finally {
      // 无论成功还是失败,都必须关闭 Loading 状态
      this.isProcessing = false 
      // 最佳实践:建议在此处调用 subjectSegmentation.release() 释放 AI 引擎占用的内存资源
      // subjectSegmentation.release() 
    }
  }

3.3 结果展示

分割成功后,result.subjectDetails[i].foregroundImage 就是我们需要的透明背景 PixelMap。在 ArkUI 中,可以直接通过 Image 组件显示它:

// 这里的 segmentedImage 就是上面获取到的 foregroundImage
Image(personImage.segmentedImage)
  .width(80)
  .height(80)
  .objectFit(ImageFit.Cover)
  .border({ 
    width: personImage.isSelected ? 3 : 1, 
    color: personImage.isSelected ? $r('app.color.primary_color') : $r('app.color.border_color') 
  })

4. 最佳实践与注意事项

结合官方文档和项目开发经验,以下几点需要特别注意:

  1. 图片质量与比例

    • 输入图片建议分辨率在 720p 以上,以保证分割精度。
    • 图片宽高应在 [20px, 9000px] 范围内。
    • 高宽比限制:建议高宽比小于 3:1。如果图片过于细长(如长截图),可能会导致识别失败或精度下降。
  2. 主体占比

    • 文档指出,物体占比不小于原图大小的 5‰ 才会被认定为主体。如果人物太小(例如远景合影),可能无法被识别。
  3. 异步处理

    • 图像处理是耗时操作,务必使用 async/awaitPromise,并在 UI 上给予用户 Loading 提示(如本项目中的 isProcessing 状态),防止应用假死。
  4. 资源管理

    • PixelMap 占用内存较大。在处理完逻辑后,对于不再需要的临时 PixelMap,建议及时释放内存。
    • 虽然本项目示例中未显式展示 release,但规范的做法是在业务流程结束或页面销毁(aboutToDisappear)时调用 subjectSegmentation.release()
  5. 模拟器限制

    • 该 AI 能力通常依赖端侧 NPU,当前不支持在模拟器上运行。开发调试时请务必使用真机。

5. 总结

通过集成 HarmonyOS 的主体分割能力,我们在“卡通云合影”项目中轻松实现了智能抠图功能,极大地提升了用户体验,且无需引入庞大的第三方库。这体现了 HarmonyOS “原生智能”的优势——让 AI 能力像调用普通 API 一样简单。

6. 最终效果

微信图片_20260123002502_678_203.jpg

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐