前言
做过工程管理的都见过这个场景:现场拍一张墙上的图纸,回来想核对某个尺寸,结果照片糊得连房间分隔都看不分明。
工地相机、手机翻拍、隔着几米远、光线还不匀——拍回来的图纸十张有八张是"看得出是张图,看不出画了什么"。
台账系统要的其实不是"好看",而是能看清 + 能检索:这张图对应哪个轴线区域、上面的编号是多少、尺寸能不能读出来。
这篇讲怎么用鸿蒙 7 的两个端侧能力把这条链路搭起来:图像超分(imageSuperResolution)负责"看清",通用文字识别(textRecognition)负责"能查"。所有数据都是我在 MatePad Pro 真机上跑出来的,包括一个和上一篇不一致的耗时结论。
为什么是这两个能力
现场归档的痛点拆开是三个:
| 痛点 | 靠什么解决 |
|---|---|
| 图纸线条细、拍糊了分不清房间边界 | 图像超分,把高频细节还原 |
| 图上的编号、尺寸标注要人工抄进台账 | 通用文字识别,直接出文本 |
| 照片在工地上没网络 | 两个能力都是端侧推理,不联网、不要权限 |
这里有个顺序问题值得单独说:必须先超分,再 OCR。
我实测过反过来的情况——300×225 的原图上那些 5~8 像素高的数字,OCR 会给出成片的错字;先超分成 1200×900,同样的数字就稳定了。原因是 OCR 模型对字符的笔画完整性有要求,低分辨率下笔画直接粘连在一起,模型无从判断。
接口与约束
两个能力都在 @kit.CoreVisionKit 下,但生命周期模型不一样,这是最容易写乱的地方:
// 图像超分:分析器实例模式
const analyzer = await imageSuperResolution.ImageSRAnalyzer.create();
const res = await analyzer.process({ inputData: { pixelMap: pm } } as visionBase.Request);
await analyzer.destroy();
// 通用文字识别:全局服务模式
await textRecognition.init();
const txt = await textRecognition.recognizeText({ pixelMap: pm });
await textRecognition.release();| 项目 | 图像超分 | 通用文字识别 |
|---|---|---|
| 起始版本 | 26.0.0 | 4.0.0(10),init/release 自 5.0.0(12) |
| 系统能力 | SystemCapability.AI.Vision.VisionBase |
同 |
| 支持形态 | phone / tablet / 2in1 | phone / tablet(2in1 自 5.0.1(13)) |
| 需要权限 | 无 | 无 |
| 放大倍率 | 固定 4 倍 | — |
| 输入上限 | 输出单边 ≤ 8192px,即输入长边 ≤ 2048 | 无明确上限,但越大越慢 |
- 超分(Super-Resolution):从低分辨率图重建高分辨率细节,不改变画面内容。
- 端侧推理:模型跑在设备本地,图纸和照片不出机器——工地场景里这往往是硬性要求。
- 台账:工程现场按部位/轴线归档的影像记录。
超分抛 401 The parameter check failed. 就是踩了输入上限;OCR 侧要防的是 init() 失败和返回空文本。
开发步骤
第一步:定义台账条目结构
先想清楚一条台账记录要存什么,后面的处理都是往里填字段:
export interface LedgerItem {
point: string; // 轴线/部位,如 "3轴-A轴"
rawPath: string; // 原图沙箱路径
sharpPath: string; // 超分后路径
ocrText: string; // 识别原文
code: string; // 从原文抽出的图纸编号
costMs: number; // 总耗时
}costMs 一定要留。工地设备型号杂,没有耗时埋点你根本不知道慢在哪台机器上。
第二步:解码时限住长边
这是超分能不能跑起来的前提。先读元数据,把约束下推到解码阶段,避免解出全尺寸位图再缩放:
static readonly MAX_INPUT_EDGE: number = 2048;
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);
}现场直接翻拍的原图常常是 4000×3000,不限这一步就是 401。
第三步:超分
async superResolve(src: image.PixelMap): Promise<SrResult> {
if (!(await this.ready()) || !this.analyzer) {
throw new SrError(-1, '超分引擎未就绪');
}
const imageData: visionBase.ImageData = { pixelMap: src };
const request: visionBase.Request = { inputData: imageData };
const start: number = Date.now();
const response = await this.analyzer.process(request);
const cost: number = Date.now() - start;
// 采集输入输出尺寸与 cost
}create() 要 catch 1018700001 Service exception,工地设备上系统版本参差,这个异常不算罕见。
第四步:把超分结果喂给 OCR
注意 process() 返回的是新的 PixelMap,可以直接用,不需要落盘再读:
async readNumbers(sharp: image.PixelMap): Promise<string> {
await this.ocr.init();
const info: textRecognition.VisionInfo = { pixelMap: sharp };
const res: textRecognition.TextRecognitionResult =
await textRecognition.recognizeText(info);
return res.value;
}拿到全文后,图纸编号用正则抽。这里有个实际经验:编号往往出现在标题栏,而标题栏在右下角,所以按行匹配比在整段文本里捞数字可靠:
static pickDrawingCode(raw: string): string {
const m: RegExpMatchArray | null = raw.match(/(?:图号|图纸编号|编号|比例)[::]?\s*([A-Za-z0-9\-\/]{3,20})/);
return m ? m[1] : '未识别';
}第五步:串行批处理
一次归档几十张是常态。两个能力都是重资源,必须串行,而且要能中断:
for (let i = 0; i < jobs.length; i++) {
if (this.cancelled) {
break;
}
const pm: image.PixelMap = await SrEngine.decodeCapped(sources[i]);
const sharp: SrResult = await this.sr.superResolve(pm);
const text: string = await this.ocr.readNumbers(sharp.output);
items.push(this.build(sharp, text));
this.progress = `第 ${i + 1}/${jobs.length} 张`;
}进度按张数报,别报百分比——现场用户只关心还剩几张。
真机数据
测试机:HUAWEI MatePad Pro(MRDI-W00),HarmonyOS 7.0.0.107,API 26。样片是一张 300×225 的图纸现场翻拍图。
super resolved 300x225 -> 1200x900 in 916ms首页四张待处理样片,第四张就是工地翻拍图纸:

