☰
模拟专家工作流的甲骨文辅助释读系统原型设计
2026/9/26 1:44:44 网站建设 项目流程

甲骨文释读并不是把一张拓片丢给模型、让它直接输出一个字那么简单。真正的释读过程,是一套围绕字形、部件、辞例、历史演变和出土信息展开的多重证据考据流程。这也是为什么很多 OCR 或图像识别方案在普通文字资料上表现不错,一旦遇到甲骨文就失效:因为系统缺少对人类专家工作流的模拟。这篇文章要讨论的是,如何把一个模仿人类专家工作流的辅助释读系统落地成一个可运行、可追溯、可扩展的原型,而不是做一个“一键出字”的黑盒。

所谓“模仿人类专家工作流”,并不是让产品界面长得像某个考古工具,而是要把专家释读甲骨时经历的观察、拆解、检索、假设、验证、定案这一套动作,抽象成计算机可以执行的阶段和状态。系统只负责提供证据、排序候选、记录判断过程,最终是否采信仍由专家决定。这样既保留考据的严谨性,也让每一次释读结论都有过程留痕,便于复查和修正。

1. 为什么甲骨文释读需要一套可模仿专家工作流的系统

1.1 甲骨文释读的难点不在单个字形,而在证据链

甲骨文距今约三千年,很多字形在现代人看来非常陌生。单个字形可以拆成若干部件,但部件与部件之间的组合方式并不固定,同一个字在不同时期、不同贞人、不同版面上又存在异写。更麻烦的是,很多字在史料中没有现成对照,需要结合卜辞语境、同版对贞、后世金文、篆书演化等线索综合判断。

这意味着,释读一个甲骨字,本质上是建立一条证据链:字形结构支持什么解读,辞例位置是否吻合,后世字书有没有佐证,同批甲骨中是否出现可类比写法。如果系统只输出一个候选结果,不展示证据链,专家很难判断这个结果是否可信,也无法在论文或考释中引用系统依据。

1.2 常见 AI 辅助方案的短板

现在有不少项目会把甲骨文识别做成图像分类问题:收集一批已标注字形图片,训练卷积神经网络,输入新字形后直接输出类别。这种方案在公开数据集上可以取得不错的分数,但在真实释读场景中会暴露出三个明显问题。

第一,训练数据通常是已经整理好的单个字形图片,而真实拓片上字形与刻痕、裂纹、拓印噪声混在一起,系统缺乏从原图到单字的定位能力。第二,模型输出的是一个固定字表里的类别,遇到未登录字或异写字时无法给出“最接近的部件组合”或“可参考的考释意见”。第三,释读过程不可解释,专家不知道模型为什么给出这个结论,也无法在模型结果基础上局部修正。

所以,只做端到端识别不足以支撑真正的辅助释读。更合理的方式是,把专家工作流拆成若干可校验的步骤,每个步骤的输出都作为下个步骤的输入,同时把每一步的关键证据保存下来。

1.3 把专家工作流建模成系统流程

一位甲骨文专家拿到一份拓片或字形图片时,通常不会立刻下结论。他首先会观察字形轮廓、刻痕方向、残断情况;然后尝试拆出偏旁或部件,与已知部件库对照;再把候选字形放回卜辞原文,看辞例通不顺;最后结合历史考释文献和后世文字演化,综合给出释读建议。

这个过程可以映射为一条计算流水线:

  1. 观察:对原始图像做增强、去噪和字形定位。
  2. 拆解:把字形切分为部件,或者提取局部特征块。
  3. 检索:用部件和字形全局特征匹配已有字形库、部件库和语料库。
  4. 假设:根据匹配结果生成候选释读,并附上证据文本。
  5. 验证:把候选字放回辞例,计算上下文合理性。
  6. 定案:由专家确认或修正,并将结果写回知识库。

这套流程既能支撑专家复审,也能让系统在数据积累后逐步提升。后续训练的模型可以替换流水线中的单个模块,而不必推翻整体设计。

