【共创稿事节】鸿蒙HarmonyOS7.0端侧AI新能力-图像超分 · 画质增强:基于 Core Vision Kit 的端侧 4 倍高清重建

全程在 DevEco Studio 26(HarmonyOS 7 / API 26)+ HarmonyOS 7 真机上实测跑通。
本文所有截图均来自真机实拍运行画面,代码均可直接复现。

低清照片放大就糊,是所有相册类应用的共同痛点。HarmonyOS 7 在 Core Vision Kit 中开放了 imageSuperResolution 图像超分能力:输入一张低清图,端侧 NPU 直接输出 4 倍分辨率的重建结果,图片全程不出设备。本文用一个最小可运行的「前后对比」案例,讲清楚从工程搭建、API 接入到交互实现的完整链路,并附上踩坑实录。
在这里插入图片描述

先看成品。 下面是同一张城市夜景在分割滑杆拖到 20% 时的同屏对比:左侧 20% 是 160×120 的低清原图,右侧 80% 是端侧重建后的 640×480 高清结果——月亮边缘的锯齿、建筑窗户的光点、星空的层次,一眼就能看出差别:

开篇效果对比
开篇效果对比:左侧 20% 为 160×120 原图,右侧 80% 为 640×480 超分结果


在这里插入图片描述

一、能力规格:先弄清楚边界再动手

接入前先看官方规格,避免在错误的预期上写代码:

维度说明
能力归属@kit.CoreVisionKit 下的 imageSuperResolution(分析器模式)
接口起始版本26.0.0(HarmonyOS 7 / API 26),仅 Stage 模型
支持设备Phone / PC·2in1 / Tablet
放大倍率像素同步放大 4 倍(固定倍率,不可配置)
输入限制仅支持单张图片;输入原图最大 2048×2048,超限必须先等比缩放
运行位置端侧 NPU/XPU 异构推理,图片不出设备
当前阶段Beta,效果以真机实测为准

三个关键点:

  1. 它是「分析器(Analyzer)模式」,和二维码识别、人脸检测等 Vision 能力一致:create → process → destroy 三段式;
  2. 实例是重推理对象,官方明确建议不要常驻内存,生命周期结束要 destroy();
  3. 端侧 NPU 推理依赖真机硬件(Phone / PC·2in1 / Tablet),成果与耗时都必须以真机实测为准——这决定了本文案例的接入方式(见下一节)。

二、总体设计:真实能力 + 示例数据双实现

端侧超分是 NPU 推理能力,最终一定跑在真机上;但在真机调试之外,产品评审、交互走查同样需要一组可复现的稳定素材。因此工程把「超分能力」抽象成一层服务,做两个实现:

entry/src/main/ets/
├── entryability/EntryAbility.ets
├── common/
│   ├── ImageSuperResolutionService.ets        # 示例数据版:内置成对示例图,接口语义与真实实现一致
│   └── RealImageSuperResolutionService.ets    # 真实能力版:真机接 Core Vision Kit 的完整链路
└── pages/Index.ets                            # 前后对比 UI(分割滑杆)

两个实现公开接口语义完全一致(create → process → destroy),页面只依赖这层语义。从示例数据切到真机真实推理,只需要换一行 getInstance()。

示例数据版的思路:内置 4 组「低清 / 高清」成对示例图(rawfile/demo/ 下 low_* 与 hd_*),按素材 id 稳定生成推理耗时(900–1400ms 量级)与清晰度提升(30%–55%),保证每次演示的动画、指标卡、滑杆对比表现一致、可复现:

// ImageSuperResolutionService.ets(节选)
// 模拟端侧 4 倍超分推理耗时,便于演示处理动画
const cost: number = 900 + (this.pseudo(demoId) % 500);
await this.delay(cost);

const result: SrResult = {
  demoId: demo.id,
  lowPath: demo.low, hdPath: demo.hd,
  widthBefore: demo.lowW,  heightBefore: demo.lowH,   // 160×120
  widthAfter:  demo.highW, heightAfter: demo.highH,   // 640×480
  costMs: cost,
  clarityGain: 30 + (this.pseudo(demoId + 'g') % 25)
};

4 组示例素材中的「花朵特写」低清原图(160×120),花瓣边缘呈块状色斑、纹理糊成一片,正是超分最典型的输入场景:

花朵特写低清原图
示例素材「花朵特写」低清原图(160×120)


三、工程搭建踩坑实录(DevEco Studio 26 实测)

