Skip to main content
Mandacode mandacode
YOLOオブジェクト検出で解決するナンバープレート認識問題
·
OCR Python YOLO

YOLOオブジェクト検出で解決するナンバープレート認識問題

伝統的なOCRの限界を超え、YOLOベースの3段階パイプラインで作られた実戦ナンバープレート認識システム

問題意識

従来のOCRはきれいな文書ではよく動作しますが、ナンバープレートのようにぼやけて傾いた画像では弱点があります。文字の間隔が不規則であったり一部が隠れていると、認識率が急落します。また、韓国の車のナンバープレートは文字配置が多様で、地域名と数字が混在しているため、一般的なOCRモデルには限界がありました。

このプロジェクトでは、文字をテキストではなくオブジェクトとして見る観点を取り入れました。YOLOベースのオブジェクト検出で各文字を独立して探し、元の画像と補正された画像の両方でOCRを実行し、二重検証を行います。NMSとエクストリームポイントベースのラインフィッティングを使用して地域名と数字を分離し、伝統的なOCRが失敗する環境でも頑丈に動作します。

3段階パイプライン

全体の流れはdetect.pyget_num()関数一つで制御します。3つのONNXモデルがそれぞれの専門分野を担当します。

ステージ

モデル

役割

1

plate_detect_v1

全体画像からナンバープレート領域の検出

2

vertex_detect_v1

四隅を検出後に遠近補正

3

syllable_detect_v1

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より遅く、重いという欠点があります。また、複数の段階に分かれてそれぞれ異なるモデルを利用するため最適化の問題も残っています。

しかし、このプロジェクトの本質は、前処理を使わない方法で認識率を確認することにありました。今後、前処理を有効に利用し、単一のモデルでナンバープレートと文字を認識し組み合わせる方法を使えば、認識率を維持または向上しながら検出速度を向上できるでしょう。

締めくくりに

このプロジェクトを進めながら、個人的に機械学習を初めてサービスに取り込んだサービスです。モデルをファインチューニングし、新しい方向でアプローチを試みた点においても意義のあるプロジェクトです。

現在の視点で数多くの改善点を再確認し、発見することができました。将来プログラムをアップデートしながら、より良い性能と速度を持つように改善したいと思います。