ancient-ocr-viewer/docs/三大关键问题.md

191 lines
5.0 KiB
Markdown
Raw Normal View History

# 古籍OCR渲染的三大关键问题
## 问题1逻辑行到物理行的划分换行问题
### 场景
一个逻辑列line_id有100个字但版式只有20行如何换行填充
### 答案
**根据y坐标计算每个字符的物理行号**
```javascript
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列是空的
如何正确映射?
### 错误做法(当前程序)
```javascript
// 按物理列顺序映射
物理列0 → 网格列0
物理列1 → 网格列1
```
**问题**:忽略了空列,导致位置错误
### 正确做法1基于x坐标计算
```javascript
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坐标
```javascript
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坐标排序后检查相邻字符的行号差
```javascript
// 合并所有字符按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坐标**作为这一格的位置
```javascript
// 右列: [字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分左右