我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

目标很直白:把摄像头采集到的画面,实时(或按需)丢给模型推理,然后把“它是什么”变成用户能一眼看懂的标签与置信度。下面按你的大纲来:图像采集 → 模型推理 → 结果展示;技术栈选 Camera Kit 做取流,MindSpore Lite 做端侧推理,并用一个我们自定义的 ImageClassifier 封装器承接模型加载与预测(你也可以把这个类就当作“ImageClassifier”能力)。涉及到的官方能力与 API 我都标了出处,便于你对照文档实现与扩展。(华为开发者)

架构一眼懂

  • Camera@kit.CameraKit 建会话 → 绑定 Preview 输出(给 UI)+ ImageReceiver(给推理)。(华为开发者)
  • Preprocess@kit.ImageKitImage/PixelMap 统一到模型输入尺寸,做 resize / color convert / normalize。(华为开发者)
  • Classifier(ImageClassifier):基于 @ohos.ai.mindSporeLite;负责 模型加载、tensor 填充、predict、Top-K。提供 CPU → NNRt/NPU 的可选加速(视设备能力与模型而定)。(华为开发者)
  • UI 展示:ArkUI 页面上显示相机预览 + 右上角 Top-K 标签;点击卡片可展开完整标签列表或“历史识别记录”。

一、图像采集:稳定、低延迟地拿到像素

1)权限与能力声明(必做)

  • ohos.permission.CAMERA(拍摄)+ 如需落盘到媒体库,再加媒体库读写权限。
  • 机能来自 Camera Kit;预览/拍照/录像都走同一会话体系。(华为开发者)

2)会话搭建思路

  1. 创建 CameraManager,选择后置/前置摄像头。
  2. 创建 PreviewOutput(给 UI 组件 Surface)与 ImageReceiver(给推理线程吃帧)。
  3. 组装 Sessionstart() 即可跑预览并回调帧。

HarmonyOS 的相机最佳实践里有“拍照与取流”的完整流程,ImageReceiver 则由 Image Kit 提供,它可以从 Surface 中接收帧(YUV/JPEG)。(华为开发者)

关键片段(ArkTS,思路示例)

import { camera } from '@kit.CameraKit';
import { image } from '@kit.ImageKit';

let camMgr: camera.CameraManager;
let session: camera.CaptureSession;
let preview: camera.PreviewOutput;
let receiver: image.ImageReceiver;

async function setupCamera(surfaceForUI: any /* e.g. XComponent surface */) {
  camMgr = camera.getCameraManager();
  const cam = camMgr.getSupportedCameras()[0]; // 简化演示:挑第一个
  // 1) 预览输出(绑定到 UI 的 Surface)
  preview = camMgr.createPreviewOutput(surfaceForUI);

  // 2) 推理用的 ImageReceiver(从摄像头会话拿帧)
  receiver = image.createImageReceiver({
    width: 640, height: 480, // 取个推理友好分辨率
    format: image.ImageFormat.YUV_420_SP, // 常见 YUV
    capacity: 4
  });
  const inferSurface = receiver.getReceivingSurface();

  // 3) 组装会话
  session = camMgr.createCaptureSession();
  await session.beginConfig();
  await session.addInput(camMgr.createCameraInput(cam));
  await session.addOutput(preview);
  await session.addOutput(camMgr.createPreviewOutput(inferSurface)); // 给推理流
  await session.commitConfig();
  await session.start();

  // 4) 帧回调(你可以按 5~10FPS 抽帧推理)
  receiver.on('imageArrival', () => onImageArrival());
}

ImageReceiver / ImageFormat / getReceivingSurface() 等能力见 Image Kit;预览/拍照 Session/Output 能力见 Camera Kit。(华为开发者)

二、模型推理(ImageClassifier):装进 MindSpore Lite,跑在端侧

1)为什么选 MindSpore Lite?

HarmonyOS 内置的端侧推理引擎,支持 ArkTS 直调;提供 模型加载、张量输入/输出、预测 的统一接口,并能利用 CPU/NNRt/NPU 等后端(取决于设备与模型)。官方还提供了图像分类 Codelab 示例与开源 Demo,直接拿来改就行。(华为开发者)