2. 系统设计:从专家考据过程到模块化流水线

2.1 整体架构与数据流向

辅助释读系统的架构可以分成四层:数据层、算法层、工作流层和交互层。

数据层存放原始拓片、切分后的字形图、部件标注、卜辞语料、专家释读记录。算法层负责图像预处理、特征提取、候选检索和置信度计算。工作流层维护每条释读任务当前处于哪个阶段,以及阶段之间允许的转移关系。交互层面向专家,提供观察、修改、批注和确认入口。

数据流向是这样的:原始图片经过预处理模块,得到候选字形区域;每个字形区域进入特征提取模块,转成向量;向量在数据库中检索相似字形和部件;检索结果交给解释模块,生成候选字表和证据文本;工作流状态机记录这个过程中的每一步;专家在交互层看到结果后,可以同意、修改或补充证据,最终写回标注库。

这种分层的好处是模块之间只通过标准数据结构通信。例如预处理模块输出的是归一化坐标和裁剪图,检索模块不关心坐标来自哪个预处理算法;工作流模块只负责状态转移,不关心特征向量具体如何计算。

2.2 工作流状态机设计

为了让系统真正“模仿专家工作流”,状态机设计是关键。每个释读任务必须明确处于哪个阶段,不能跳过必要步骤,也不能在证据不足时直接进入最终结论。

这里定义一套最小状态集:

状态含义输入输出
observe观察字形与图像质量原始图片字形定位框、预处理图
decompose拆解字形结构字形图部件列表、局部特征块
retrieve检索相似字形与部件特征向量候选字形集、相似度分数
hypothesize生成释读假设候选结果候选字表、证据说明
verify验证辞例与文献候选字表上下文匹配度、文献线索
dictate专家定案候选结果与证据最终释读、批注
review复核并入库最终释读标注记录、反馈日志

状态转移不是发散的,而是有方向的。比如从 hypothesize 可以回到 retrieve,说明候选集不足;从 verify 可以回到 hypothesize,说明辞例验证不通过;但不能从 observe 直接跳到 dictate,因为这样会绕过证据构建过程。

状态机中每个节点都应当保留当前任务的操作人、时间、输入摘要和输出摘要。这样做一方面方便专家回看,另一方面也为数据集迭代积累过程数据。

2.3 模块边界与接口约定

模块之间的接口越稳定,系统越好维护。这里定义几个核心数据接口。

字形区域用归一化坐标表示,而不是直接保存绝对像素。因为同一张拓片可能被缩放、裁切或放到不同屏幕上展示,归一化坐标可以避免坐标错位。

部件的候选结构建议包含:部件名、匹配分数、匹配区域坐标、来源字形编号。这样解释模块可以知道“这个部件来自哪个字形”,而不是只看到一个孤零零的名字。

候选释读结果建议包含:字头、置信度、证据列表、支持材料编号。证据列表要尽量结构化,比如“部件匹配得分 0.81”“辞例《合集》12345 中出现该字形”“同版有对贞字形”。结构化证据比一段自然语言更容易被后续程序二次处理。

3. 环境准备与项目初始化

3.1 技术栈选型

原型系统可以使用 Python 3.9 以上版本,配合 OpenCV、NumPy、scikit-learn、Flask 和 SQLite。这套组合在普通开发机上就能运行,不依赖 GPU,适合学习阶段先跑通工作流。

生产环境如果要处理大批量拓片,通常会引入更深的模型、向量数据库和对象存储,但原型阶段越简单越好。选型时重点考虑三点:图像处理库是否成熟、Web 框架是否容易写接口、数据库是否方便存储结构化证据。

这里列出原型依赖的清单:

依赖用途版本建议
Python开发语言3.9+
opencv-python图像预处理与特征提取4.x
numpy向量计算1.24+
scikit-learn相似度检索与指标计算1.3+
flask提供 Web API2.3+
sqlite3存储字形、任务与反馈Python 内置

