# 买家发来的糊图，怎么直接上详情页 # 智绘鸿蒙·如 7 而至#

## 前言

做电商工具的都懂这个流程：买家在聊天里甩过来一张商品图，说"用这个做主图"。你点开一看——250 像素宽，压缩过，糊得连拉链都看不清。

扔给超分就完事了？功能上是的，鸿蒙 7 的 `imageSuperResolution` 三行代码就能放大四倍。

但我这次特意换了台设备跑，结果让我改了这篇文章的结论：

**同一张 250×333 的图，Mate 70 Pro 上大约 165ms，MatePad Pro 上要 820ms。差五倍。**

所以这篇不讲 API 长什么样（那部分文档里都有），讲**把它塞进一条真实业务流水线时你会撞上的几件事**。

## 这条流水线长什么样

一张聊天截图要变成能上架的主图，中间有四步，超分只是其中一步：

```
拿到图（聊天转发/截图） → 解码并限住长边 → 超分重建 → 出图（白底 / 尺寸合规）
```

第三步是系统能力，另外三步都是你自己的活。多数人栽在第二步和第四步。

**第二步的坑**：超分固定 4 倍，输出单边硬上限 8192px，所以**输入长边必须 ≤ 2048**，超了直接抛 `401 The parameter check failed`。

有意思的是，聊天工具转发出来的图往往已经被压得很小，反而不会触发这个限制；真正会触发的是用户点"原图"发过来的那种 4000×3000 扫描件。所以两种都要防。

**第四步的坑**：超分只补细节，不改背景、不去污。买家图里的沙发、床单、桌面杂物，它会**清清楚楚地给你放大**。电商主图要的白底它不会给，后面必须接一步分割。

## 开发步骤

### 第一步：起引擎，别在点击回调里 create

```typescript
import { imageSuperResolution, visionBase } from '@kit.CoreVisionKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class SrEngine {
  static readonly MAX_INPUT_EDGE: number = 2048;
  private analyzer: imageSuperResolution.ImageSRAnalyzer | null = null;

  async ready(): Promise<boolean> {
    if (this.analyzer) {
      return true;
    }
    try {
      this.analyzer = await imageSuperResolution.ImageSRAnalyzer.create();
      return true;
    } catch (err) {
      const e = err as BusinessError;
      throw new Error(`超分服务初始化失败（${e.code}）：${e.message}`);
    }
  }
}
```

> - **超分（Super-Resolution）**：把低分辨率图像重建为高分辨率图像，恢复丢失的细节。
> - **端侧推理**：模型跑在设备本地，图片不出机器。对电商场景来说，"商品原图不外传"本身就是卖点。

需要注意的是，`create()` 可能抛 `1018700001 Service exception`（低版本系统、AI 引擎未就绪），**必须 catch 并给降级 UI**。

### 第二步：解码时就限长边

这是内存和 401 的双重防线。先读元数据，再决定解码尺寸：

```typescript
static async decodeCapped(source: image.ImageSource): Promise<image.PixelMap> {
  const info: image.ImageInfo = await source.getImageInfo();
  const long: number = Math.max(info.size.width, info.size.height);
  if (long <= SrEngine.MAX_INPUT_EDGE) {
    return source.createPixelMap();
  }
  const k: number = SrEngine.MAX_INPUT_EDGE / long;
  return source.createPixelMap({
    desiredSize: {
      width: Math.round(info.size.width * k),
      height: Math.round(info.size.height * k)
    }
  } as image.DecodingOptions);
}
```

反过来做——先 `createPixelMap()` 拿到全尺寸位图再 `scale()` 缩小——一张 4000×3000 的 ARGB 位图是 48MB，内存先炸为敬，然后才轮到超分报错。

相册导入、内置样片、以后加的相机拍照，三条路径共用这一个入口，上限只改一处。

### 第三步：跑超分，并且把耗时记下来

```typescript
async superResolve(src: image.PixelMap): Promise<SrResult> {
  if (!(await this.ready()) || !this.analyzer) {
    throw new Error('超分引擎未就绪');
  }
  const imageData: visionBase.ImageData = { pixelMap: src };
  const request: visionBase.Request = { inputData: imageData };
  const start = Date.now();
  const response = await this.analyzer.process(request);
  const cost = Date.now() - start;
  // 把 cost 记进埋点，后面你会感谢自己
}
```

为什么强调记耗时？因为不同设备能差五倍。你不埋点，就永远以为自己手机上那 200ms 是普遍水平，然后被用户投诉"点了没反应"。

### 第四步：批量要串行，别并发

聊天里一次转发十张图是常事。`ImageSRAnalyzer` 是重资源，实测并发调用没有收益、还会拉长单次耗时。老老实实串行加进度条：

```typescript
for (let i = 0; i < jobs.length; i++) {
  const r: SrResult = await this.engine.superResolve(jobs[i]);
  this.progress = `${i + 1}/${jobs.length}`;
  results.push(r.output);
}
```