文档要点:ArkTS 里通过 @ohos.ai.mindSporeLite 加载 .ms 模型,设置上下文(线程数/设备类型),predict() 返回输出张量,随后解析为概率分布并取 Top-K。(华为开发者)

2)工程准备

  • 把模型(如 mobilenetv2.ms)与 labels.txt 放到 resources/rawfile

  • syscap.json 里加上

    { "development": { "addedSysCaps": ["SystemCapability.AI.MindSporeLite"] } }
    
  • UI 层引入 @kit.MindSporeLiteKit(ArkTS 绑定)。(华为开发者)

3)封装一个 ImageClassifier(加载→预处理→预测→Top-K)

// classifier/ImageClassifier.ets
import { mindSporeLite as msl } from '@kit.MindSporeLiteKit';
import { image } from '@kit.ImageKit';

type TopK = { label: string; prob: number };

export class ImageClassifier {
  private model?: msl.Model;
  private labels: string[] = [];
  private inputW = 224; private inputH = 224; // 以 MobileNet 为例
  private mean = [0.485, 0.456, 0.406];
  private std  = [0.229, 0.224, 0.225];

  async load(modelBuf: ArrayBuffer, labelsText: string) {
    const ctx = msl.createContext({ // 线程、后端按需配置
      deviceType: msl.DeviceType.CPU, // 设备若支持 NNRt/NPU,可切换
      threadNum: 2
    });
    this.model = await msl.loadModelFromBuffer(modelBuf, ctx);
    this.labels = labelsText.split('\n').filter(Boolean);
  }

  // 把 PixelMap 预处理成 Float32Array(NHWC) 或 NCHW(看模型)
  async preprocess(pix: image.PixelMap): Promise<Float32Array> {
    const resized = await pix.scaleToFit({ // ImageKit 支持缩放/裁剪
      size: { width: this.inputW, height: this.inputH }
    });
    const info = resized.getImageInfo();
    const pixels = new Uint8Array(info.size.width * info.size.height * 4);
    await resized.readPixelsToBuffer(pixels.buffer); // RGBA

    const out = new Float32Array(this.inputW * this.inputH * 3);
    for (let i = 0, o = 0; i < pixels.length; i += 4) {
      const r = pixels[i]   / 255;
      const g = pixels[i+1] / 255;
      const b = pixels[i+2] / 255;
      out[o++] = (r - this.mean[0]) / this.std[0];
      out[o++] = (g - this.mean[1]) / this.std[1];
      out[o++] = (b - this.mean[2]) / this.std[2];
    }
    return out;
  }

  async predictNHWC(input: Float32Array, topK = 3): Promise<TopK[]> {
    if (!this.model) throw new Error('model not loaded');
    const inputs = this.model.getInputs();
    // NHWC: [1, H, W, 3]
    inputs[0].setData(input.buffer);
    const outputs = await this.model.predict(inputs);
    const logits = new Float32Array(outputs[0].getData());
    return this.softmaxTopK(logits, topK);
  }

  private softmaxTopK(logits: Float32Array, k: number): TopK[] {
    const max = Math.max(...logits);
    const exps = logits.map(v => Math.exp(v - max));
    const sum  = exps.reduce((a,b)=>a+b, 0);
    const probs = exps.map(v => v / sum);
    // 取 Top-K
    const idx = [...probs.keys()].sort((a,b)=>probs[b]-probs[a]).slice(0, k);
    return idx.map(i => ({ label: this.labels[i] ?? `#${i}`, prob: probs[i] }));
  }
}

以上流程与官方“ArkTS 实现图像分类”的开发步骤一致:加载模型 → 准备输入张量 → 调用 predict() → 解析输出。你也可以直接参考开源 Demo 的目录结构与代码组织。(华为开发者)

4)把相机帧喂给分类器

ImageReceiver 拿到的 Image 转成 PixelMap(或先转 JPEG/ARGB)后做预处理,再调用 predictNHWC()。建议抽帧(如每秒 5 次),并把推理放到 TaskPool/Worker,避免占用 UI 线程。

import { image } from '@kit.ImageKit';
import { ImageClassifier } from './classifier/ImageClassifier';

const clf = new ImageClassifier();
let busy = false;

