مقالات › هوش مصنوعی

پلاک‌خوان با YOLO و OCR؛ پیاده‌سازی کامل سامانه‌ی تشخیص پلاک خودرو

یک سامانه‌ی پلاک‌خوان (ALPR) بسازید: آماده‌سازی و برچسب‌گذاری داده، آموزش مدل YOLO برای تشخیص پلاک، خواندن کاراکترهای پلاک ایرانی، ردیابی در ویدیو، نکات افزایش دقت و اجرای مدل در برنامه‌ی .NET.

پلاک‌خوانی خودکار (ALPR یا ANPR) قلب سامانه‌های پارکینگ، عوارضی، کنترل تردد و پایش ترافیک است. یک سامانه‌ی پلاک‌خوان مدرن زنجیره‌ای از مدل‌های یادگیری عمیق است: یکی پلاک را پیدا می‌کند و دیگری آن را می‌خواند. این مقاله ساخت چنین سامانه‌ای را با YOLO شرح می‌دهد و توجه ویژه‌ای به پلاک‌های ایرانی دارد که قالب و مجموعه‌ی حروف خاص خود را دارند.

زنجیره‌ی پردازش

فریم دوربین → [۱] تشخیص پلاک (YOLO) → برش و صاف‌سازی → [۲] خواندن کاراکترها → [۳] اعتبارسنجی و ردیابی → نتیجه
  1. تشخیص (Detection) — یافتن پلاک‌ها در تصویر به‌صورت کادر.
  2. بازشناسی (Recognition) — خواندن کاراکترهای داخل هر برش پلاک.
  3. پس‌پردازش — اعتبارسنجی قالب، ترکیب خوانش‌ها در فریم‌های ویدیو و ذخیره‌ی نتیجه.

تقسیم مسئله به این شکل باعث می‌شود هر مدل روی یک کار تمرکز کند و مستقل بهبود یابد.

گام ۱: جمع‌آوری و برچسب‌گذاری داده

کیفیت مدل بیش از هر چیز به داده بستگی دارد. تصاویری جمع کنید که با شرایط واقعی نصب هم‌خوان باشند:

  • زاویه و ارتفاع واقعی دوربین (دوربین درب ورودی در برابر دوربین خیابان)،
  • روز، شب، مادون‌قرمز، باران، بازتاب نور و تاری حرکت،
  • انواع خودرو، پلاک‌های کثیف یا نیمه‌پوشیده،
  • پلاک موتورسیکلت در صورت نیاز.

چند هزار تصویر با برچسب دقیق از دوربین‌های واقعی خودتان، از ده‌ها هزار تصویر تصادفی اینترنتی بهتر است.

با ابزاری مثل CVAT، Label Studio یا Roboflow برچسب بزنید و خروجی را در قالب YOLO بگیرید؛ برای هر تصویر یک فایل .txt شامل class x_center y_center width height نرمال‌شده بین ۰ تا ۱:

0 0.512 0.734 0.186 0.061

داده را به آموزش/اعتبارسنجی/آزمون تقسیم کنید (مثلاً ۸۰/۱۰/۱۰) و مطمئن شوید تصاویر یک ویدیو یا یک مکان بین بخش‌ها پخش نشده باشند؛ وگرنه امتیاز اعتبارسنجی بهتر از واقعیت به نظر می‌رسد.

# plates.yaml
path: datasets/plates
train: images/train
val: images/val
test: images/test
names:
  0: plate

گام ۲: آموزش مدل تشخیص پلاک

با پکیج Ultralytics، آموزش یک مدل YOLO سفارشی چند خط است:

pip install ultralytics
from ultralytics import YOLO

model = YOLO("yolo11n.pt")          # مدل کوچک ازپیش‌آموزش‌دیده؛ برای دقت بیشتر "s" یا "m" را امتحان کنید
model.train(
    data="plates.yaml",
    epochs=100,
    imgsz=640,
    batch=16,
    patience=20,                    # توقف زودهنگام
    degrees=5, translate=0.1, scale=0.5, fliplr=0.0,   # بدون قرینه‌ی افقی: کاراکترها را آینه‌ای می‌کند
)
metrics = model.val()
print(metrics.box.map50, metrics.box.map)

نکات کلیدی:

  • از وزن‌های ازپیش‌آموزش‌دیده شروع کنید (Transfer Learning)؛ با داده‌ی کمتر خیلی سریع‌تر همگرا می‌شود.
  • Augmentation قرینه‌ی افقی را غیرفعال کنید؛ پلاک آینه‌ای در واقعیت وجود ندارد.
  • به mAP@50 و Recall روی داده‌ی اعتبارسنجی توجه کنید: در پلاک‌خوانی، از دست دادن پلاک (Recall پایین) معمولاً بدتر از یک کادر اشتباه گاه‌به‌گاه است که مرحله‌ی بازشناسی می‌تواند آن را رد کند.
  • پلاک‌های کوچک و دور از imgsz بزرگ‌تر (مثلاً ۹۶۰ یا ۱۲۸۰) سود می‌برند؛ به قیمت کاهش سرعت.

