【共创稿事节】鸿蒙HarmonyOS7.0端侧AI新能力-图像超分 · 画质增强:基于 Core Vision Kit 的端侧 4 倍高清重建
【共创稿事节】鸿蒙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,效果以真机实测为准 |
三个关键点:
- 它是「分析器(Analyzer)模式」,和二维码识别、人脸检测等 Vision 能力一致:
create → process → destroy三段式; - 实例是重推理对象,官方明确建议不要常驻内存,生命周期结束要
destroy(); - 端侧 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:分割滑杆的联动实现
「左原图 / 右超分后」的经典对比交互,拆解成三层:
- 底层:超分后的高清图铺满整个展示区;
- 上层:低清原图放进一个宽度受状态控制的容器,配合
.clip(true)裁剪——容器多宽,低清图就露多少; - 分割线:绝对定位在
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%:左侧原图 / 右侧超分结果
继续拖到 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 能力,效果与耗时必须真机验证。工程默认即真机可用的完整链路,接入相册真实图片只需三步:
Index.ets中确认使用RealImageSuperResolutionService.getInstance(),并在aboutToAppear里调用setContext(this.getUIContext().getHostContext());- 入口从「内置示例图」换成
pickAndProcess()(相册选图 → 缩放 → 超分),返回的是image.PixelMap,直接喂给Image组件; - 处理完成后用
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 真机上实测运行并实拍截图。
更多推荐

所有评论(0)