ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

泛黄扉页上的墨香密码:用多模态小模型提取旧书题跋并建立私有藏书志

泛黄扉页上的墨香密码:用多模态小模型提取旧书题跋并建立私有藏书志

深秋的周末,我总喜欢去老街上的旧书店淘书。书架深处弥漫着干燥纸张与老油墨特有的陈香,在那些泛黄脆弱的旧书页里翻找,就像在时间的长河里打捞漂流瓶。

买旧书最迷人的时刻,往往发生在轻轻翻开扉页的那一瞬间。很多被岁月浸润过的老书上,静静沉睡着前人亲笔写下的墨迹:或是民国时期的一方朱红藏书印,或是一九八二年深秋一位长辈赠予友人的行草题跋,亦或是贴在内封的一张手绘木刻藏书票(Ex Libris)。

“某年秋深,雨窗闲坐,得此卷于琉璃厂,谨以此册赠某某兄,愿文思如秋水从容……”

这些笔锋里带着呼吸与温度的手写短句,承载着一本书在不同生命之间流转的温情履历。然而,岁月无情,宣纸与道林纸在几十年间悄然氧化,墨迹在纤维间微微渗透晕染,加上古朴的行草连笔与干支纪年,让普通的文字识别工具在它们面前彻底变成了睁眼瞎。

为了不让这些散落在旧纸堆里的墨香随风消散,我们可以借助端侧运行的多模态视觉小模型,给书房打造一位兼具金石学常识与文字通感的私家藏书整理助手。

为什么传统 OCR 在手写题跋前无能为力?

识别旧书扉页上的题记,几乎把计算机视觉所能遇到的所有极端挑战集合在了一起:

  1. 纸张背景的斑驳与泛黄:数十年的氧化在纸页上留下了大面积不规则的黄色晕斑,传统基于二值化(Binarization)的边缘检测算法会把污渍误当成笔画;
  2. 行草连笔与书写个性:前人书写往往意到笔随,减笔、连笔极多,字与字之间相互穿插牵丝,传统的字符切割算法在第一个字就会碰壁;
  3. 金石篆刻与干支历法:古雅的藏书印章大多是九叠篆或汉印风格,题记落款动辄是“岁次乙丑中秋”或“草堂灯下”。没有深厚中华文史常识的规则引擎,根本无法将这些古雅的信息转化为现代的结构化年份。

而端侧多模态小模型具备全图自注意力与庞大的跨模态语义库。它在审视一张泛黄的扉页时,既是在识别孤立的像素点,同时也是在结合上下文推敲全篇文气:看到“中秋”能联想到“乙丑”,看到“谨赠”能向下推导受赠人的名号,看到那方四方的朱红印泥,能自动结合篆体特征拼读出印文的字样。

本地流水线构建:让老墨迹化作数字卡片

我们选用端侧视觉模型构建一套完全离线、不上传任何私密书影的提纯流程。

首先编写核心的题跋与印章视觉解析器:

import os import json import base64 import sqlite3 import httpx DB_PATH = os.path.expanduser("~/data/book_collection.db") OLLAMA_API = "http://localhost:11434/api/generate" def encode_image(img_path: str) -> str: with open(img_path, "rb") as f: return base64.b64encode(f.read()).decode("utf-8") def decipher_old_inscription(image_path: str, book_title: str) -> dict: """调用本地多模态小模型,释读旧书扉页的手写题跋、印章与藏书票""" base64_img = encode_image(image_path) prompt = f""" 你是一位精通版本目录学、金石书法与古典文学的私家藏书整理专家。 请仔细观察这张《{book_title}》旧书扉页的高清图片,耐心地释读其中的岁月痕迹。 请提取以下内容并严格输出为 JSON 格式: 1. transcription: 手写题跋的完整简体释文,遇到无法辨识的字用□占位,尽量按原本断句排版; 2. author_signer: 题写人/赠书人的署名或字号; 3. recipient: 受赠人称谓; 4. date_inscribed: 题记时间(若原文为干支纪年如“甲子年”,请推算其最有可能对应的公历年份并附在括号中); 5. seal_text: 辨识画面中朱红印章的篆体印文内容; 6. sentiment_note: 用一段温润质朴的技术随笔口吻,赏析这段题跋背后的生活心境与流转故事。 严格只输出合法 JSON,绝不附加任何额外客套话。 """ payload = { "model": "minicpm-v:latest", # 或其他本地多模态模型 "prompt": prompt, "images": [base64_img], "stream": False, "format": "json" } resp = httpx.post(OLLAMA_API, json=payload, timeout=60.0) resp.raise_for_status() return json.loads(resp.json()["response"])

打造个人私有藏书志

提取出来的结构化元数据,不仅包含文字的忠实释读,还融合了对历史背景的温情考据。我们将它们持久化保存在本地的book_collection.db中:

def init_collection_db(): with sqlite3.connect(DB_PATH) as conn: conn.execute(""" CREATE TABLE IF NOT EXISTS antique_inscriptions ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_title TEXT, image_rel_path TEXT, transcription TEXT, signer TEXT, recipient TEXT, date_inscribed TEXT, seal_text TEXT, sentiment_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); """) def save_inscription(book_title: str, img_path: str, data: dict): with sqlite3.connect(DB_PATH) as conn: conn.execute(""" INSERT INTO antique_inscriptions ( book_title, image_rel_path, transcription, signer, recipient, date_inscribed, seal_text, sentiment_note ) VALUES (?, ?, ?, ?, ?, ?, ?, ?); """, ( book_title, img_path, data.get("transcription", ""), data.get("author_signer", "佚名"), data.get("recipient", "未知"), data.get("date_inscribed", "岁次不详"), data.get("seal_text", "无印"), data.get("sentiment_note", "") )) conn.commit() print(f"📖 《{book_title}》的墨香题跋已归入私家藏书志。")

跨越时空的笔谈

前几天在旧书市集上,我以微薄的代价淘到了一本一九五八年出版的《唐诗三百首新注》。翻开微卷的牛皮纸衬页,上面斜斜题着四行清秀的钢笔小楷,右下角盖着一枚小小的椭圆朱砂章。

我用手机在柔和的台灯下拍了一张照送进脚本。半分钟后,控制台排布出了一张让人心头一颤的卡片:

释文:“秀琴贤妹留念:千里之行,始于足下。莫畏秋霜肃杀,常读此卷,心存暖阳。兄长于江城客栈灯下。”
署名:兄长
受赠:秀琴
考证时间:一九六二年深秋(根据背面粮票印痕推断)
印文:寄情于文
志语:六十多年前江城旅店的一盏油灯,映照着兄长对远行小妹的牵挂。窗外秋霜虽冷,字里行间却尽是长兄如父的温润厚意。

看着屏幕上的这段释文,那本原本沉寂在故纸堆里的旧书,仿佛瞬间被注入了灵魂。六十年前那位伏案写字的长者、那个怀揣诗卷走出家门的小妹,他们的悲欢与祈愿,跨越了漫长的甲子岁月,在这个秋夜的书房里,与我的目光静静相遇。

代码并不只能用来追逐浮躁的商业指标。当我们用算法为那些即将湮灭的手写温度搭建一座数字方舟,技术便拥有了对抗遗忘、连接岁月的心跳。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进