画像データ構造化の究極解を出す

株式会社ノーコードソリューションズ COO 吉村祐樹

AI-OCRを選ぶとき、カタログの99%は自社の帳票では出ません。無料の3系統に同じ帳票を通して、どこまでOCRで足りるかを実際に測りました。

株式会社ノーコードソリューションズ COO 吉村 祐樹

目次

はじめに

AI-OCRの製品比較を読んで、結局どれを選べばよいか決まらなかった経験はないでしょうか。認識精度99%という数字がいくつも並んでいるのに、自社の帳票で本当にその精度が出るのかは、どこにも書かれていません。

そして比較記事の最後は、たいてい同じ一文で終わります。カタログの精度は条件の良い活字の定型帳票での値なので、導入前にPoC(試験導入)で自社の帳票を検証してください。

もっともな助言ですが、その検証をどうやるのかを書いた記事が見当たりません。何枚用意するのか、正解データは誰が作るのか、どう採点すれば公平なのか。この部分が書かれないまま、読者に任されています。

本記事では、その手順を示します。帳票のサンプルを自動生成し、3つの系統に同じ画像を通して精度と処理時間を測るところまでを、実行できるコードとともに示します。使うのは Tesseract(画像から文字を読み取る無料のオープンソースOCR)、macOS標準のVision OCR、画像を直接読ませる VLM(画像と言語をまとめて扱うモデル)です。本記事で AI-OCR と呼ぶのはこの3系統すべてで、前の2つは完全に無料、VLMもこの検証では追加費用が発生していません。ただし取得率を並べて比べるのは前の2つだけです(理由は2-5)。

先に結論を書きます。今回の検証範囲では、2つの無料OCRが正反対の壊れ方をしました。 片方は文字を完璧に読む代わりに、どの値がどの項目のものかを失います。もう片方は項目と値の対応を保つ代わりに、金額の桁区切りのカンマをピリオドとして読み、48,000を48.000に変えてしまいます。

さらに手書きを1枚混ぜたところ、活字で84.3%(59/70)だった系統が、7項目中0項目になりました。 条件が変わると順位ではなく可否が変わります。片方だけを試して結論を出すと判断を誤ります。

前回の記事「【脱・OCR】Dify×VLMで、あらゆる画像・PDFを思い通りのJSONに変換する」では、従来のOCRの限界を挙げてVLMへの置き換え手順を紹介しました。本記事はその続編にあたります。置き換えるかどうかを、自分の手で測った数字で決められるようにします。

1. AI-OCRの精度がカタログ通りに出ない理由

1-1. 99%という数字が成り立つ条件

製品比較の記事に並ぶ認識精度は、多くが活字で書式の決まった帳票を対象にした値です。同じ位置に同じ項目が並び、印刷が鮮明で、傾きもない状態が前提です。

実際の現場の帳票は、この前提から外れます。取引先ごとに書式が違い、古い複合機でスキャンした原稿は文字がかすれ、手書きの追記が入り、明細行の数も一定ではありません。比較記事が検証を勧めるのは、この落差を知っているからです。

問題は、検証の方法が示されないことです。自社の帳票を業者に渡すには機密の問題があり、社内で測ろうにも正解データを人手で作る手間が大きくなります。この記事では、正解データを人が作らない方法を採ります。

1-2. OCR依存型とOCR非依存型は何が違うか

比較の前に、何と何を比べているのかを整理します。文書からデータを取り出す方法は、大きく2つの系統に分かれます。

文書からデータを取り出す2つの系統

自然言語処理の国際会議 ACL 2026 の Findings に採択された文書理解の調査論文は、この2つを次のように分類しています。

系統処理の流れ代表的な手法つまずきやすい点
OCR依存型文字を読み取ってから、その文字列に意味を割り当てるDocLLM、LayoutLLM文字認識で生じた誤りが、後段の処理まで持ち越される
OCR非依存型画像を直接読み、文字と配置と意味を同時に扱うDonut、mPLUG-DocOwl、UReader高い解像度の画像を扱うため、処理の負荷が大きい

補足: VLM(画像と言語を同時に扱うモデル)に画像を渡す方法は、OCR非依存型にあたります。前回記事で紹介した手法もここに含まれます。

本記事では以降、Tesseractのように文字認識だけを行う昔からのエンジンを従来型OCRと呼びます。macOS標準のVision OCRも文字認識だけを行いますが、機械学習で作られた新しい世代なので分けて呼びます。

注目したいのは、この調査ではどちらが優れているとは述べていないことです。文字認識の誤りが後段へ伝わる問題と、処理の負荷が大きい問題を、どちらもトレードオフとして並べています。学術側でも決着はついていません。

1-3. 条件によって優劣が入れ替わる

決着がついていないだけでなく、条件によって勝敗が入れ替わることが実測で示されています。

コンピュータビジョンの国際会議 CVPR 2025 に採択された OmniDocBench は、9種類の文書と19のレイアウト分類、15の属性ラベルで、パイプライン型(この記事でいう従来型OCRにあたります)のOCRツールとVLMを同じ条件で評価したベンチマークです。属性別の結果にはっきりした傾向が出ています。

  • 学術論文や財務報告書のように、よく使われる書式の文書では、パイプライン型のツールが良い成績を出す
  • スライドや手書きのノートのように専門的な文書では、汎用のVLMのほうが応用が利く
  • ぼやけたスキャン、透かし、色の付いた背景では、評価に含まれた一部のVLM(InternVL2とQwen2-VL)が最も崩れにくい。ただし同じ条件で、パイプライン型のMinerUも良い成績を出している
  • 複雑なレイアウトでは、どの手法も精度が落ちる