如果原始材料没有明确版本,落地前要先确认当前环境中依赖版本彼此兼容,尤其是 opencv-python 与 numpy 的版本组合。

3.2 目录结构与配置文件

一个清晰的项目目录能降低理解成本。推荐按职责划分目录,而不是把代码全部堆在入口文件里。

oracle_assistant/ ├── config.yaml ├── requirements.txt ├── app/ │ ├── __init__.py │ ├── main.py │ ├── workflow.py │ ├── preprocessing.py │ ├── feature.py │ ├── retrieval.py │ ├── explain.py │ └── store.py ├── data/ │ ├── raw/ │ ├── crops/ │ ├── components.json │ ├── corpus.json │ └── oracle.db └── frontend/ └── index.html

raw目录放原始拓片图,crops放切分出来的单字图,components.json存放部件字典,corpus.json存放卜辞语料。初始阶段都可以用少量示例数据验证流程。

config.yaml用来集中管理参数,避免把阈值写死在代码里。

project: name: oracle_assistant data_dir: ./data db_path: ./data/oracle.db preprocess: min_contour_area: 100 target_size: 64 feature: method: hog hog_block_size: 16 hog_cell_size: 8 hog_bins: 9 retrieval: top_k: 10 similarity_threshold: 0.72 workflow: stages: - observe - decompose - retrieve - hypothesize - verify - dictate - review

参数集中管理后,调参不需要改代码,也方便实验对比。

3.3 数据库表结构与初始化

数据库主要保存字形图片、释读任务、专家决策和反馈日志。表结构要尽量简单,但需要支持状态流转和证据追溯。

CREATE TABLE glyphs ( id INTEGER PRIMARY KEY, image_path TEXT NOT NULL, source_book TEXT, source_rubbing TEXT, label TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE decisions ( id INTEGER PRIMARY KEY, glyph_id INTEGER NOT NULL, stage TEXT NOT NULL, status TEXT NOT NULL, confidence REAL, evidence TEXT, expert_comment TEXT, created_by TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (glyph_id) REFERENCES glyphs(id) ); CREATE TABLE feedback_log ( id INTEGER PRIMARY KEY, decision_id INTEGER NOT NULL, action TEXT NOT NULL, detail TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (decision_id) REFERENCES decisions(id) );

这里把每个阶段的决策都存成一条decisions记录,而不是只存最终结果。这样专家可以在feedback_log里看到“什么人在什么时间改了什么候选字”,这是构建可审计释读流程的基础。

初始化数据库时,可以先往glyphs表插入少量图片路径,用于测试。代码中建议加一个init_db()函数,在服务启动时自动建表。

4. 核心实现:按专家步骤拆解算法模块

4.1 图像预处理与字形定位

图像预处理的目的是把拓片上的刻痕从背景中分离出来。真实拓片往往有纸纹、裂纹和深浅不一的墨色,直接二值化容易把噪声也当成字形。

这里使用高斯模糊消除部分噪声,再用 Otsu 自适应阈值做二值化,最后用轮廓检测找出候选字形区域。min_contour_area用于过滤过小的噪声块,避免把污渍当作字形。

import cv2 import numpy as np def preprocess_and_locate(image_path, min_area=100): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f"Cannot read image: {image_path}") blurred = cv2.GaussianBlur(img, (3, 3), 0) _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 反转二值图,确保字形区域为白色前景 binary_inv = cv2.bitwise_not(binary) contours, _ = cv2.findContours( binary_inv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) boxes = [] for contour in contours: area = cv2.contourArea(contour) if area < min_area: continue x, y, w, h = cv2.boundingRect(contour) boxes.append({"x": x, "y": y, "width": w, "height": h, "area": area}) return boxes

这里要注意,坐标值是相对原图的像素坐标。如果后续要展示到网页或保存到数据库,建议额外保存图像宽高,或者转换为归一化坐标,避免前端拿到不同分辨率的图时对不齐。

4.2 字形部件分解与特征提取

