AI-розпізнавання чеків: вилучення даних із точністю 94%

Бухгалтери витрачають до 30% робочого часу на ручне введення даних з касових чеків та квитанцій. При цьому 3–5% записів містять помилки: пропущена позиція, невірна сума, втрачений ІНН. Чеки — найскладніший вид документів для [OCR](https://en.wikipedia.org/wiki/Optical_character_recognition): термопа

Напрямки AI-розробки

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1302
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    998
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1267
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    714
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    1006

Бухгалтери витрачають до 30% робочого часу на ручне введення даних з касових чеків та квитанцій. При цьому 3–5% записів містять помилки: пропущена позиція, невірна сума, втрачений ІНН. Чеки — найскладніший вид документів для OCR: термопапір з часом вигорає, якість друку низька, формати у кожного рітейлера свої, структура чека нелінійна (позиції, знижки, підсумки можуть йти в будь-якому порядку). На датасеті SROIE LayoutLMv3 дає F1 0.974 — але це чисті, скановані документи. На мобільних фотографіях реальної якості — 0.87–0.91. Ми розробили pipeline, який підіймає F1 до 0.95+ на реальних даних за рахунок агресивної передобробки та гібридного парсингу (OCR + регулярки + LLM-верифікації). Наш досвід включає 40+ проектів з вилучення даних з чеків, накладних та рахунків.

Чому передобробка — основне джерело помилок?

Мобільна фотографія чека страждає від: перспективного спотворення, змазаності, тіні від пальців, пересвітів. Без виправлення PaddleOCR помиляється на 15–25% символів. Після нашого pipeline CER знижується в 2–3 рази: з 4–5% до 1.5% на хороших фото, з 18% до 6% на поганих.

import cv2 import numpy as np from PIL import Image def preprocess_receipt_photo( image: np.ndarray, target_width: int = 768 # ширина нормалізованого чека ) -> np.ndarray: """ Кроки: шумозаглушення → вирівнювання яскравості → deskew → binarization """ # 1. Шумозаглушення denoised = cv2.fastNlMeansDenoisingColored( image, h=10, hColor=10, templateWindowSize=7, searchWindowSize=21 ) # 2. CLAHE для вирівнювання освітленості lab = cv2.cvtColor(denoised, cv2.COLOR_BGR2LAB) clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)) lab[:, :, 0] = clahe.apply(lab[:, :, 0]) enhanced = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) # 3. Автоматичне визначення контуру чека та deskew gray = cv2.cvtColor(enhanced, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray, 50, 150) contours, _ = cv2.findContours( edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) if contours: # Знаходимо найбільший контур — ймовірно чек largest = max(contours, key=cv2.contourArea) hull = cv2.convexHull(largest) if cv2.contourArea(hull) > 0.1 * image.shape[0] * image.shape[1]: rect = cv2.minAreaRect(hull) angle = rect[2] if abs(angle) > 5: M = cv2.getRotationMatrix2D( (image.shape[1]//2, image.shape[0]//2), angle, 1 ) enhanced = cv2.warpAffine( enhanced, M, (image.shape[1], image.shape[0]) ) # 4. Адаптивна бінаризація для термодруку gray = cv2.cvtColor(enhanced, cv2.COLOR_BGR2GRAY) binary = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blockSize=11, C=2 ) # 5. Нормалізація ширини h, w = binary.shape scale = target_width / w resized = cv2.resize( binary, (target_width, int(h * scale)), interpolation=cv2.INTER_LANCZOS4 ) return resized 
Деталі налаштування середовища Рекомендуємо використовувати Docker з CUDA 12.1 та драйвером NVIDIA >=525. Модель PaddleOCR v4 запускаємо через ONNX Runtime для CPU, для GPU — через TensorRT.

Як парсити структуру чека?

Після OCR потрібно розібрати структуру: рядки товарів, знижки, підсумки, касир, ІНН.

import re from dataclasses import dataclass, field from typing import Optional @dataclass class ReceiptLineItem: name: str quantity: float unit_price: float total_price: float discount: float = 0.0 @dataclass class ParsedReceipt: store_name: Optional[str] inn: Optional[str] # ІНН продавця datetime_str: Optional[str] items: list[ReceiptLineItem] = field(default_factory=list) subtotal: Optional[float] = None tax_amount: Optional[float] = None total: Optional[float] = None payment_method: Optional[str] = None fiscal_sign: Optional[str] = None class ReceiptParser: # Патерни для російських чеків (ФФД 1.05/1.1) PATTERNS = { 'inn': r'ІНН\s*[:№]?\s*(\d{10,12})', 'total': r'(?:ПІДСУМОК|ПІДСУМОК ДО ОПЛАТИ|ПІДСУМОК)\s*[:=]?\s*([\d\s,\.]+)', 'tax': r'(?:ПДВ|В т\.ч\. ПДВ)\s+\d+%\s*[:=]?\s*([\d\s,\.]+)', 'fiscal_sign': r'ФП\s*[::]?\s*(\d+)', 'line_item': r'^(.+?)\s+([\d,\.]+)\s*[xх×]\s*([\d,\.]+)\s*=?\s*([\d,\.]+)', 'datetime': r'(\d{2}[\.\/]\d{2}[\.\/]\d{4})\s+(\d{2}:\d{2}(?::\d{2})?)', } def parse(self, ocr_text: str) -> ParsedReceipt: lines = ocr_text.split('\n') receipt = ParsedReceipt( store_name=lines[0].strip() if lines else None, inn=self._extract(ocr_text, 'inn'), datetime_str=self._extract_datetime(ocr_text), total=self._parse_amount(self._extract(ocr_text, 'total')), tax_amount=self._parse_amount(self._extract(ocr_text, 'tax')), fiscal_sign=self._extract(ocr_text, 'fiscal_sign') ) for line in lines: item = self._parse_line_item(line) if item: receipt.items.append(item) return receipt def _extract(self, text: str, key: str) -> Optional[str]: m = re.search(self.PATTERNS[key], text, re.IGNORECASE) return m.group(1).strip() if m else None def _extract_datetime(self, text: str) -> Optional[str]: m = re.search(self.PATTERNS['datetime'], text) if m: return f'{m.group(1)} {m.group(2)}' return None def _parse_amount(self, text: Optional[str]) -> Optional[float]: if not text: return None cleaned = re.sub(r'\s', '', text).replace(',', '.') try: return float(cleaned) except ValueError: return None def _parse_line_item(self, line: str) -> Optional[ReceiptLineItem]: m = re.match(self.PATTERNS['line_item'], line.strip()) if not m: return None try: return ReceiptLineItem( name=m.group(1).strip(), quantity=float(m.group(2).replace(',', '.')), unit_price=float(m.group(3).replace(',', '.')), total_price=float(m.group(4).replace(',', '.')) ) except (ValueError, AttributeError): return None 

Класичний підхід — регулярки — працює для фіксованих форматів, але на 100+ мережах магазинів кількість патернів вибухає. Ми комбінуємо regex-парсер з LLM-валідацією (GPT-4o або кастомна модель). Якщо регулярка не знаходить поле, запускається few-shot промпт для вилучення. Це робить парсер універсальним, але вимагає налаштування на конкретний домен.

Порівняння підходів до вилучення полів

Підхід F1 (рівні поля) Швидкість (мс на чек) Вартість (відн.)
Regex-only 0.82 5 Безкоштовно
Regex + LLM validation 0.94 300 Середня
End-to-end LLM (GPT-4o) 0.97 3000 Висока

Гібридний підхід дає оптимальний баланс: точність, близьку до LLM, при вартості в 10 разів нижчій.

Порівняння OCR-двигунів

Двигун CER (хороше фото) CER (погане фото) Швидкість Відносна вартість
Tesseract 5 (без препроц.) 4.2% 18.7% 200ms Безкоштовно
Tesseract 5 + preprocessing 1.8% 8.3% 350ms Безкоштовно
PaddleOCR v4 0.9% 4.1% 280ms Безкоштовно
Azure Read API 0.6% 2.8% 1.2s Середня
GPT-4V 0.4% 1.9% 3–5s Висока

PaddleOCR v4 в 4.5 рази точніший за Tesseract на поганих фото і при цьому повністю безкоштовний.

Як ми це робимо: стек і процес

Стек: PaddleOCR v4 для детекції тексту, кастомна модель на LayoutLMv3 для вилучення полів, PyTorch + ONNX Runtime для інференсу на CPU. Для складних випадків — GPT-4o як fallback. Векторизація ембедингів позицій через Hugging Face.

Процес роботи:

  1. Аналіз вибірки чеків замовника (мінімум 200 зразків).
  2. Розмітка полів: дата, сума, ІНН, позиції.
  3. Тренування/донавчання моделі на розмічених даних.
  4. Інтеграція через REST API або бібліотеку на Python.
  5. Пілот на реальних даних — оцінка точності (F1) та швидкості (p99 latency).
  6. Передача документації, навчання операторів, підтримка 1 місяць.

Терміни: від 2 тижнів для однієї мережі, від 6 тижнів для універсального рішення.

Що входить в роботу?

  • Переднавчена модель на ваших даних
  • REST API з документацією (swagger)
  • Навчання співробітників (2 години)
  • Підтримка протягом 1 місяця
  • Гарантія точності >95% на цільових форматах

Чому варто замовити у нас?

5+ років досвіду в комп'ютерному зорі, 40+ впроваджень OCR у фінансах, логістиці, рітейлі. Сертифіковані партнери OpenAI та Microsoft. Ми гарантуємо, що F1 на ваших чеках буде не нижче 0.93, інакше доопрацьовуємо безкоштовно. Зв'яжіться для розрахунку пілотного проекту. Отримайте консультацію з вашого завдання — ми запропонуємо оптимальний стек і терміни.