補足: OmniDocBenchの対象は英語と中国語の文書で、日本語は入っていません。 傾向の参考にはなりますが、日本語の帳票での値ではありません。

国内でも、物流ラベルを対象にした比較が公開されています。物流向けAI-OCRを提供する事業者の自社ブログで、現場3拠点から集めた1,000枚超の実画像を5条件に分けて測ったものです。対象フィールドの全文字が一致した場合だけを正解と数えています(使用製品名は非公開)。

条件従来型のテンプレートOCRVLM
標準(きれいな印字)99.2%97.8%
汚れ・かすれ78.4%93.6%
角度の変動72.1%91.2%
照明の変動83.7%92.4%
フォント・レイアウトの変動61.3%94.7%

条件がそろっているときだけ、従来型が1.4ポイント上回ります。そこから条件が崩れると差は逆転し、フォントやレイアウトが変わる場面では33.4ポイントの開きになります。

処理の速さと費用では、逆の向きの報告があります。請求書からのデータ抽出サービスを提供する事業者のベンチマーク記事によると、スキャン文書の抽出精度はVLMのほうが10ポイントから15ポイント高い一方、1ページあたりの処理時間はTesseractの50ミリ秒から200ミリ秒に対しVLMは5秒から30秒、費用は大量の定型文書で最大5倍とされています(引用元の表記はLLM。ここでいうVLMを含みます)。同じ記事は、レイアウトが変わらず、遅延が重要で、量が多く文書が単純な場面では従来型OCRが向くとしています。

補足: ここで引いた2件はいずれも、この分野で製品やサービスを提供する事業者の自社ブログです。数値の傾向は他の報告とも整合しますが、中立な第三者の測定ではありません。

つまり、脱OCRという方針は常に正しいわけではありません。OCRで足りるのはどこまでか、どこからVLMが必要か。 その線引きを、自分の帳票で決める必要があります。

2. 自社帳票でAI-OCRの精度を検証する手順

この章の手順を実行すると、書式の異なる帳票が正解データ付きで自動生成され、同じ画像を2つのOCRに通した取得率と処理時間が表として出ます。必要なものはmacOSと標準のコマンドだけで、追加の課金は発生しません。

自社帳票で AI-OCR の精度を検証する流れ

2-1. 検証用の帳票を生成する

最初に、検証に使う帳票を作ります。ここが要点です。帳票を自分で描いて生成すれば、描いた内容がそのまま正解データになり、人が画像を見て正解を書き起こす作業が丸ごと不要になります。

生成するのは、請求書を模した5つの書式です。各書式について、金額や日付の異なる2件分のデータで生成するので、合計10枚になります。これに手書きを1枚足して、6書式11枚で測ります。

記号書式何を測るための書式か
P1定型・活字基準となる、最も条件の良い状態
P2非定型項目の位置がそろわず、右寄せが混ざる書式
P3表あり明細行が入れ子になった書式
P4かすれぼかしとコントラスト低下で、劣化したスキャンを模した状態
P5色付き背景白以外の下地に印字された状態
P6手書きP1と同じ内容を紙に手書きし、それを撮影したもの

2件目は数量3・請求日2026年8月3日・単価150,000・合計450,000と、値をすべて変えています(以降に出る 150.000450.000 はこちらのものです)。P6だけは生成できず、紙に書いて撮影します。正解データはP1から流用できますが、そのままでは使えません。理由はこの節の後半で扱います。

macOS標準のAppKitで描画し、CoreImageで劣化を加えます。次に載せるのはP1(定型)の描画とかすれ加工の抜粋です。WH は画像の大きさ、draw() は指定した位置に文字を描く関数で、P2からP5のレイアウトや表の内訳行を描く処理とあわせて同じファイル内にあります。

import AppKit                                     // 画像の描画に使う(macOS標準)
import CoreImage                                  // かすれ・ぼかしの加工に使う(macOS標準)

struct Field { let label: String; let value: String }

// この配列がそのまま正解データになる(人が別途つくる必要がない)
let fields = [
    Field(label: "請求書番号", value: "INV-2026-0413"),
    Field(label: "請求日",     value: "2026年7月14日"),
    Field(label: "宛先",       value: "株式会社山田製作所"),
    Field(label: "品目",       value: "画像処理システム保守"),
    Field(label: "数量",       value: "12"),
    Field(label: "単価",       value: "48,000"),
    Field(label: "合計金額",   value: "576,000"),
]

// 定型の書式で描く(ラベルを左端、値を一定の位置から並べる)
func layoutTeikei(_ f: [Field]) {
    NSColor.white.setFill()
    NSRect(x: 0, y: 0, width: W, height: H).fill()
    draw("請求書", 60, H - 90, 30, bold: true)
    var y = H - 180
    for item in f {
        draw(item.label, 60, y, 17)               // 項目名
        draw(item.value, 260, y, 17)              // 値
        y -= 48                                   // 1行ぶん下へ移動する
    }
}