在简单原型中,部件分解可以先用连通域把字形拆成多个局部块。更复杂的方式是训练一个部件检测模型,但学习阶段不需要一上来就依赖标注数据。

如果components.json已经记录了字形中每个部件的外接框,那么系统可以直接按框裁剪图片,再用特征提取得到向量。

{ "components": [ { "id": "c001", "name": "又", "image": "data/crops/c001.png", "description": "表示手部的部件" }, { "id": "c002", "name": "示", "image": "data/crops/c002.png", "description": "表示神主或祭祀的部件" } ] }

特征提取这里采用 HOG 特征。HOG 描述的是图像局部梯度方向分布,对笔画结构比较敏感,适合作为字形检索的基线方法。先把图像统一缩放到 64x64,再计算 HOG 向量。

import cv2 def extract_hog(image_path, target_size=(64, 64)): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f"Cannot read image: {image_path}") img = cv2.resize(img, target_size) hog = cv2.HOGDescriptor( target_size, (16, 16), (8, 8), (8, 8), 9 ) return hog.compute(img).flatten()

HOG 参数不是唯一选择。target_size太小会丢失笔画细节,太大会让向量维度过高、计算变慢。在 64x64 下,HOG 向量维度通常已经足够用于原型验证。

4.3 候选检索与证据拼接

候选检索阶段,系统拿查询字形向量,去字形库里检索相似字形。这里使用余弦相似度作为距离指标,因为它对向量长度不敏感,更适合比较图像描述子。

import numpy as np from sklearn.metrics.pairwise import cosine_similarity def retrieve_candidates(query_vec, gallery_vecs, gallery_ids, top_k=10): scores = cosine_similarity([query_vec], gallery_vecs)[0] ranked = np.argsort(scores)[::-1][:top_k] candidates = [] for idx in ranked: candidates.append( { "glyph_id": gallery_ids[idx], "score": float(scores[idx]), } ) return candidates

在真实项目中,gallery_vecs可能来自数千张已标注字形图。计算所有相似度虽然可行,但数据量增大后会变慢。生产环境一般会改用向量数据库,或者先用部件检索缩小候选范围,再对少量候选做精细匹配。

检索结果还需要和部件拆解结果合并。假设查询字形被拆成了三个部件,每个部件都能在部件库中匹配到多个候选,那么最终证据就可以拼接成“‘又’部件匹配到字形 A,相似度 0.84;‘示’部件匹配到字形 B,相似度 0.79”。这种结构化证据比单一整字匹配分数更容易让专家理解。

4.4 释读假设生成与置信度计算

释读假设生成模块负责把候选字形、部件匹配和辞例线索整理成可读的证据,并计算一个综合置信度。原型阶段置信度不需要太复杂,可以用加权平均。

def build_candidate(glyph_id, feature_score, component_hits, corpus_hits): score = 0.5 * feature_score if component_hits: score += 0.3 * np.mean([h["score"] for h in component_hits]) if corpus_hits: score += 0.2 * min(1.0, len(corpus_hits) / 3) evidence = [] evidence.append(f"整字特征匹配得分:{feature_score:.2f}") if component_hits: parts = "、".join([h["name"] for h in component_hits[:3]]) evidence.append(f"可拆解部件:{parts}") if corpus_hits: evidence.append("相关辞例:" + ";".join(corpus_hits[:2])) return { "glyph_id": glyph_id, "confidence": round(score, 2), "evidence": evidence, }

这里权重只是示例。实际项目中,权重应当由专家根据任务类型调整:如果字形清晰但辞例缺失,特征权重可以调高;如果字形模糊但辞例信息丰富,辞例权重应更高。把权重放到config.yaml里,比硬编码更容易实验。

置信度不代表真实正确率,只代表系统对证据一致性的判断。因此界面中不建议显示为“系统判断 90% 正确”,而应显示为“候选有 0.82 的证据置信度,共 3 条证据”。

5. 专家交互与结果留痕

5.1 工作流状态流转接口

工作流状态机要限制非法跳转。例如专家还没完成部件拆解,就不能直接进入检索阶段。这里用一个 Python 类维护状态。

