はじめに
RAG(検索拡張生成)を「本当に業務で使う」段になると、いちばん地味で、いちばん効いてくるのがOCRです。
社内ドライブには、テキスト層のあるきれいなPDFだけでなく、チャット画面のスクリーンショット、matplotlibで吐き出したチャート、テキスト層のない提案スライドPDFが平気で混ざっています。ここでOCRがコケると、その資料はRAGの根拠から丸ごと消えます。検索がどれだけ賢くても、そもそも本文がテキスト化されていなければ勝負になりません。
いま手元のRAGパイプラインでは、.env で4種類のOCR/文書解析エンジンを使い分けられるようにしています。
- glm-ocr … 視覚モデル系OCR(画像を丸ごと読む)
-
dots.ocr(
rednote-hilab/dots.mocr)… レイアウト検出付きの文書パーサ - Unlimited-OCR … 構造化(HTMLテーブル)出力に強いドキュメントパーサ
- MinerU … PDF/文書解析の定番。Markdown+HTMLテーブルで返すパイプライン型
「4つも要る? 1個に寄せたい」というのが本音です。そこで本記事では、実際の社内ドライブから性格の違う6ファイルを抜き出して、4エンジンを同じ条件で走らせ、出力・速度・精度を並べて比較しました。スクショと生の出力を全部載せるので、自分の目でどのエンジンがどこまでやれるのかを確かめられます。
結論から言うと「用途で2系統に割って混合採用」が正解でした。理由は本文で。
検証環境
.env の該当部分です。4つとも別エンドポイントに分離してあります。
# glm-ocr(画像の全面視覚OCR)
OCR_API_HOST=http://127.0.0.1:8888/v1
OCR_MODEL=glm-ocr
# dots.ocr(レイアウト検出付き文書パーサ)
DOTS_MOCR_API_HOST=http://127.0.0.1:8883/v1
DOTS_MOCR_MODEL=rednote-hilab/dots.mocr
# Unlimited-OCR(構造化出力に強いパーサ)
UNLIMITED_OCR_API_HOST=http://127.0.0.1:8884/v1
UNLIMITED_OCR_MODEL=/models/Unlimited-OCR
# MinerU Web API(PDF/文書解析。/file_parse を叩く)
MINERU_API_HOST=http://127.0.0.1:8887
呼び出しはそれぞれクライアントを用意していて、ファイルパスを渡すと解析テキストが得られます。
uv run python ocr_client.py <画像> # glm-ocr(画像のみ)
uv run python dots_mocr_client.py <画像/PDF> # dots.ocr(画像・PDF両対応)
uv run python unlimited_ocr_client.py <画像/PDF> # Unlimited-OCR(画像・PDF両対応)
uv run python -m rag.mineru_client <画像/PDF> -o out # MinerU(Markdown+JSON を出力)
最初のハマりどころ:
glm-ocrのクライアントは画像専用で、PDFを渡すと画像 MIME ではありません: application/pdfで落ちます。パイプライン側ではPDFを一度ページ画像にラスタライズしてから glm-ocr に渡す運用です。dots.ocr / Unlimited-OCR / MinerU はPDFをそのまま食えます。この差は地味に運用へ効いてきます。
評価に使ったのは、社内ドライブから抜いた性格の違う6ファイルです。
| # | ファイル | 種類 | OCRの難所 |
|---|---|---|---|
| 1 | AIエージェントの回答スクショ | PNG(表を含むUI画像) | 日英混在+表構造 |
| 2 | 特徴量相関ヒートマップ | PNG(matplotlib図) | 図中の数値マトリクス |
| 3 | 会議録 | PDF(4ページ・テキスト主体) | 日本語長文+改ページ |
| 4 | 目的変数の棒グラフ | PNG(matplotlib図) | 棒に焼き込まれた件数 |
| 5 | 特徴量ヒストグラム6面 | PNG(matplotlib図) | 小さいラベルが大量 |
| 6 | 提案スライド(前処理・モデル方針) | PDF(テキスト層なし・図主体) | SmartArt風の図中テキスト |
サンプル1: スクリーンショットの表(日英混在+テーブル)
「AIがドライブを突合して答えた画面」のスクショ。日本語・英語・記号・表が全部入っています。
- glm-ocr(2.8s): 文字はほぼ完璧。表は1行=1レコードのスペース区切りで人にもLLMにも読みやすい。
- dots.ocr(9.8s): 文字は正確だが、表がセル単位でバラバラに縦積みされ行の対応が崩れる。しかも最遅。
-
Unlimited-OCR(4.2s): 表を
<table>で構造ごと復元。行列対応が機械的に取れる。 -
MinerU(4.7s): こちらも
<table>で構造復元。文字精度も高い。
Unlimited-OCR と MinerU の表出力(HTMLテーブル):
<table><tr><td>AI ID</td><td>Action</td><td>Owner</td><td>M01時点</td>...</tr>
<tr><td>A08</td><td>分析用リポジトリ/環境・アクセス権の整備(...)</td><td>伊藤翔太/岡田佑樹</td><td>Open</td><td>Closed</td><td>2025-04-07</td>...</tr>
<tr><td>A09</td>...<td>伊藤翔太</td><td>Open</td><td>Closed</td><td>2025-04-04</td>...</tr></table>
所感: 素の可読性は glm-ocr、表構造の厳密さは Unlimited-OCR と MinerU。dots.ocr は文字は正確だが表がほどけて遅い。
サンプル2: 相関ヒートマップ(図中の数値マトリクス)
matplotlibで出力した特徴量相関ヒートマップ。10×10のセルに相関係数が焼き込まれた「画像化された表」で、テキスト層はありません。
glm-ocr(2.7s)— 行・列ラベルと100個の数値を完全復元
特徴量相関ヒートマップ
Age - 1.00 -0.03 -0.03 -0.07 -0.02 -0.04 0.01 -0.00 0.02 -0.09
T_Bil - -0.03 1.00 0.84 0.06 0.23 0.34 -0.15 -0.21 -0.27 0.34
D_Bil - -0.03 0.84 1.00 0.07 0.25 0.34 -0.16 -0.20 -0.25 0.33
...
disease - -0.09 0.34 0.33 0.21 0.20 0.31 -0.15 -0.27 -0.27 1.00
Age T_Bil D_Bil ALP ALT_GPT AST_GOT TP Alb AG_ratio disease
他3エンジンは中身を読めない
dots.ocr (1.7s): (empty) ← 図として無視
Unlimited (2.4s): 特徴量相関ヒートマップ / 1.0 ← タイトルとカラーバーのみ
MinerU (1.7s):  ← 画像プレースホルダのみ
正解のマトリクスと照合し -?\d\.\d{2} にマッチする数値をカウント(10×10=100セル):
| エンジン | 数値セル検出 | 判定 |
|---|---|---|
| glm-ocr | 100 / 100 | ✅ 完全復元 |
| dots.ocr | 0 / 100 | ❌ 図として無視 |
| Unlimited-OCR | 0 / 100 | ❌ ほぼ拾えず |
| MinerU | 0 / 100 | ❌ 画像化のみ |
所感: 図・チャートの数値を読ませたいなら glm-ocr 一択。他3つは「レイアウト解析器」であり、図は"1枚の画像"として素通りする設計です。
サンプル3: 会議録PDF(日本語長文・4ページ)
素直な、テキスト主体の日本語PDF(4ページ)。ここは差が出にくい想定です。
- glm-ocr(31.4s): 本文はクリーン。ただしPDFを直接食えず4ページを画像化して1枚ずつOCRするため遅い。
-
dots.ocr(30.2s):
#/##のMarkdown見出し付きで構造化して返す。 -
Unlimited-OCR(24.4s): 速く本文もほぼ正確だが、
secondary_metric→second_dary_metricなど英単語の分割ミスがちらほら。 -
MinerU(8.9s): 最速。
## 会議録## 2. 議題と見出しを付けたクリーンなMarkdown。
MinerU の出力(抜粋):
## 会議録
1. 会議情報
会議ID: M03
...
## 2. 議題
候補モデルの比較観点(評価軸)の最終決定
再現率(Recall)0.70目標に関する達成見込み評価と検証方針
所感: テキストPDFは4エンジンとも実用レベル。差がつくのは速度と出力形式で、MinerU が最速+Markdown構造でいちばん扱いやすい。glm-ocr は画像化が要るぶん不利。
サンプル4: 棒グラフ(棒に焼き込まれた件数)
「目的変数の分布」を示す棒グラフ。棒の上に 23954 / 3174 という件数ラベルが乗っています。
--- glm-ocr (1.0s) ---
目的変数の分布 / 件数 / 23954 / 3174 / 0 / 1 / 目的変数 ← タイトル・軸・件数を全取得 ✅
--- dots.ocr (1.4s) ---
23954 / 3174 ← 値だけ拾えた(タイトル・軸は欠落)
--- Unlimited-OCR (1.2s) ---
目的变数の分布 / 目的变数 ← タイトルのみ。件数を落とし、"変"→"变"の字化けも
--- MinerU (2s) ---
 ← 画像プレースホルダのみ ✗
