В начале 2013 года концепция была разумной: сотрудники подают авансовые отчёты, фотографируя чеки телефоном. Приложение читает чек, извлекает сумму, дату и продавца, автозаполняет форму расходов.
Реализация потребовала более глубокого понимания Optical Character Recognition, чем мы ожидали.
Tesseract OCR: open-source стандарт
Tesseract разрабатывался в Hewlett-Packard в 1980-х, выпущен в open-source в 2005, принят Google в 2006. К 2013 году Tesseract 3.02 был наиболее точным доступным OCR-движком с открытым исходным кодом.
Мы компилировали его под iOS:
#import "Tesseract.h"
@implementation ReceiptOCRProcessor
- (NSString *)extractTextFromImage:(UIImage *)image {
Tesseract *tesseract = [[Tesseract alloc] initWithLanguage:@"eng"];
// Режим сегментации страницы 6 = один блок текста (хорошо для чеков)
[tesseract setVariableValue:@"6" forKey:@"tessedit_pageseg_mode"];
// Белый список символов улучшает точность
[tesseract setVariableValue:@"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,/$:-()"
forKey:@"tessedit_char_whitelist"];
[tesseract setImage:image];
[tesseract recognize];
return [tesseract recognizedText];
}
@end
Сырой результат на реальном фото чека (камера iPhone 5):
МАГАЗИН №1823
ГЛ. УЛ. 15
МОЛОКО 2.5% 1Л 85.00
ХЛЕБ ПШЕНИЧНЫЙ 45.00
ЯЙЦА С1 10ШТ 95.00
ИТОГО: 225.О0 ← '0' вместо '0'
НАЛИЧНЫЕ: 300.00
СДАЧА: 75.00
«225.О0» вместо «225.00» - буква О вместо цифры ноль. 68% точность символов на 20 тестовых чеках. Примерно 1 из 3 символов был неправильным или пропущенным. Нельзя использовать как сырой вывод.
Пайплайн предобработки
OCR сильно зависит от качества изображения. Tesseract разрабатывался для отсканированных документов - горизонтальных, высокого разрешения, равномерного освещения. Фотографии чеков с камеры телефона не были никакими из этих: перекошенные, низкоконтрастные, переменное освещение.
Мы построили пайплайн предобработки в OpenCV:
- (UIImage *)preprocessForOCR:(UIImage *)inputImage {
cv::Mat gray;
cv::cvtColor(mat, gray, cv::COLOR_RGB2GRAY);
// Увеличить разрешение если слишком маленькое
if (gray.cols < 1000) {
double scale = 1000.0 / gray.cols;
cv::resize(gray, gray, cv::Size(), scale, scale, cv::INTER_CUBIC);
}
// Исправление перекоса
gray = [self deskewImage:gray];
// Адаптивная пороговая обработка - обрабатывает неравномерное освещение
cv::Mat thresh;
cv::adaptiveThreshold(gray, thresh, 255,
cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY,
11, 2);
// Удаление шума
cv::Mat denoised;
cv::medianBlur(thresh, denoised, 3);
return MatToUIImage(denoised);
}
После предобработки точность символов выросла с 68% до 81%.
Извлечение структурированных данных
def extract_receipt_data(ocr_text):
result = {'vendor': None, 'date': None, 'total': None}
lines = [l.strip() for l in ocr_text.split('\n') if l.strip()]
if lines:
result['vendor'] = lines[0]
# Паттерны для суммы
total_patterns = [
r'(?:итого|итог|сумма)[:\s]+(\d+[\.,]\d{2})',
r'(?:к оплате|к получению)[:\s]+(\d+[\.,]\d{2})',
]
full_text = ' '.join(lines).lower()
for pattern in total_patterns:
match = re.search(pattern, full_text, re.IGNORECASE)
if match:
result['total'] = float(match.group(1).replace(',', '.'))
break
if not result['total']:
# Если явной суммы нет - берём наибольшую сумму (обычно это итог)
amounts = re.findall(r'\b(\d+\.\d{2})\b', full_text)
if amounts:
result['total'] = max(float(a) for a in amounts)
return result
Слой исправления ошибок
81% точность на символах означала некоторые некорректные извлечения полей. Добавили шаг подтверждения пользователем:
- (void)showExtractionConfirmation:(NSDictionary *)extracted {
// Предзаполняем форму извлечёнными данными
self.vendorField.text = extracted[@"vendor"] ?: @"";
self.amountField.text = extracted[@"total"] ?
[NSString stringWithFormat:@"%.2f", [extracted[@"total"] floatValue]] : @"";
// Подсвечиваем поля с низкой уверенностью
if (!extracted[@"total"]) {
[self highlightFieldAsNeedsReview:self.amountField];
}
// Показываем оригинальное фото рядом для сверки
self.receiptImageView.image = self.capturedImage;
}
Паттерн «машинное извлечение + подтверждение пользователем» повысил принятие. Пользователи доверяли приложению, потому что могли видеть фото и проверить извлечённые данные. Исправления стали обучающими данными для улучшения модели.
Что пришло после
К 2016 году Google Cloud Vision API и Amazon Textract сделали OCR чеков тривиальной задачей. Отправь фото на API - получи структурированный JSON с извлечённым текстом. Точность 95%+.
К 2020 году on-device модели ML (Core ML на iOS) запускали OCR чеков полностью офлайн с точностью, сравнимой с облачными API 2016 года.
Система 2013 года - Tesseract, предобработка OpenCV, regex-извлечение, подтверждение пользователем - была заменена одним API-вызовом.
Обучение не пропало. Понимание предобработки изображений показало: качество важнее алгоритма для задач компьютерного зрения. Понимание провалов Tesseract сделало нас грамотными пользователями API, пришедших на замену. «Мусор на входе - мусор на выходе» применимо к ML-системам любого уровня сложности.