【共创稿事节】HarmonyOS 7.0 图像超分实战:压缩存储 + 超分查看,空间画质两不误
HarmonyOS 7.0 图像超分实战:压缩存储 + 超分查看,空间画质两不误
本文基于 HarmonyOS 7.0(API 26)的图像超分能力(CoreVisionKit
imageSuperResolution),结合真实项目的完整落地实践,讲清楚图像超分"是什么、怎么用、怎么用好"。
一、什么是图像超分,以及使用场景
1.1 官方定义
按官方文档的说法:超分,即超分辨率重建,是指在放大图片尺寸的同时,尽可能恢复和增强图片中的纹理、边缘等细节信息,减少普通插值缩放带来的模糊、锯齿或细节丢失问题。 HarmonyOS 从 API 26.0.0 版本开始,新增支持对输入的低分辨率图像进行超分辨率重建,使图像更加清晰,官方给出的典型场景是提升图片质量、修复老照片。
一个关键事实:超分输出的 PixelMap 在提高图像质量的同时,像素同步放大四倍——也就是说 1000×1000 的输入,会得到 4000×4000 的输出,这不是简单的插值放大,而是 AI 模型重建出的真实细节。
1.2 通用使用场景
- 图片来源分辨率较低:聊天截图、网络图片、历史存档图,本身清晰度不足;
- 图片需要放大显示:大图查看、缩放浏览时,普通插值放大一片模糊;
- 缩略图需要高清展示:缩略图存储小,点开大图时用超分"无损"补清;
- 修复老照片:低质量历史照片的细节重建。
1.3 本项目的用法:压缩保存 + 大图查看超分,节省空间
本项目的图片链路是"压缩存、超分看、无损出"三段式,核心目标是节省存储空间的同时不牺牲查看体验:
① 保存侧:压缩存储(省空间的源头)
工作日志图片保存时,在 saveWorkLogImage 中做两级瘦身:
- 解码时等比下采样,最长边压到 1440px(
MAX_SIDE),desiredPixelFormat用 RGBA_8888; - 编码时
createImagePacker().packing(pixelMap, { format: 'image/jpeg', quality: 80 }),再写入filesDir/worklog_images/{logId}/。
代价是图片细节有损,画质上不去——这正是超分登场的前提。
② 查看侧:大图查看时超分(补偿细节)

