AIQR 识别字符串规范 v1.1

AIQR Identifier String — Normative Specification

状态:v1.1。字符集、格式与校验算法为稳定接口,v1.x 内不作破坏性变更。

v1.1 变更(2026-07-27)

v1.0 的字符集是靠排版学分簇挑的,从未在真实 OCR 上验过。现已实测 (docs/ocr-measurement.md,2,900+ 次识别),结果改动如下:

改动 原因
擦除上限由 3 降为 2(§5.5) 三擦除会吃光全部三个校验式,求解必然成功——那不是修复,是伪造。实测端到端误解析 7 例全部由此产生;封顶后归零
新增 §7.4:解析器 SHOULD 把 OCR 约束到字符集 零成本,实测把误解析从 7 例降到 0
§5.4 折叠表理由重写 原理由("折错比擦除更糟")被实测推翻,方向相反
§8 补入端到端实测数字 原 §8 只有符号层数学性质,不含光学性质

线格式(字符集、长度、校验算法)未变;v1.0 生成的标识符在 v1.1 下完全有效。 变的只有解析器的严格度:v1.0 解析器会接受 v1.1 拒绝的三擦除输入。 许可:CC0。任何人可自由实现,无需授权、无需注册、无需接入任何服务。

本文档中的 MUST / MUST NOT / SHOULD / SHOULD NOT / MAY 按 RFC 2119 解释。

本规范只定义印在标签上、给人和 OCR 同时读的那串字符。 二维码本体、字幕版式、Manifest 与发现机制见 aiqr-v0.2.md


1. 目标

一个印在物理标签上的标识符,同时满足:

  1. 人能念、能抄、能在电话里报
  2. OCR 与视觉模型能读,且在翻拍、模糊、压缩、污损条件下仍能读
  3. 读错时必须能被发现,而不是安静地解析到另一个真实存在的实体

第 3 条是硬约束。当扫码的一端是能执行动作的 Agent 时,"读错一位、解析到另一台设备" 不是显示错误,是执行到错误的物理对象上。因此本规范宁可拒绝,绝不猜测。

三条防线,按承担的工作量排列:

机制 作用
1 回避(Avoidance) 字符集里每个易混簇只留一个成员,常见误读拼不出来
2 折叠(Folding) 集外字符若有唯一近邻,直接映射回去,不消耗校验预算
3 校验(Parity) 3 个校验符,定位并修复剩余损伤,或明确拒绝

2. 字符集

0 2 3 5 6 8 9 A C E H J M P T X Y        (17 个符号)

符号 MUST 为大写。符号在 GF(17) 中的值等于它在上串中的下标(0=0 … Y=16)。

2.1 构造依据

从 36 个字母数字出发,按视觉混淆簇分组,每簇只保留一个成员:

混淆簇 保留 被排除
0 O D Q 0 O D Q
1 I L (无) 1 I L
2 Z 2 Z
4 A A 4
5 S 5 S
6 G 6 G
7 T T 7
8 B 8 B
E F E F
K X X K
M N W M N W
P R P R
U V Y Y U V
无簇 3 9 C H J

1 I L刻意不保留任何成员。这一簇没有安全的幸存者:在多数无衬线字体下三者几乎不可分辨, 留任何一个都会把误读引入字符集内部——那是校验也救不回来的错误类型。宁可牺牲一个符号位。

2.2 为什么是 17

17 是质数,因此 GF(17) 的运算就是普通的 % 17,不需要多项式表或对数表。 一个符合规范的实现在任何语言里都是几十行。对一个要求"大家都遵循"的标准, 可实现性的价值高于每字符多榨出的零点几个比特。

(对比:Crockford Base32 是为人手打字优化的,它保留了 0/D/Q5/S2/Z8/B6/G —— 这些在打字时不混,在 OCR 时混。它不适合本用途。)


3. 格式

3.1 规范形式

AIQR:XXXX-XXXX-XXXX-XXXX

示例(真实值,由 §9 的派生算法产生):

AIQR:0ATA-XJH3-YXYC-PCC5
AIQR:5PYA-M8YM-E82Y-635M

3.2 哨兵

AIQR: 是给机器的信号,不是给人的说明。它的作用是让视觉模型在一张杂乱的照片里 确定这里存在一个可解析的标识符,从而触发解析流程,而不必靠猜。

