# 一天两百个单号怎么进表 # 智绘鸿蒙·如 7 而至#

## 前言

仓库那边找到我：一天两百多个快递面单，两个人拿键盘敲进 Excel，敲到下午四点眼睛是花的。老板的原话是"你不是搞技术的吗，这个东西不是很简单"。

是挺简单。鸿蒙 7 的 Core Vision Kit 里那个通用文字识别就是干这个的，端侧跑、不要网络、不要调云端 API 按次付费。我在 MatePad Pro 上把整条链跑通了，最后的结果是：**四张面单，三张全对，一张崩得很难看**。

崩的那张教会我的东西，比前三张加起来都多。这篇文章就按我干活的顺序写：先造数据，再调接口，最后处理那些接口文档不会告诉你的破事。

## 先说我不信 demo 图

任何 OCR 需求，拿官方示例图跑通就汇报"做完了"的，都是给自己埋坑。所以我没有用网图，而是用 PIL 自己画了面单——因为**只有我自己画的，我才确切知道标准答案是什么**。

四张图，同一批版式，退化做成阶梯：

| 编号 | 尺寸 | 退化处理 | 标准答案 |
| --- | --- | --- | --- |
| 标准面单 | 760×1000 | 无 | SF1350778829431 |
| 手持翻拍 | 456×600 | 旋转 3.2°、高斯模糊 1.7、JPEG q58 | YT7512098834126 |
| 小图压缩 | 300×395 | 旋转 −6.5°、模糊 0.8、q45 | JD0017283946510 |
| 极限压图 | 200×263 | 在小图基础上再模糊 1.4、q30 | JD0017283946510 |

前三档就是仓库手机随手拍的正常水平，第四档是我故意拿来找死的。

## 接口长什么样

三个调用就够，没什么花活：

```typescript
this.inited = await textRecognition.init();
const info: textRecognition.VisionInfo = { pixelMap: src };
const conf: textRecognition.TextRecognitionConfiguration = { isDirectionDetectionSupported: false };
const res: textRecognition.TextRecognitionResult = await textRecognition.recognizeText(info, conf);
```

几点实在的：

- `init()` 是有状态的，我放在单例里做幂等，整个批量过程只初始化一次。别每张图 init/release 一遍，那是给自己加延迟。
- `isDirectionDetectionSupported` 是给"图片可能是横着/倒着拍的"准备的。我的四张图旋转角都在 7° 以内，直接关掉。这个开关对耗时的影响我没有单独测，不拿它当优化手段。
- 返回值是一整块文本 `res.value`，外加 `blocks → lines → words` 三层结构和每个 `line` 的 `cornerPoints`。**注意 `value` 是把所有行拼起来的**，这一点后面会害我。
- 一次只能一张图。想批量只能自己排队串行。

## 结果：三张对一张崩

真机上一遍跑完四张：

| 样本 | 耗时 | 识别字符数 | 行数 | 单号结果 | 收件人 | 电话 |
| --- | --- | --- | --- | --- | --- | --- |
| 标准面单 | 1512ms | 197 | 22 | SF1350778829431 ✓ | 张伟 ✓ | 138****4471 ✓ |
| 手持翻拍 | 1310ms | 183 | 19 | YT7512098834126 ✓ | 李静 ✓ | 139****2280 ✓ |
| 小图压缩 | 1240ms | 178 | 20 | JD0017283946510 ✓ | 王强 ✓ | 137***9056 **少一个星号** |
| 极限压图 | 888ms | **33** | **7** | **JO300017283946510 ✗** | 空 | 空 |

四张合计 5010ms。同一张标准面单我前后跑了三次，耗时 1288 / 1402 / 1512ms，波动 ±15%，所以别拿单次耗时做 SLA。

## 坑一：崩溃是断崖式的，不是慢慢变差

极限压图那张最有意思。它不是"识别得差一点"，而是**整页只剩 33 个字符、7 行**——前面三档都是 178~197 字符、19~22 行。也就是说 OCR 面对糊到一定程度的图，是直接放弃，而不是勉强认几个。

更要命的是它错得很自信：

```
标准答案：JD0017283946510
实际输出：JO300017283946510
```

`D` 认成了形近的 `O`，中间还凭空多出 `3000`。返回值里没有任何"我不确定"的信号——没有置信度字段，没有 partial 标记。你正则一匹配，`[A-Z]{2}\d{10,15}` 照样命中，看起来像个合法单号，直接进表了。

所以判据应该换成输入侧的信号：**字符数低于 100、或行数低于 15 的面单，直接标黄退回人工**，不看它抽出来什么。这个门槛比任何正则都管用，因为它检测的是"OCR 放弃了"这个事实本身。

