测试环境:ALN-AL80 真机,OpenHarmony 7.0.0.32,API 26;输入为一张 4439×2959 的 CC0 湖边照片。

为什么要做“清晰修复”

照片被裁切、压缩或放到大屏上展示时,普通缩放只能增加像素数量,边缘和纹理不会随之恢复。HarmonyOS 7/API 26 在 Core Vision Kit 中新增图像超分能力,应用可以把 PixelMap 交给系统进行超分辨率重建。

华为官方版本说明将图像超分描述为“对低分辨率图像进行超分辨率重建,使图像更加清晰”。对应的图像超分开发指南API 参考给出了 ImageSRAnalyzer 的调用方式;API 26 变更清单将该能力列入 Core Vision Kit 的新增 API。本机 SDK 中的 create()process()destroy() 均标注为 @since 26.0.0

双镜记忆相机接入这项能力时确定了两个产品要求:处理结果保存成新文件,原图继续保留;页面同时展示处理前后的真实尺寸和同区域画面,避免只用“处理成功”代替效果判断。

处理前:原始文件是 4439×2959

测试图来自应用沙箱。详情页通过 ImageSource.getImageInfo() 读取 JPEG 的实际宽高,得到 4439×2959

原始 JPEG 为 4439×2959

图 1:处理前,详情页读取到原始 JPEG 尺寸为 4439×2959。

如果把这组尺寸直接和最终文件比较,很容易得出“系统把 4439×2959 放大到 6400×4264”的结论。实际处理链路中还有一次解码缩放,模型没有直接接收完整原图。

API 26 的最小调用

图像超分的系统调用集中在三步:创建分析器、传入 PixelMap、取得输出 PixelMap

const analyzer = await imageSuperResolution.ImageSRAnalyzer.create();
const response = await analyzer.process({
  inputData: { pixelMap: sourcePixelMap }
});
const outputPixelMap = response.pixelMap;

接口返回的是像素对象,不会替应用保存 JPEG,也不会创建相册记录。文件解码、输入尺寸控制、结果编码、异常清理和原图保护都由应用完成。

点击“清晰修复”后,页面进入忙碌状态,禁止同一张照片重复启动任务:

系统图像超分执行中

图 2:真机正在执行图像超分,处理期间按钮不可重复触发。

4439×2959 → 1600×1066 → 6400×4264

项目把模型输入长边限制为 1600。解码比例按原图长边计算:

const maxInputSide = 1600;
const decodeScale = Math.min(
  1,
  maxInputSide / Math.max(sourceWidth, sourceHeight)
);

const desiredWidth = Math.floor(sourceWidth * decodeScale);
const desiredHeight = Math.floor(sourceHeight * decodeScale);

代入本次原图尺寸:

floor(4439 × 1600 / 4439) = 1600
floor(2959 × 1600 / 4439) = 1066

ImageSRAnalyzer 在这台真机上返回 6400×4264

1600 × 4 = 6400
1066 × 4 = 4264

完整链路为:

4439×2959 原始 JPEG
        ↓ 按长边 1600 解码
1600×1066 输入 PixelMap
        ↓ ImageSRAnalyzer.process()
6400×4264 输出 PixelMap
        ↓ JPEG,quality 94
6400×4264 新文件

本次观察到的 4 倍倍率属于 ALN-AL80、OpenHarmony 7.0.0.32 和当前输入条件下的返回结果。输出宽高直接读取 response.pixelMap,设备、系统服务或输入条件变化后,页面会显示新的实际尺寸。

处理后:生成新文件,原图继续保留

超分完成后,应用将输出 PixelMap 以 JPEG quality 94 写入新路径,再从新文件重新读取尺寸,页面显示 6400×4264

超分输出为 6400×4264

图 3:处理后,新文件的实际尺寸为 6400×4264。

相册中新增一条“由清晰修复生成”的记录,原始记录和原始文件没有写操作:

原图和超分结果作为两条记录保留

图 4:原图与超分结果同时保存在相册中。

检查项 处理前 处理后
文件尺寸 4439×2959 6400×4264
模型实际输入 1600×1066
相册记录 原始照片 新增“清晰修复”记录
原文件 存在 继续保留

为什么限制模型输入为 1600

RGBA_8888 每个像素至少占 4 字节。1600×1066 的输入像素数据约为 6.51 MiB,6400×4264 的输出约为 104.10 MiB;两块 PixelMap 同时存在时,像素缓冲已经超过 110 MiB。

