【HarmonyOS AI 系列文章】 图像超分:用端侧 AI 实现小图传输,高清图呈现

0 阅读9分钟

HarmonyOS 图像超分:用端侧 AI 实现“小图传输,大图呈现”

在这里插入图片描述

一、前言

最近看到鸿蒙推出的非常有意思的功能,图像超分,今天给大家做一个深度梳理。

今年重心转向AI方向,最近对鸿蒙+AI的概念又深入的来了一波追踪学习,接下来这段时间会陆续出相关的技术文章介绍分享给大家,欢迎一起讨论进步。花了两个多小时整理,接下来进入正文。

二、图像超分,是什么?

图像超分不是简单把图片拉大,而是用 AI 根据低清图中的纹理、边缘、颜色过渡和局部结构进行重建,让图片看起来更清晰、更自然。

说人话理解:云端只发小图,终端用 HarmonyOS 系统级 AI 能力做 4 倍高清重建。低质量图片用鸿蒙手机端侧AI能力渲染成高清。

普通放大只是把像素撑开,所以越放越糊;AI 超分会尝试补全细节、优化边缘、降低噪点,让低分辨率图像获得更接近高清图的视觉观感。

本质:不是“恢复原图”,而是“生成一个视觉上更高清、更接近原图观感的版本”。

三、 为什么需要它:图片业务的三难问题

图片业务常见的矛盾可以总结成三类:

做法好处问题
传高清图清楚,细节多费带宽、费存储、加载慢,弱网体验差
传压缩图省带宽、省存储、加载快图片放大后模糊,影响用户体验和转化
自研 AI 超分能做定制化能力算法、性能、功耗、发热、多机型适配成本高

HarmonyOS 图像超分的价值就在于:让开发者少传大图,但仍然给用户更清晰的图片观感。

行业内针对图片这方面的处理,一般是先下载使用缩略图预览占位,然后动态加载高清图,再加上缓存机制,防止高清大图重复下载。

四、 核心思路:小图传输,大图呈现

HarmonyOS 7 从 API 26 开始开放图像超分能力。开发者可以通过 Core Vision Kit 调用系统级图像超分 API,对输入的低分辨率图像进行超分辨率重建。

典型链路如下:

小图 CDN
  -> App 下载低分辨率图片
  -> URI / 图片文件转换为 PixelMap
  -> Core Vision Kit 图像超分
  -> 获取 response.pixelMap
  -> 页面展示 / 结果缓存

规格要点:

  • 起始版本:26.0.0
  • 系统能力:SystemCapability.AI.Vision.VisionBase
  • 模型约束:仅可在 Stage 模型下使用
  • 输入原图最大尺寸:2048*2048
  • 输出倍率:4 倍高清放大
  • 相关模块:imageSuperResolution
  • 当前文档标注:Beta

传输阶段:传低分辨率/小体积图片,降低带宽、流量、存储成本。 显示阶段:在端侧用系统级 AI 图像超分,把小图重建成更清晰的大图。

刚才也说了,这个技术的本质是使用鸿蒙手机端侧的AI能力,它不是把“原始高清图”真正找回来,而是根据低清图里的纹理、轮廓、语义信息, 推测并补全更像高清图的细节。

所以在使用场景上要注意,我个人认为适合:商品图、资讯图、相册预览、内容流缩略图。但是像社交图片,证件图片,特别依赖真实细节的场景并不推荐。

五、技术原理

AI 超分主要做三类事情:

  1. 增强边缘:让物体轮廓、文字边界、线条更清晰。
  2. 补全纹理:根据上下文重建商品表面、建筑线条、照片纹理等细节。
  3. 降低噪点:抑制压缩图常见的块状噪声和模糊噪点。

HarmonyOS 方案还提到 NPU 量化、XPU 异构、多线程流水等底层优化。简单理解,就是系统把合适的计算交给合适的硬件单元,让端侧处理更快、更省电。

六、HarmonyOS 方案优势

对比项传统做法HarmonyOS 端侧图像超分
云侧超分 vs 端侧超分依赖云端服务,可能增加调用成本、上传下载成本和链路延迟基于系统级 AI 能力在端侧处理,减少对云侧服务的依赖
普通放大 vs AI 超分只改变尺寸,缺少细节重建,越放越糊通过 AI 重建纹理、边缘和细节,放大后观感更清晰
App 自研模型 vs Core Vision Kit需要算法团队、模型优化、功耗控制和多设备适配调用系统提供的 ImageSRAnalyzer,接入门槛更低

七、 代码实战:用 Core Vision Kit 调用图像超分

下面代码基于开发指南和 API 参考中的真实 ArkTS API:


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';

@Entry
@Component
struct Index {
  @State inputImage: PixelMap | undefined = undefined;
  @State outputImage: PixelMap | undefined = undefined;
  @State statusText: string = '请选择一张低质量图片';
  @State isProcessing: boolean = false;

  private analyzer: imageSuperResolution.ImageSRAnalyzer | null = null;
  private inputImageSource: image.ImageSource | undefined = undefined;