哨兵同时承载版本:本规范定义的一切属于 AIQR:。未来的破坏性版本 MUST 使用新哨兵 (如 AIQR2:),MUST NOT 复用 AIQR: 表示不兼容的格式。

标识符脱离标签被引用时(口头、工单、数据库),SHOULD 连哨兵一起引用。

3.3 容量

13 个数据符 × log₂17 = 53.1 比特,即 17¹³ ≈ 9.90 × 10¹⁵ 个标识符。

碰撞风险(生日界,实测计算):

已分配标识符数 至少一对碰撞的概率
1,400 万 1%
9,950 万 50%

4. 校验

4.1 定义

v[0..15] 为 16 个符号值。三个校验式 MUST 全部成立:

S_j = Σ (i+1)^j · v[i]  ≡  0  (mod 17)      j = 0, 1, 2

编码时,数据符给定,解 3×3 线性方程组得到三个校验符。系数矩阵是节点为 (i+1) 的 范德蒙矩阵;因为字长 16 < 17,各节点在 mod 17 下互不相同,方程组恒有唯一解。

4.2 为什么是 3 个而不是 2 个

2 个校验符足以定位单字符错误,但定位之后就没有余量去判断损伤是否其实更严重。 在本字符集上实测:2 个校验符会把 68.2% 的双字符错误"修"成另一个合法标识符—— 对一个解析到物理设备的标签,这意味着自信地打开错误的产品。

第 3 个校验符买到的是一个一致性判据。单个错误(位置 p,偏移 e)必然满足:

S0 = e ,  S1 = (p+1)e ,  S2 = (p+1)²e     ⟹     S1² ≡ S0·S2

而两个错误(位置 p≠q,偏移 e₁,e₂ ≠ 0)代入后:

S1² − S0·S2  =  −e₁e₂(p−q)²

因为 e₁,e₂ ≢ 00 < |p−q| < 17,该值在 mod 17 下恒不为零。 所以 双字符错误 100% 必然被检出,这是代数保证,不是概率。 (穷举验证 184,320 例,拒绝率 100.0000%。)


5. 规范化与折叠

解析器在校验前 MUST 依次执行:

  1. 大小写 —— 全部转为大写。
  2. 分隔符 —— 忽略 -、空格、_.·、U+2010–U+2015(各种连字符/破折号)。 OCR 会把 - 读成 、读成空格、或整个丢掉,三种都 MUST 接受。
  3. 哨兵 —— AIQR 后允许半角 : 或全角 ,前后允许空白。
  4. 折叠 —— 按下表把集外字符映射为集内字符:
O → 0    D → 0    Q → 0
G → 6    S → 5    Z → 2    B → 8
U → Y    V → Y    N → M    W → M
F → E    R → P    K → X    4 → A    7 → T

折叠 MUST 在校验之前完成,因此不消耗校验预算——被折叠的字符与正确字符等价。

  1. 擦除(Erasure) —— 既不在字符集、也不在折叠表中的字符(典型如 1IL, 以及任何无法辨认的墨点),MUST 标记为擦除:位置已知、值未知。

5.5 擦除上限(规范性)

解析器 MUST NOT 尝试求解超过 2 个擦除,即使字长允许。

解 k 个擦除消耗 k 个校验式,剩下 3−k 个用于验证结果。k=3 时没有任何校验式剩下, 求解在数学上必然成功——无论其余 13 个字符是否也被读错。那不是修复,是伪造。

实测:允许 3 擦除时,1,200 次端到端识别产生 7 例解析到错误标识符;上限降到 2 后归零, 正确率仅从 74.7% 降至 74.1%。见 docs/ocr-measurement.md §3。

折叠与擦除的分工,实测结论与直觉相反:折叠的价值不在于折得对,而在于把字符挡在 擦除计数之外。折错只变成一个替换错误,而单个替换错误 100% 可修复;三个擦除完全不可修复。 但把 1 I L 也折叠、让擦除永不发生,会让任意 16 字符垃圾串只靠校验把关, 约 5% 蒙混过关——实测重新产生 3 例误解析。1 I L 不可折叠正是挡住这笔交易的护栏。


6. 解析结果

解析器 MUST 返回以下四种状态之一:

