ancient-ocr-viewer/docs/三大关键问题.md
Yuuko 1018416a7a Initial commit: Ancient OCR Viewer
- Canvas-based dual display (image + text)
- Grid rendering system with layout support
- Uniform font size rendering
- Double-line small character handling
- Comprehensive documentation of OCR rules and algorithms

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 16:59:40 +08:00

5.0 KiB
Raw Blame History

古籍OCR渲染的三大关键问题

问题1逻辑行到物理行的划分换行问题

场景

一个逻辑列line_id有100个字但版式只有20行如何换行填充

答案

根据y坐标计算每个字符的物理行号

const cellHeight = canvasHeight / rowsPerColumn;  // 每行的高度
const row = Math.round(char.yCenter / cellHeight); // 字符中心y坐标 / 行高

关键点

  1. 不是顺序填充不能简单地从第0行开始依次填充
  2. 基于坐标计算:每个字符的行号 = y坐标 ÷ 行高(四舍五入)
  3. 人工校对保证:人工校对后的数据,每列字符数不会超过版式限制

示例

版式20行行高 = 6000px / 20 = 300px

字符A: y=1500  → row = 1500/300 = 5  → 第5行
字符B: y=1800  → row = 1800/300 = 6  → 第6行
字符C: y=2100  → row = 2100/300 = 7  → 第7行

问题2空列的判断物理列到网格列的映射

场景

  • line_id=0 对应物理第1列最右
  • line_id=1 对应物理第8列最左
  • 中间的2-7列是空的

如何正确映射?

错误做法(当前程序)

// 按物理列顺序映射
物理列0  网格列0
物理列1  网格列1

问题:忽略了空列,导致位置错误

正确做法1基于x坐标计算

const cellWidth = canvasWidth / totalColumns;  // 每列宽度
const gridCol = Math.round((canvasWidth - xCenter) / cellWidth);

问题:如果页面有左右边距,计算会不准

正确做法2基于列间距推断推荐

步骤

  1. 计算所有物理列的x中心[2900, 2600, 2300, ...]
  2. 计算相邻物理列的间距:[300, 300, 300, ...]
  3. 识别异常大的间距如900px说明中间有空列
  4. 根据间距计算空列数:空列数 = floor(大间距 / 平均间距) - 1

示例

物理列1: x=2900
物理列2: x=900   间距=2000px
平均间距: 300px
→ 中间空列数 = floor(2000/300) - 1 = 5列
→ 物理列1 → 网格列0
→ 物理列2 → 网格列60+1+5空列

问题3起始位置和列内空格

3.1 列的起始行(是否顶格)

判断方法看该列第一个字符的y坐标

const firstChar = column.chars[0];  // 按y排序后的第一个字符
const startRow = Math.round(firstChar.yCenter / cellHeight);

if (startRow === 0) {
    console.log("顶格开始");
} else {
    console.log(`从第${startRow}行开始,前面有${startRow}个空格`);
}

3.2 同一物理列内逻辑列之间的空格

场景:一个物理列包含多个逻辑列

物理列1:
  line_id=0: 3个大字
  line_id=1: 2个小字配成1格
  line_id=2: 5个大字

如何判断 line_id=0 和 line_id=1 之间有没有空格?

方法按y坐标排序后检查相邻字符的行号差

// 合并所有字符按y排序
allChars.sort(by y);

for (let i = 0; i < allChars.length - 1; i++) {
    const currentRow = Math.round(allChars[i].yCenter / cellHeight);
    const nextRow = Math.round(allChars[i+1].yCenter / cellHeight);

    const gap = nextRow - currentRow - 1;  // 减1是因为相邻字符行号差1是正常的

    if (gap > 0) {
        console.log(`第${currentRow}行到第${nextRow}行之间有${gap}个空格`);
    }
}

3.3 双行小字内部的处理

重要同一格内的双行小字左右两个字它们的y坐标可能略有不同

处理

  1. 按line_id分组右列一个line_id左列一个line_id
  2. 右列第i个字 配 左列第i个字都按y排序
  3. 配对后,取右字的y坐标作为这一格的位置
// 右列: [字A(y=1500), 字B(y=1800)]
// 左列: [字C(y=1510), 字D(y=1790)]

配对1: [A|C]  y=1500  row=5
配对2: [B|D]  y=1800  row=6

完整处理流程

1. 按line_id分组 → 逻辑列
2. 按x坐标聚合 → 物理列
3. 计算列间距 → 识别空列 → 映射到网格列
4. 处理每个物理列:
   4.1 合并所有字符(大字+小字配对)
   4.2 按y坐标排序
   4.3 为每个字符/配对计算行号
   4.4 检测空行
   4.5 填充到grid[col][row]

0019A.json 的正确结果

根据你的描述:

物理列1line_ids: 0,1,2,3

  • 小字[左|手] 从第2行开始 → 1180000...
  • l2和l3中间有1个空格 → 需要检测l2最后一字和l3第一字的行号差

物理列2line_id: 4

  • 前面1个空格 → 10000...

物理列3line_id: 5

  • 前面2个空格 → 110000...

物理列4line_id: 6和物理列5line_id: 7

  • 都是顶格 → 0000...

物理列6line_ids: 8,9

  • 前面1个空格 → 10000...
  • l8和l9之间1个空格 → 需要检测

物理列7line_id: 10

  • 前面1个空格 → 10000...

待实现的算法

  1. y坐标 → 行号 的准确计算
  2. x坐标 → 网格列 的映射(含空列检测)
  3. 空行检测 逻辑
  4. 双行小字 按line_id配对不是按x分左右