async function onImageArrival() {
  if (busy) return; // 抽帧+限流
  busy = true;
  try {
    const img: image.Image = await receiver.readImage();
    const pix = await image.createPixelMap(img); // 简化:按机型可能需要先做YUV->RGB
    const input = await clf.preprocess(pix);
    const top3 = await clf.predictNHWC(input, 3);
    updateUI(top3); // 在页面状态里更新
    img.release(); pix.release();
  } catch (e) {
    // 日志&容错
  } finally {
    busy = false;
  }
}

三、结果展示:把“模型话术”变成人话

1)UI 设计要点

  • 叠加层:在预览画面上方用 Positioned/Row 贴一个半透明卡片,展示 Top-K(标签 + 置信度条)
  • 交互:点击卡片可展开完整概率列表;新增“截图/保存结果到相册”;
  • 状态:用 @State 缓存最近 N 次结果,支持“结果历史”。

页面示意(片段)

@Entry
@Component
struct LiveClassifyPage {
  @State topk: {label: string; prob: number}[] = [];
  build() {
    Stack() {
      // 1) 相机预览(XComponent 或 ImageSurface)
      CameraPreview() // 这里是你封装的预览组件
      // 2) 结果叠加
      Column() {
        ForEach(this.topk, (it) => {
          Row() {
            Text(it.label).fontSize(16).layoutWeight(1)
            Progress({ value: Math.round(it.prob * 100) })
              .width('40%')
          }
          .padding(8).backgroundColor('#66000000').borderRadius(12)
          .margin({ bottom: 6 })
        })
      }.align(Alignment.TopEnd).padding(12)
    }
  }
}

端到端打通(落地 checklist)

  1. 相机:按最佳实践创建 Session,绑定 UI 预览 + ImageReceiver,注册 imageArrival 回调。(华为开发者)
  2. 图像处理:用 Image Kit 把帧转 PixelMapresize/normalize,注意 YUV→RGB 的转换策略(不同机型输出格式不同)。(华为开发者)
  3. 推理@ohos.ai.mindSporeLite 加载 .ms,设置上下文,predict() 得到 logits,做 softmax/Top-K。(华为开发者)
  4. 展示:UI 叠加层展示 Top-K,提供“历史记录/截图/分享”。
  5. 性能:抽帧(5~10FPS)+ Worker 推理 + 预热模型(首帧前先跑一次 dummy)+ 输入尺寸与后端(CPU/NNRt)调优。(华为开发者)

性能与稳定性小抄

  • 吞吐:优先 减小输入分辨率(如 224×224 / 256×256),抽帧限频;
  • 延迟:模型预热;在可用设备上启用 NNRt/NPU 后端(若模型兼容);(华为开发者)
  • 内存:复用 PixelMap / 预分配 Float32Array
  • 容错:相机热插拔、权限拒绝、ImageReceiver 容量溢出时的降级处理;
  • 模型:用量化/裁剪的轻量模型(MobileNet、ShuffleNet、EfficientNet-Lite 等);MindSpore Lite 支持多框架模型经转换为 .ms。(华为开发者)

可扩展:从“单标签”到“智能标签系统”

  • 多任务:在同一帧上并行跑分类 + 目标检测(检测到的 bbox 再跑局部分类);
  • 本地标签库:允许业务上传/下发 labels.txt
  • 业务规则:给标签绑定跳转动作/推荐卡片(例如识别到“咖啡”,弹出“附近咖啡店”);
  • 离线包:模型与标签随版本迭代,放 rawfile 或首次启动下发到 filesDir

参考与可复用资源(强烈建议对照实现)

  • Camera Kit:相机会话/输出/最佳实践(ArkTS)。(华为开发者)
  • Image KitImageReceiver/PixelMap/scale/读写像素 等(ArkTS)。(华为开发者)
  • MindSpore Lite(ArkTS)图像分类指南:从模型加载到推理完整流程;含接口说明与示例。(华为开发者)
  • 官方开源示例:MindSporeLiteArkTS(图片分类 Demo)。(Gitee)
  • MindSpore Lite Kit 简介:后端加速、模型格式/转换与端侧优化。(华为开发者)

(未完待续)

Logo

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

更多推荐