状态 含义 调用方应做什么
valid 校验全过 直接使用
repaired 校验定位并修复了损伤 MUST 二次确认,见 §7.3
unrepairable 损伤超出 3 个校验符的能力 MUST NOT 解析,向用户报告读取失败
malformed 根本不是 AIQR 标识符 不是本规范的输入

7. 一致性要求

7.1 生成器

7.2 解析器

7.3 修复结果不得静默使用(规范性)

repaired 状态的标识符 MUST NOT 被静默解析并用于后续动作。

Agent MUST 满足以下之一:

理由:修复是基于"损伤不超过设计预算"这一假设的推断。假设不成立时(例如三处替换), 修复可能产生一个合法但错误的标识符。校验能保证双错必被拒,但不覆盖更严重的损伤。 在物理世界里,标签被恶意替换、涂改、部分覆盖都是现实场景,见 aiqr-v0.2.md §8。

7.4 约束 OCR 的字符集(规范性)

解析器 SHOULD 在识别阶段就把 OCR 引擎限制到 §2 的 17 个符号。

这是零成本的,而且实测收益比任何字符集调优都大:同一批 1,200 次识别,只改白名单, 误解析从 7 例降到 0 例(正确率 74.7% → 66.3%,降的部分全部变成"拒绝"而非"错误")。

机制是引擎输不出集外字符,因而不产生擦除,三个校验式全部留作验证。


8. 保证与限制

本节分两层:8.1–8.2 是符号层的数学性质(由 scripts/test-id.mjs 穷举产出);8.3 是光学层的端到端表现(由 scripts/ocr-confusion.mjs 在真实 OCR 上测得)。两层不可互相外推。本节不写未经测量的断言。

8.1 保证(符号层,穷举)

损伤 结果 实测
任意单字符替换 100% 定位并修复为原值 20,480 / 20,480
1 个不可读字符 100% 精确恢复 1,280 / 1,280
2 个不可读字符 100% 精确恢复 3,600 / 3,600
3 个不可读字符 100% 拒绝(见 §5.5) 280 / 280
4 个不可读字符 100% 拒绝 260 / 260
任意两字符换位 100% 绝不解析为错误标识符 8,998 / 8,998
任意双字符替换 100% 拒绝(代数保证,见 §4.2) 184,320 / 184,320
16 组折叠对 100% 恢复,不消耗校验预算 252 / 252
折叠 + 1 个真实错误 100% 修复 225 / 225

8.2 限制(同样是规范的一部分)

  1. 3 个及以上不可读字符:拒绝。 不是能力不足,是刻意的——见 §5.5。
  2. 3 个及以上替换错误:不保证。 超出设计预算。一致性判据可能被偶然满足, 产生一个合法但错误的标识符。这正是 §7.3 强制二次确认的原因。
  3. 错误 + 擦除混合:不支持。 存在擦除时,本规范只按纯擦除处理;若同时存在 位置未知的替换错误,解析 MUST 落到 unrepairableMUST NOT 尝试联合求解。
  4. 碰撞不是零。 见 §3.3。标识符不是全局主键——全局身份由标签第 2 行的 URL 承载,DNS 已经保证其唯一性。标识符的职责是:标记这是 AIQR、在 URL 污损时提供冗余路径、 让误读可被发现。仅凭标识符解析 MAY 产生歧义,MUST 与解析到的 Manifest 交叉确认。

8.3 端到端表现(光学层,真实 OCR)

Tesseract,4 种字体 × 12 档劣化(最狠一档 0.18 倍缩放 + 模糊 + JPEG q20 + 旋转), 1,200 次整串识别走完整解析器:

正确解析 正确拒绝 解析到错误标识符
擦除上限 3(v1.0) 74.7% 24.3% 7 (0.6%)
擦除上限 2(v1.1) 74.1% 25.9% 0
上限 2 + OCR 白名单(§7.4) 66.3% 33.7% 0

视觉模型臂(盲测,Claude Opus 5,8 档劣化,n=8):8/8 逐字符全对

必须一起读的限定:单引擎、合成劣化、每字符 48 次样本、视觉臂 n=8 且评分者即作者。 完整方法、被推翻的推理、以及这次测量不能证明什么,见 docs/ocr-measurement.md


9. 派生

