Реализация верификации документов (паспорт, права, ID-карта)
Верификация документов — задача проверки подлинности физического документа по его цифровому изображению. Используется в KYC-процессах (банки, финтех, каршеринг, мобильные операторы). Задача сложнее простого OCR: нужно не только прочитать данные, но и убедиться в их достоверности.
Многоуровневая проверка документа
class DocumentVerificationSystem:
def __init__(self):
self.ocr = PaddleOCR(use_angle_cls=True, lang='ru')
self.mrz_reader = MRZReader()
self.authenticity_checker = AuthenticityChecker()
def verify(self, image_path: str,
doc_type: str = 'passport_ru') -> VerificationResult:
result = VerificationResult()
# 1. Качество изображения
quality = self.assess_image_quality(image_path)
if quality.score < 0.6:
result.rejected = True
result.reason = 'low_image_quality'
return result
# 2. OCR всех видимых полей
result.ocr_fields = self.extract_fields(image_path, doc_type)
# 3. MRZ (Machine Readable Zone) для паспортов
if doc_type in ['passport_ru', 'foreign_passport']:
mrz_data = self.mrz_reader.read(image_path)
result.mrz_data = mrz_data
# Перекрёстная проверка MRZ vs OCR полей
result.mrz_consistency = self.cross_check_mrz(
result.ocr_fields, mrz_data
)
# 4. Проверка защитных элементов
result.security_features = self.authenticity_checker.check(image_path)
# 5. Итоговый вердикт
result.verified = self._make_verdict(result)
return result
Чтение MRZ (Machine Readable Zone)
MRZ — стандартизированная зона в нижней части паспорта с закодированными данными. Контрольные суммы позволяют верифицировать корректность данных:
from passporteye import read_mrz
class MRZReader:
def read(self, image_path: str) -> dict | None:
mrz = read_mrz(image_path)
if mrz is None:
return None
data = mrz.to_dict()
return {
'surname': data.get('surname', ''),
'names': data.get('names', ''),
'country': data.get('country', ''),
'number': data.get('number', ''),
'nationality': data.get('nationality', ''),
'date_of_birth': data.get('date_of_birth', ''),
'sex': data.get('sex', ''),
'expiry_date': data.get('expiry_date', ''),
'personal_number': data.get('personal_number', ''),
'valid_composite': data.get('valid_composite', False),
'valid_number': data.get('valid_number', False),
'valid_dob': data.get('valid_dob', False),
'valid_expiry_date': data.get('valid_expiry_date', False),
}
Проверка срока действия и формата
def validate_passport_fields(fields: dict) -> dict:
"""Проверка форматов полей российского паспорта"""
errors = []
# Серия и номер: 4 цифры серия, 6 номер
if fields.get('series'):
if not re.match(r'^\d{4}$', fields['series']):
errors.append({'field': 'series', 'error': 'invalid_format'})
if fields.get('number'):
if not re.match(r'^\d{6}$', fields['number']):
errors.append({'field': 'number', 'error': 'invalid_format'})
# Дата выдачи: не позднее сегодня, не ранее 2000 года
if fields.get('issue_date'):
issue_date = parse_date(fields['issue_date'])
if issue_date:
from datetime import date
if issue_date > date.today():
errors.append({'field': 'issue_date', 'error': 'future_date'})
if issue_date.year < 2000:
errors.append({'field': 'issue_date', 'error': 'too_old'})
# Дата рождения
if fields.get('birth_date'):
birth_date = parse_date(fields['birth_date'])
if birth_date:
age = (date.today() - birth_date).days / 365
if age < 14 or age > 120:
errors.append({'field': 'birth_date', 'error': 'invalid_age'})
return {'valid': len(errors) == 0, 'errors': errors}
Детектируемые типы документов
| Документ | Извлекаемые поля | MRZ |
|---|---|---|
| Паспорт РФ | Серия, номер, ФИО, дата рождения, пол, место рождения, дата выдачи, кем выдан | Нет (внутренний) |
| Загранпаспорт РФ | ФИО, номер, дата рождения, срок действия | Да |
| СНИЛС | Номер, ФИО, дата рождения | Нет |
| Водительское удостоверение | Серия/номер, категории, ФИО, срок действия | Нет |
| ИНН | Номер | Нет |
| Задача | Срок |
|---|---|
| Верификация 1 типа документа | 3–4 недели |
| Полный KYC (паспорт + СНИЛС + фото) | 5–8 недель |
| Anti-fraud верификация | 7–12 недель |