class WorkflowState: VALID_TRANSITIONS = { "observe": {"decompose"}, "decompose": {"retrieve"}, "retrieve": {"hypothesize"}, "hypothesize": {"verify", "retrieve"}, "verify": {"dictate", "hypothesize"}, "dictate": {"review"}, "review": {"done"}, } def __init__(self, glyph_id, stage="observe"): self.glyph_id = glyph_id self.stage = stage self.payload = {} def transition(self, next_stage): allowed = self.VALID_TRANSITIONS.get(self.stage, set()) if next_stage not in allowed: raise ValueError( f"Illegal transition: {self.stage} -> {next_stage}" ) self.stage = next_stage

接口层用 Flask 暴露一个状态流转接口。请求体里只需要传next_stage,系统返回当前状态和提示信息。

curl -X POST http://127.0.0.1:8000/api/glyph/1/stage \ -H "Content-Type: application/json" \ -d '{"next_stage": "decompose"}'

响应示例:

{ "glyph_id": 1, "stage": "decompose", "message": "当前任务可进入部件拆解阶段" }

这种接口设计确保前端只能引导专家按步骤操作,避免“一键跳到最后”的坏习惯。

5.2 专家确认与修正流程

专家在界面中看到候选释读结果后,可以选择“采纳”“修改”或“补充证据”。采纳操作会把当前候选写入decisions表,同时记录操作人。修改操作需要填写新的字头和理由,系统将这些信息存入feedback_log。

def confirm_decision(decision_id, result_label, expert_comment, created_by): # 检查当前决策是否存在 # 更新 decisions 表的 label、expert_comment、created_by # 在 feedback_log 中追加一条 action=confirm 的记录 pass

这里不建议直接删除候选结果。即使专家认为某个候选是错误的,错误的候选及其证据仍然有研究价值,应该被保留下来。系统后续做模型迭代时,可以把这些被否定的负样本加入训练集。

专家的修正意见会写回知识库,供下一次检索使用。例如专家补充“这个部件更接近‘止’而不是‘又’”,系统可以把这一条记录为部件匹配规则,未来遇到类似字形时优先推荐接近“止”的解释。

5.3 运行验证:一个最小闭环示例

为了让整个流程可验证,可以准备一张只有单个字形的拓片图,然后依次调用各模块,最后确认一条释读记录。这个最小闭环不需要完整前端,通过 Python 脚本即可跑通。

python app/main.py --init-db python tools/run_pipeline.py --image data/raw/example.jpg

run_pipeline.py的逻辑是:调用预处理模块定位字形,对每个字形提取 HOG 特征,检索字形库,生成候选,打印证据。输出大致如下:

字形 1: 定位框: x=120, y=80, width=90, height=110 候选1: 字形编号 101, 置信度 0.78 证据: 整字特征匹配得分:0.76 可拆解部件:又、示 相关辞例:暂无 候选2: 字形编号 202, 置信度 0.65 证据: 整字特征匹配得分:0.64 可拆解部件:又、口

如果系统能按照这个顺序输出结构化证据,说明流水线已经跑通。接下来可以接入 Web 前端,把同样的结果展示在浏览器里。

6. 常见问题与排查路径

6.1 图像切分与坐标错位

现象:预处理得到的字形框要么把整个拓片当成一个字形,要么漏掉小字。

原因:min_contour_area设置不合适;二值化后噪声太多;字形粘连严重。

检查方式:把二值化结果和定位框画到图上,人工观察是阈值问题还是轮廓合并问题。

解决方案:先在config.yaml中调低或调高min_contour_area;如果噪声太多,改用形态学开运算去掉小噪点;如果字形粘连,需要使用分水岭或基于标注框的切分方法。

6.2 候选检索结果发散

现象:查询字形和返回的候选字形在视觉上差异很大,相似度分数却接近。

原因:特征提取粒度太粗;字形库太小;不同刻痕风格导致特征偏移。

