Реализация распознавания номерных знаков автомобилей (ANPR/LPR)
ANPR (Automatic Number Plate Recognition) / LPR (License Plate Recognition) — система автоматического считывания государственных регистрационных знаков. Applications: контроль въезда на парковку, контроль скорости, система «Безопасный город», таможенный контроль, розыск угнанных автомобилей.
Architecture двухэтапного пайплайна
Видео/Фото → Детекция автомобиля → Детекция номерного знака → OCR → База данных
Двухэтапный подход (авто → номер) точнее однозтапного, так как позволяет обрабатывать разные форматы номеров из разных стран.
from ultralytics import YOLO
from paddleocr import PaddleOCR
import cv2
import numpy as np
import re
class ANPRSystem:
def __init__(self,
vehicle_model: str = 'yolov8l.pt',
plate_model: str = 'plate_detector.pt'):
self.vehicle_detector = YOLO(vehicle_model)
self.plate_detector = YOLO(plate_model) # дообученный на номерах
self.ocr = PaddleOCR(
use_angle_cls=True,
lang='en',
rec_algorithm='SVTR_LCNet'
)
def process(self, frame: np.ndarray) -> list[dict]:
# Детекция транспортных средств
vehicles = self.vehicle_detector(frame, classes=[2, 3, 5, 7], # car/moto/bus/truck
conf=0.5)
results = []
for vehicle_box in vehicles[0].boxes.xyxy:
x1, y1, x2, y2 = map(int, vehicle_box)
vehicle_crop = frame[y1:y2, x1:x2]
# Детекция номерного знака в кропе автомобиля
plates = self.plate_detector(vehicle_crop, conf=0.5)
for plate_box in plates[0].boxes.xyxy:
px1, py1, px2, py2 = map(int, plate_box)
plate_crop = vehicle_crop[py1:py2, px1:px2]
# OCR номера
plate_text = self._recognize_plate(plate_crop)
if plate_text:
results.append({
'plate': plate_text,
'vehicle_bbox': [x1, y1, x2, y2],
'plate_bbox': [x1+px1, y1+py1, x1+px2, y1+py2],
'confidence': float(plates[0].boxes.conf[0])
})
return results
def _recognize_plate(self, plate_img: np.ndarray) -> str | None:
# Предобработка
plate_img = self._preprocess_plate(plate_img)
result = self.ocr.ocr(plate_img, cls=False)
if not result or not result[0]:
return None
text = ''.join([line[1][0] for line in result[0]])
text = re.sub(r'[^A-Z0-9А-Я]', '', text.upper())
# Валидация формата российского номера
if re.match(r'^[АВЕКМНОРСТУХ]\d{3}[АВЕКМНОРСТУХ]{2}\d{2,3}$', text):
return text
return text if len(text) >= 6 else None
Предобработка изображения номера
def _preprocess_plate(self, image: np.ndarray) -> np.ndarray:
# Масштабирование до стандартной высоты
target_h = 64
scale = target_h / image.shape[0]
new_w = int(image.shape[1] * scale)
image = cv2.resize(image, (new_w, target_h), interpolation=cv2.INTER_CUBIC)
# Конвертация в оттенки серого
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Нормализация яркости
normalized = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX)
return normalized
Работа с разными форматами номеров
Российские номера: X000XX00[0] (стандарт), X000XX000 (транзитные). Дополнительные форматы: таможенные, дипломатические, военные.
Для международных систем — мультиязычная OCR + несколько наборов регулярных выражений валидации.
Performance в продакшене
| Метрика | Значение |
|---|---|
| Accuracy (хорошее освещение, < 80 км/ч) | 96–99% |
| Accuracy (ночь, ИК-подсветка) | 92–96% |
| Accuracy (высокая скорость, 120+ км/ч) | 80–88% |
| Latency (T4 GPU, 1080p кадр) | 35–50 ms |
| False positive rate | < 2% |
Хранение и поиск по базе
Для систем контроля въезда: Redis для горячего списка (whitelist/blacklist), PostgreSQL с pg_trgm для нечёткого поиска (учитывает ошибки OCR 0/O, I/1, B/8).
| Масштаб системы | Срок |
|---|---|
| 1–4 камеры, парковочный контроль | 3–5 недель |
| 8–16 камер, городская система | 6–10 недель |
| 50+ камер, распределённая инфраструктура | 12–18 недель |