  async aboutToAppear(): Promise<void> {
    try {
      this.analyzer = await imageSuperResolution.ImageSRAnalyzer.create();
      this.statusText = '图像超分服务已就绪';
      hilog.info(0x0000, 'ImageSRSample', 'ImageSRAnalyzer created');
    } catch (error) {
      this.analyzer = null;
      this.statusText = '当前环境暂不支持图像超分,选择图片后将仅展示原图';
      hilog.error(0x0000, 'ImageSRSample', 'Failed to create ImageSRAnalyzer: %{public}s', JSON.stringify(error));
    }
  }

  async aboutToDisappear(): Promise<void> {
    if (this.analyzer) {
      await this.analyzer.destroy();
      this.analyzer = null;
      hilog.info(0x0000, 'ImageSRSample', 'ImageSRAnalyzer released successfully');
    }

    this.releaseImages();
  }

  build() {
    Column() {
      Text('HarmonyOS 图像超分 Demo')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#172033')
        .margin({ top: 20, bottom: 6 })

      Text('上方为低质量原图,下方为 Core Vision Kit 图像超分结果')
        .fontSize(14)
        .fontColor('#667085')
        .margin({ bottom: 16 })

      Column({ space: 12 }) {
        this.InputImagePanel()
        this.OutputImagePanel()
      }
      .width('100%')
      .layoutWeight(1)
      .padding({ left: 16, right: 16 })

      Text(this.statusText)
        .fontSize(14)
        .fontColor('#475467')
        .textAlign(TextAlign.Center)
        .width('100%')
        .padding({ left: 16, right: 16 })
        .margin({ top: 16, bottom: 12 })

      Row({ space: 12 }) {
        Button('选择图片')
          .type(ButtonType.Capsule)
          .fontColor(Color.White)
          .backgroundColor('#2568C7')
          .layoutWeight(1)
          .height(48)
          .enabled(!this.isProcessing)
          .onClick(() => {
            void this.selectImage();
          })

        Button(this.isProcessing ? '处理中...' : '调用图像超分')
          .type(ButtonType.Capsule)
          .fontColor(Color.White)
          .backgroundColor('#16856A')
          .layoutWeight(1)
          .height(48)
          .enabled(!this.isProcessing && this.inputImage !== undefined)
          .onClick(() => {
            void this.processImageSuperResolution();
          })
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 24 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F7FB')
  }

  @Builder
  InputImagePanel() {
    Column() {
      Text('低质量原图')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#172033')
        .margin({ bottom: 10 })

      Stack() {
        if (this.inputImage !== undefined) {
          Image(this.inputImage)
            .objectFit(ImageFit.Contain)
            .width('100%')
            .height('100%')
        } else {
          Column() {
            Text('点击下方“选择图片”')
              .fontSize(14)
              .fontColor('#98A2B3')
              .textAlign(TextAlign.Center)
              .padding({ left: 12, right: 12 })
          }
          .width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
        }
      }
      .width('100%')
      .layoutWeight(1)
      .backgroundColor(Color.White)
      .borderWidth(1)
      .borderColor('#D8E1EA')
      .borderRadius(16)
      .clip(true)
    }
    .layoutWeight(1)
    .height('100%')
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(18)
  }

  @Builder
  OutputImagePanel() {
    Column() {
      Text('超分后高清图')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#172033')
        .margin({ bottom: 10 })

      Stack() {
        if (this.outputImage !== undefined) {
          Image(this.outputImage)
            .objectFit(ImageFit.Contain)
            .width('100%')
            .height('100%')
        } else {
          Column() {
            Text('点击“调用图像超分”生成结果')
              .fontSize(14)
              .fontColor('#98A2B3')
              .textAlign(TextAlign.Center)
              .padding({ left: 12, right: 12 })
          }
          .width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
        }
      }
      .width('100%')
      .layoutWeight(1)
      .backgroundColor(Color.White)
      .borderWidth(1)
      .borderColor('#D8E1EA')
      .borderRadius(16)
      .clip(true)
    }
    .layoutWeight(1)
    .height('100%')
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(18)
  }

  private async selectImage(): Promise<void> {
    if (this.isProcessing) {
      return;
    }

    const uri = await this.openPhoto();
    if (!uri) {
      this.statusText = '未选择图片';
      hilog.error(0x0000, 'ImageSRSample', 'Failed to get uri.');
      return;
    }

    await this.loadImage(uri);
  }

  private async openPhoto(): Promise<string> {
    return new Promise<string>((resolve) => {
      const 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 async loadImage(uri: string): Promise<void> {
    let imageSource: image.ImageSource | undefined = undefined;
    let fileSource: fileIo.File | undefined = undefined;
    const oldInput = this.inputImage;
    const oldOutput = this.outputImage;
    const oldImageSource = this.inputImageSource;

    this.inputImage = undefined;
    this.outputImage = undefined;
    this.statusText = '图片加载中...';

    try {
      await this.delay(100);
      fileSource = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
      imageSource = image.createImageSource(fileSource.fd);
      this.inputImage = await imageSource.createPixelMap();
      this.inputImageSource = imageSource;
      imageSource = undefined;
      this.statusText = '图片已加载,请点击“调用图像超分”';

      this.releasePixelMap(oldInput);
      if (oldOutput !== oldInput) {
        this.releasePixelMap(oldOutput);
      }
      this.releaseImageSource(oldImageSource);
    } catch (error) {
      this.inputImage = oldInput;
      this.outputImage = oldOutput;
      this.inputImageSource = oldImageSource;
      this.statusText = '图片加载失败,请重新选择';
      hilog.error(0x0000, 'ImageSRSample', 'Failed to load image: %{public}s', JSON.stringify(error));
    } finally {
      if (fileSource) {
        await fileIo.close(fileSource);
      }
      if (imageSource) {
        void imageSource.release();
      }
    }
  }

  private async processImageSuperResolution(): Promise<void> {
    if (!this.inputImage) {
      this.statusText = '请先选择一张图片';
      return;
    }

    if (!this.analyzer) {
      this.outputImage = this.inputImage;
      this.statusText = '当前环境暂不支持图像超分,右侧已回退展示原图';
      return;
    }

    this.isProcessing = true;
    this.statusText = '图像超分处理中...';

    const oldOutput = this.outputImage;
    const imageData: visionBase.ImageData = {
      pixelMap: this.inputImage
    };
    const request: visionBase.Request = {
      inputData: imageData
    };

    try {
      const response: imageSuperResolution.ISPResponse = await this.analyzer.process(request);
      this.outputImage = response.pixelMap;
      this.statusText = '图像超分完成,右侧为高清重建结果';

      if (oldOutput !== this.inputImage) {
        this.releasePixelMap(oldOutput);
      }
      hilog.info(0x0000, 'ImageSRSample', 'Super resolution completed');
    } catch (error) {
      const businessError = error as BusinessError;
      this.outputImage = this.inputImage;
      this.statusText = `图像超分失败,右侧已回退展示原图。Error: ${businessError.message}`;
      hilog.error(0x0000, 'ImageSRSample', `Image super resolution failed. Code: ${businessError.code}, message: ${businessError.message}`);
    } finally {
      this.isProcessing = false;
    }
  }

  private releaseImages(): void {
    const oldInput = this.inputImage;
    const oldOutput = this.outputImage;
    const oldImageSource = this.inputImageSource;

    this.inputImage = undefined;
    this.outputImage = undefined;
    this.inputImageSource = undefined;

    this.releasePixelMap(oldInput);
    if (oldOutput !== oldInput) {
      this.releasePixelMap(oldOutput);
    }
    this.releaseImageSource(oldImageSource);
  }

  private releasePixelMap(pixelMap: PixelMap | undefined): void {
    if (pixelMap) {
      void pixelMap.release();
    }
  }

  private releaseImageSource(imageSource: image.ImageSource | undefined): void {
    if (imageSource) {
      void imageSource.release();
    }
  }

  private async delay(ms: number): Promise<void> {
    return new Promise<void>((resolve) => {
      setTimeout(() => {
        resolve();
      }, ms);
    });
  }
}


9. 这段代码实际做了什么

步骤对应 API作用
创建分析器imageSuperResolution.ImageSRAnalyzer.create()创建系统级图像超分分析器实例
选择图片photoAccessHelper.PhotoViewPicker拉起图库选择一张图片,拿到图片 URI
转 PixelMapfileIo.open()image.createImageSource()createPixelMap()把图库 URI 解码成图像超分接口可处理的 PixelMap
构造请求visionBase.ImageDatavisionBase.Request把输入图片放入 request.inputData
执行超分analyzer.process(request)返回 imageSuperResolution.ISPResponse,其中 pixelMap 是超分后的图片
释放资源analyzer.destroy()imageSource.release()fileIo.close()组件退出或图片解码完成后释放资源

10. 工程落地建议

  1. 不要在列表里批量超分

    信息流、商品列表、相册宫格里图片数量多,全部立即处理会浪费算力。更好的做法是列表先展示小图,用户进入详情页或点击放大后再处理。

  2. 失败时静默回退

    图像超分应该是体验增强能力,不应该阻塞主流程。创建分析器失败或 process() 报错时,直接展示原图。

  3. 对结果做缓存

    同一张图片不要重复超分。可以用图片 URI、文件 hash 或业务图片 ID 作为缓存键,把超分结果接入项目已有的图片缓存体系。

  4. 控制输入尺寸

    输入原图最大尺寸为 2048*2048。超过规格的图片要先按业务需求裁剪、压缩或降采样,再进入超分流程。

  5. 弱网优先小图

    弱网环境下先下发小图,页面快速可见;用户停留、点击查看或进入详情后,再触发端侧增强。

  6. 排除真实细节场景

    涉及识别、证据、诊断、合规审查的图片,不要让 AI 超分结果替代原图判断。

参考资料

在这里插入图片描述

说句题外话, 吹爆鸿蒙开发者官网。开发这么多年,见过最好的技术官网。自带AI助手,哪里不会直接点哪里,学习起来太方便了。