# Quy Trình 04 — OCR Pipeline: PDF → Markdown RAW

> **Dành cho Architect & Editor Agent**
> Cập nhật: 2026-06-13 | Phiên bản: 4.0 (V7 — Y-bucket + Cleanup tích hợp)

---

## Tổng Quan Pipeline

```
PDF scan → [Preprocess] → PDF sạch → [Cloud Vision] → JSON raw → [JSON→MD V7] → Markdown sạch
```

| Bước | Công cụ | Input | Output |
|------|---------|-------|--------|
| 0. Preprocess | Adaptive Threshold + Morphology | PDF scan (giấy kém) | PDF sạch |
| 1. OCR | Google Cloud Vision API | PDF | JSON (ocr/raw/) — 3 trang/file |
| 2. JSON→MD + Cleanup | `json_to_markdown_v7.py` ⭐ | JSON (ocr/raw/) | **Markdown sạch** (extracted/) — layout Y-bucket + header/footer đã xóa |

> **v4.0:** Thay v6+v5 bằng `json_to_markdown_v7.py` — tích hợp Y-bucket sorting (layout đẹp) + Header/Footer cleanup (tọa độ Y) trong **1 bước duy nhất**. Không cần chạy cleanup riêng. Xem chi tiết: [[../huong-dan/JSON-to-Markdown]].
>
> **v3.0:** Thay `json_to_markdown_raw.py` bằng `json_to_markdown_v6.py` — dùng Y-bucket sorting thay vì dump text thô.
> **v2.2:** Thêm bước JSON→Markdown RAW (dùng `## PAGE X` marker).
> **v2.1:** Thêm bước Preprocess cho giấy kém chất lượng. Chiến lược cleanup mới dùng tọa độ Y.

---

## Bước 1: OCR với Cloud Vision API

### Thiết lập Service Account
- **Key file:** `/home/tuan-nguyen/.openclaw/workspace/old/google_service_account.json`
- **Project ID:** `zen-490314`
- **Service Account:** `openclaw-service@zen-490314.iam.gserviceaccount.com`

### Quyền cần có
- `roles/storage.objectAdmin` — đọc/ghi Cloud Storage
- `roles/visionai.user` — gọi Vision API

### Script OCR

```bash
cd /home/tuan-nguyen/.openclaw/workspace
export GOOGLE_APPLICATION_CREDENTIALS="/home/tuan-nguyen/.openclaw/workspace/old/google_service_account.json"

python3 scripts/ocr_pdf_to_raw.py \
  "003-tang-chi-bo-giang-giai/Tang Chi Bo Giang Giai.pdf" \
  "zen-ocr-pdf" \
  "003-tang-chi-bo-giang-giai/ocr/raw/"
```

Xem chi tiết script tại: [[Cloud-Vision-OCR]]

---

## Bước 0: Preprocess Ảnh Scan (giấy kém chất lượng)

> **Khi nào cần:** Giấy mỏng, thấu quang, chữ mờ, nhiều đốm nhiễu.
> **Không cần nếu:** PDF scan từ sách in chất lượng tốt.

### Giải pháp: Adaptive Thresholding + Morphology

```yaml
preprocessing:
  bilateral_d: 9
  bilateral_sigmaColor: 75
  bilateral_sigmaSpace: 75
  adaptive_threshold:
    method: ADAPTIVE_THRESH_GAUSSIAN_C
    block_size: 21
    C: 25          # C càng cao → càng trắng, chữ mờ biến mất
  morphology:
    kernel: [3, 3]  # xóa đốm li ti
    mode: MORPH_CLOSE
  output:
    dpi: 300
    format: png
```

### Cách tinh chỉnh