// かすれ加工(ぼかしとコントラスト低下で劣化したスキャンを模す)
func degrade(_ rep: NSBitmapImageRep) -> NSBitmapImageRep {
    let ci = CIImage(bitmapImageRep: rep)!
    let out = ci
        .applyingFilter("CIGaussianBlur", parameters: [kCIInputRadiusKey: 1.6])  // ぼかしの強さ
        .applyingFilter("CIColorControls", parameters: [
            kCIInputContrastKey: 0.55,            // コントラストを下げる
            kCIInputBrightnessKey: 0.18,          // 明るくして文字を薄くする
        ])
        .cropped(to: ci.extent)               // ぼかしで広がった分を元の大きさに戻す
    let cg = CIContext().createCGImage(out, from: ci.extent)!
    return NSBitmapImageRep(cgImage: cg)
}

生成と同時に、項目名と値の対応を truth.json として書き出します。これが採点の基準になります。

swift gen_samples.swift samples
生成: 10枚 + truth.json

手書きの1枚は、同じ内容を紙に書いて撮影します。正解データはP1から流用できますが、そのまま流用してはいけません。

import json                                      # 正解データの読み書きに使う
from pathlib import Path                         # ファイルの読み書きに使う

# 手書きサンプル(P6)は生成物ではなく撮影した紙。
# P1を書き写したものなので大部分は流用できるが、
# 書き写しの時点で字が変わった箇所は「紙に書かれている字」へ直す。
p = Path('samples/truth.json')
truth = json.loads(p.read_text(encoding='utf-8'))
truth['P6_tegaki_A'] = dict(truth['P1_teikei_A'])
truth['P6_tegaki_A']['宛先'] = '株式会社山田製作署'   # 原稿は「所」ではなく「署」
p.write_text(json.dumps(truth, ensure_ascii=False, indent=2, sort_keys=True), encoding='utf-8')

最後の1行が要点です。紙を拡大したところ、宛先の末尾が「所」ではなく「署」でした。書いた人の書き間違いです。

正解データは、元にしたデータではなくその画像に写っている文字で作ります。 流用したままだと「署」と正しく読んだ系統が不正解になり、「所」と出力した系統が正解になります。測る対象が逆さまになります。撮影した実物を混ぜるときは、拡大して1文字ずつ確認してください。

検証に使った6つの書式

もう1つ落とし穴があります。撮影した写真は、画像ビューアでは正しく見えていてもプログラムから読むと横倒しのままです。しかもVision OCRは横倒しでもある程度読んでしまうので、読めたことを向きの証拠にできません。文字の座標で判定します。

import Vision                                       // macOS標準の画像解析フレームワーク
import AppKit                                        // 画像ファイルの読み込みに使う

// 認識した文字がどこにあるかで、画像の向きが正しいかを判定する。
// 正しい向き(横長)なら、合計金額は左下に来る。
// cg は2-2と同じ手順で読み込んだ画像。
let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate
request.recognitionLanguages = ["ja-JP", "en-US"]    // 指定しないと英語だけになり、下の判定が一致しない
try VNImageRequestHandler(cgImage: cg, options: [:]).perform([request])

for obs in request.results ?? [] {
    guard let top = obs.topCandidates(1).first else { continue }
    if top.string == "合計金額" {
        let b = obs.boundingBox                   // 左下が原点、0から1で正規化された座標
        print(String(format: "合計金額 x=%.2f y=%.2f", b.midX, b.midY))
    }
}

4通りの向きで試すと、次のように出ました。

回転270度: 合計金額 x=0.19 y=0.16  → 左下にある。正しい向き
回転180度: 合計金額 x=0.83 y=0.19  → 右下にある。横倒し(0度の逆)
回転90度 : 合計金額 x=0.81 y=0.83  → 右上にある。上下が逆
回転0度  : 合計金額 x=0.16 y=0.81  → 左上にある。横倒し

sips -r 270 で回転を焼き込んでから測りました。

2-2. macOS標準のVision OCRで読み取る

macOSには、追加のインストールなしで使える文字認識が入っています。Visionフレームワークの VNRecognizeTextRequest です。対応言語は supportedRecognitionLanguages() で確認でき、macOS 26.5.2 では精度優先の設定で30言語が返り、日本語(ja-JP)も含まれます。

import Foundation                                // 日時の計測などに使う
import Vision                                     // macOS標準の画像解析フレームワーク
import AppKit                                     // 画像ファイルの読み込みに使う

let path = CommandLine.arguments[1]
guard let img = NSImage(contentsOfFile: path),
      let cg = img.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
    exit(1)
}

let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate              // 速度より精度を優先する
request.recognitionLanguages = ["ja-JP", "en-US"] // 日本語と英語を同時に認識する
request.usesLanguageCorrection = false            // 言語補正を切る(記号の書き換えを抑える)

let start = Date()                                // 処理時間の計測を開始する
let handler = VNImageRequestHandler(cgImage: cg, options: [:])
try handler.perform([request])
let elapsed = Date().timeIntervalSince(start)     // 認識だけにかかった時間

for obs in request.results ?? [] {                // 認識した行を上から順に出力する
    guard let top = obs.topCandidates(1).first else { continue }
    print(top.string)
}
// 認識だけにかかった時間を標準エラーへ出す(読み取り結果とは分けて受け取るため)
FileHandle.standardError.write(String(format: "%.4f\n", elapsed).data(using: .utf8)!)

この elapsed は認識部分だけの参考値です。2-5に並べる処理時間は、Tesseractと条件をそろえるため /usr/bin/time -p で起動から終了までを測った値を使います。

実行します。処理時間は2-5でTesseractと並べるため、起動から終了までを同じ方法で測れるようコンパイルしておきます。

swiftc -O vision_ocr.swift -o vision_ocr
/usr/bin/time -p ./vision_ocr samples/P1_teikei_A.png

