|
|
@@ -0,0 +1,217 @@
|
|
|
+正在收集工作区信息正在筛选到最相关的信息这是一个非常好的问题!让我详细解释 bbox 坐标的处理逻辑和原因。
|
|
|
+
|
|
|
+## 坐标归一化的原因
|
|
|
+
|
|
|
+### 1. **为什么要归一化(乘以 1000 再除以页面尺寸)**
|
|
|
+
|
|
|
+```python
|
|
|
+# 原始代码
|
|
|
+para_content['bbox'] = [
|
|
|
+ int(x0 * 1000 / page_width), # 归一化到 [0, 1000] 范围
|
|
|
+ int(y0 * 1000 / page_height),
|
|
|
+ int(x1 * 1000 / page_width),
|
|
|
+ int(y1 * 1000 / page_height),
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+**核心目的**:将 bbox 坐标从**绝对坐标**转换为**相对坐标**(归一化到 0-1000 范围)
|
|
|
+
|
|
|
+### 2. **PDF 坐标 vs 图片坐标**
|
|
|
+
|
|
|
+让我用流程图说明:
|
|
|
+
|
|
|
+```mermaid
|
|
|
+graph TB
|
|
|
+ Start([PDF 原始文档]) --> Convert[PDF → 图片转换<br/>DPI=200]
|
|
|
+ Convert --> ImageCoord[图片坐标系统<br/>分辨率依赖]
|
|
|
+
|
|
|
+ ImageCoord --> Example1[示例1: 小页面<br/>page_size: 1654x2338<br/>bbox: 378,111,591,142]
|
|
|
+ ImageCoord --> Example2[示例2: 大页面<br/>page_size: 3308x4676<br/>bbox: 756,222,1182,284]
|
|
|
+
|
|
|
+ Example1 --> Normalize1[归一化<br/>x0: 378/1654*1000=228<br/>y0: 111/2338*1000=47]
|
|
|
+ Example2 --> Normalize2[归一化<br/>x0: 756/3308*1000=228<br/>y0: 222/4676*1000=47]
|
|
|
+
|
|
|
+ Normalize1 --> Same[✅ 相同的归一化坐标<br/>228,47,...]
|
|
|
+ Normalize2 --> Same
|
|
|
+
|
|
|
+ Same --> Benefits[优势]
|
|
|
+
|
|
|
+ Benefits --> Benefit1[1. 分辨率无关<br/>不同DPI结果一致]
|
|
|
+ Benefits --> Benefit2[2. 设备无关<br/>手机/电脑显示一致]
|
|
|
+ Benefits --> Benefit3[3. 易于比较<br/>不同页面可比]
|
|
|
+ Benefits --> Benefit4[4. 数据紧凑<br/>0-1000整数范围]
|
|
|
+
|
|
|
+ style Start fill:#e1f5ff
|
|
|
+ style Same fill:#c8e6c9
|
|
|
+ style Benefits fill:#fff3e0
|
|
|
+```
|
|
|
+
|
|
|
+## 详细说明
|
|
|
+
|
|
|
+### **原始坐标系统的问题**
|
|
|
+
|
|
|
+#### 场景1: PDF 转图片时的分辨率变化
|
|
|
+
|
|
|
+```python
|
|
|
+# PDF 原始尺寸(点单位, 72 DPI)
|
|
|
+pdf_page_size = [595, 842] # A4 纸
|
|
|
+
|
|
|
+# 转换为图片(200 DPI)
|
|
|
+dpi = 200
|
|
|
+scale = dpi / 72
|
|
|
+image_size = [595 * scale, 842 * scale] = [1653, 2339]
|
|
|
+
|
|
|
+# 如果使用 300 DPI
|
|
|
+dpi = 300
|
|
|
+scale = 300 / 72
|
|
|
+image_size = [595 * scale, 842 * scale] = [2480, 3508]
|
|
|
+```
|
|
|
+
|
|
|
+**问题**:同一个 PDF 元素,在不同 DPI 下的绝对坐标不同!
|
|
|
+
|
|
|
+#### 场景2: VLM 模型的输入限制
|
|
|
+
|
|
|
+```python
|
|
|
+# VLM 模型通常会 resize 图片
|
|
|
+original_image_size = (1654, 2338)
|
|
|
+model_input_size = (1000, 1000) # 模型固定输入
|
|
|
+
|
|
|
+# 如果不归一化,bbox 坐标会失效
|
|
|
+original_bbox = [378, 111, 591, 142]
|
|
|
+# 需要按比例缩放才能对应到模型输入
|
|
|
+```
|
|
|
+
|
|
|
+### **归一化的优势**
|
|
|
+
|
|
|
+```python
|
|
|
+# 原始坐标(绝对坐标)
|
|
|
+bbox_absolute = [378, 111, 591, 142]
|
|
|
+page_size = (1654, 2338)
|
|
|
+
|
|
|
+# 归一化后(相对坐标,0-1000 范围)
|
|
|
+bbox_normalized = [
|
|
|
+ 378 * 1000 / 1654, # 228
|
|
|
+ 111 * 1000 / 2338, # 47
|
|
|
+ 591 * 1000 / 1654, # 357
|
|
|
+ 142 * 1000 / 2338, # 60
|
|
|
+]
|
|
|
+# 结果: [228, 47, 357, 60]
|
|
|
+```
|
|
|
+
|
|
|
+**优势**:
|
|
|
+1. ✅ **设备无关**:在任何屏幕尺寸下都能正确显示
|
|
|
+2. ✅ **分辨率无关**:不受 DPI 影响
|
|
|
+3. ✅ **模型友好**:VLM 模型训练时使用归一化坐标
|
|
|
+4. ✅ **数据压缩**:0-1000 整数比浮点数更紧凑
|
|
|
+
|
|
|
+### **为什么是 1000 而不是 1.0**
|
|
|
+
|
|
|
+```python
|
|
|
+# 选项1: 归一化到 [0, 1]
|
|
|
+bbox_float = [0.228, 0.047, 0.357, 0.060] # 浮点数
|
|
|
+
|
|
|
+# 选项2: 归一化到 [0, 1000]
|
|
|
+bbox_int = [228, 47, 357, 60] # 整数
|
|
|
+
|
|
|
+# 优势对比:
|
|
|
+# 1. 整数占用空间更小(JSON 序列化)
|
|
|
+# 2. 整数计算更快(无浮点误差)
|
|
|
+# 3. 1000 提供足够精度(0.1% 精度)
|
|
|
+# 4. 符合行业惯例(很多 CV 模型使用 1000)
|
|
|
+```
|
|
|
+
|
|
|
+## 实际应用示例
|
|
|
+
|
|
|
+### **使用归一化坐标的完整流程**
|
|
|
+
|
|
|
+```python
|
|
|
+# ========== 第1步: VLM 模型识别(使用归一化坐标) ==========
|
|
|
+# 模型输出(已归一化)
|
|
|
+model_output = {
|
|
|
+ "bbox": [228, 47, 357, 60], # 0-1000 范围
|
|
|
+ "type": "text",
|
|
|
+ "content": "审计报告"
|
|
|
+}
|
|
|
+
|
|
|
+# ========== 第2步: 保存到 JSON(直接使用归一化坐标) ==========
|
|
|
+content_list = [
|
|
|
+ {
|
|
|
+ "type": "text",
|
|
|
+ "text": "审计报告",
|
|
|
+ "bbox": [228, 47, 357, 60], # 归一化坐标
|
|
|
+ "page_idx": 0
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+# ========== 第3步: 还原到原始坐标(用于可视化) ==========
|
|
|
+def denormalize_bbox(bbox_normalized, page_size):
|
|
|
+ """将归一化坐标还原为绝对坐标"""
|
|
|
+ page_width, page_height = page_size
|
|
|
+ x0, y0, x1, y1 = bbox_normalized
|
|
|
+
|
|
|
+ return [
|
|
|
+ int(x0 * page_width / 1000), # 228 * 1654 / 1000 = 377
|
|
|
+ int(y0 * page_height / 1000), # 47 * 2338 / 1000 = 109
|
|
|
+ int(x1 * page_width / 1000), # 357 * 1654 / 1000 = 590
|
|
|
+ int(y1 * page_height / 1000), # 60 * 2338 / 1000 = 140
|
|
|
+ ]
|
|
|
+
|
|
|
+# ========== 第4步: 在图片上绘制 bbox ==========
|
|
|
+from PIL import Image, ImageDraw
|
|
|
+
|
|
|
+img = Image.open("page_001.png")
|
|
|
+draw = ImageDraw.Draw(img)
|
|
|
+
|
|
|
+page_size = (1654, 2338)
|
|
|
+bbox_normalized = [228, 47, 357, 60]
|
|
|
+
|
|
|
+# 还原坐标
|
|
|
+bbox_absolute = denormalize_bbox(bbox_normalized, page_size)
|
|
|
+# [377, 109, 590, 140]
|
|
|
+
|
|
|
+draw.rectangle(bbox_absolute, outline='red', width=2)
|
|
|
+img.save("output_with_bbox.png")
|
|
|
+```
|
|
|
+
|
|
|
+## 代码位置对比
|
|
|
+
|
|
|
+### **Pipeline 后端(同样的处理)**
|
|
|
+
|
|
|
+查看 `pipeline_middle_json_mkcontent.py`:
|
|
|
+
|
|
|
+```python
|
|
|
+# Line 240-250
|
|
|
+page_width, page_height = page_size
|
|
|
+para_bbox = para_block.get('bbox')
|
|
|
+if para_bbox:
|
|
|
+ x0, y0, x1, y1 = para_bbox
|
|
|
+ para_content['bbox'] = [
|
|
|
+ int(x0 * 1000 / page_width), # 同样的归一化处理
|
|
|
+ int(y0 * 1000 / page_height),
|
|
|
+ int(x1 * 1000 / page_width),
|
|
|
+ int(y1 * 1000 / page_height),
|
|
|
+ ]
|
|
|
+```
|
|
|
+
|
|
|
+### **VLM 后端(当前文件)**
|
|
|
+
|
|
|
+`vlm_middle_json_mkcontent.py` Line 218-228
|
|
|
+
|
|
|
+两个后端使用相同的归一化策略,确保输出格式一致。
|
|
|
+
|
|
|
+## 总结
|
|
|
+
|
|
|
+| 特性 | 绝对坐标 | 归一化坐标(0-1000) |
|
|
|
+|------|---------|---------------------|
|
|
|
+| **分辨率依赖** | ❌ 依赖 DPI | ✅ 无关 |
|
|
|
+| **设备兼容性** | ❌ 需要适配 | ✅ 通用 |
|
|
|
+| **数据大小** | ❌ 较大(浮点) | ✅ 紧凑(整数) |
|
|
|
+| **计算精度** | ❌ 浮点误差 | ✅ 整数精确 |
|
|
|
+| **模型友好** | ❌ 不友好 | ✅ 标准格式 |
|
|
|
+| **可视化** | ✅ 直接使用 | ⚠️ 需要反归一化 |
|
|
|
+
|
|
|
+**最佳实践**:
|
|
|
+- ✅ **存储时**:使用归一化坐标(0-1000)
|
|
|
+- ✅ **传输时**:使用归一化坐标(减少数据量)
|
|
|
+- ✅ **模型训练/推理时**:使用归一化坐标
|
|
|
+- ⚠️ **绘制时**:反归一化到实际图片尺寸
|