所感: ここでも glm-ocr だけが「タイトル+軸+データ値」を丸ごと取得。RAGで「多数派クラスは何件?」に答えたいなら glm-ocr 以外は使えません。
サンプル5: ヒストグラム6面(小さいラベルが大量)
6つのサブプロットに、それぞれタイトル・軸ラベル・目盛りが並ぶ密度の高い図。
--- glm-ocr (3.2s) ---
Age の分布 / 件数 / 0 10 20 30 40 50 60 70 / Age
T_Bil の分布 / 件数 / 0 5 10 15 20 25 / T_Bil
...(6面すべてのタイトル・件数・目盛りを取得)✅
--- dots.ocr (3.0s) ---
"This is a layout that reproduces the provided image. Each subplot represents
a distribution ... The grid layout is 2 rows by 3 columns." ← まさかの英語"説明文"を生成(誤答)
--- Unlimited-OCR (2.5s) ---
Age の分布 / T_Bil の分布 / D_Bil の分布 / ... ← 6タイトルのみ
--- MinerU (1.7s) ---
 ← 画像プレースホルダのみ ✗
所感: dots.ocr が図の中身を読まずに英語のキャプションを"想像"して返したのは要注意。RAGに混ざると事実でない説明が根拠化してしまいます。ここも glm-ocr が圧勝。
サンプル6: 提案スライド(テキスト層なし・図主体)
PowerPoint由来の提案スライド。テキスト層がゼロで、SmartArt風のフロー図+概念ボックスに文字が全部埋まっています。OCRの真の難所です。
glm-ocr(3.5s)— 図中テキストをほぼ全部復元
アプローチ詳細②:前処理・モデル構築方針
4.3 前処理・特徴量方針
欠損値処理 → 数値は中央値、カテゴリは最頻値補完(※本データは欠損0件を想定)
カテゴリ変数 → しきい値未満はワンホットエンコーディング、高カード数は除外
時系列データ → 本データは単一時点のため除外
4.4 モデル構築・評価観点
Accuracy(正解率):全体の予測正答率
Precision(適合率):離職予測のうち、実際の離職者の割合
Recall(再現率):実際の離職者のうち、予測できた割合
F1 Score:PrecisionとRecallの調和平均
ROC-AUC:モデルの総合的な分類性能
Cost Evaluation:譜判定時の人事施策コスト観点での補足評価 ← "誤"→"譜" の1文字ミスのみ
他3エンジンはタイトルしか読めない
dots.ocr (6.8s): アプローチ詳細②:前処理・モデル構築方針 / 6 ← 見出しとページ番号だけ
Unlimited (3.2s): アプローチ詳細②:前処理・モデル構築方針 / 6 ← 同上
MinerU (2.0s): ## アプローチ詳細②:前処理・モデル構築方針
 ← 見出し+図は画像化