## 坑二：抢字段的不是 OCR，是你的正则

前三张单号全对，我正准备收工，回头看了一眼电话字段——标准面单那行是：

```
电话  1350778829431
```

我当时的表情大概是这样的：面单上明明印着 `138****4471`。

原因很简单，我的正则写成了这样：

```typescript
raw.match(/1[3-9]\d[\d*]{4,9}\d{3,4}/)   // 只要像手机号就行
```

而 `res.value` 是按行拼接的一大串，运单号在上面，`SF1350778829431` 里的 `1350778829431` 恰好以 `13` 开头、长度合适，**先被匹配到了**。手持那张侥幸对了，纯粹是因为它的运单号被识别成了 `YT7512...`，`7` 打头不成手机号。

改成锚定标签就对了：

```typescript
raw.match(/联系电话\s*(1[3-9][\d*\-]{6,12})/)
```

教训一句话：**在拼平的 OCR 文本上做字段抽取，等于在没有任何版式信息的情况下猜**。要么锚标签，要么用 `cornerPoints` 拿到行的坐标，按"同一 y 行的右侧内容"来取——后者更稳，但代码量翻三倍。我这次选了锚标签，因为面单的标签词是固定的。

## 坑三：`*` 不是字符，是玄学

小图那张的电话是 `137***9056`，标准答案 `137****9056`，少一个星号。面单上的脱敏掩码是四个 `*`，OCR 认成三个。

这类非文字符号（`*`、`·`、`-`、空格）的个数不可信。所以任何"拿掩码位数校验手机号"的想法都要丢掉，脱敏字段只做前缀匹配：`137` 开头、`9056` 结尾，中间不管。

## 那这两百单到底怎么进表

按实测 1.3s/张串行算：

- 200 张 ≈ 260 秒 ≈ **4.4 分钟**，中间不用人管。
- 剩下的是复核时间。按我这次的样本，四张里退一张，也就是 25% 的退回率——样本太小，这个比例只能当心理预期，不能当指标。
- 想再快只能并行多实例，但接口本身不支持批量，端侧模型吃不吃得住并发我没测，不吹。

导出这一步我没写进 demo：字段抽完之后拼成 CSV 文本、丢进剪贴板，回工位 Ctrl+V 到表格里就完事了。这一步跟鸿蒙没关系，几行字符串拼接的事，反而是仓库那边最在意的——他们那张 Excel 已经用了六年，你让他们换系统不如让他们 Ctrl+V。

## 真机数据

| 项目 | 实测值 |
| --- | --- |
| 单张识别耗时 | 888 ~ 1512ms（随内容量变化，不是随像素） |
| 四张串行合计 | 5010ms |
| 同图重复波动 | 1288 / 1402 / 1512ms，约 ±15% |
| 正常面单字符数 | 178 ~ 197 字，19 ~ 22 行 |
| 崩图字符数 | 33 字，7 行 |
| 单号准确率 | 3/4（含一张故意压坏的） |
| 字段准确率 | 收件人 3/3、电话 2/3（星号数错） |

测试机 MatePad Pro（HarmonyOS 7 / API 26，序列号略）。面单为脚本合成，标准答案已知。

![四张面单的批量结果，最后一行标红](assets/01-batch.png)

![字段抽取结果，电话列已经锚定标签](assets/03-fields.png)

![极限压图：单号错成 JO3000…，右侧红字给出正确答案](assets/02-error-zoom.png)

## 总结

这套东西适合"版式固定、印刷体、量又大"的单据：快递面单、发票、送货单、对账单、体检报告首页。这类单子仓库里一天几百张，人工敲纯属浪费生命。

不适合手写体、倾斜超过十度的随手拍、以及任何分辨率低于 300px 的图。最后这条是硬线，过了就断崖。真要在手机上现场拍，先把取景框和"太糊了重拍"的提示做出来，比在识别之后想办法划算得多。

> 💡 **一句话**：OCR 本身很少骗你，它放弃的时候会留下痕迹——字符数和行数突然掉一半，就先别信抽出来的内容。

## 参考文献

1. 通用文字识别（开发指南）：https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-text-recognition
2. textRecognition（通用文字识别）API 参考：https://developer.huawei.com/consumer/cn/doc/harmonyos-references/core-vision-text-recognition-api
3. Core Vision Kit 错误码：https://developer.huawei.com/consumer/cn/doc/harmonyos-references/errorcode-core-vision
4. Core Vision Kit 目录：https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-kit-guide
