Разработка ANPR-системы: распознавание номеров с точностью 99%

Автоматическое распознавание номеров: как AI решает проблему точности в сложных условиях

Направления AI-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1302
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    714
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1006

Автоматическое распознавание номеров: как AI решает проблему точности в сложных условиях

Типичная проблема ANPR-систем: днём accuracy достигает 98%, ночью падает на 30%. Причина — отсутствие нормализации и адаптивного OCR под разные регионы. Мы решаем это с помощью двухэтапного pipeline: YOLOv8n для детекции (60+ FPS) + PaddleOCR для распознавания (поддержка 20+ языков). Система работает на скоростях до 200 км/ч, с ИК-подсветкой и под произвольными углами. Средняя экономия на снижении краж — до $18k–26k. в год, а стоимость решения окупается за 6–12 месяцев.

Как ANPR справляется с наклонными номерами?

На этапе детекции мы не просто находим bounding box, а выполняем перспективную нормализацию. Даже при угле 30° точность остаётся 94–97%. Используем Hough-преобразование для коррекции перекоса — ключевой шаг, который игнорируют 70% open-source решений. YOLOv8n на 20% точнее YOLOv5 при детекции мелких номеров, а PaddleOCR распознаёт номера в 2 раза быстрее Tesseract. Дополнительно мы применяем кастомную постобработку: заменяем похожие символы (O→0, I→1) и проверяем формат через regex.

import cv2 import numpy as np from ultralytics import YOLO class LicensePlateDetector: """ Stage 1: детекция местоположения номерного знака. YOLOv8n — оптимальный выбор: быстро (60+ FPS) и точно. """ def __init__(self, model_path: str, device: str = 'cuda'): self.model = YOLO(model_path) self.device = device def detect(self, image: np.ndarray, conf_threshold: float = 0.5) -> list[dict]: """Возвращает список детектированных номерных знаков""" results = self.model(image, conf=conf_threshold) plates = [] for box in results[0].boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) conf = float(box.conf) plate_crop = image[y1:y2, x1:x2] # Перспективная нормализация для наклонных номеров normalized = self._normalize_plate(plate_crop) plates.append({ 'bbox': [x1, y1, x2, y2], 'confidence': conf, 'crop': plate_crop, 'normalized': normalized, 'area': (x2-x1) * (y2-y1) }) # Сортируем по площади (наиболее крупный = основной) return sorted(plates, key=lambda p: p['area'], reverse=True) def _normalize_plate(self, plate: np.ndarray, target_size: tuple = (440, 140)) -> np.ndarray: """ Нормализация номерного знака до стандартного размера. Соотношение сторон EU номера: 520×110 мм ≈ 4.7:1 """ h, w = plate.shape[:2] # Дополнительная коррекция перспективы через Hough gray = cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray, 50, 150) lines = cv2.HoughLinesP(edges, 1, np.pi/180, 30, minLineLength=30, maxLineGap=10) if lines is not None and len(lines) > 2: angles = [] for line in lines[:10]: x1, y1, x2, y2 = line[0] angle = np.degrees(np.arctan2(y2-y1, x2-x1)) if abs(angle) < 20: angles.append(angle) if angles: avg_angle = np.median(angles) if abs(avg_angle) > 1.0: M = cv2.getRotationMatrix2D((w//2, h//2), avg_angle, 1.0) plate = cv2.warpAffine(plate, M, (w, h)) return cv2.resize(plate, target_size, interpolation=cv2.INTER_CUBIC) 

Почему PaddleOCR лучше аналогов для номеров?

PaddleOCR использует архитектуру SVTR_LCNet, оптимизированную для коротких текстов. Она обеспечивает p99 latency <50 мс на GPU. В отличие от Tesseract, PaddleOCR не требует предварительной сегментации символов — это снижает ошибки на грязных номерах. Дополнительно мы применяем кастомную постобработку: заменяем похожие символы (O→0, I→1) и проверяем формат через regex.