所感: 図形の中に文字が入る資料(提案書・SmartArt・ポンチ絵)は、レイアウト解析器が総崩れになる領域。glm-ocr だけが約15ブロックの図中テキストをほぼ全取得しました。テキスト層のない提案PDFが多い会社ほど、この差は致命的です。
総合比較
6サンプル×4エンジンの実測をまとめます(時間は今回の1回計測の目安。MinerUの画像は初回コールドスタートを除くと概ね2秒前後)。
| サンプル | 種類 | glm-ocr | dots.ocr | Unlimited-OCR | MinerU |
|---|---|---|---|---|---|
| 1 スクショの表 | 画像 | 文字◎/表フラット | 文字◎/表バラけ | 文字○/表HTML◎ | 文字◎/表HTML◎ |
| 2 ヒートマップ | 図 | 100/100 ✅ | 0/100 ✗ | 0/100 ✗ | 0/100 ✗ |
| 3 会議録PDF | PDF文書 | ◎(要画像化・31s) | ◎ MD構造 | ○ 分割ミス | ◎ MD構造・最速8.9s |
| 4 棒グラフ | 図 | 全項目◎ ✅ | 値のみ | タイトルのみ+字化け | 画像化 ✗ |
| 5 ヒストグラム6面 | 図 | 全タイトル+軸◎ ✅ | 英語で誤答 ⚠ | タイトルのみ | 画像化 ✗ |
| 6 提案スライド | PDF図主体 | 図中テキスト◎ ✅ | タイトルのみ | タイトルのみ | 見出し+画像化 |
わかったこと
エンジンは大きく2系統に分かれます。
- glm-ocr は「画像を読む視覚OCR」。 図・チャート・SmartArt に焼き込まれた数値やテキストを拾えるのは、4つの中でこれだけ。反面、PDFは自前で画像化が要り、テキストPDFでは遅くなる。
- dots.ocr / Unlimited-OCR / MinerU は「レイアウト解析器」。 テキスト文書・表には強い(特に MinerU=最速+Markdown、Unlimited/MinerU=HTMLテーブル)が、図は"1枚の画像"として素通りする。dots.ocr は図に対して英語の説明文を捏造することすらあり、RAGでは危険。
結論:単独採用か、混合か
用途で2系統に割った混合採用を推奨します。 単独で全部をまかなえるエンジンは、今回の6ファイルの範囲では存在しませんでした。1つに寄せると、必ずどこかの資料タイプがRAGの根拠から抜け落ちます。
現実的な使い分けはこうです。
- テキスト主体のPDF・文書・表 → MinerU を主力。最速でMarkdown構造+HTMLテーブルを返し、後段のチャンク分割と相性が良い。表の厳密さが要る資料は Unlimited-OCR も候補。
- 図・チャート・スクショ・テキスト層のない提案スライド → glm-ocr。図中の数値・SmartArtの文字まで拾える唯一の選択肢。
実装としては、いきなり4本走らせるのではなく**優先ラダー(fallback)**にするのが効率的です。今のパイプラインでも、PDFはまず MinerU で解析し、テキストが取れなかったページや独立画像・図だけを glm-ocr(→ dots/Unlimited)へ回す段構えにしています。
PDF : MinerU で解析 → 図・欠落ページだけ glm-ocr で補完
独立画像 : 図/チャートなら glm-ocr(数値を取りこぼさない)
表資料 : MinerU / Unlimited-OCR で HTML テーブル確保
「まず速くて構造化の効く MinerU で大半をさばき、図と取りこぼしだけ glm-ocr で拾う」——これがコストと精度のバランスとして、いまのところの最適解でした。
まとめ
- OCRはRAGの土台。ここで落ちた資料は、検索がどれだけ賢くても根拠にできない。
- 実データで比べると、glm-ocr=図・チャート・SmartArtに唯一強い / MinerU=文書PDFが最速&高構造 / Unlimited-OCR=表HTMLが厳密 / dots.ocr=文書向きだが図で誤答リスク、と役割がキレイに分かれた。
- 万能な1本は無い。「文書系=MinerU、図系=glm-ocr」の2系統ルーティングが、いちばん取りこぼしが少ない。
自分の環境の資料でも、まずは「性格の違う5〜6ファイル」を実際に食わせてみるのがおすすめです。ベンチマークの数字より、自分のドライブの生データでの挙動がすべてを教えてくれます。