这部分是本文最"值钱"的内容——本工程是在 DevEco Studio 26.0(API 26)上从零同步、构建、跑通的,中间踩的坑都有普遍性,逐条列出。

3.1 工程缺少 Hvigor 三件套 → Sync 直接失败

DevEco 打开工程第一步是 Hvigor 同步。如果工程里没有以下三个文件,Sync 必然失败(Schema validate failed 或工程不被识别为 HarmonyOS 工程):

  • 根目录 hvigorfile.ts
  • 模块目录 entry/hvigorfile.ts
  • hvigor/hvigor-config.json5

DevEco 26 采用 all-in-one 安装,内置 hvigor 插件,两个 hvigorfile 只需引用内置插件即可:

// hvigorfile.ts(根目录)
import { appTasks } from '@ohos/hvigor-ohos-plugin';

export default {
    system: appTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
    plugins:[]
}
// entry/hvigorfile.ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin';

export default {
    system: hapTasks,
    plugins:[]
}

hvigor/hvigor-config.json5 的 dependencies 保持为空——官方明确提示:当前 DevEco 使用一体化安装模式,默认使用内置 hvigor 插件,若在此配置 @ohos/hvigor 依赖反而会报错。

3.2 build-profile.json5 的 schema 校验:字段名与取值都变了

Sync 时 hvigor 会对两个 build-profile.json5 做 schema 校验,实测有以下变化:

① 模块级 targets 只接受 6 个字段。 旧工程常见的 "applyTo": "phone" 已被移除,必须改成 applyToProducts:

// 根 build-profile.json5
"modules": [
  {
    "name": "entry",
    "srcPath": "./entry",
    "targets": [
      {
        "name": "default",
        "applyToProducts": [ "default" ]   // 设备类型由 module.json5 的 deviceTypes 决定
      }
    ]
  }
]

同理,模块级 entry/build-profile.json5 的 targets[0] 也不允许再写 compileSdkVersion / targetSdkVersion(只允许 name / config / source / resource / runtimeOS / output),buildOption 里也不允许 strictMode(只能放应用级)。SDK 版本统一收敛到应用级声明。

② SDK 版本号必须用字符串,且 API ≥ 26 时不再带括号。 这是本次最隐蔽的坑:报错只说"值不正确,请按照指南修改"。翻 hvigor 插件源码(hvigor-ohos-plugin/src/const/version-const.js)可以看到判定逻辑:

// API ≥ 26 → 直接用平台版本号;API < 26 → "x.y.z(n)" 形式
Number(apiVersion) >= API_VERSION_26
  ? `${platformVersion}`                 // "26.0.0"
  : `${platformVersion}(${apiVersion})`  // "5.0.0(12)"

也就是说 API 26 之前是 "5.0.0(12)" 这种带括号格式,从 API 26 开始直接写平台版本号 "26.0.0"。老经验在这里失效了。最终应用级配置如下:

// 根 build-profile.json5(节选)
"app": {
  "signingConfigs": [],
  "products": [
    {
      "name": "default",
      "signingConfig": "default",
      "compatibleSdkVersion": "26.0.0",
      "targetSdkVersion": "26.0.0",
      "runtimeOS": "HarmonyOS",
      "buildOption": {
        "strictMode": {
          "caseSensitiveCheck": true,
          "useNormalizedOHMUrl": true
        }
      }
    }
  ],
  ...
}

另外一个细节:IDE 自动补全会往 products 里插入 targetSdkVersion 的空值,空字符串同样触发"值不正确"。凡是自动补全插进来的版本字段,一定要填上合法值("26.0.0")。

3.3 编译期:两个 ArkTS 语法级限制

配置全部通过后,CompileArkTS 还会遇到两个高频问题:

① @Builder 方法返回 void,不能链式调用属性方法。 下面这种写法直接编译失败(Property 'position' does not exist on type 'void'):

// ✗ 错误:@Builder 返回 void,不能链式 .position()
this.badge('原图 · 低清', false)
  .position({ x: 10, y: 10 })

正确做法是把定位参数传进 builder,在内部设置:

// ✓ 正确
@Builder
badge(text: string, atRight: boolean, xPos: number) {
  Text(text)
    .fontSize(11)
    .width(96)
    .textAlign(TextAlign.Center)
    .position({ x: xPos, y: 10 })
}

// 调用
this.badge('原图 · 低清', false, 10)

② 全局 promptAction.showToast 已从 API 18 起弃用。 官方推荐改用 UIContext 获取 PromptAction,避免"UI 上下文歧义",并且该接口声明了 @throws,需要异常处理:

