技術
約3分で読めます
NDLOCRの段組認識問題をヒストグラム解析で力技解決
🎯 目的
縦書き日本語書籍(4段組)のOCR処理
💻 動作環境
| 項目 | 内容 |
|---|---|
| OS | Windows 11 |
| Python | 3.14.0 |
試行1: NDLOCR単体
結果: △ 動くがレイアウト認識が弱い
- 段組が正しく取れず、読み順が崩れる
- NDLOCRのレイアウト解析は古い縦書き書籍向けで、現代的な段組に弱い
試行2: Layout Parser + NDLOCR
結果: ✗ インストール失敗
- Layout Parser自体は
pip install layoutparserで入る - Detectron2がWindowsでビルドエラー
pip install "detectron2@git+..."→ ビルド失敗- Windows環境でのC++コンパイラ問題
試行3: PyMuPDF + ヒストグラム解析(代替案)
結果: ○ 動作OK
- Detectron2不要
- 縦方向ヒストグラムで段の境界(谷)を検出
- 4段固定で切り出し → NDLOCRに渡す
✅ 最終構成
PDF
↓
extract_blocks.py(PyMuPDF + ヒストグラム解析)
↓
4分割画像(page01_1.jpg ~ page01_4.jpg)
↓
NDLOCR(OCRのみ)
↓
テキスト出力
必要なもの
- Python + PyMuPDF, PIL, numpy
- Docker + NDLOCR
- extract_blocks.py(ヒストグラム解析スクリプト)
📄 extract_blocks.py
import fitz # PyMuPDF
from PIL import Image
import io
import os
import numpy as np
# --- 設定項目 ---
pdf_path = r"C:\ndlocr_work\input\test.pdf"
output_dir = r"C:\ndlocr_work\input\img"
# ----------------
os.makedirs(output_dir, exist_ok=True)
try:
pdf_document = fitz.open(pdf_path)
except fitz.errors.FitzError as e:
print(f"エラー: PDFファイルを開けません。パスが正しいか確認してください。: {e}")
exit()
print(f"{len(pdf_document)}ページの処理を開始します。")
def find_split_point(histogram, center_y, search_range):
"""ヒストグラムの谷の中央を見つける関数"""
start = max(0, center_y - search_range)
end = min(len(histogram), center_y + search_range)
# 探索範囲内のヒストグラムを切り出す
segment = histogram[start:end]
# 谷の深さ(最小値)を見つける
min_val = np.min(segment)
# 最小値を持つすべてのインデックス(谷底)を取得
valley_indices = np.where(segment == min_val)[0]
# 谷底の中央のインデックスを取得
middle_of_valley = valley_indices[len(valley_indices) // 2]
# 元のヒストグラム全体での座標を返す
return start + middle_of_valley
for page_num in range(len(pdf_document)):
page = pdf_document[page_num]
print(f"\n--- ページ {page_num + 1} ---")
# 1. 高解像度で画像化
mat = fitz.Matrix(3, 3)
pix = page.get_pixmap(matrix=mat, alpha=False)
img = Image.open(io.BytesIO(pix.tobytes("jpeg")))
# 2. 2値化
gray_img = img.convert('L')
binary_img = gray_img.point(lambda x: 0 if x < 200 else 255, '1')
# 3. 縦方向のヒストグラムを計算
np_img = np.array(binary_img)
histogram = np.sum(255 - np_img, axis=1)
# 4. 3つの分割点を谷の中央として検出
height, width = np_img.shape
search_range = int(height * 0.05) # 中心の上下5%を探索
split_points = [
find_split_point(histogram, int(height * 0.25), search_range),
find_split_point(histogram, int(height * 0.50), search_range),
find_split_point(histogram, int(height * 0.75), search_range)
]
split_points.sort()
print(f"検出された分割点のY座標: {split_points}")
# 5. 画像を切り出して保存
boundaries = [0] + split_points + [height]
for i in range(4):
y_start = boundaries[i]
y_end = boundaries[i+1]
segment = img.crop((0, y_start, width, y_end))
output_path = os.path.join(output_dir, f"page{page_num+1:02d}_{i+1}.jpg")
segment.save(output_path, "JPEG", quality=95)
print(f" 段{i+1}を保存しました: {output_path}")
pdf_document.close()
print("\n全ての処理が完了しました!")