| Tham số | Tăng lên | Giảm xuống |
|---------|----------|------------|
| **C** | Chữ mờ → trắng, chữ đậm dễ đứt nét | Giữ nét chữ, chữ mờ còn |
| **block_size** | Giữ nét chữ to, bỏ sót chữ mờ vùng tối | Nhạy chi tiết, dễ sinh nhiễu hạt |
| **morph kernel** | Diệt đốm mạnh, dễ mất dấu câu nhỏ | — |

### Script

```bash
# Trích xuất PDF → PNG
pdftoppm -r 300 -png input.pdf raw_pages/page

# Xử lý
python3 preprocess_all.py

# Gộp lại thành PDF
img2pdf cleaned/page-*.png -o output-clean.pdf
```

### Kết quả thực tế (Pali-Taykha, 44 trang)

- PDF gốc 21.7 MB → PDF sạch 5.6 MB (-74%)
- Số đốm đen nhỏ giảm 75% (340 → 86 đốm/trang)

Xem chi tiết: [[Pali-Taykha-Cleanup-Guide]]

---

## Bước 2: JSON → Markdown + Cleanup (V7) ⭐

> **Mục tiêu:** Từ JSON Cloud Vision → file Markdown **sạch**, layout đẹp, header/footer đã xóa. **1 bước duy nhất.**
> **Công cụ:** `obsidian/scripts/json_to_markdown_v7.py` — Y-bucket sorting + Header/Footer detection tích hợp.

### Pipeline trong V7

```
JSON → [Detect Header/Footer bằng tọa độ block]
     → [Extract paragraphs, Y-bucket sort]
     → [Filter fragments: header zone + footer zone]
     → Markdown sạch (layout đẹp + header/footer removed)
```

### Thuật toán

**Phase 0 — Detection:** Quét tất cả block, phát hiện:
- Block có `y_min ≤ 0.15` → header zone candidate
- Nếu header zone chứa **số trang** (Myanmar/Latin) → đánh dấu cleanup
- Block có `y_max > 0.82` → footer zone

**Phase 1 — Y-bucket Layout (từ V6):**
1. **Split multi-line blocks** → từng dòng riêng (estimate Y)
2. **Nhóm dòng** theo `int(y / BUCKET_SIZE)` → các dòng cùng độ cao vào cùng bucket
3. **Sort X** (trái→phải) trong mỗi bucket
4. **Join** với spacing phù hợp giữa các cột/từ

**Phase 2 — Fragment Filter:**
- Fragment có `y_min ≤ 0.15` → **xóa** (header zone)
- Còn lại → **giữ** (body text + footer)

> 💡 **Footer không filter.** Artifact footer (số trang lẻ, chữ Latin rác) rất dễ phát hiện và xóa ở bước editor. Chỉ header mới cần tự động xóa vì lặp lại trên mọi trang. Block merge: chỉ fragment header bị xóa, body fragment giữ nguyên.

### Cách chạy

```bash
cd /home/tuan-nguyen/.openclaw/workspace

# Chạy bình thường
python3 obsidian/scripts/json_to_markdown_v7.py \
  "010-pali-thaykha/ocr/raw/" \
  "010-pali-thaykha/extracted/"

# Dry-run (xem preview)
python3 obsidian/scripts/json_to_markdown_v7.py \
  "010-pali-thaykha/ocr/raw/" \
  "010-pali-thaykha/extracted/" \
  --dry-run

# Debug (hiện fragment/bucket count + log cleanup từng trang)
python3 obsidian/scripts/json_to_markdown_v7.py \
  "010-pali-thaykha/ocr/raw/" \
  "010-pali-thaykha/extracted/" \
  --debug
```

### Tham số tinh chỉnh (trong script)

**Layout (Y-bucket):**

| Tham số | Default | Ý nghĩa |
|---------|---------|---------|
| `BUCKET_SIZE` | `0.022` | Ngưỡng Y (normalized) để gộp vào cùng dòng |
| `COLUMN_GAP_THRESHOLD` | `0.15` | Khoảng cách X → 4 spaces (cột) |
| `WORD_GAP_THRESHOLD` | `0.03` | Khoảng cách X → 2 spaces (từ) |