检查方式:打印每个候选的相似度分数,并对候选图做缩略图对比。

解决方案:增加特征向量维度,使用更稳定的局部特征;扩大字形库样本量;或者用部件级检索替换整字级检索,先找部件再组合。

6.3 状态机流程卡死或重复提交

现象:专家点击下一步后页面无变化,接口返回 400;或者同一个阶段被重复记录多次。

原因:前端重复发送请求;状态转移没有做幂等;状态机不允许回退。

检查方式:查看后端日志,确认请求参数和当前状态;检查数据库里decisions表是否出现重复记录。

解决方案:接口层增加状态校验,当前阶段与请求目标阶段一致时直接返回成功但不重复写日志;允许从verify回退到hypothesize,避免专家发现问题后无法修改。

下表汇总常见问题:

问题现象常见原因检查方式处理建议
定位框包含整页面积阈值过大或轮廓合并绘制定位框到原图调低 min_contour_area,分离连通域
小字被漏掉面积阈值过小被过滤查看轮廓面积分布按边界框宽高比和面积双重过滤
特征匹配得分虚高字形库太小或特征太简单打印相似度分数分布使用更精细特征,增加负样本
状态机重复提交前端无幂等控制查看 feedback_log添加请求唯一标识,后端去重
前端坐标对不上使用绝对像素坐标对比原图与缩略图保存归一化坐标,前端还原

7. 从原型到生产:落地注意事项与最佳实践

7.1 学习环境与生产环境的差异

学习环境的目标是快速跑通工作流,可以使用少量本地图片和 SQLite,算法模块用 HOG 这类传统方法即可。生产环境则要额外考虑数据规模、权限、审计和模型更新。

生产环境至少需要补齐以下能力:

  • 配置外置化:数据库连接、对象存储路径、模型路径不能写死在代码里。
  • 日志与监控:记录每次释读操作,监控接口耗时和检索失败率。
  • 权限与审计:只有具备考古或文字学背景的专家才能定案,所有修改操作要留痕。
  • 模型版本管理:特征提取模型升级后,要能区分新旧向量,避免新旧特征混用。
  • 回滚方案:当专家修正了错误释读后,系统要支持把已经入库的结果修改回来,并保留原始记录。

如果原型阶段不提前考虑这些,生产接入时往往会推倒重来。

7.2 可复用的工程清单

以下清单可以在开发前逐项检查,避免遗漏关键节点。

  • 是否定义完整的释读状态集合?
  • 是否限制状态转移方向?
  • 每个阶段是否保存输入、输出和操作人信息?
  • 图像定位框是否使用归一化坐标?
  • 特征提取参数是否集中在配置文件中?
  • 检索阈值和 Top-K 是否可以动态调整?
  • 候选结果是否包含结构化证据,而不是只有分数?
  • 专家确认后是否写回知识库,形成闭环?
  • 反馈日志是否保留负样本,供后续模型迭代?
  • 是否区分原型演示数据和生产可信数据?

7.3 后续扩展方向

原型系统跑通后,可以从三个方向继续深入。

第一,把整字特征匹配替换为深度学习特征提取模型,例如用孪生网络学习“字形是否相似”,提高异写字和残断字形的召回能力。

第二,构建甲骨文知识图谱。把字形、部件、辞例、出处、历代考释文献连接成图,检索阶段不只做图像匹配,还可以沿着知识图谱扩展相关证据,生成更完整的考释建议。

第三,引入可配置的专家规则。专家可以把个人经验写成规则,例如“当出现某两个部件组合时,优先考虑指向祭祀类动词的释读”。规则可以接入explain.py,与图像特征共同决定候顺序。

辅助释读系统的核心价值不在某个模型有多强,而在于它能把专家的工作方法沉淀为系统流程,让每一份释读结论都有据可查、有迹可循。对开发者的建议是,不要一头扎进最深的模型里,先搭好工作流骨架,再逐步替换算法模块,这样才能真正做出能被文字学研究者使用的工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询