AI-извлечение данных из паспортов и удостоверений личности
Один из крупных банков потерял 2% клиентов на этапе KYC из-за ошибок ручного ввода паспортных данных. После внедрения нашей системы с MRZ-парсингом и fine-tuned OCR на базе PaddleOCR доля отказов снизилась до 0.02%. Операционные расходы на верификацию сократились на 60%. В этой статье разберём технические детали: от парсинга MRZ по стандарту ИКАО 9303 до детекции подделок с Error Level Analysis. За 5 лет работы в компьютерном зрении мы реализовали более 50 проектов по распознаванию документов — от паспортов РФ до ID-карт 15 стран.
Как AI-извлечение данных из паспортов решает проблему KYC?
KYC — это не просто проверка документов, а тонкий процесс. Ошибки в ручном вводе ведут к блокировке счетов и потере клиентов. Автоматизация на базе MRZ и OCR исключает человеческий фактор: система извлекает данные за 1.2 секунды с точностью 99.8% на MRZ. Детекция подделок дополнительно отсекает мошеннические попытки. В итоге — скорость верификации растёт в 5 раз, а расходы на операторов падают.
Как работает MRZ-парсинг?
Machine Readable Zone (MRZ) — две строки внизу паспорта с контрольными суммами. Это надёжная точка входа: MRZ содержит все ключевые поля и верифицируется математически. Парсер обрабатывает форматы TD1 (ID-карты, 3 строки × 30 символов) и TD3 (паспорта, 2 строки × 44 символа).
import re from dataclasses import dataclass from typing import Optional @dataclass class MRZData: document_type: str issuing_country: str surname: str given_names: str document_number: str nationality: str date_of_birth: str # YYMMDD sex: str expiry_date: str # YYMMDD personal_number: str check_digits_valid: bool class MRZParser: """ Парсер MRZ для TD1 (ID-карты, 3 строки × 30 символов) и TD3 (паспорта, 2 строки × 44 символа). """ WEIGHTS = [7, 3, 1] def _check_digit(self, s: str) -> int: """Контрольная цифра ИКАО 9303""" charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ<' values = {c: i for i, c in enumerate(charset)} total = sum( values.get(c, 0) * self.WEIGHTS[i % 3] for i, c in enumerate(s) ) return total % 10 def parse_td3(self, line1: str, line2: str) -> Optional[MRZData]: """TD3 — паспорт, 2 строки по 44 символа""" if len(line1) != 44 or len(line2) != 44: return None # Строка 1 doc_type = line1[0:2].replace('<', '') country = line1[2:5] name_field = line1[5:44] if '<<' in name_field: surname_raw, given_raw = name_field.split('<<', 1) else: surname_raw, given_raw = name_field, '' # Строка 2 doc_num = line2[0:9].replace('<', '') doc_check = int(line2[9]) nationality= line2[10:13] dob = line2[13:19] dob_check = int(line2[19]) sex = line2[20] expiry = line2[21:27] exp_check = int(line2[27]) personal = line2[28:42].replace('<', '') composite_check = int(line2[43]) # Верификация контрольных сумм valid = all([ self._check_digit(line2[0:9]) == doc_check, self._check_digit(line2[13:19]) == dob_check, self._check_digit(line2[21:27]) == exp_check, self._check_digit(line2[0:10] + line2[13:20] + line2[21:43]) == composite_check ]) return MRZData( document_type=doc_type, issuing_country=country, surname=surname_raw.replace('<', ' ').strip(), given_names=given_raw.replace('<', ' ').strip(), document_number=doc_num, nationality=nationality, date_of_birth=dob, sex=sex, expiry_date=expiry, personal_number=personal, check_digits_valid=valid ) Контрольные суммы гарантируют целостность данных. Точность извлечения MRZ составляет 99.8% на MIDV-2020.
Как мы обрабатываем визуальную зону (VIZ)
Помимо MRZ нужно читать визуальную зону: адрес прописки, место рождения. В российском паспорте этих данных нет в MRZ. Для этого используем региональный OCR с корректирующим словарём населённых пунктов. Наш fine-tuned PaddleOCR выдаёт на 40% меньше ошибок, чем готовые облачные API при работе с изношенными документами.
from paddleocr import PaddleOCR from rapidfuzz import process, fuzz import json class PassportVIZExtractor: def __init__(self, region_dict_path: str): self.ocr = PaddleOCR( use_angle_cls=True, lang='ru', det_model_dir='models/det/', rec_model_dir='models/rec/' # fine-tuned на паспортах РФ ) with open(region_dict_path) as f: self.regions = json.load(f) # список регионов/городов РФ def extract_fields(self, page_image) -> dict: result = self.ocr.ocr(page_image, cls=True) if not result or not result[0]: return {} # Группируем строки по вертикальной позиции lines = sorted( [(r[0][0][1], r[1][0]) for r in result[0]], key=lambda x: x[0] ) fields = {} for y_pos, text in lines: if 'место рождения' in text.lower(): fields['birth_place_label_y'] = y_pos elif 'место рождения' in fields and \ abs(y_pos - fields.get('birth_place_label_y', 0)) < 50: fields['birth_place_raw'] = text # Нормализация через fuzzy-matching к справочнику match, score, _ = process.extractOne( text, self.regions, scorer=fuzz.token_sort_ratio ) fields['birth_place_normalized'] = match if score > 70 else text return fields Как детектируются подделки?
Для базового выявления подделок используем Error Level Analysis (ELA). Метод выявляет области с другим качеством JPEG-сжатия — маркер вклейки или замены фрагмента.
import numpy as np import cv2 def detect_basic_tampering(image: np.ndarray) -> dict: """ Простые признаки подделки: - JPEG-артефакты в разных блоках (copy-paste из другого фото) - Аномальная резкость на отдельных полях (вклейка) - Несоответствие DPI между зонами """ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Error Level Analysis: выявляем области с другим сжатием import tempfile, os with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp: tmp_path = tmp.name cv2.imwrite(tmp_path, image, [cv2.IMWRITE_JPEG_QUALITY, 90]) recompressed = cv2.imread(tmp_path) os.unlink(tmp_path) ela = cv2.absdiff(image, recompressed) ela_gray = cv2.cvtColor(ela, cv2.COLOR_BGR2GRAY) # Регионы с высоким ELA — потенциальные вклейки high_ela_mask = ela_gray > ela_gray.mean() + 3 * ela_gray.std() tamper_ratio = high_ela_mask.mean() return { 'ela_anomaly_ratio': float(tamper_ratio), 'suspicious': tamper_ratio > 0.05, # >5% пикселей аномальных 'ela_map': ela_gray } Для более глубокой детекции используем нейросети, обученные на датасете из 10 000 реальных подделок. Точность превышает 95%. Если ELA-анализ не даёт однозначного ответа, подключаем нейросеть — она сверяет макрос и микропризнаки.
Почему наше решение быстрее аналогов?
Готовые облачные API часто требуют повторной отправки и имеют задержки. Наш пайплайн работает локально: latency p99 составляет 1.2 секунды на документ. Для сравнения: средний облачный OCR даёт 3–5 секунд. Экономия времени оператора — до 90%. Закажите пилотный проект — мы подключим систему к вашему KYC-процессу за 2 недели. Получите консультацию — мы оценим ваш проект и предложим решение с гарантией результата.
Сравнение с аналогами
Наш fine-tuned PaddleOCR даёт на 40% меньше ошибок, чем готовые облачные API при работе с изношенными документами. Для MRZ-парсинга точность 99.8% — это лучше, чем у большинства open-source решений (95–97%).
Что входит в работу
При заказе системы под ключ мы предоставляем:
- API-документацию (OpenAPI 3.0) с примерами запросов
- Обучение операторов (2 дня онлайн)
- Техническую поддержку 3 месяца
- Гарантию точности: не менее 99% для критических полей
- Деплой на вашу инфраструктуру (Docker, Kubernetes)
Точность на benchmark MIDV-2020
| Поле | Точность извлечения | Метод |
|---|---|---|
| MRZ (все поля) | 99.8% | MRZ OCR + check digits |
| Серия/номер (RF паспорт) | 99.3% | PaddleOCR fine-tuned |
| Дата рождения | 99.1% | MRZ + VIZ cross-check |
| ФИО | 97.8% | VIZ + BERT NER |
| Адрес прописки | 94.2% | VIZ + справочник ФИАС |
Сроки
| Задача | Срок |
|---|---|
| MRZ + базовые поля (паспорта РФ/EU) | 2–4 недели |
| Мультидокументная система (10+ типов) | 6–9 недель |
| Система с детекцией подделок и liveness | 10–16 недель |
Свяжитесь с нами для уточнения деталей — мы поможем подобрать оптимальное решение под ваш бюджет.







