Text Recognition from Document Photos

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
Text Recognition from Document Photos
Medium
~3-5 business days
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822

Реализация распознавания текста с фотографий документов

Фотография документа — не скан. Типичные проблемы: перспективные искажения, тени от пальцев и переплёта, неравномерное освещение, смазанность при движении, отражения на глянцевых поверхностях. Качественная система распознавания должна уметь исправлять все эти искажения до передачи изображения в OCR-движок.

Полный пайплайн обработки фото документа

import cv2
import numpy as np
from paddleocr import PaddleOCR

class DocumentPhotoOCR:
    def __init__(self):
        self.ocr = PaddleOCR(use_angle_cls=True, lang='ru', use_gpu=True)

    def process(self, image_path: str) -> dict:
        image = cv2.imread(image_path)

        # 1. Детекция документа в кадре
        doc_corners = self.detect_document(image)

        # 2. Перспективная коррекция
        if doc_corners is not None:
            image = self.four_point_transform(image, doc_corners)

        # 3. Улучшение качества изображения
        image = self.enhance_document(image)

        # 4. OCR
        result = self.ocr.ocr(image, cls=True)

        return {
            'text': self._extract_text(result),
            'words': self._extract_words_with_positions(result),
            'corrected': doc_corners is not None
        }

Детекция и выравнивание документа

def detect_document(self, image: np.ndarray) -> np.ndarray | None:
    """Нахождение углов документа для перспективной коррекции"""
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blur, 75, 200)

    # Расширяем для соединения разрывов
    dilated = cv2.dilate(edges, np.ones((3, 3)), iterations=1)

    contours, _ = cv2.findContours(dilated, cv2.RETR_LIST,
                                    cv2.CHAIN_APPROX_SIMPLE)
    contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]

    for contour in contours:
        peri = cv2.arcLength(contour, True)
        approx = cv2.approxPolyDP(contour, 0.02 * peri, True)

        if len(approx) == 4:
            area_ratio = cv2.contourArea(approx) / (image.shape[0] * image.shape[1])
            if area_ratio > 0.2:  # документ занимает > 20% кадра
                return approx.reshape(4, 2)

    return None

def four_point_transform(self, image: np.ndarray,
                          pts: np.ndarray) -> np.ndarray:
    """Перспективная трансформация по 4 точкам"""
    rect = self._order_points(pts)
    tl, tr, br, bl = rect

    # Вычисляем размеры результирующего изображения
    width = int(max(np.linalg.norm(br - bl), np.linalg.norm(tr - tl)))
    height = int(max(np.linalg.norm(tr - br), np.linalg.norm(tl - bl)))

    dst = np.array([
        [0, 0], [width - 1, 0],
        [width - 1, height - 1], [0, height - 1]
    ], dtype='float32')

    M = cv2.getPerspectiveTransform(rect.astype('float32'), dst)
    return cv2.warpPerspective(image, M, (width, height))

Коррекция освещения и теней

def enhance_document(self, image: np.ndarray) -> np.ndarray:
    """Адаптивное улучшение для неравномерного освещения"""
    # Разбиваем на каналы LAB
    lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)

    # CLAHE для яркостного канала
    clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(16, 16))
    l_enhanced = clahe.apply(l)

    enhanced = cv2.merge([l_enhanced, a, b])
    enhanced = cv2.cvtColor(enhanced, cv2.COLOR_LAB2BGR)

    # Дополнительное sharpen для размытого текста
    kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
    sharpened = cv2.filter2D(enhanced, -1, kernel)

    return sharpened

Работа с отражениями (глянцевые документы)

Ламинированные удостоверения и карты создают блики. Решение: поляризационный фильтр на камеру (hardware) или алгоритмически — inpainting зон с пересвеченными пикселями (значения > 250 в RGB) с последующей интерполяцией из соседних областей.

Качество распознавания на реальных фото

Условия съёмки CER (PaddleOCR)
Хорошее освещение, фронтальный снимок 2–5%
Небольшой наклон (до 20°) 3–7%
Тень на части документа 8–15%
Размытие движения 15–30%
После перспективной коррекции -3–5% к CER
Задача Срок
OCR с базовой предобработкой 1–2 недели
Полный пайплайн с детекцией документа 3–4 недели
Мобильное приложение с live preview 5–7 недель