打开大图预览页时,标题栏有超分按钮,点击后 AI 重建补偿压缩损失的细节,输出像素放大 4 倍,配合捏合缩放手势看得更清。压缩图平时不占太多空间,只有用户主动想看高清时才花一次超分的算力。
③ 导出侧:超分态无损导出(不污染原图)
超分态下保存/分享时,把超分结果以 quality: 100 编码为临时 jpg 导出(详见第三章),原图永远不动。
三段配合的结果:存储成本由压缩保证,查看画质由超分保证——用"查看时算力"换"存储空间",这是本项目用超分的核心思路。
二、鸿蒙图像超分 API 介绍与基本使用
2.1 API 定位
图像超分能力由 CoreVisionKit 提供,模块为 imageSuperResolution,仅支持 Stage 模型,系统能力为 SystemCapability.AI.Vision.VisionBase,phone / 2in1 / tablet 均从 API 26.0.0 起支持。
导入方式:
import { imageSuperResolution, visionBase } from '@kit.CoreVisionKit';
2.2 核心类与方法
ImageSRAnalyzer(图像超分分析器类,继承自 visionBase.Analyzer)只有三个方法:
| 方法 | 签名 | 说明 |
|---|---|---|
create() | create(): Promise<ImageSRAnalyzer> | 创建分析器实例;失败抛错误码 1018700001(Service exception) |
process() | process(request: visionBase.Request): Promise<ISPResponse> | 超分处理,仅支持传入一张图片;返回 ISPResponse.pixelMap(像素放大 4 倍) |
destroy() | destroy(): Promise<void> | 释放分析器服务 |
配套数据结构:
// 输入:ImageData 包裹待处理 PixelMap
const imageData: visionBase.ImageData = { pixelMap: inputImage };
const request: visionBase.Request = { inputData: imageData };
// 输出:ISPResponse 继承 visionBase.Response,pixelMap 即超分结果
const response = await analyzer.process(request);
2.3 基本使用:官方四步法
以下开发步骤与示例代码均复用自官方文档《图像超分》。
第一步:添加导入。
import { imageSuperResolution, visionBase } from '@kit.CoreVisionKit'
import { image } from '@kit.ImageKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
第二步:创建与释放分析器。 官方推荐在 aboutToAppear 中 create、在 aboutToDisappear 中 destroy:
private analyzer: imageSuperResolution.ImageSRAnalyzer | null = null;
async aboutToAppear(): Promise<void> {
this.analyzer = await imageSuperResolution.ImageSRAnalyzer.create();
hilog.info(0x0000, 'ImageSRSample', 'ImageSRAnalyzer created');
}
async aboutToDisappear(): Promise<void> {
if (this.analyzer) {
await this.analyzer.destroy();
hilog.info(0x0000, 'ImageSRSample', 'ImageSRAnalyzer released successfully');
}
}
第三步:选图并解码为 PixelMap。 通过 photoAccessHelper.PhotoViewPicker 拉起图库,用 fileIo 与 image 模块将 URI 转换为 PixelMap:
private async openPhoto(): Promise<string> {
return new Promise<string>((resolve) => {
let photoPicker: photoAccessHelper.PhotoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoPicker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
}).then(res => {
resolve(res.photoUris[0]);
}).catch((err: BusinessError) => {
hilog.error(0x0000, 'ImageSRSample', `Failed to get photo image uri.code: ${err.code}, message: ${err.message}`);
resolve('');
});
});
}
private loadImage(name: string) {
setTimeout(async () => {
let imageSource: image.ImageSource | undefined = undefined;
let fileSource = await fileIo.open(name, fileIo.OpenMode.READ_ONLY);
imageSource = image.createImageSource(fileSource.fd);
this.inputImage = await imageSource.createPixelMap();
await fileIo.close(fileSource);
}, 100);
}
第四步:构造 Request 并调用超分。
Button('图像超分')
.onClick(() => {
if (!this.inputImage || !this.analyzer) {
return;
}
// 调用图像超分接口
let imageData: visionBase.ImageData = { pixelMap: this.inputImage };
let request: visionBase.Request = { inputData: imageData };
request.inputData = imageData
this.analyzer.process(request)
.then((response: imageSuperResolution.ISPResponse) => {
this.outputImage = response.pixelMap;
})
.catch((error: BusinessError) => {
hilog.error(0x0000, 'ImageSRSample', `Image super resolution failed. Code: ${error.code}, message: ${error.message}`);
});
})
2.4 显示质量配套:Image 组件参数
超分结果在 Image 组件上显示时,官方对 Image 组件也有清晰度建议:图片放大显示时设置 .interpolation(.High);若解码图源与显示尺寸不匹配出现失真,可选择 .autoResize(false) 按原图尺寸解码(会增加内存占用)。本项目超分结果的显示层正是按放大场景配置插值,保证 4 倍像素在缩放下渲染到位。
补充:Image Kit 的
image-processing-arkts(图片细节增强)也提供清晰度增强/缩放能力,与 CoreVisionKit 的 AI 超分是两条独立路径——前者是图像处理级的轻量增强,后者是模型级的重建放大,按效果需求选型。
示例:
图片超分前:

图片超分后:
也许这样看效果并不明显,但是如果两张图片放到统一同一尺寸下对比还是挺明显的:

(示例图片来源于互联网,如有侵权请联系删除)
三、结合本项目的最佳实践
官方示例是"最短可用路径",生产环境还差好几层防护。以下是本项目实际落地的工程化改造。