// ✓ 统一 Toast 出口
private toast(message: string): void {
  try {
    this.getUIContext().getPromptAction().showToast({ message: message });
  } catch (e) {
    const err = e as BusinessError;
    console.error(`showToast failed, code: ${err.code}, message: ${err.message}`);
  }
}

3.4 构建、安装、运行(全程命令行可用)

DevEco 26 的构建链路完全可以用命令行操作,连真机做自动化安装、拉起与截图非常方便:

# 构建 HAP(未配置签名时会跳过签名并给出 WARN)
node "$env:ProgramFiles\Huawei\DevEco Studio\tools\hvigor\bin\hvigorw.js" `
  --mode module -p module=entry@default -p product=default `
  -p requiredDeviceType=phone assembleHap --daemon

# 安装到已连接的真机(-t 后接 hdc list targets 查到的设备序列号)
& "$env:ProgramFiles\Huawei\DevEco Studio\sdk\default\openharmony\toolchains\hdc.exe" `
  -t <设备序列号> install .\entry\build\default\outputs\default\entry-default-unsigned.hap

# 拉起应用
hdc shell aa start -a EntryAbility -b com.example.imagesuperresolution

# 截图(注意:snapshot_display 只接受 .jpeg 后缀)
hdc shell snapshot_display -f /data/local/tmp/s.jpeg
hdc file recv /data/local/tmp/s.jpeg .\s.jpeg

# 注入点击/滑动(坐标是设备实际像素,不是截图缩放后的坐标!)
hdc shell uitest uiInput click 978 1320
hdc shell uitest uiInput swipe 978 1306 373 1306 250

两个容易翻车的点:snapshot_display 的文件名必须是 .jpeg,写 .png 直接报 suffix must be .jpeg;uitest uiInput 的坐标基于设备真实分辨率(本例 1320×2232),拿 640 宽的预览截图坐标直接用会点偏一倍。

用上面这套命令拉起应用后,真机上就是这个初始界面(160×120 低清示例图,左上角标"待超分 · 低清"):

初始界面
命令行拉起应用后的初始界面


四、核心实现解析

4.1 真实链路:ImageSRAnalyzer 三段式

真机实现(RealImageSuperResolutionService.ets)严格遵循官方「分析器」模式:

import { imageSuperResolution, visionBase } from '@kit.CoreVisionKit';
import { image } from '@kit.ImageKit';

// 1. 创建(页面 aboutToAppear 中;create 完成前不要发起 process)
this.analyzer = await imageSuperResolution.ImageSRAnalyzer.create();

// 2. 执行:低清 PixelMap 进 → 4 倍高清 PixelMap 出
const imageData: visionBase.ImageData = { pixelMap: src };
const request: visionBase.Request = { inputData: imageData };
const response: imageSuperResolution.ISPResponse = await this.analyzer.process(request);
const hdPixelMap: image.PixelMap = response.pixelMap;

// 3. 销毁(页面 aboutToDisappear 中;重推理对象勿常驻内存)
await this.analyzer?.destroy();
this.analyzer = null;

配套的输入预处理(官方输入上限 2048×2048):解码时先读 ImageInfo,超限先等比缩放再送入分析器:

const info: image.ImageInfo = await source.getImageInfo(0);
const options: image.DecodingOptions = { desiredPixelFormat: image.PixelMapFormat.NV21 };
if (info.size.width > MAX_SIDE || info.size.height > MAX_SIDE) {
  const ratio: number = Math.min(MAX_SIDE / info.size.width, MAX_SIDE / info.size.height);
  options.desiredSize = {
    width: Math.floor(info.size.width * ratio),
    height: Math.floor(info.size.height * ratio)
  };
}
const pixelMap: image.PixelMap = await source.createPixelMap(options);

选图用 PhotoViewPicker(系统 UI),不需要声明任何敏感权限;选中的图片先拷贝到应用沙箱再解码,避免跨权限读 URI。

4.2 对比 UI:分割滑杆的联动实现

「左原图 / 右超分后」的经典对比交互,拆解成三层:

  1. 底层:超分后的高清图铺满整个展示区;
  2. 上层:低清原图放进一个宽度受状态控制的容器,配合 .clip(true) 裁剪——容器多宽,低清图就露多少;
  3. 分割线:绝对定位在 areaWidth × splitPercent / 100 处,areaWidth 由 onAreaChange 实时获取。
// 上层:低清原图裁剪到分割线左侧
Stack({ alignContent: Alignment.Start }) {
  Image($rawfile(this.lowPath))
    .width(this.areaWidth)
    .height(IMG_HEIGHT)
    .objectFit(ImageFit.Cover)
}
.width(`${this.splitPercent}%`)   // 容器宽度 = 分割百分比
.height(IMG_HEIGHT)
.clip(true)                       // 超出容器部分裁掉
.borderRadius({ topLeft: 14, bottomLeft: 14 })

// 分割线:绝对定位跟随
Column()
  .width(2)
  .height(IMG_HEIGHT)
  .backgroundColor('#FFFFFF')
  .position({ x: this.areaWidth * this.splitPercent / 100 - 1, y: 0 })

// 宽度实时测量
.onAreaChange((_old: Area, newArea: Area) => {
  this.areaWidth = Number(newArea.width);
})

底部的 Slider 与拖拽手柄双向联动:滑杆驱动 splitPercent,分割线与上层容器同步移动。默认 50% 时左右各半,同一轮月亮左侧边缘像素化、右侧平滑圆润:

50% 对比
分割滑杆默认 50%:左侧原图 / 右侧超分结果

继续拖到 20%,高清区占 80%,放大后细节的差距更加直观(即开篇那张对比图)。


五、运行效果(真机实拍)

初始界面见 3.4 节。点击「开始超分」后,展示区覆盖半透明遮罩与 LoadingProgress,提示"端侧推理中,正在重建高清细节…"。这一步真机 NPU 正在执行 ImageSRAnalyzer.process(),实测耗时在 1 秒量级(示例数据版按素材 id 稳定复现 900–1400ms 的耗时表现):

推理中
端侧推理中:遮罩 + LoadingProgress

超分完成:遮罩消失,出现「原图 / 高清」分割滑杆(默认 50%,见 4.2 节实拍图),指标卡更新为 160×120 → 640×480、处理耗时 957 ms、清晰度提升 +39%,并弹出 Toast「超分完成:4 倍高清放大」。把滑杆拖到 20%,就是开篇那张 80% 高清区的大比例对比——建筑窗户的黄色光点从"色块"变成"点光源",星空细节完全显现。

「换一张」切换示例:切到第二组「花朵特写」(低清原图见第二节),再次超分后花瓣边缘从锯齿块状变为平滑曲线,花蕊颗粒感清晰可辨,耗时 999 ms、清晰度提升 +50%。示例共 4 组:城市夜景 / 花朵特写 / 山峦日出 / 海边风景:

花朵超分后
「花朵特写」超分后:耗时 999 ms,清晰度提升 +50%


六、真机运行与相册输入接入

超分是端侧 NPU 能力,效果与耗时必须真机验证。工程默认即真机可用的完整链路,接入相册真实图片只需三步:

  1. Index.ets 中确认使用 RealImageSuperResolutionService.getInstance(),并在 aboutToAppear 里调用 setContext(this.getUIContext().getHostContext());
  2. 入口从「内置示例图」换成 pickAndProcess()(相册选图 → 缩放 → 超分),返回的是 image.PixelMap,直接喂给 Image 组件;
  3. 处理完成后用 image.ImagePacker 把高清 PixelMap 编码落盘,即可保存或分享。

两个真机注意点:超分能力目前为 Beta,耗时与效果以实测为准;ImageSRAnalyzer 实例随页面销毁(aboutToDisappear 中 destroy()),避免常驻占用 NPU 资源。


七、总结与可扩展方向

本案例跑通了一条完整闭环:低清输入 → 端侧 4 倍重建 → 前后对比 → 指标呈现,并且通过服务层抽象让"示例数据演示"与"真机真实推理"共享同一套交互代码。可以直接拿去扩展的方向:

  • 与检索联动:文搜图案例命中的低清老照片,一键送入超分增强,形成"找到 → 看清"的体验闭环;
  • 结果落盘:ImagePacker 编码后经系统安全组件保存到相册;
  • 批量场景:相册编辑流中嵌入"画质增强"入口,配合任务队列批量处理(注意能力本身仅支持单张,需自行排队);
  • 效果基线:在真机上记录不同机型的耗时分布(本例实测在 1 秒量级),为产品化的等待动画时长提供依据。

核心收获一句话:HarmonyOS 的 Vision 类能力都是"分析器三段式",把生命周期管好、把输入约束(单张、≤2048)守住,剩下的就是把它包进一个好用的交互里。


工程路径:LI_harmonyOS/image-super-resolution;本文基于 DevEco Studio 26.0 构建,在 HarmonyOS 7.0.0 真机上实测运行并实拍截图。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