标识符 SHOULD 由 Manifest URL 确定性派生:

canonical = lowercase(trim(url)) 去掉尾部所有 '/'
digest    = SHA-256(canonical)
n         = digest 的前 128 位,按大端解释为整数
数据符    = n 在 17 进制下的低 13 位(高位在前)
校验符    = 按 §4.1 求解

同一 URL 恒得同一标识符。无需分配服务器,无需注册表,无需任何协调。

128 位归约到 17¹³(约 53 位)产生的模偏差量级为 10⁻⁷,可忽略。

实现 MAY 改用注册表分配而非派生;两种方式产生的标识符在格式与校验上无区别。


10. 版式建议(非规范性)

标识符印在标签上时:


11. 测试向量

完整机读版本:spec/test-vectors.json。 符合规范的解析器 MUST 对全部向量给出标注的状态与结果。

输入 期望状态 期望结果
AIQR:0ATA-XJH3-YXYC-PCC5 valid 0ATA-XJH3-YXYC-PCC5
0ata-xjh3-yxyc-pcc5 valid 0ATA-XJH3-YXYC-PCC5
0ATAXJH3YXYCPCC5 valid 0ATA-XJH3-YXYC-PCC5
AIQR: 0ATA XJH3 YXYC PCC5 valid 0ATA-XJH3-YXYC-PCC5
the sticker reads AIQR:0ATA-XJH3-YXYC-PCC5 underneath valid 0ATA-XJH3-YXYC-PCC5
OATAXJH3YXYCPCC5(首位 0 被读成 O valid 0ATA-XJH3-YXYC-PCC5
单字符替换(下标 3) repaired 0ATA-XJH3-YXYC-PCC5
1 个字符不可读(I repaired 0ATA-XJH3-YXYC-PCC5
3 个字符不可读 repaired 0ATA-XJH3-YXYC-PCC5
4 个字符不可读 unrepairable
两处替换 unrepairable
相邻换位 unrepairable
AIQR:23AC-5H8M(长度不足) malformed
AIQR: hello world not an identifier malformed

12. 未决问题

  1. C 应当被移除。 v1.1 的实测给出了明确信号:C 在字符集内正确率最低(52%), 且与 493G 都混。字符集内部还残留 C/9 C/3 J/3 P/3 T/Y X/3 Y/2 七对混淆——§2.1 的排版学分簇漏掉了它们。 未在 v1.1 动手的原因:移除 C 会让字符集变成 16(非质数),必须补一个符号, 而补哪个不能再靠猜。每字符 48 次的样本量只够观测 2% 量级的事件,不足以支撑重新推导。 前置条件是把样本量提到每字符 ≥500 次。
  2. 折叠表 12 条零观测。 3 条被数据否定(4→A 实为 4←CG→6 实为 G←CS→5 实为 S←3),12 条既未证实也未否定。之所以保留,是因为实测显示条目的 存在正确更重要(见 §5.5 注);但这不等于它们是对的。
  3. 合成劣化 ≠ 真实印刷。 尚未测:真实纸张、反光、曲面(贴在圆柱形机身上)、 低光、远距离、部分遮挡。这些是工业现场的常态。
  4. 17 是否最优。 19 或 23 个符号能提高密度,13 个能提高安全余量。当前取值是权衡判断, 不是优化结果。等 (1) 的数据到位后应重算。
  5. 更短的档位。 16 符对小型标签(药品、元件)可能偏长。可以定义一个共用字符集与校验 算法、但数据符更少的短档;代价是容量与碰撞风险。在有真实需求前不定义。
  6. 签名。 标识符本身不携带真实性证明。防标签替换最终需要来源签名,属 Manifest 层, 见 aiqr-v0.2.md §10。
  7. 英文版。 公开发布前 MUST 提供 aiqr-id-v1.1.en.md

附录 · 参考实现

src/aiqr-id.mjs(CC0,零依赖)

node bin/aiqr.mjs id https://aiqr.cc/m/7F92A          # 派生
node bin/aiqr.mjs id --verify "0ATA-XJH3-YXYC-PCC5"   # 校验 / 修复
node scripts/test-id.mjs                              # 产出 §8.1 全部数字
node scripts/ocr-confusion.mjs                        # 产出 §8.3 光学层数字