超分前后对比(蓝线可拖动)——左侧线条发虚、房间分隔几乎看不出,右侧细线恢复、右下角标题栏表格重新可读:

一个和上一篇不一致的结论
上一篇(《买家发来的糊图,怎么直接上详情页》)里我给过平板上的耗时:250×333(83k 像素)稳定在 772~845 ms。
这次 300×225 只有 67.5k 像素,反而用了 916 ms。
像素更少、耗时更长,说明在小尺寸区间,平板上的耗时被固定开销主导,和输入像素数已经不线性了。而手机上那组数据(240×298 → 148ms、400×496 → 300ms…)在 45 万像素以上才呈现 ≈1.18 μs/px 的线性关系。
所以之前那个线性模型,适用边界要说清楚:
| 条件 | 模型是否成立 |
|---|---|
| 手机、输入 ≥ 45 万像素 | 成立,约 1.18 μs/px |
| 手机、小尺寸输入 | 有约 50ms 固定开销,仍可估 |
| 平板、小尺寸输入 | 不成立,固定开销 700ms 以上且波动 |
工程上的处理办法:不要按公式估时,按机型分桶实测。我在埋点里记 deviceInfo.productModel + 输入像素数 + 实测耗时,跑几天就有真实分布。批量场景的超时阈值取该机型 p95,而不是取平均值。
台账场景另外三件事
图纸上的污损超分不会处理。 样片那张纸有折痕和水渍,超分之后折痕被完整地、更清晰地保留了下来。它只补细节,不做清理。要出"干净电子版",得再加一步透视矫正和去污,那是另一个话题。
OCR 不做语义纠错。 图纸上小字被识别成错字时,它会照原样输出。所以台账里凡是自动抽取的编号,都要留人工复核入口,不能直接当主键用。
没网络反而是优势。 这两个能力全程端侧,工地上信号差甚至无信号都能跑,回办公室再同步结果。这也是选端侧方案而不是云端 API 的实际理由。
总结
什么场景值得这么搭:工程、监理、巡检、资产盘点这类"现场翻拍图纸或铭牌、回来要能查能核对"的活。超分管看清,OCR 管能查,全程端侧适配无网环境。
不适合的是把它当"扫描件生成工具"——折痕、水渍、透视变形它都不管,那些得单独处理。
💡 一句话:先超分再 OCR,顺序不能反;耗时别套公式,按机型实测取 p95。
参考文献
- Core Vision Kit 开发指南 · 图像超分:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-image-super-resolution
- Core Vision Kit API 参考 · imageSuperResolution(图像超分):https://developer.huawei.com/consumer/cn/doc/harmonyos-references/core-vision-image-super-resolution-api
- Core Vision Kit 开发指南 · 通用文字识别:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-text-recognition
- Core Vision Kit API 参考 · textRecognition(文字识别):https://developer.huawei.com/consumer/cn/doc/harmonyos-references/core-vision-text-recognition-api