Реализация AI-извлечения данных из паспортов и удостоверений

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Реализация AI-извлечения данных из паспортов и удостоверений
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-извлечение данных из паспортов и удостоверений личности

Распознавание документов удостоверяющих личность — область с особыми требованиями: точность поля >99.5% для критичных полей (серия/номер, дата рождения), обработка износа документа, работа с документами разных стран, детекция подделок.

MRZ — зона машиносчитываемости

Machine Readable Zone (MRZ) — две строки внизу паспорта с контрольными суммами по ИКАО 9303. Это надёжная точка входа: MRZ содержит все ключевые поля и верифицируется математически.

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
        )

OCR зон VIZ (Visual Inspection Zone)

Помимо MRZ, нужно читать визуальную зону: адрес прописки, место рождения (в российском паспорте нет в MRZ). Для этого — региональный OCR с корректирующим словарём населённых пунктов:

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

Детекция подделок (базовый уровень)

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
    }

Точность на 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 недель