گام ۳: برش و نرمال‌سازی پلاک

قبل از بازشناسی، هر پلاک تشخیص‌داده‌شده را با کمی حاشیه برش دهید و سپس:

  • با حفظ نسبت ابعاد به ارتفاع ثابت (مثلاً ۶۴ پیکسل) تغییر اندازه دهید،
  • در صورت نیاز پرسپکتیو را اصلاح کنید (چهار گوشه را با مدل Keypoint/OBB پیدا و به مستطیل تبدیل کنید)،
  • اگر مدل بازشناسی این‌طور آموزش دیده، به سیاه‌وسفید تبدیل و کنتراست را بهبود دهید (CLAHE).
import cv2

def crop_plate(frame, box, pad=0.08):
    x1, y1, x2, y2 = box
    w, h = x2 - x1, y2 - y1
    x1, y1 = max(0, int(x1 - w * pad)), max(0, int(y1 - h * pad))
    x2, y2 = min(frame.shape[1], int(x2 + w * pad)), min(frame.shape[0], int(y2 + h * pad))
    return frame[y1:y2, x1:x2]

گام ۴: خواندن کاراکترها

دو رویکرد رایج وجود دارد.

گزینه‌ی الف: کاراکترها به‌عنوان کلاس‌های تشخیص

یک مدل YOLO دوم آموزش دهید که در آن هر کاراکتر یک کلاس است (ارقام ۰ تا ۹ به‌علاوه‌ی حروف پلاک فارسی). کاراکترهای تشخیص‌داده‌شده را بر اساس مختصات x مرتب کنید تا رشته ساخته شود. این روش برای پلاک‌های با قالب ثابت بسیار خوب کار می‌کند و نسبت به فاصله و فونت مقاوم است.

پلاک استاندارد خودروی شخصی ایران این چیدمان را دارد:

[۲ رقم] [حرف] [۳ رقم] | [کد ۲ رقمی شهر]

مثلاً ۱۲ ب ۳۴۵ - ۶۷. چون کد شهر در یک کادر جدا در سمت راست قرار دارد، کاراکترها را بر اساس موقعیت به بخش اصلی یا بخش کد شهر نسبت دهید.

گزینه‌ی ب: بازشناسی دنباله‌ای (CRNN یا OCR مبتنی بر Transformer)

یک CRNN با تابع هزینه‌ی CTC یا یک مدل OCR کوچک مبتنی بر Transformer کل برش را به‌صورت یک دنباله می‌خواند. این روش پلاک‌های با طول متغیر را مدیریت می‌کند و به‌عنوان برچسب فقط متن پلاک را لازم دارد، نه کادر تک‌تک کاراکترها؛ اما معمولاً داده‌ی آموزشی بیشتری می‌خواهد.

هشدار: موتورهای OCR عمومی (مثل Tesseract بدون آموزش) روی پلاک ضعیف عمل می‌کنند: فونت غیرمعمول، مجموعه‌ی حروف فارسی، وضوح پایین و کثیفی. مدلی که روی پلاک‌های خودتان آموزش دیده به‌طور چشمگیری بهتر است.

گام ۵: اعتبارسنجی قالب

با استفاده از ساختار مشخص پلاک، خوانش‌های غیرممکن را رد و اشتباهات رایج را اصلاح کنید:

import re

PERSIAN_LETTERS = "بجدسصطقلمنوهیالفپتثزشعژ"   # با حروفی که مدل شما خروجی می‌دهد تطبیق دهید
PATTERN = re.compile(rf"^(\d{{2}})([{PERSIAN_LETTERS}])(\d{{3}})(\d{{2}})$")

def normalize(text: str) -> str:
    return text.translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹", "0123456789"))

def validate(raw: str):
    m = PATTERN.match(normalize(raw))
    if not m:
        return None
    two, letter, three, region = m.groups()
    return {"plate": f"{two}{letter}{three}", "region": region}

قوانین قالب بخش بزرگی از خطاها را حذف می‌کنند (مثلاً پیش‌بینی یک حرف در جایگاه رقم).

گام ۶: ویدیو، ردیابی و رأی‌گیری

در ویدیو به یک فریم اعتماد نکنید. هر خودرو را در طول فریم‌ها ردیابی کنید (ByteTrack یا BoT-SORT داخل Ultralytics موجودند) و بین خوانش‌ها رأی‌گیری کنید:

from collections import Counter, defaultdict

readings = defaultdict(Counter)

for result in model.track(source="rtsp://camera/stream", stream=True, persist=True):
    for box in result.boxes:
        if box.id is None:
            continue
        track_id = int(box.id)
        text = recognize(crop_plate(result.orig_img, box.xyxy[0].tolist()))
        if (plate := validate(text)) and box.conf > 0.5:
            readings[track_id][plate["plate"] + plate["region"]] += 1

# وقتی ردیابی یک خودرو تمام شد، پرتکرارترین خوانش برنده است
best, votes = readings[track_id].most_common(1)[0]

