OCR распознал таблицу, но перепутал строки: как проверить связь артикула и суммы
OCR верно прочитал два артикула и две суммы, но итоговая таблица связала их неправильно. У товара A появилась цена товара B и наоборот. Проверка общей суммы прошла: слагаемые просто поменялись местами. Такая ошибка опасна для загрузки накладных, где смысл строки задаётся связью ячеек, а не наличием всех нужных слов на странице.
Список распознанных текстов нужно превратить в структуру таблицы. Для простого прямого бланка помогает геометрия: артикул и сумма одной строки занимают близкие вертикальные интервалы. При этом нельзя просто отсортировать все блоки по порядку, в котором их вернул OCR. Движок может обходить сначала один столбец, затем другой, а небольшие наклоны изменят порядок центров.
Сопоставим ячейки по вертикальному пересечению
В примере координаты заданы в пикселях одной уже выровненной страницы. Артикулы стоят слева, суммы справа. Список сумм намеренно передан в обратном порядке. Для каждой пары считаем длину общего вертикального участка и делим на меньшую высоту ячеек. Это локальная мера совпадения строк, а не двумерный IoU рамок: горизонтально разные столбцы как раз не пересекаются.
import numpy as np
from scipy.optimize import linear_sum_assignment
articles = [{"text": "A-10", "box": [10, 10, 50, 20]},
{"text": "B-20", "box": [10, 30, 50, 40]}]
amounts = [{"text": "200.00", "box": [80, 31, 120, 41]},
{"text": "100.00", "box": [80, 11, 120, 21]}]
def vertical_overlap(a, b):
shared = max(0, min(a[3], b[3])-max(a[1], b[1]))
return shared/min(a[3]-a[1], b[3]-b[1])
score = np.array([[vertical_overlap(a["box"], b["box"])
for b in amounts] for a in articles])
left, right = linear_sum_assignment(score, maximize=True)
linked = []
for i, j in zip(left, right):
if score[i, j] < .5:
raise ValueError("ambiguous row")
linked.append((articles[i]["text"], amounts[j]["text"]))
print("overlap", score.tolist())
print("linked", linked)
assert linked == [("A-10", "100.00"), ("B-20", "200.00")]Матрица получает 0,9 для правильных пар и ноль для соседних строк. Назначение один к одному возвращает A-10 → 100.00 и B-20 → 200.00 независимо от исходного порядка списков. После сопоставления сохраняйте идентификаторы ячеек и рамки: это позволит показать спорную строку оператору и проверить источник каждой связи.
Где геометрического правила уже мало
Многострочное название товара может занимать высоту нескольких соседних сумм. Объединённые ячейки, наклон и перенос таблицы на следующую страницу тоже нарушают простое совпадение интервалов. Поэтому сначала выделяйте таблицу, выравнивайте страницу и определяйте столбцы. Если структура сложная, используйте модель распознавания таблиц либо правила конкретного бланка. Порог 0,5 в примере подходит только для показанного механизма и требует настройки на ваших документах.
Оптимальное назначение всегда старается найти пары, даже если все варианты плохие. Поэтому нужен порог допуска и возможность оставить ячейку несвязанной. В больших таблицах удобно добавлять фиктивные варианты «нет пары» или строить соответствия только внутри уверенно выделенных строк. Иначе лишняя сумма из итога может занять место отсутствующей суммы товара и сдвинуть последующие связи.
Проверим смысл строки после геометрии
У строки могут быть количество, цена, скидка и сумма. Их арифметическая связь помогает обнаружить ошибку сопоставления, но сначала согласуйте округление и налоги. Нельзя молча переставлять суммы до совпадения общего итога: разные комбинации способны дать один результат. Если арифметика не сходится, отметьте строку для проверки вместе с исходными ячейками.
Оценивайте качество по полным строкам: артикул, количество и сумма должны относиться к одному товару. Отдельная точность OCR по символам эту ошибку не видит. В тестах нужны обратный порядок выдачи блоков, пропущенная сумма, многострочное название, лишний итог и граница страниц. Отчёт должен различать ошибочно связанную строку и строку без уверенной пары.
При исправлении загрузчика сохраните предыдущий JSON и новый вариант связей для одной и той же распознанной страницы. Так можно проверить именно реконструкцию таблицы, не смешивая её результат с новым запуском OCR. После локальной проверки уже оцените полный путь на реальных сканах с теми же правилами разметки.
Для наклонных строк используйте координаты после одного согласованного выравнивания страницы. Сравнение вертикальных интервалов рамок до и после разных преобразований не имеет общего смысла. При сохранении результата укажите систему координат и возможность возврата к исходному скану.
В курсе вы работаете с OCR и Doc-AI, подготовкой документов и передачей результата в JSON и базу данных. Следующая задача — собрать извлечение таблицы с проверяемыми связями ячеек и измерить качество на уровне готовой строки, которую использует учётная система. Откройте программу платного курса «Computer Vision Engineer: от детекции и Doc-AI до продакшна».
Продолжить на практике
- Computer Vision Engineer: от детекции и Doc-AI до продакшна ↗
Практический курс по Computer Vision. Соберёте продакшн-сервис: датасет → обучение (YOLO/RT-DETR, SAM), трекинг, OCR/Doc-AI, веб-приложение (UI+API) и мониторинг. Экспорт в ONNX/TensorRT/OpenVINO, батчинг/квантование под CPU/GPU. Плюс профессиональные блоки: mAP/IoU/Dice и калибровка доверия, QA разметки и аугментации, p95/стоимость, дрейф данных и алерты, guardrails (NMS/правила), A/B-тесты, Triton/gRPC/FastAPI.
