YOLOオブジェクト検出で解決するナンバープレート認識問題
伝統的なOCRの限界を超え、YOLOベースの3段階パイプラインで作られた実戦ナンバープレート認識システム
問題意識
従来のOCRはきれいな文書ではよく動作しますが、ナンバープレートのようにぼやけて傾いた画像では弱点があります。文字の間隔が不規則であったり一部が隠れていると、認識率が急落します。また、韓国の車のナンバープレートは文字配置が多様で、地域名と数字が混在しているため、一般的なOCRモデルには限界がありました。
このプロジェクトでは、文字をテキストではなくオブジェクトとして見る観点を取り入れました。YOLOベースのオブジェクト検出で各文字を独立して探し、元の画像と補正された画像の両方でOCRを実行し、二重検証を行います。NMSとエクストリームポイントベースのラインフィッティングを使用して地域名と数字を分離し、伝統的なOCRが失敗する環境でも頑丈に動作します。
3段階パイプライン
全体の流れはdetect.pyのget_num()関数一つで制御します。3つのONNXモデルがそれぞれの専門分野を担当します。
ステージ | モデル | 役割 |
|---|---|---|
1 |
| 全体画像からナンバープレート領域の検出 |
2 |
| 四隅を検出後に遠近補正 |
3 |
| 75クラスで各文字検出 |
最初のモデルは全体画像からナンバープレートの領域を一つ見つけます。複数の候補の中から信頼度が最も高いものを選び、クロップします。2番目のモデルはクロップされた画像から4つのコーナーを検出し、遠近補正を行います。3番目のモデルは75クラスで各文字をオブジェクトとして検出します。伝統的なOCRのようにテキストラインを読むのではなく、ナンバープレート内で各文字の位置を見つけるため、文字間隔が不規則であっても一部が隠れていても他の部分を認識できます。
graph LR
Input[入力画像] --> Plate[ナンバープレート検出]
Plate --> Crop[ナンバープレートクロップ]
Crop --> Vertex[四隅検出]
Vertex --> Warp[遠近補正]
Warp --> OCR1[warped OCR]
OCR1 --> Validate1[正規表現検証]
Validate1 -->|通過| Output[最終番号]
Validate1 -->|失敗| OCR2[cropped OCR]
Crop --> OCR2
OCR2 --> Validate2[正規表現検証]
Validate2 --> Output
検証とフォールバック: 安定性と効率のバランス
遠近変換が常に完璧ではありません。角の検出が失敗したり、傾いたナンバープレートに直面することがあります。
このプロジェクトはwarped画像を優先してOCRし、正規表現検証を通過すると即座に結果を返します。失敗した場合にのみ、元のcropped画像でフォールバックし、追加のOCRを行います。
def get_num(img):
cropped = plate_detector.detect_and_crop(img)
warped = vertex_detector.detect_and_warp(cropped)
# Step 1: warped画像で優先してOCR
if warped is not None:
res = syllable_detector.get_num_from_img(warped)
result = validate_plate_num(res, mask=False)
if result:
return result
# Step 2: fallback - cropped画像でOCR
if cropped is not None:
res = syllable_detector.get_num_from_img(cropped)
result = validate_plate_num(res, mask=False)
if result:
return result
return None
def validate_plate_num(plate_num, mask=True):
"""単一OCR結果に対するregex検証 + マスク適用"""
if plate_num is None:
plate_num = ''
m = re.fullmatch(PLATE_REGEX, plate_num)
if m is None:
return None
if mask:
plate_num = re.search(OUTPUT_REGEX, plate_num).group()
return plate_numこの方法はwarpingが成功した場合、OCR一回で処理し、失敗したときだけcropped画像でフォールバックします。不必要な推論を減少させつつ、安定性を維持する設計です。
文字検出後にはNMSで重複するバウンディングボックスを除去し、左右の端点を基準にして線を引き、地域名と数字の領域を分離します。地域名はソウル、京畿、釜山などハードコーディングされた有効地域一覧と比較して修正します。
sequenceDiagram
participant Detect as detect.py
participant ONNX as ONNX Runtime
participant Validate as validate_plate_num
Detect->>ONNX: warped画像で文字を認識
ONNX-->>Detect: 結果
Detect->>Validate: 正規表現検証
alt 検証通過
Validate-->>Detect: 最終番号を返す
else 検証失敗
Detect->>ONNX: cropped画像で文字を認識
ONNX-->>Detect: 結果
Detect->>Validate: 正規表現検証
alt 検証通過
Validate-->>Detect: 最終番号を返す
else
Validate-->>Detect: 失敗を返す
end
endユーザーを考慮したGUI設計
バッチ処理中に一部の画像が検出に失敗した場合、どうすれば良いでしょうか? 無視して進めばデータ品質が低下し、全体を中断すれば効率性が落ちます。
PySide6ベースのGUIは検出失敗時に自動的に一時停止し、当該画像を画面に表示します。ユーザーが直接ナンバープレート番号を入力し、確認ボタンを押すまで処理を止めます。
これはすべてのデータに対して正確な検出が必要な状況のために設計されており、不完全な部分を勝手に修正するのではなく、ユーザーが判断するよう設計されています。
トレードオフと設計哲学
YOLOベースの文字検出は性能面で前処理ベースのOCRより遅く、重いという欠点があります。また、複数の段階に分かれてそれぞれ異なるモデルを利用するため最適化の問題も残っています。
しかし、このプロジェクトの本質は、前処理を使わない方法で認識率を確認することにありました。今後、前処理を有効に利用し、単一のモデルでナンバープレートと文字を認識し組み合わせる方法を使えば、認識率を維持または向上しながら検出速度を向上できるでしょう。
締めくくりに
このプロジェクトを進めながら、個人的に機械学習を初めてサービスに取り込んだサービスです。モデルをファインチューニングし、新しい方向でアプローチを試みた点においても意義のあるプロジェクトです。
現在の視点で数多くの改善点を再確認し、発見することができました。将来プログラムをアップデートしながら、より良い性能と速度を持つように改善したいと思います。