出力は次のようになりました。

請求書
請求書番号
請求日
宛先
品目
数量
単価
合計金額
INV-2026-0413
2026年7月14日
株式会社山田製作所
画像処理システム保守
12
48,000
576,000

文字はすべて正しく読めています。ここで、項目名が7つ並んだあとに値が7つ続いていることに注目してください。この点は3章で扱います。

2-3. 従来型OCRで読み取る

比較対象として、従来型OCRの代表であるオープンソースの Tesseract を使います。1980年代にヒューレット・パッカードで開発が始まり、現在も更新が続いています。バージョン4系で、文字の並びを前後関係から推定する仕組み(LSTM)の認識エンジンが加わって精度が大きく上がりました。本記事で使う5.5.3もこの系統です。

brew install tesseract tesseract-lang
/usr/bin/time -p tesseract samples/P1_teikei_A.png stdout -l jpn+eng

同じ帳票の出力は次のようになりました。

Ake

請求書番号                     INV-2026-0413
請求日                               2026年7月14晶
宛先                            株式会社山田製作所
品目                                  画像処理システム保守
数量                               12
単価                          48,000
合計金額                     576,000

Vision OCRの出力と見比べてください。項目名と値が同じ行に並んでいます。 どの値がどの項目のものか、この出力からそのまま読み取れます。

その代わり、文字に誤りが出ています。表題の請求書がAkeになり、請求日の日が晶になりました。2つの系統は、まったく違う壊れ方をします。

2-4. 採点する

読み取った文字列を正解データと突き合わせます。先に決めておくことが1つあります。正規化のルールです。

OCRの出力には、見た目が同じでも文字コードが異なる箇所が現れます。今回も半角のコロンが全角に変わる例が出ました(言語補正を切っても発生します)。この違いを吸収せずに比較すると、正しく読めているのに不正解と数えてしまいます。

この論点は学術側でも独立した課題として扱われています。2026年5月に公開されたレシート理解のベンチマーク ReceiptBench は、評価を4段階に分け、そのうちの1つを書式の正規化に割り当てています。正規化は採点の付帯作業ではなく、測るべき能力の1つという位置付けです。

もう1つ、短い値の取り違えにも手当てが要ります。数量が3の帳票では、請求日の2026年8月3日に含まれる3にも一致します。数量を読めていない出力が、日付のおかげで正解と数えられてしまいます。実際にTesseractで1件起きていました。

import json                                     # 正解データの読み込みに使う
import re                                       # 短い値の照合に使う
import sys                                      # 対象の系統を引数で受け取るために使う
import unicodedata                              # 全角と半角をそろえるために使う
from pathlib import Path                         # ファイルの読み書きに使う

truth = json.loads(Path('samples/truth.json').read_text(encoding='utf-8'))
engine = sys.argv[1]                            # vision / tesseract / claude

# 素材側の都合で最初から取りようがない項目名は、分母から外す
LABEL_EXCLUDE = {('P3_hyou_A', '宛先'), ('P3_hyou_B', '宛先')}
# 項目名の採点はOCR系だけ。VLMは決めた項目を埋める形なので指標にならない
SCORE_LABELS = engine in ('vision', 'tesseract')

def normalize(s):
    s = unicodedata.normalize('NFKC', s)        # 全角の英数字と記号を半角にそろえる
    return ''.join(s.split())                   # 空白と改行をすべて取り除く

def found(value, text):
    v = normalize(value)
    if len(v) > 2:                              # 長い値は取り違えが起きにくい
        return v in normalize(text)
    for line in text.split('\n'):               # 行をまたいで数字がつながらないよう1行ずつ見る
        line = unicodedata.normalize('NFKC', line)
        line = re.sub(r'\d+年\d+月\d+日', '', line)   # 日付の中の数字を照合対象から外す
        if re.search(rf'(?<!\d){re.escape(v)}(?!\d)', line):
            return True
    return False

raw = norm = total = lab_hit = lab_n = 0        # 集計用のカウンタ
for name in sorted(truth):
    f = Path(f'out/{engine}/{name}.txt')
    if not f.exists():                          # その系統で読ませていない画像は飛ばす
        continue
    text = f.read_text(encoding='utf-8')
    for label, value in truth[name].items():
        total += 1
        if value in text:                       # そのまま照合する
            raw += 1
        if found(value, text):                  # 正規化と取り違え対策を入れて照合する
            norm += 1
        else:
            print(f'取れなかった値: {name} {label}(正解は {value})')
        if SCORE_LABELS and (name, label) not in LABEL_EXCLUDE:
            lab_n += 1
            if normalize(label) in normalize(text):
                lab_hit += 1

print(f'{engine} 値の取得率: 単純な部分一致 {raw}/{total} = {raw/total:.1%}')
print(f'{engine} 値の取得率: 対策あり     {norm}/{total} = {norm/total:.1%}')
if lab_n:
    print(f'{engine} 項目名の取得率: {lab_hit}/{lab_n} = {lab_hit/lab_n:.1%}')
python3 score.py vision

2つの数え方を並べておくと、採点ルールが結果をどれだけ動かすかが分かります。短い値の取り違え対策では、Tesseractが11枚で60項目から59項目へ1項目動きました。

一方、全角と半角の正規化では、活字10枚のスコアは動きませんでした。値に揺れが入らなかったためです。ただし揺れ自体は起きています。