3.1 兼容性与版本控制:三级能力探测
本工程中 compatibleSdkVersion: "6.0.2(22)"、targetSdkVersion: "26.0.0"——应用要跑在 API 22 的老设备上,而图像超分是 API 26 新增能力。在低版本设备上 imageSuperResolution 模块根本未定义,直接访问属性就会 crash,所以能力探测必须前置且分三级:
/** 设备超分能力探测:API 版本不满足直接隐藏按钮;syscap 不满足同上;否则预创建分析器验证
* (图像超分为 API 26 新增,低版本设备上 imageSuperResolution 模块未定义,
* 直接访问属性会 crash,必须先用 API 版本拦截;不支持超分的设备 create 抛 801,
* 创建成功则复用避免二次开销) */
private probeSrSupport(): void {
if (getSdkApiVersion() < 26 || !canIUse('SystemCapability.AI.Vision.VisionBase')) {
this.srSupported = false;
return;
}
imageSuperResolution.ImageSRAnalyzer.create()
.then((analyzer: imageSuperResolution.ImageSRAnalyzer) => {
if (this.srDisposed) {
void analyzer.destroy(); // 离页后 create 才 resolve:立即销毁,防无主泄漏
return;
}
this.srAnalyzer = analyzer; // 探测成功的实例直接复用
})
.catch(() => {
this.srSupported = false; // 不支持超分的设备 create 抛 801
});
}
三级递进的逻辑:
- API 版本拦截(
getSdkApiVersion() < 26):先于一切模块访问,防 crash 的生命线; - syscap 校验(
canIUse('SystemCapability.AI.Vision.VisionBase')):系统级能力声明过滤; - 预创建探测(
ImageSRAnalyzer.create()):前两级通过但设备实际不支持(抛 801)的最终兜底,且创建成功的实例直接复用,不浪费。
探测不通过时隐藏超分按钮而非点击报错,用户无感。
3.2 输入治理:2048px 硬约束下的下采样解码
超分接口对输入尺寸有硬约束(单边不超过 2048px),而用户图库里的照片动辄 4000px+。本项目的 decodeInputPixelMap 先取图源信息,超限时等比下采样再解码:
/** 解码超分输入 PixelMap:沙箱路径直接创建,photoUri 按 fd 创建;
* 输入超 2048px(API 硬约束)时等比下采样解码 */
private async decodeInputPixelMap(uri: string): Promise<image.PixelMap | undefined> {
let imageSource: image.ImageSource | undefined = undefined;
let fd: number = -1;
try {
if (uri.includes('data/storage/el2')) {
imageSource = image.createImageSource(uri); // 沙箱路径
} else {
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
fd = file.fd;
imageSource = image.createImageSource(file.fd); // 媒体 uri 按 fd
}
const info = await imageSource.getImageInfo(0);
const options: image.DecodingOptions = {};
const maxSide = Math.max(info.size.width, info.size.height);
if (maxSide > 2048) {
const scale = 2048 / maxSide;
options.desiredSize = {
width: Math.floor(info.size.width * scale),
height: Math.floor(info.size.height * scale)
};
}
return await imageSource.createPixelMap(options);
} catch (e) {
return undefined;
} finally {
imageSource?.release(); // ImageSource 用完即释放,只留 PixelMap
if (fd !== -1) fs.closeSync(fd);
}
}
注意输入 PixelMap 在 process 完成后在 finally 中 release(),超分输入只服务一次。
3.3 状态管理:@Observed 整体替换 + 缓存 + 离页防御
结果显示状态用 @Observed 类整体替换实例驱动刷新,携带三个语义字段:
@Observed
export class SrResult {
index: number = -1; // 当前应用超分的图片下标,-1 表示未开启
pixelMap: image.PixelMap | undefined = undefined; // 超分输出
animate: boolean = false; // 新结果播扫描揭示动画,缓存恢复不重播
}
结果缓存:srCache: Map<number, image.PixelMap> 按图片下标缓存超分结果,切图往返不重复计算;但处理期间用户可能已切图,用下标守卫避免串图:
const response = await this.srAnalyzer.process(request);
if (this.srDisposed) {
response.pixelMap.release(); // 离页后 process 才 resolve:结果无主,直接释放防内存幽灵
return;
}
this.srCache.set(targetIndex, response.pixelMap);
if (targetIndex === this.curIndex) { // 仅当仍停留在该图时切换显示
this.srResult = this.buildSrResult(targetIndex, response.pixelMap, true);
}
揭示动画:新结果用 @Watch onSrResultChange 驱动左→右扫描揭示(1600ms 属性动画:揭示层宽度 0→全宽 clip 裁剪 + 扫描线 position 同步移动),缓存恢复态直接完整显示不重播
3.4 生命周期:统一资源释放
页面离页时按序释放全部超分资源(releaseSrResources):
/** 离页释放超分资源:分析器、缓存 PixelMap、临时文件 */
private releaseSrResources(): void {
this.srDisposed = true; // 置位后迟到的 process 结果直接释放
void this.srAnalyzer?.destroy();
this.srAnalyzer = null;
this.srCache.forEach((pm: image.PixelMap) => pm.release());
this.srCache.clear();
this.clearSrTemp();
this.srResult = new SrResult();
}
超分输出是 4 倍像素的大图,缓存多张就是可观的内存占用,离页必须清干净。
3.5 超分态导出:临时文件 + 安全组件保存
超分态保存/分享时,把超分结果编码为临时 jpg,走完导出即删(getCurrentImagePath):
const packer: image.ImagePacker = image.createImagePacker();
data = await packer.packToData(this.srResult.pixelMap, { format: 'image/jpeg', quality: 100 });
const path = `${context.cacheDir}/${SR_EXPORT_FILE}`; // cacheDir/sr_export.jpg,固定文件名
const file = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
fs.writeSync(file.fd, data);
保存到系统相册用 SaveButton 安全组件(点击即获临时授权,免 WRITE_IMAGEVIDEO 权限)+ photoAccessHelper.createAsset + 按 fd 拷贝——注意 createAsset 只创建空相册资源,内容必须 fs.openSync(uri, READ_WRITE) 后按 fd 拷贝,直接 copyFileSync(srcPath, uri) 传媒体 uri 字符串会保存失败:
const helper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
const outUri: string = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
const dstFile = fs.openSync(outUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
try {
fs.copyFileSync(srcPath, dstFile.fd);
} finally {
fs.closeSync(dstFile);
}
幽灵文件兜底:分享面板打开期间进程被杀/闪退时,离页清理不会执行,cacheDir/sr_export.jpg 会残留。解法是固定文件名 + 启动时按名单清扫幽灵文件。
四、注意事项
结合官方文档与本项目踩坑,图像超分落地时重点盯住以下几条:
- API 版本拦截必须最先做。图像超分是 API 26 新增,低版本设备上
imageSuperResolution模块未定义,任何属性访问都会 crash——先getSdkApiVersion()再碰模块,这是生命线。 - 设备能力三级探测。API 版本 →
canIUse('SystemCapability.AI.Vision.VisionBase')→ 预创建探测(不支持设备create()抛 801),三级全过才亮按钮。 - 仅支持单张图片。
process(request)不支持批量,列表场景需自行循环排队。 - 输入单边 ≤2048px(硬约束)。超限需自行等比下采样解码(
DecodingOptions.desiredSize),否则处理失败。 - 内存是 4 倍放大。输入 2048px 输出即 8192px,RGBA_8888 下单张结果约 268MB 像素数据——缓存多张超分结果要掂量内存,离页必须全部
release()。 - process 是耗时 AI 操作。要有 loading 态(本项目标题栏 loading + 完成后扫描揭示动画),且要防御异步时序:处理期间切图用下标守卫,离页后迟到结果直接释放。
- 资源配对使用。
create/destroy配对、ImageSource解码后即释放、输入 PixelMap 用完释放、导出临时文件即用即删——漏掉任何一对都是泄漏。 - 临时文件加双保险。页面离页清理 + 启动时按固定文件名清扫,消灭进程被杀残留的幽灵文件。
- 保存权限走安全组件。
SaveButton点击即获临时授权,免敏感权限申请;媒体 uri 内容必须按 fd 拷贝,不能直接传 uri 字符串。 - 错误码兜底。
1018700001(Service exception)是 create/process 共同的错误码,用户侧给 Toast + 可重试,不要静默失败。
五、总结
HarmonyOS 7.0(API 26)的图像超分给了应用一张"画质后悔药":存储时大胆压缩省空间,查看时用 AI 重建把细节补回来。本项目"压缩存→ 超分看(4 倍像素重建)→ 无损出(q100 临时导出)"的三段式链路,验证了这条"用查看时算力换存储空间"的路线完全可行。
API 层面的心智模型一句话:create() 创建分析器 → 构造 visionBase.Request → process() 拿 4 倍像素的 ISPResponse.pixelMap → destroy() 释放——三步四注意(版本拦截、尺寸约束、资源配对、时序防御)。
而真正决定落地质量的,是官方示例之外的工程化功夫:三级能力探测让老设备无感降级,2048px 下采样解码守住硬约束,@Observed 整体替换 + 缓存 + 离页防御管住状态与内存,临时文件清扫消灭幽灵。API 决定能不能用,工程实践决定好不好用——两者齐备,图像超分才能从演示走向生产。
参考文档
- 图像超分(开发指南):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-image-super-resolution
- imageSuperResolution(图像超分)API 参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/core-vision-image-super-resolution-api
- Image 组件(autoResize / interpolation 最佳清晰度配置):https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-image
- 图片细节增强(image-processing-arkts):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/image-processing-arkts
更多推荐




所有评论(0)