Реализация AI-распознавания номерных знаков (ANPR/LPR) в мобильном приложении
ANPR (Automatic Number Plate Recognition) на мобильном устройстве — зрелая задача с понятными инструментами. Основная инженерная проблема не в самом OCR, а в pipeline от захвата кадра до надёжного считанного номера: детекция пластины в реальных условиях (ночь, грязь, отражения, нестандартные шрифты СНГ).
Выбор подхода
Два пути в зависимости от требований:
On-device — для задач с высокой частотой сканирований (паркинг, пропускной пункт) или без гарантированного интернета. Модели: OpenALPR (open source, поддерживает 60+ стран, включая RU/BY/UA), Plate Recognizer Edge SDK, Google ML Kit Text Recognition v2 (для простых стандартных номеров).
Cloud API — Plate Recognizer API, OpenALPR Cloud, AWS Rekognition. Точнее на сложных кейсах (нестандартный угол, нечёткий шрифт), лучше справляется с различными регионами СНГ.
// iOS: on-device ANPR через Vision + кастомная YOLOv8 для детекции пластины
class LicensePlateRecognizer {
// Шаг 1: детекция пластины через CoreML (YOLOv8n — быстрый вариант)
private let plateDetector: VNCoreMLModel
// Шаг 2: OCR через Vision Text Recognition
private func recognizeText(in croppedImage: CGImage,
completion: @escaping (String?) -> Void) {
let request = VNRecognizeTextRequest { request, _ in
let text = (request.results as? [VNRecognizedTextObservation])?
.compactMap { $0.topCandidates(1).first?.string }
.joined()
completion(text)
}
request.recognitionLevel = .accurate
request.usesLanguageCorrection = false // отключить — номера не слова
request.minimumTextHeight = 0.1
try? VNImageRequestHandler(cgImage: croppedImage).perform([request])
}
func recognize(sampleBuffer: CMSampleBuffer) async -> PlateResult? {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil }
// Детектируем пластину
let plateBBox = await detectPlate(in: pixelBuffer)
guard let bbox = plateBBox else { return nil }
// Crop и OCR
let croppedCGImage = cropCGImage(pixelBuffer: pixelBuffer, rect: bbox)
let rawText = await recognizeTextAsync(croppedCGImage)
// Нормализация и валидация
return normalizePlate(rawText)
}
}
Нормализация номеров СНГ
OCR даёт сырой текст. Для СНГ-номеров важна постобработка:
struct PlateNormalizer {
// Замена визуально похожих символов (типичные ошибки OCR)
static let ocrCorrections: [Character: Character] = [
"0": "O", // иногда наоборот
"1": "I",
"8": "B",
]
// Паттерны для разных стран
static let patterns: [(country: String, regex: String)] = [
("RU", #"^[АВЕКМНОРСТУХ]{1}\d{3}[АВЕКМНОРСТУХ]{2}\d{2,3}$"#),
("BY", #"^\d{4}[ABCEHIKMOPTX]{2}-\d{1}$"#),
("UA", #"^[АВСЕКМНРОТХBCEKMNOPTX]{2}\d{4}[АВСЕКМНРОТХBCEKMNOPTX]{2}$"#),
("KZ", #"^\d{3}[A-Z]{3}\d{2}$"#)
]
func normalize(_ rawText: String) -> PlateResult? {
let cleaned = rawText.uppercased()
.replacingOccurrences(of: " ", with: "")
.replacingOccurrences(of: "-", with: "")
for (country, pattern) in Self.patterns {
if cleaned.range(of: pattern, options: .regularExpression) != nil {
return PlateResult(text: cleaned, country: country, confidence: .high)
}
}
// Если не совпало с паттернами — low confidence, вернуть как есть
return PlateResult(text: cleaned, country: nil, confidence: .low)
}
}
Режим видеопотока для непрерывного сканирования
Для паркинга или КПП — непрерывное сканирование кадров без нажатия кнопки:
// Android: CameraX + непрерывный анализ через ImageAnalysis
class ContinuousPlateAnalyzer(
private val onPlateDetected: (PlateResult) -> Unit
) : ImageAnalysis.Analyzer {
private val frameThrottler = FrameThrottler(maxFps = 5) // 5 кадров/сек достаточно
private val consecutiveMatchThreshold = 3 // 3 подряд одинаковых → триггер
private val recentResults = ArrayDeque<String>(maxOf = 5)
override fun analyze(image: ImageProxy) {
if (!frameThrottler.shouldProcess()) { image.close(); return }
val bitmap = image.toBitmap()
val result = plateRecognizer.recognize(bitmap)
image.close()
result?.let { plate ->
recentResults.add(plate.text)
if (recentResults.size >= consecutiveMatchThreshold &&
recentResults.takeLast(consecutiveMatchThreshold).all { it == plate.text }) {
onPlateDetected(plate)
recentResults.clear()
}
}
}
}
Порог 3 последовательных одинаковых результата устраняет ложные срабатывания на случайных объектах, похожих на пластину.
Ориентиры по срокам
On-device ANPR с Vision/ML Kit, нормализацией для одной страны и базовым UI — 3–5 дней. Мультистрановая система с поддержкой RU/BY/UA/KZ, непрерывным видеоанализом, историей сканирований, интеграцией с внешней базой (база угнанных авто, база клиентов) и iOS + Android — 1–2 недели.







