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 тижнів |
Зв'яжіться з нами для уточнення деталей – ми допоможемо підібрати оптимальне рішення під ваш бюджет.