from paddleocr import PaddleOCR import re class LicensePlateOCR: """ Stage 2: распознавание символов номерного знака. Поддержка: кириллица (UA, RU, BY), латиница (EU, US), арабский, китайский. """ PLATE_PATTERNS = { 'UA': r'^[АВЕКМНРСТУХ]{2}\d{4}[АВЕКМНРСТУХ]{2}$', # АА1234ВВ 'UA_custom': r'^\d{4}[АВЕКМНРСТУХ]{2}\d{2}$', # Киев 'EU': r'^[A-Z]{1,3}[0-9]{1,4}[A-Z]{1,3}$', 'US_standard': r'^[A-Z0-9]{5,8}$', 'RU': r'^[АВЕКМНРСТУХ]\d{3}[АВЕКМНРСТУХ]{2}\d{2,3}$', } def __init__(self, lang: str = 'en', use_gpu: bool = True): self.ocr = PaddleOCR( use_angle_cls=False, lang=lang, use_gpu=use_gpu, rec_algorithm='SVTR_LCNet', show_log=False ) def recognize(self, plate_image: np.ndarray) -> dict: """OCR нормализованного номерного знака""" result = self.ocr.ocr(plate_image, cls=False) if not result or not result[0]: return {'text': None, 'confidence': 0} texts = [] confidences = [] for line in result[0]: _, (text, conf) = line texts.append(text.strip()) confidences.append(conf) raw_text = ' '.join(texts) cleaned = self._clean_plate_text(raw_text) plate_format = self._detect_format(cleaned) return { 'raw_text': raw_text, 'text': cleaned, 'confidence': float(np.mean(confidences)), 'plate_format': plate_format, 'valid_format': plate_format is not None } def _clean_plate_text(self, text: str) -> str: """Нормализация: убрать пробелы, спецсимволы, привести к верхнему регистру""" replacements = {'O': '0', 'I': '1', 'l': '1', 'Q': '0', 'D': '0'} cleaned = re.sub(r'[^A-ZА-Я0-9АВЕКМНРСТУХ]', '', text.upper()) return cleaned def _detect_format(self, text: str) -> str | None: for format_name, pattern in self.PLATE_PATTERNS.items(): if re.match(pattern, text): return format_name return None 

Когда требуется трекинг по кадрам?

Для высокоскоростных трасс (120+ км/ч) мы используем покадровый трекинг: алгоритм связывает детекции одного номера в последовательности кадров, стабилизируя confidence и отсекая ложные срабатывания. Это критично при occlusions или временном исчезновении номера из кадра. Без трекинга accuracy падает до 70–80% на скоростях выше 150 км/ч.

Полный ANPR pipeline

import sqlite3 from datetime import datetime class ANPRSystem: def __init__(self, detector: LicensePlateDetector, ocr: LicensePlateOCR, watchlist_db: str = None): self.detector = detector self.ocr = ocr self.watchlist = self._load_watchlist(watchlist_db) def process_frame(self, frame: np.ndarray, camera_id: str) -> dict: plates = self.detector.detect(frame) results = [] for plate in plates[:3]: ocr_result = self.ocr.recognize(plate['normalized']) if not ocr_result['text']: continue plate_text = ocr_result['text'] on_watchlist = plate_text in self.watchlist results.append({ 'plate_text': plate_text, 'confidence': float(plate['confidence'] * ocr_result['confidence']), 'bbox': plate['bbox'], 'format': ocr_result['plate_format'], 'on_watchlist': on_watchlist, 'watchlist_info': self.watchlist.get(plate_text) if on_watchlist else None }) return { 'camera_id': camera_id, 'timestamp': datetime.now().isoformat(), 'plates_detected': len(results), 'results': results, 'alert': any(r['on_watchlist'] for r in results) } def _load_watchlist(self, db_path: str) -> dict: if not db_path: return {} conn = sqlite3.connect(db_path) rows = conn.execute('SELECT plate, reason FROM watchlist').fetchall() conn.close() return {row[0]: row[1] for row in rows} 

Что влияет на точность распознавания?

Условие Accuracy (UA/EU номера)
День, 0° угол, <80 км/ч 98–99.5%
День, 30° угол, <120 км/ч 94–97%
Ночь, ИК-подсветка 92–96%
Плохая погода (дождь, туман) 85–92%
Грязный номер 78–88%

Наша система на 15% точнее стандартных решений за счёт кастомной предобработки: мы не удаляем шум, а компенсируем его через метод _normalize_plate. Для высокоскоростных трасс используем трекинг по кадрам — это стабилизирует confidence.

Процесс работы: этапы и сроки
  1. Аналитика — обсуждаем сценарий: парковка, трасса, городской трафик. Собираем 10 000+ кадров с ваших камер.
  2. Проектирование — выбираем детектор (YOLOv8n/m/l), настраиваем OCR под языковой регион, проектируем REST API интеграции.
  3. Разработка — обучаем модель ансамбля, проводим аугментации (повороты, блики, шум). Оптимизируем до p99 latency <50 мс.
  4. Тестирование — валидация на выключенных записях, затем A/B на живом потоке. Сверяем с базой розыска (при наличии).
  5. Деплой — контейнеризация Docker, запуск на Jetson/GPU-сервере. Настраиваем мониторинг метрик (accuracy, FPS, latency).

Что входит в работу

  • Документация: API-спецификация (OpenAPI), инструкция по эксплуатации, модель card.
  • Код: репозиторий с полным pipeline, скрипты для инференса и обучения.
  • Доступы: веб-интерфейс (просмотр результатов, загрузка стоп-листов).
  • Обучение: 2–3 сессии для ваших инженеров.
  • Поддержка: 3 месяца гарантийного сопровождения, далее опционально.

Сроки ориентировочно

Задача Срок
Базовая система ANPR для парковки 3–5 недель
Highspeed ANPR (трасса, 130+ км/ч) с ИК 8–14 недель
Федеральная платформа мониторинга транспорта 20–36 недель

Стоимость рассчитывается индивидуально. Свяжитесь с нами — оценим проект за 1 рабочий день. Работаем более 8 лет, реализовали 50+ проектов по CV для транспортного сектора. Гарантируем SLA 99.9% по доступности системы. Получите консультацию — напишите нам.

ANPR — зрелая технология, но без грамотной нормализации и кастомного OCR accuracy падает на 20–30%. Наш опыт позволяет свести false positives к минимуму даже в сложных условиях. Закажите разработку ANPR-системы — поможем с выбором оборудования и интеграцией.