**Cleanup (Header/Footer):**

| Tham số | Default | Ý nghĩa |
|---------|---------|---------|
| `HEADER_DETECT_Y` | `0.15` | Detection: block có y_min ≤ đây → header zone candidate |
| `HEADER_CLEAN_Y` | `0.15` | Filter: fragment có y_min ≤ đây → thực sự xóa (bằng detection) |
| `FOOTER_Y_MIN` | *(không dùng)* | Footer KHÔNG filter — artifact dễ xóa ở bước editor |

### Output

- Mỗi file JSON (3-5 trang) → 1 file `.md`
- Mỗi trang bắt đầu bằng `## PAGE X`
- Có debug comment: `<!-- N→M fragments, K buckets, R removed -->` (khi dùng `--debug`)
- Layout Y-bucket: đúng thứ tự đọc, spacing chuẩn
- Header/footer đã xóa (nếu detect được số trang)
- Tự động tạo file merged `Pali-Taykha-full.md`

**Xem chi tiết:** [[../huong-dan/JSON-to-Markdown]]

---

## Bước 3: Cleanup Thủ Công (Dự Án Legacy)

> ⚠️ **V7 đã tích hợp cleanup.** Chỉ dùng các script dưới đây cho dự án cũ không dùng V7, hoặc khi cần cleanup đặc biệt ngoài khả năng của V7.

| Script | Chiến lược | Dự án phù hợp |
|--------|-----------|--------------|
| `cleanup_header_leaks.py` | Text pattern + block index | Tăng Chi Bộ (running header cố định) |
| `cleanup_pali_taykha_v5.py` | Tọa độ Y (block-level) | ⚠️ Đã thay bằng V7 — giữ lại tham khảo |

### Chiến lược Text Pattern (cleanup_header_leaks.py)

Dành cho sách có running header cố định, pattern text dễ nhận diện (VD: Tăng Chi Bộ — 639 trang, header chẵn/lẻ rõ ràng).

**Nguyên lý:** Kiểm tra 3 block đầu mỗi trang (y < 0.12), match pattern text:
- Trang chẵn: số trang + tên sách
- Trang lẻ: section/chapter title
- Xóa bằng prefix matching ở text-level (10 dòng đầu)

**Edge cases:** OCR nhầm `ဝ`↔`၀`, block merge, artifact `-` ở vị trí lạ, số quoted `'60'`.

> Chi tiết đầy đủ về Tăng Chi Bộ patterns: xem commit history của `cleanup_header_leaks.py`.

### Chiến lược Tọa Độ Y (cleanup_*_v5.py) — ĐÃ LỖI THỜI

> ⚠️ **Không dùng nữa.** V5 dùng `fullTextAnnotation.text` thô → mất layout Y-bucket. V7 thay thế hoàn toàn với fragment-level filter chính xác hơn. Giữ lại script để tham khảo logic detection.

---

## Checklist OCR

Xem: [[OCR-Checklist]] — 90+ lỗi hệ thống đã tích lũy

---

## File liên quan
- [[01-Dieu-Phoi-Agent]] — Điều phối agent
- [[02-Dich-Song-Ngu]] — Dịch song ngữ
- [[03A-Hieu-Dinh-Myanmar-OCR]] — Hiệu đính OCR Myanmar (Editor)
- [[03B-Hieu-Dinh-Ban-Dich-Viet]] — Hiệu đính bản dịch Việt (Translator)
- [[05-Cron-Quan-Ly]] — Quản lý cron job
- [[Cloud-Vision-OCR]] — Chi tiết script OCR
- [[Pali-Taykha-Cleanup-Guide]] — Guide cleanup tọa độ Y (case study)
- [[../huong-dan/JSON-to-Markdown]] — Hướng dẫn trích xuất JSON → Markdown RAW (`## PAGE X`)