grep -ohE "[^ ]+:" out/vision/*.txt | sort | uniq -c
   4 請求日:
   4 請求書番号:

非定型(P2)と表あり(P3)では、項目名の後ろに半角のコロンを描いています。請求日と請求書番号の2項目 × 4枚で8か所。それが全角で返ってきました。

採点では表面化しませんが、実装では表面化します。項目名と値をコロンで分解する処理は、半角のコロンを探しても何も見つけられません。 精度の数字が良くても後段が動かない形で出ます。測るときと使うときで、必要な正規化の範囲が違います。

2-5. 実測の結果

11枚の帳票、7項目ずつ、合計77項目で測りました。

取得率は、正解データの値が出力のどこかに含まれていれば「取れた」と数えた割合です(位置や行の対応は問いません)。並べるのはTesseractとVision OCRの2系統だけで、VLMは条件がそろわないため横に置きません(理由は表の下)。

系統値の取得率(11枚77項目)項目名の取得率(11枚75項目)1枚あたりの処理時間費用
Tesseract 5.5.376.6%(59/77)88.0%(66/75)0.19秒から0.31秒0円
macOS標準のVision OCR96.1%(74/77)92.0%(69/75)0.20秒から0.26秒0円
  • 項目名の分母だけ75です。P3(表あり)のレイアウトには宛先というラベルを描いていないので、どちらも最初から取りようがありません。この2件を分母から外しました
  • 処理時間は2系統とも /usr/bin/time -p で起動から終了までを測った値です。同じ方法でそろえています

VLMを表に入れなかったのは、正解データを作った本人が、正解を知った状態で読ませているからです。 知っている側の結果を同じ表に並べると、条件のそろった比較に見えてしまいます。実際、後述する手書きの1枚で紙にない字を出力しました。VLMは数値ではなく出力の中身を3章で扱います。処理時間も測っていません。

活字10枚(70項目)と手書き1枚に分けると、次のようになります。

系統値の取得率(活字10枚70項目)手書き1枚(7項目中)
Tesseract 5.5.384.3%(59/70)0項目
macOS標準のVision OCR100.0%(70/70)4項目

手書きは1枚だけなので、率ではなく取れた項目数で示しています。

Vision OCRは活字70項目すべての値を取り切りました。かすれさせた画像でも、色付き背景でも、金額と日付が正しく読めています。

活字での取りこぼしは、ほぼ金額に集中していた

Tesseractが活字10枚で取りこぼした11項目の内訳は、金額(単価と合計金額)が8項目、日付が2項目、数量が1項目です。金額の8項目は、すべて同じ原因でした。

grep -ohE "[0-9]+\.[0-9]{3}" out/tesseract/*.txt | sort | uniq -c
   2 150.000
   4 450.000
   2 48.000
   2 49.636
   1 576.000

桁区切りのカンマが、ピリオドとして読まれています。

出力に現れる該当は11か所ですが、取りこぼした項目数は8です。差の3か所は、採点対象に入らない内訳行(表あり書式の(内訳)で始まる明細)が2か所と、表ありの書式で同じ金額が金額欄と合計金額欄に重複しているぶんが1か所です(片方が読めていれば取得と数えます)。

48,000が48.000になると、数値として解釈したときに48になります。金額が1000分の1になる誤りが、文字としては1文字違うだけで起きます。

日付の2項目は、いずれも2026年7月14日の日が晶になりました。

手書きで差がはっきり開いた

P6の手書き1枚だけを取り出すと、3系統の結果はまったく違いました。

系統取れた項目(7項目中)中身
Tesseract 5.5.30項目なし
macOS標準のVision OCR4項目請求日、数量、単価、合計金額

VLMにも同じ画像を読ませています。数値は並べませんが、出力の中身は3系統でいちばん整っていました。 ただし1項目、紙に書かれていない字が出ています。3系統の出力をそのまま並べます。まずTesseractです。

のOO

eae! eh

~)
be by~ LY ee at YY B
#1 ave yee
Al i 0 deg

€) po —Jeor> ANT

文字として成立していません。活字では84.3%(59/70)だった系統が1項目も取れません。書式の差ではなく、手書きという条件そのもので使えなくなります。

次にVision OCRです。

請求畫
請球事番号
請求日
宛光
数量
軍西
合計金額
INV-2028-0413
2026年7月14日
株式会社山日製作署
西像処理システム保寺
12
48,000
576,000

読めている項目もありますが、文字と構造の両方が崩れています。

  • 項目名が7行並んでから値が続く読み順は活字と同じで、行の対応は失われたままです
  • そこに文字の崩れが重なります。請求書が請求畫、請求書番号が請球事番号、宛先が宛光、単価が軍西。品目の行は丸ごと出ていません
  • 値も、株式会社山田製作所が株式会社山日製作署、画像処理システム保守が西像処理システム保寺になりました

活字ではVision OCRの誤りは行の対応だけでした。手書きではそこに文字の誤りが加わります。

そして、いちばん注意が必要なのがこの1行です。

INV-2028-0413

正しくは INV-2026-0413 です。書式は正しいまま、数字が1文字だけ違います。 請求書番号が1文字違えば別の伝票ですが、出力を見ただけでは分かりません。この性質は3-2で扱います。

最後にVLMです。

請求書番号: INV-2026-0413
請求日: 2026年7月14日
宛先: 株式会社山田製作所
品目: 画像処理システム保守
数量: 12
単価: 48,000
合計金額: 576,000

項目名と値が対応した形で返り、7項目のうち6項目は紙のとおりです。手書きでは、文字を読む力と構造を組み立てる力の差がまとめて出ます。 ただし宛先の1項目だけ、紙に無い字が出ています。この誤りの意味は3-2で扱います。

項目名の取りこぼし

項目名は活字10枚では2系統とも66/68で同数ですが、中身が違います。Vision OCRはかすれた画像で請求書番号を請求害番号と読み、Tesseractは表とかすれの2書式で数量を取り落としました。手書きを含めた11枚ではVision OCRが69/75、Tesseractが66/75です。

分母が枚数×7にならないのは、P3(表あり)に宛先というラベルを描いていないためです。どの系統も最初から取りようがないので2件を外しました。素材側の都合で取れない項目を分母に残すと、その分だけ成績が実際より低く出ます。

オフィスで設定手順を説明する株式会社ノーコードソリューションズ COO 吉村祐樹

ここまでが実測です。同じ帳票でも、系統によって壊れ方がまったく違いました。ここからは、この結果をどう選択に落とすかを見ていきます。

株式会社ノーコードソリューションズ COO 吉村 祐樹

3. OCRとVLMの使い分けを実測から決める

3-1. 2つの無料OCRは、正反対の壊れ方をする

収穫は精度の順位ではなく、2つのOCRがまったく逆の壊れ方をしたことでした。表を含む書式で並べます。まずVision OCR(表の部分の抜粋)。

品目
画像処理システム保守
(内訳)初期設定費
(内訳) 月額保守費
数量
12
1
11
請求書番号:INV-2026-0413
請求日:2026年7月14日
単価
48,000
30,000
49,636
金額
576,000
30,000
546,000

文字は正確で、金額のカンマも合っています。しかし列ごとに固まって出力され、行としての対応が失われています。 単価49,636と金額546,000がどの品目のものか、この文字列だけでは決められません。

次にTesseract(同じく抜粋)。

請求書番号: INV-2026-0413
請求日: 2026年7月14日

株式会社山田製作所
品目                          数量        単価          金額
画像処理システム保守                12         48.000       576,000
(AR) 初期設定費                1        30,000        30,000
(ARR) 月額保守費               11        49.636       546,000
合計金額            576,000

行の対応は保たれています。 品目と数量と単価と金額が同じ行に並んでいます。その代わり文字が壊れました。48,000のカンマがピリオドになって48.000と読まれ、(内訳)は(AR)と(ARR)になっています。

整理すると次のようになります。

系統文字の正確さ行の対応主な失敗の形
Vision OCR保たれる失われる出力が列単位に並び、どの値がどの行のものか決められない
Tesseract崩れる保たれる桁区切りのカンマがピリオドになり、金額が別の数になる
VLM(参考)保たれる保たれる紙に無い字を補うことがある

補足: この表は活字の場合です。正反対に壊れるという関係が成り立つのは活字の範囲で、手書きではTesseractが文字として成立せず、Vision OCRは文字も崩れます(2-5)。

同じ帳票(活字・明細が入れ子の表)を3系統に通した出力

片方だけを試すと判断を誤ります。 Vision OCRだけなら文字認識は完璧に見え、Tesseractだけなら構造は取れているように見えるからです。VLMに同じ画像を読ませると項目と値の対応が付いた形で返り、入れ子の内訳も親の行との関係を保っていました。文字を読む力ではなく、読んだ文字を構造として組み立てられるかで差が付きます。

これは論文の報告とも一致します。ReceiptBench はレシートからの情報抽出を4段階に分けて測ったベンチマークで、最も低いスコアが出るのは一貫して構造解析です。F1(取りこぼしと余計な出力を1つにまとめた指標。1に近いほどよい)はGPT-5が0.4893、Gemini-3-Proが0.5781。同じ2モデルの他の3段階が0.73から0.91なので、入れ子構造だけが際立って難しいと分かります。最新のVLMでも、明細行の構造は半分程度しか取れていません。 追加学習した(用途に合わせて再学習した)Qwen3-VL-8Bでも0.6373です。

補足: ReceiptBenchの対象は約1万件のレシートで、その98.0%が英語です。日本語の帳票を測ったものではありません。 入れ子構造が難しい傾向は言語によらないと考えられますが、数値そのものは日本語での値ではありません。

3-2. 失敗の質が系統によって違う

精度の数字では見えない差がもう1つあります。間違え方です。

ReceiptBench は、モデルごとの失敗を種類別に数えています。追加学習したQwen3-VL-4Bは欠落エラー(項目を空のまま返す)849件、知覚エラー(字を読み違える)727件。Gemini-3-Proは幻覚(紙に無いものを作って埋める)516件、推論エラー(読めてはいるが意味の取り方を誤る)599件。内訳がまったく違います。

この違いは実務では重大です。

  • 欠落型の失敗は、項目が空で返るため気づけます。人の確認へ回す仕組みを作れば救えます
  • 幻覚型の失敗は、もっともらしい値が埋まって返るため気づけません。金額が実在しない数字で埋まっていても、書式が正しければ通過します

今回の手書きでも同じ性質の誤りが出ました。Vision OCRが請求書番号を INV-2028-0413 と読んだ件です(正しくは INV-2026-0413)。

厄介なのは、出力そのものは何も壊れていないことです。桁数も書式も正しく、伝票番号として完全に成立しています。値が空なら気づけますし、48.000のように区切りが崩れていれば数値変換で落ちます。同じ1文字の誤りでも、気づける形で壊れるか、成立したまま間違うかで、必要な対策がまったく違います。

画像に無い字が出てくる

もう1件、同じ手書きの帳票から、より分かりやすい例が出ました。宛先です。

出力
紙に書かれている字株式会社山田製作
macOS標準のVision OCR株式会社山製作
VLM(Claude Opus 5)株式会社山田製作

紙に書いたのは製作署です(2-1の書き間違い)。Vision OCRは田を日と読み違えていますが、署はそのまま署と読んでいます。紙にある形をなぞった結果です。

VLMは製作と出力しました。所という字は、この紙のどこにも書かれていません。 会社名は製作所で終わるという知識が、目の前の字より優先されました。これが幻覚です。

VLMが便利なのは、崩れた字や欠けた情報を文脈で補うからです。補う力と、勝手に直す性質は同じものです。 切り分けて片方だけ使うことはできません。

用途によって、この性質は正反対に働きます。帳票の内容が正しいかを確かめたいなら、取引先名が実在の会社名に直されて返るため、原本と照合しない限り気づけません。逆に崩れた字を実用的な値に整えたいなら利点になります。どちらの用途かを先に決めてから系統を選んでください。

請求金額や口座番号のように、間違えたときの損害が大きい項目では、埋めてくれる系統より空欄で返す系統のほうが安全です。精度が同じなら、間違え方で選ぶ。 これが実測から出る判断です。

3-3. 使い分けの判断基準

ここまでの実測と論文の報告をまとめると、次の基準になります。

条件適した系統根拠
書式が固定で、活字で、枚数が多いmacOS標準のVision OCR費用がかからず、10枚70項目の値をすべて取得できた。1枚0.2秒前後
金額や数量が主役の帳票macOS標準のVision OCR、またはVLMTesseractは桁区切りのカンマをピリオドに変える誤りが10枚で11か所あった
項目と値の対応をそのまま使いたいTesseract、またはVLMVision OCRの出力は列単位になり、行の対応が失われる
明細行が入れ子になっているVLM。ただし人の確認を前提にする最新のVLMでも構造解析のF1は0.49から0.64
スキャンがかすれている、背景に色があるmacOS標準のVision OCR、またはVLMかすれと色付き背景では、Vision OCRも値を100%取得できた
手書きが含まれるVLM手書き1枚の実測で、7項目中Tesseractが0項目、Vision OCRが4項目。従来型OCRは選択肢から外れる。VLMが読めることはOmniDocBenchの報告と今回の出力から言えるが、率としては測っていない
原本と1文字ずつ突き合わせたいmacOS標準のVision OCRVLMは紙に無い字を補うことがある。手書きの宛先で実際に起きた
崩れた字を実用的な値に整えたいVLM同じ性質が利点になる
間違いを見逃したくない項目がある欠落型で失敗する系統幻覚型は気づけない。手書きでは書式が正しいまま数字が1文字違う誤りも出た
とにかく速度と費用を優先するmacOS標準のVision OCR1枚0.20秒から0.26秒、費用は0円
OCR と VLM の使い分け

組み合わせも選択肢に入ります。 行の対応が保たれるTesseractの出力をVLMに渡して項目と値へ割り当てれば、文字の崩れをVLM側で直せます。ただしVision OCRの出力を渡す形では、列単位に崩れて失われた行の対応をVLMでも復元できません。この構成は今回検証していないので、試すなら自分の帳票で測ってください。

3-4. 費用と速度をどう見積もるか

TesseractとVision OCRは費用がかかりません。VLMもセッション内で動かすぶんには発生しませんが、API経由で組み込む場合は1枚ごとに課金されます。 有料サービスと比べるなら、公開されている単価に処理枚数を掛ければ実測なしで見積もれます。

処理時間は、今回の実測でVision OCRが1枚0.20秒から0.26秒、Tesseractが0.19秒から0.31秒でした。どちらも起動から終了までの実時間です。1万枚ならVision OCRで33分から43分です。

VLMは他の2系統と同じ条件で測れないため計測していません。 1-3で引いた記事の値(1ページ5秒から30秒)をあてはめると、同じ1万枚で14時間から83時間。25倍から100倍以上の差なので、枚数の多い業務では処理の設計そのものが変わります。夜間にまとめるのか、その日のうちに終わらせるのか。精度と同じ重さで時間を見積もってください。

構造化したデータをRAGなどに流し込む場合の設計は、「AI時代の構造化データの置き方|情シスのためのガバナンスとPoC評価」もあわせてご覧ください。手法そのものの一覧は「AIの構造化データとは?図面・帳票を「AIが使える形」に変える全手法」にまとめています。

3-5. 今回の検証で測っていないこと

測っていない軸について、この記事の数字は根拠になりません。

  • 手書きは1枚・筆跡は1人分:傾向は出ていますが、率として一般化できる枚数ではありません
  • 紙のスキャンではない:かすれはぼかしとコントラスト低下で模したもので、網点(印刷の細かい点の模様)、傾き、影、裏写りは含みません
  • 多言語の混在:日本語と英数字だけが対象です
  • 表の複雑さ:明細3行、結合セルなしまで。数十行の明細は測っていません
  • 枚数は11枚:ばらつきを論じるには足りません。自社で回すときは書式ごとに10枚から20枚が目安です
  • VLMの取得率と処理時間:どちらも測っていません(理由は2-5)。自社で測るときは、正解を知らない人か仕組みに抽出させてください
  • 有料サービス:クラウドのDocument AI(帳票を送ると構造化して返すサービス)は対象外です

生成した帳票は実際の取引先のものとは違います。ここで示したのは検証のやり方であって、自社の帳票での答えではありません。同じコードに自社の書式を通した数字が、判断の根拠になります。

4. まとめと結論

  • 活字なら、無料のOCRで足ります。macOS標準のVision OCRは、かすれた画像でも色付き背景でも、活字10枚70項目の値を100%読み取りました。費用は0円、1枚あたり0.2秒前後です。ただし出力は列単位に並ぶため、どの値がどの項目のものかは別に組み立てる必要があります
  • 手書きが入ると、順位ではなく可否が変わります。 手書き1枚の7項目で、活字では84.3%(59/70)だったTesseractが0項目、Vision OCRが4項目でした。VLMは7項目のうち6項目が紙のとおりでしたが、条件がそろわないため同じ土俵の数字ではありません。手書きを含む帳票では、従来型OCRは選択肢から外れます
  • 間違え方で選んでください。Tesseractは金額の桁区切りを崩し(48,000が48.000)、VLMは紙に無い字を補いました(製作署が製作所)。前者は数値変換で弾けますが、後者は原本と照合しないと気づけません。取得率を横に並べられるのは、条件をそろえて測れたものだけです
  • カタログの数字ではなく、自社の書式で測ってください。帳票を生成すれば正解データは自動で決まります。ただし撮影した実物を混ぜるときは、拡大して1文字ずつ確認してください。素材側の書き間違いは、そのままツールの性能に化けます

導入前チェックリスト

  • [ ] 自社の帳票を書式ごとに分類し、手書きが含まれるものを分けた
  • [ ] 検証用のサンプルを、機密を含まない形で用意できる目処が立った
  • [ ] 採点の正規化ルール(全角と半角、空白、桁区切り、短い値の取り違え)を先に決めた
  • [ ] 項目と値の対応が必要か、文字列が取れれば足りるかを整理した
  • [ ] 間違いを見逃したくない項目を洗い出し、人の確認を挟む工程を決めた

検証環境:

  • macOS 26.5.2(Apple Silicon)
  • Swift 6.2、Vision フレームワーク(VNRecognizeTextRequestrecognitionLevel = .accurateusesLanguageCorrection = false
  • Python 3.13.5
  • Tesseract 5.5.3(言語データ jpn / eng)
  • VLM: Claude Opus 5(claude-opus-5)。Claude Code のセッション内で画像を直接読み込ませています(ローカル実行ではありません)。利用料に含まれるため追加課金は発生していませんが、API経由で再現する場合は1枚ごとに従量課金がかかります。 取得率は測っていません(3-5参照)
  • 処理時間は /usr/bin/time -p の実時間で、起動から終了までを含みます。TesseractとVision OCRを同じ方法で測りました
  • 検証日: 2026年8月5日
  • 検証に使った帳票は11枚。本記事のコードで生成した10枚(5書式 × 2件)と、同じ内容を手書きして撮影した1枚です。実在の取引先や金額は含みません
  • 各ツールの最新の仕様は公式ドキュメントで確認してください
オフィスで設定手順を説明する株式会社ノーコードソリューションズ COO 吉村祐樹

検証の手順はこの記事のコードでそのまま回せます。自社の帳票で試してみたいけれど時間が取れない、という段階でもご相談ください。

株式会社ノーコードソリューションズ COO 吉村 祐樹

最後に

私たちは、単にシステムを組むだけの開発会社ではありません。低コストで高品質なAIツールの構築から、ROI(投資対効果)を最大化する導入ロードマップの策定、社内スタッフが自らAIを運用・改善できる体制の構築まで、AI導入の成功に必要なすべてを最初から最後まで丸ごと支援いたします。

実は、ご相談いただく方のほとんどが「何が分からないかも分からない」という状態からのスタートです。構想段階でも、ただのアイデアベースでも構いません。
まずは、あなたのお困りごとをそのまま聞かせていただけませんか?貴社のビジネスを加速させるパートナーとして伴走いたします。
無料オンライン相談で、最適な導入プランを相談する

参考文献

  1. Linke Ouyang ほか「OmniDocBench: Benchmarking Diverse PDF Document Parsing with Comprehensive Annotations」(2026年8月5日参照)
  2. OpenDataLab「OmniDocBench リポジトリと評価結果」(2026年8月5日参照)
  3. Yihao Ding ほか「A Survey on MLLM-based Visually Rich Document Understanding: Methods, Challenges, and Emerging Trends」(2026年8月5日参照)
  4. Yandi Wang ほか「From Recognition to Reasoning: Benchmarking and Enhancing MLLMs on Real-World Receipt Document Understanding」(2026年8月5日参照)
  5. Apple「Recognizing Text in Images(Vision)」(2026年8月5日参照)
  6. Tesseract OCR「公式ドキュメント」(2026年8月5日参照)
  7. Parsli「LLM OCR vs Traditional OCR: When AI Wins (and When It Doesn’t) 2026 Benchmark」(2026年8月5日参照)。請求書からのデータ抽出サービスを提供する事業者の自社ブログ。費用の最大5倍という値は、同記事が引用しているMindeeの調査による
  8. Nsight「物流ラベルOCR精度比較|従来OCR vs VLMのベンチマーク実測データ」(2026年8月5日参照)。物流向けAI-OCRを提供する事業者の自社ブログ。使用したOCR製品名とVLMのモデル名は公開されていない
  9. Apple「VNRecognizeTextRequest.supportedRecognitionLanguages()」(2026年8月5日参照)
ぜひ共有お願いします!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次