رأی‌گیری بین ۵ تا ۱۵ فریم، دقت ۹۰ درصدی تک‌فریم را به دقت نهایی بسیار بالاتری می‌رساند و نوسان نتیجه را حذف می‌کند.

اندازه‌گیری درست دقت

چیزی را بسنجید که برای کسب‌وکار مهم است:

شاخص معنا
Recall تشخیص درصد پلاک‌هایی که پیدا شدند
دقت کاراکتر درصد کاراکترهایی که درست خوانده شدند
دقت کل پلاک درصد پلاک‌هایی که همه‌ی کاراکترهایشان درست است؛ عدد اصلی
تأخیر زمان از فریم تا نتیجه

روی یک مجموعه‌ی آزمون از دوربین‌های خودتان و به تفکیک روز/شب و شرایط آب‌وهوایی ارزیابی کنید و خطاها را بررسی کنید؛ دقیقاً به شما می‌گویند چه داده‌ای را باید اضافه کنید.

استقرار و اجرا

مدل‌های آموزش‌دیده را به ONNX صادر کنید (یا TensorRT روی NVIDIA و OpenVINO روی پردازنده‌های Intel):

model.export(format="onnx", imgsz=640, dynamic=False, simplify=True)

معماری‌های رایج:

  • سرویس پایتون (FastAPI) که مدل‌ها را اجرا می‌کند و برنامه‌ی اصلی از طریق HTTP یا صف پیام آن را صدا می‌زند.
  • استنتاج مستقیم در .NET با Microsoft.ML.OnnxRuntime؛ بدون پایتون در محیط تولید:
using var session = new InferenceSession("plate-detector.onnx");
var input = new DenseTensor<float>(Preprocess(frame), [1, 3, 640, 640]);
using var results = session.Run([NamedOnnxValue.CreateFromTensor("images", input)]);
var output = results[0].AsTensor<float>();
var plates = PostProcess(output, confidenceThreshold: 0.4f, iouThreshold: 0.5f); // رمزگشایی کادرها + NMS

سپس یک بک‌اند ASP.NET Core می‌تواند خوانش‌ها را ذخیره کند، آن‌ها را با فهرست مجاز (برای درب‌ها) تطبیق دهد، رویدادهای زنده را با SignalR ارسال کند و گزارش ارائه دهد.

نکات عملی

  • نصب دوربین نیمی از دقت است: سرعت شاتر کافی برای جلوگیری از تاری حرکت، نور مادون‌قرمز در شب و عرض پلاک حداقل حدود ۱۰۰ تا ۱۵۰ پیکسل در تصویر.
  • فقط ناحیه‌ی مورد نظر (لاین عبور) را پردازش کنید تا تشخیص اشتباه و مصرف پردازنده کم شود.
  • در سامانه‌های حساس (عوارضی، جریمه) یک صف بازبینی انسانی برای خوانش‌های با اطمینان پایین داشته باشید.
  • قوانین حریم خصوصی را رعایت کنید: پلاک داده‌ی شخصی است؛ مدت نگه‌داری را محدود و ذخیره‌سازی و دسترسی را امن کنید.

جمع‌بندی

یک سامانه‌ی پلاک‌خوان، مدل تشخیص پلاک YOLO، یک بازشناس کاراکتر آموزش‌دیده روی قالب پلاک شما، اعتبارسنجی مبتنی بر قانون و رأی‌گیری چندفریمی را ترکیب می‌کند. داده از شرایط واقعی نصب بزرگ‌ترین عامل دقت است و Transfer Learning، Augmentation مناسب و اعتبارسنجی قالب بقیه‌ی کار را انجام می‌دهند. با صدور به ONNX مدل را به‌طور کارآمد روی دستگاه‌های لبه یا مستقیماً داخل برنامه‌ی .NET اجرا کنید.

پرسش‌های متداول

به چند تصویر نیاز دارم؟

برای مدل تشخیص پلاک، ۱٬۰۰۰ تا ۳٬۰۰۰ تصویر متنوع و دقیق برچسب‌خورده از دوربین‌های خودتان شروع خوبی است. بازشناسی کاراکتر به نمونه‌ی بیشتری برای هر کاراکتر نیاز دارد، به‌خصوص برای حروف کم‌تکرار.

آیا روی CPU به‌صورت بلادرنگ اجرا می‌شود؟

بله؛ با مدل کوچک (YOLO با اندازه‌ی n یا s)، ورودی کوچک‌تر، ناحیه‌ی مورد نظر و ONNX Runtime یا OpenVINO، پردازش چند فریم در ثانیه روی یک CPU مدرن واقع‌بینانه است. GPU یا شتاب‌دهنده‌ی لبه چندین جریان تصویر را هم‌زمان پردازش می‌کند.

چرا دقت در شب خیلی پایین‌تر است؟

نور کم باعث نویز و تاری می‌شود و چراغ‌های جلو بازتاب ایجاد می‌کنند. از دوربین و نور مادون‌قرمز استفاده کنید و تصاویر شب را به داده‌ی آموزشی اضافه کنید.