进程峰值还包括原始 JPEG 字节、ImageSource、分析器内部数据、编码缓冲和目标文件。完整的 4439×2959 RGBA 输入本身约为 50 MiB,如果系统继续返回数倍尺寸结果,内存和处理时间都会明显增加。

长边 1600 是双镜记忆相机当前单图预览场景的资源边界,不是 Core Vision Kit 的固定倍率或最佳输入值。更换输入限制后,需要重新测量输出尺寸、峰值内存、处理时间和失败率。

应用在 createPixelMap() 后还会读取一次实际解码尺寸。若得到的长边仍超过 1600,再对 PixelMap 执行缩放,保证交给分析器的真实对象符合产品限制。

输出文件怎样避免留下半成品

系统接口返回 PixelMap 后,应用才创建独立输出路径。编码失败时先关闭文件句柄,再删除未完成的目标文件;分析器、输入输出 PixelMapImageSourceImagePacker 全部在 finally 中释放。

let targetPath = '';

try {
  imageSource = image.createImageSource(readFile(sourcePath));
  sourcePixelMap = await decodeForSuperResolution(imageSource, 1600);

  analyzer = await imageSuperResolution.ImageSRAnalyzer.create();
  outputPixelMap = (await analyzer.process({
    inputData: { pixelMap: sourcePixelMap }
  })).pixelMap;

  targetPath = createIndependentTargetPath();
  targetFile = fs.openSync(
    targetPath,
    fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY | fs.OpenMode.TRUNC
  );

  packer = image.createImagePacker();
  await packer.packToFile(outputPixelMap, targetFile.fd, {
    format: 'image/jpeg',
    quality: 94
  });

  return targetPath;
} catch (error) {
  closeFileQuietly(targetFile);
  deletePartialTargetQuietly(targetPath);
  throw error;
} finally {
  closeFileQuietly(targetFile);
  await releaseQuietly(packer);
  await destroyQuietly(analyzer);
  await releaseQuietly(outputPixelMap);
  await releaseQuietly(sourcePixelMap);
  await releaseQuietly(imageSource);
}

相册记录在文件成功返回后创建。这样可以避免“页面已经出现新照片,磁盘上却只有空文件”的不一致状态。

同一区域比较:像素增加后画面发生了什么

尺寸链路页同时展示源文件尺寸、模型输入约束和最终输出:

4439×2959 到 1600×1066 再到 6400×4264 的真机链路

图 5:真机页面记录原始文件、模型输入和最终输出三组尺寸。

画质比较使用相同的中心区域和相同的 3.2 倍显示比例:

原图与超分输出的同区域真机对比

图 6:左侧为原始照片,右侧为超分输出,两侧使用相同取样中心和显示倍率。

本次结果中,建筑檐口、栏杆和明暗边缘更突出;山脊和树林的细碎纹理更平整,局部带有人工增强感。最终文件经过 JPEG quality 94 编码,图 6 展示的是“系统超分 + JPEG 编码”的组合效果。

这组素材没有计算 PSNR 或 SSIM。两项指标需要可信的高分辨率真值,而本次模型输入由原图降采样得到,输出尺寸也与原图不同。直接把 4439×2959 原图插值到 6400×4264 再比较,会把插值和对齐误差混入指标。

什么照片值得做一次超分

本次链路把 4439×2959 的 JPEG 解码为 1600×1066,再由 ImageSRAnalyzer 生成 6400×4264 的输出。像素数量明显增加,同区域比较也能看到建筑边缘变得突出;与此同时,树林和山脊的细碎纹理出现了平整化。超分改变了画面的表现,但不会把降采样过程中丢失的信息重新变成真实记录。

裁切后的照片、聊天软件压缩图、旧照片预览以及大屏展示前的临时增强,更容易获得可见收益。原图已经足够大、最终只在手机上查看时,生成一份百兆像素缓冲和新的 JPEG 副本未必划算。产品可以根据原图尺寸和展示终端决定是否推荐“清晰修复”,把选择权留给用户。

落地时建议坚持三条规则:限制模型输入,避免大图推高内存峰值;从 response.pixelMap 读取真实输出尺寸,不预设固定倍率;使用独立路径保存结果,编码失败时删除半成品并保留原图。这样既能使用系统超分带来的视觉增强,也能让用户随时比较和撤销。

证件、医疗、取证和测量场景要求像素忠实反映原始信息,超分生成的纹理不应作为事实依据。这类图片可以放大查看原图,不应使用“清晰修复”结果替代原始材料。

Logo

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

更多推荐