十张 250×333 的图，在平板上按 820ms 一张算就是 8.2 秒。这个进度必须可见、可取消。

### 第五步：出图前接分割

超分完直接上架是不合格的。接 `subjectSegmentation` 抠主体、垫白底，才得到平台要的合规主图。这一步本文不展开，但流水线位置要留出来。

## 真机运行效果

测试机：HUAWEI MatePad Pro（MRDI-W00），HarmonyOS 7.0.0.107，API 26。

首页三张待处理图，第三张就是"买家发来的商品图"，250×333：

![](assets/01-home.png)

载入后，商品图糊到看不清包上的拉链：

![](assets/02-loaded.png)

超分后 1000×1332，包型、提手缝线、价签轮廓都回来了：

![](assets/03-result.png)

至此功能就跑通了，但先别急着定 SLA。

## 五倍差异是怎么测出来的

同一张 250×333 输入，在平板上连跑四次：

| 次序 | 耗时 |
| --- | --- |
| 1 | 820 ms |
| 2 | 780 ms |
| 3 | 772 ms |
| 4 | 845 ms |

稳定在 800ms 上下，**不是首帧抖动**。

对照另一台设备：71.5k px（240×298）输入实测 148ms、198k px（400×496）实测 300ms，按线性内插，83k px 在手机上约 165ms。

**所以：`耗时 ≈ 输入像素数 × 1.18 μs` 这个模型只在特定机型上成立，系数本身是设备相关的。**

## 把"跨设备"做成埋点，而不是口头承诺

具体怎么落地，我这套是这么埋的。每次调用只记四个字段，成本极低：

```typescript
interface SrStat {
  edge: number;      // 输入长边
  px: number;        // 输入像素总数
  cost: number;      // 实测耗时 ms
  model: string;     // deviceInfo.productModel
}
```

`model` 用 `deviceInfo.productModel` 取，配合 `deviceInfo.sdkApiVersion` 一起上报，回来按机型分桶。跑几天你就能得到一张"机型 → 每千像素耗时"的表，产品里的超时阈值直接查这张表，而不是拍一个 3 秒了事。

分桶之后你会发现两件事：

1. 同类芯片的手机之间差异不大，但手机和平板之间能差到五倍，所以**至少按 phone / tablet / 2in1 三档设阈值**。
2. 同一台机器连续调用会略微变快（229 → 199 → 195 ms），这是模型驻留的效果。意味着**冷启动后第一次调用的耗时才是你该拿来定超时的数**，别用热身后的漂亮数字。

## 进度、取消与失败兜底

**进度**。串行循环里更新一个 `@State progress: string`，文案写"第 3/10 张"比写百分比实在——用户不知道总数对应的百分比有什么意义，但知道还剩几张。

**取消**。给循环加一个标志位，每张处理完检查一次：

```typescript
private cancelled: boolean = false;

for (let i = 0; i < jobs.length; i++) {
  if (this.cancelled) {
    break;
  }
  const r: SrResult = await this.engine.superResolve(jobs[i]);
  results.push(r.output);
}
```

注意取消检查放在两张之间，不要强行中断 `process()`——单张 800ms 左右，等它跑完再退出的代价可以接受，强行打断反而要处理引擎状态不一致。

**兜底**。三类失败分开处理：

| 错误 | 原因 | 处理 |
| --- | --- | --- |
| `401` | 输入超限 | 走解码期限长边，本来就不该遇到 |
| `1018700001` | 服务异常 | 提示用户重启应用 |
| 其它 | 未知 | 保留原图并明确告知"这张没处理成功" |

**绝不能静默把糊图当成品交给用户**——电商场景里这比不处理更糟。

## 还有两个实际会遇到的

**深色/浅色主题**：超分结果 `PixelMap` 直接丢给 `Image` 组件显示没问题，但要存文件得走 `ImagePacker.packToData`，格式选 `image/jpeg`，质量 96 左右比较平衡。

**写相册别申请权限**：用 `SaveButton` 安全控件 + `MediaAssetChangeRequest`，用户点一次授权一次，不需要 `ohos.permission.WRITE_IMAGEVIDEO` 那个受限权限。顺带提醒，`SaveButton` 不支持 `margin()`，要控制位置就在外面套一层容器。

## 总结

什么场景值得用它：电商工具里"用户传来的图糊、又要求直接上架"这一段。超分补清晰度，后面接分割垫白底再交付。

不适合：需要去污、换背景、改角度的场景，那些得靠别的能力，超分只会把杂物一起放大。

> 💡 **一句话**：别把一台设备上的 165ms 当成全世界的 165ms——先按机型埋点分桶，再定超时阈值。

## 参考文献

1. Core Vision Kit 开发指南 · 图像超分：https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-image-super-resolution
2. Core Vision Kit 开发指南 · 主体分割：https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-subject-segmentation
3. Core Vision Kit API 参考 · imageSuperResolution（图像超分）：https://developer.huawei.com/consumer/cn/doc/harmonyos-references/core-vision-image-super-resolution-api
