← Усі статті

Порожня клітинка, повтор і закреслення: розпізнавач не повинен вирішувати, чи є замір

Продовження пілоту УЗТ: п'ять видів клітинки, чорнило, повтор угору по колонці, закреслення і чому евристика ще не замінює крихітний класифікатор.

Порожня клітинка, повтор і закреслення: розпізнавач не повинен вирішувати, чи є замір
Зміст

У нотатці про компактну CRNN ми лишили відкритим конфлікт: одна голова не повинна одночасно відповідати «чи є число» і «яке саме». На порожніх клітинках розпізнавач малює правдоподібну товщину. Тут — продовження з того самого пілоту ai-vision: що вже стоїть перед оптичним розпізнаванням символів (OCR) у конвеєрі комп'ютерного зору, які види клітинки ми розрізняємо і чому крихітний класифікатор «порожньо / є значення» все ще попереду, хоча евристика вже працює.

Це не готовий гайд і не обіцянка, що правила закрили задачу. Проєкт у розробці; репозиторій закритий — контур без сирих бланків на сторінці портфоліо. Системний шар аркуша (модель зору, оцінка надійності, звіт оператора) — в «чому однієї моделі мало».

Ціна помилки та сама, що на бланку ультразвукового контролю товщини (УЗТ) загалом: порожня клітинка, перетворена на 8,0, виглядає як упевнений замір і може поїхати в облікову систему підприємства (ERP), якщо оператор не помітить сіру підсвітку. Поруч стоїть зіпсований повтор: колонка «як зверху» стає мінусом або одиницею, і далі рядками їде вже чужа товщина.

Ключові висновки

«Чи є замір?» — не задача розпізнавання. Спочатку вид клітинки, потім читання. Інакше мережа зобов'язана щось сказати навіть на сітці без чорнила.

У пілоті п'ять видів: empty, ditto, digit, text, crossed. Це контракт між Python-контуром зору і TypeScript-збиранням результату, не «ще п'ять моделей».

Чорнило шукаємо як синю ручку і темний олівець, з відсіканням тонких ліній сітки. Поріг частки пікселів маленький: типова клітинка майже порожня, чорнило — рідкісні штрихи.

Повтор (горизонтальний прочерк) не читаємо розпізнавачем. Беремо значення угору по тій самій колонці. Якщо джерела немає — статус перевірки, не вигадана товщина.

Закреслення — окремий вид: спочатку зняти удар, потім намагатися прочитати останнє валідне число, інакше клітинка лишається закресленою, а не «ще одним заміром».

Евристика вже ріже виклики розпізнавання, але не замінює навчальний класифікатор. Сітка, пил і сусідній штрих усе ще плутаються з цифрою.

Після читання потрібні детерміновані щити: нереалістична товщина вище порога (у нас 80 мм) скидається; вузька колонка без чорнила не повинна тримати галюцинацію моделі.

Чому розпізнавач не повинен вирішувати, чи порожнє поле

Розпізнавач навчений видавати рядок. На вході — картинка. На виході — символи абетки. Якщо картинка — лише лінія таблиці, мережа не вміє «промовчати», поки ви явно не навчили її порожньому класу. У попередній нотатці донавчання на порожніх знижувало фантоми і било по заповнених. Висновок інженерний: розведіть задачі.

На бланку УЗТ це не академічна суперечка. Порожніх клітинок у сітці багато: точку не міряли, елемента немає, оператор ще не дійшов до зони. Якщо кожен такий прямокутник годувати замірною головою, отримаєте гарну таблицю товщин, частину яких ніхто не писав. Оператор потім звіряє звіт із папером і втрачає довіру до контуру швидше, ніж ви встигнете донавчити мережу.

У конвеєрі це виглядає так.

flowchart TB
  crop[Cell_crop]
  crop --> ink[Ink_and_kind]
  ink -->|empty| skip[Skip_OCR]
  ink -->|ditto| up[Resolve_up_column]
  ink -->|crossed| strike[Mask_strike_then_OCR]
  ink -->|digit_or_text| ocr[Handwriting_OCR]
  ocr --> parse[Parse_and_range]
  up --> parse
  strike --> parse
  parse --> field[Structured_field]

Поки кроп не класифіковано, будь-який рушій — і PaddleOCR, і компактна CRNN — кандидат написати товщину із сітки. Класифікація дешевша за помилку. Хмарна модель зору на фрагменті тут не виняток: без виду клітинки вона теж мусить «прочитати» порожній прямокутник.

П'ять видів клітинки як контракт

Тип клітинки в контурі зору задається явно: empty | ditto | digit | text | crossed. Збирання результату на TypeScript читає цей вид, а не намагається знову вгадати геометрію за сирим текстом.

Вид Що бачимо на бланку Що робимо
empty немає чорнила, або нижче порога не кличемо рукописне розпізнавання, значення null
ditto горизонтальний повтор «як зверху» розпізнавання вимикаємо, шукаємо джерело вгору по колонці
digit схоже на товщину читаємо, нормалізуємо кому, перевіряємо діапазон
text «немає елемента», «обігрів», коротка помітка не пихаємо в замірну CRNN з абеткою з цифр
crossed удар поверх запису знімаємо лінію, читаємо залишок або лишаємо закресленим

Це саме той поділ «за типом візуальної задачі», який у попередній статті був таблицею намірів. Тут він уже в коді: класифікатор вмісту клітинки плюс розбір повтору в колонці.

Текст у замірі — не баг бланка. Оператори пишуть короткі помітки. Парсер міряє не лише 12,3, а й словникові ярлики на кшталт «немає елемента» і «обігрів», і відсікає римські номери секцій, які розпізнавач інколи приклеює до числа. Це правила, не друга нейромережа. Вид text якраз каже збиранню: не намагайся втиснути рядок у міліметри за будь-яку ціну.

Для оператора вид клітинки має відрізнятися у звіті. Порожнє — одна поведінка. Повтор без джерела — інша: людині треба зрозуміти, чому колонка обірвалася. Закреслення — третя: це не «немає числа», а «було і скасовано». Якщо всі три статуси згорнути в empty, перевірка аркуша стає лотереєю.

Чорнило: синя ручка, олівець, сітка

Детектор чорнила дивиться простір HSV на синій діапазон ручки і сіру яскравість на темний олівець. Тонкі горизонталі й вертикалі сітки намагаємося не вважати записом: морфологічне розкриття еліпсом. Частка «чорнильних» пікселів порівнюється з порогом близько 0,8% кадру клітинки — для рукопису цього достатньо, для жирної сітки поріг легко перевищити, якщо не відняти лінії.

Класифікатор виду дивиться маску чорнила і геометрію зв'язних плям: витягнута низька пляма з великим відношенням ширини до висоти ближче до повтору, ніж до цифри. Якщо чорнильних пікселів менше чотирьох — клітинка порожня, без філософських суперечок.

Поріг — компроміс. Занадто високий: блідий олівець стане empty, замір зникне, контур перевірки потім дочитуватиме за hasInk=true і порожнім значенням. Занадто низький: сітка стане «є чорнило», розпізнавач намалює 11,1. У пілоті ми свідомо тримаємо поріг низьким і чистимо вид другим проходом: повтор, закреслення, текст.

Телефон і планшетний сканер дають різний контраст. Синя ручка на жовтуватому папері й олівець на сірій ксерокопії — не один і той самий HSV. Тому поріг не «магічна константа продукту», а налаштування під плівку і світло. Змінювати його без підрахунку пропусків заповнених клітинок — спосіб тихо втратити тонкий 8,2.

Повтор угору по колонці

Горизонтальний прочерк на бланку УЗТ означає «як у рядку вище в цій точці». Це не мінус товщини і не «немає даних». Якщо віддати прочерк у розпізнавач, отримаєте -, 1 або випадковий хвіст цифри.

У контурі зору такий кроп позначається ditto, текст розпізнавання обнуляється. Збирання колонки йде згори вниз: для кожного рядка з повтором шукаємо найближчу верхню клітинку тієї самої колонки, у якої вид не повтор і значення не порожнє. Знайшли — копіюємо значення і позначаємо джерело. Не знайшли — статус перевірки, сирий текст на кшталт «повтор без рядка-джерела». Мовчазний null тут гірший: оператор не зрозуміє, чому колонка обірвалася.

Ланцюжок повторів нормальний: три прочерки поспіль мають усі послатися на одну вихідну товщину, а не одне на одного як на порожнечу. Тому обхід згори вниз, а не «взяти сусіда і зупинитися». Якщо вихідна клітинка сама в review, повтор успадковує сумнів: копіювати впевнений ok із сумнівного джерела не можна.

Це звичайний код. Його можна покрити тестом на синтетичній колонці. Нейромережа для «знайти повтор» мала б сенс лише якщо евристика плям системно плутає прочерк з одиницею або із закресленням. Поки помилка частіше в інший бік — сітка як повтор — дешевше крутити геометрію плями, ніж навчати детектор.

Окрема пастка — прочерк у службових полях шапки. У проєктній або відбракувальній товщині горизонталь часто означає «в цьому полі числа немає», а не «як рядком вище». Один гліф - на папері, два контракти в коді. Змішати їх — отримати копіювання діаметра по колонці, якого оператор не писав.

Закреслення: спочатку зняти удар

Закреслена клітинка — частий польовий жест: написали, передумали, ударили лінію, інколи написали нове число нижче. Якщо годувати кроп розпізнавачу як є, мережа читає кашу зі старого числа і удару.

Детектор шукає сильний або слабкий сигнал удару на масці чорнила: витягнута пляма, горизонтальна морфологія, інколи прямі від перетворення Хафа. Якщо удар є, пробуємо зафарбувати його білим і прочитати залишок; із кандидатів віддаємо перевагу нижній валідній товщині — як правило, це виправлення, а не закреслене старе. Не вийшло — вид лишається crossed, значення може піти на перевірку, а не в ok.

Це крихко. Слабкий удар на тонкій дев'ятці виглядає як сітка; жирний удар з'їдає корисні штрихи. Тому закреслення не зливається ні з порожнім, ні з повтором: оператору в HTML-звіті потрібна інша підсвітка, ніж «просто немає числа».

Не кожен удар означає «прочитай нижнє». У частині регламентів закреслене поле вважається порожнім незалежно від того, що ще видно. Тоді контур має зупинитися на виді crossed і не кликати розпізнавач: інакше ви «відновите» скасований замір і відправите його в облік як живий. У пілоті ми пробуємо нижнє число, бо на наших бланках так частіше правлять товщину. Це доменне правило, його треба явно записати, а не ховати в порозі плям.

Коли рукописне розпізнавання не викликаємо

На сітці замірів виклик розпізнавача вмикається лише якщо є чорнило і попередній вид не повтор. Порожнє — запис кропа і cellKind=empty. Повтор — кроп є, розпізнавання немає. Закреслення — окрема гілка читання. Інакше — ocr_handwriting_cell: за замовчуванням друкований/рукописний каскад, за прапорцем — компактна CRNN.

Це економить час і, головне, не дає порожній клітинці шансу стати товщиною. Другий щит стоїть уже після моделей: якщо значення у вузькій колонці є, а контур зору не бачить чорнила, клітинка очищається — типова галюцинація «сіткової» моделі на порожній IV.а. Третій щит — стеля товщини 80 мм: злив сусідніх цифр (96 замість порожнього) не проходить доменну перевірку.

Вузька колонка IV.а в пілоті частіше порожня і легше годує фантоми: рамка займає більшу частку кадру, рукопис, якщо він є, тісніший. Окрема мережа на цю колонку не потрібна — потрібна жорсткіша зв'язка «немає чорнила → скинути значення». Якщо послабити щит «щоб не втратити рідкісний замір», ви повернете масові 11,1 на порожніх.

Друковані службові поля (зона, діаметр, тип) ідуть іншим розпізнавачем з обрізанням рамок сітки і збільшенням. Дату шапки не сунемо в MeasurementCrnn: день, місяць, рік перевіряє звичайний код. Замірна голова з абеткою з десяти цифр і коми не повинна «трохи» стати універсальним читачем бланка — це вже було тезисом попередньої нотатки; тут він закріплений розвилкою в run_cv.

Чому евристика ще не кінець

Правила за плямами дешеві, зрозумілі, тестовані на синтетиці. Їхня межа передбачувана: новий почерк, жирна сітка іншого сканера, повтор не горизонталлю, а галочкою, закреслення хрестом, блідий гель. Набір складних негативних прикладів для CRNN це вже показав: модель і евристика вчать різні помилки.

Наступний крок із плану — крихітний класифікатор «порожньо / є значення» (і, логічно, розширений набір видів) перед замірною головою. Евристика лишається як слабкий фільтр і як ознаки: частка чорнила, відношення сторін плями, факт удару. Класифікатор вчиться на кропах, які евристика системно плутає. Поки його немає, чесно тримаємо хвіст у статусі review і не підвищуємо поріг ok на сумнівних десятих — це вже дисципліна системної нотатки.

Чому ще не навчили. Не тому що «лінь поставити згортку». Потрібна розмітка клітинок, на яких евристика бреше впевнено: жирна сітка як цифра, блідий олівець як порожнє, короткий повтор як одиниця. Без цієї черги класифікатор вивчить те саме, що вже рахує поріг 0,8%. Збирати таку чергу дорожче, ніж ще раз донавчити замірну голову на порожніх — і саме тому спокуса знову змішати задачі така сильна.

Не робимо окрему мережу на кожну колонку. Види спільні для всієї сітки I–IV. Змінюється лише геометрія колонки IV.а (вона вужча, частіше порожня) — звідси окремий щит «немає чорнила → скинути значення», а не друга модель.

Типові помилки

Найдорожча — викликати розпізнавання «про всяк випадок», бо кроп уже вирізано. Вирізаний прямокутник не зобов'язаний містити число.

Друга — змішати повтор і порожнє в одному статусі. Оператор тоді не відрізняє «тут не міряли» від «тут має бути як рядком вище, але джерело зникло».

Третя — підняти поріг чорнила після пачки фантомів і не порахувати, скільки заповнених клітинок пішло в empty. Вентиль, який «лікує» порожні, легко душить блідий почерк.

Четверта — донавчати замірну голову на порожніх до перемоги над фантомами. У попередній нотатці це вже коштувало пунктів повного збігання на цифрах. Вентиль дешевший.

П'ята — віддати прочерк службового поля в той самий розбір колонки, що й замір. Семантика - залежить від поля, не від форми штриха.

Ці помилки дешеві на демо з десяти клітинок і дорогі на пачці з сімдесяти п'яти аркушів. Саме тому в пілоті ми рахуємо фантоми порожніх окремо від повного збігання на цифрах: одна зведена «точність розпізнавання» їх маскує.

Що зробити сьогодні

Якщо ви ріжете бланк на клітинки і одразу кличете розпізнавач — зупиніть виклик на порожніх.

Введіть явний вид клітинки в результат контуру зору, хоча б три значення: порожньо, повтор, інше. Повтор розв'язуйте кодом угору по колонці, не рядком розпізнавання.

Порахуйте окремо: скільки порожніх отримали хибну товщину; скільки заповнених втратили через високий поріг чорнила; скільки повторів поїхали в - або 1.

Не донавчайте замірну голову на порожніх «до перемоги». Спочатку вентиль, потім вузьке розпізнавання, потім людина на review.

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

Хіба не можна просто додати клас «порожньо» в CRNN?

Можна, і ми пробували змішувати негативні приклади. Фантоми падають, цифри теж. Клас «порожньо» всередині голови з часовим вирівнюванням (CTC) — компроміс у тих самих вагах, які повинні читати тонкий хвіст дев'ятки. Вентиль дешевший.

Чим повтор відрізняється від прочерка в службових полях?

У замірах прочерк — посилання на рядок вище. У проєктній або відбракувальній товщині прочерк часто означає «немає числа в цьому полі». Змішувати семантику не можна: один символ - на бланку, два різні контракти.

Закреслення завжди треба читати?

Ні. Інколи удар означає «ігнорувати клітинку». Ми намагаємося прочитати нижнє валідне число, бо на наших бланках так частіше виправляють замір. Якщо у вашому регламенті закреслене завжди порожнє — залиште вид crossed і не кличте розпізнавач.

Чи потрібна нейромережа, щоб знайти чорнило?

Не з першого дня. Поріг за синім і темним плюс морфологія сітки закриває масу. Мережа має сенс, коли хибні hasInk на конкретній плівці або сканері не лікуються порогом без зростання пропусків.

Як це стикується з хмарною моделлю зору?

Контур зору віддає вид і кроп. Хмарний прохід не повинен затирати порожнє впевненою галюцинацією: щит «немає чорнила → скинути значення» якраз про це. Низька впевненість і hasInk=true за порожнього значення — привід для перевірки людиною, не для мовчазного ok.

Що почитати далі

Замірна голова, 640 тисяч параметрів і конфлікт порожніх усередині CTCкомпактна замірна мережа. Контур усього бланка і звіт оператора — однієї моделі мало. Механіка послідовностей — від згортки до трансформера. Набір як продукт — інженерія наборів даних. Ціна пропуску — економіка збою. Цикл «один важіль за прогін» — покращення якості голови.

Висновок

Промислове розпізнавання на сітці УЗТ ламається не там, де «мало шарів», а там, де клітинці не дали виду. Порожнє, повтор, закреслення, число і коротка помітка — різні контракти. У пілоті каскад уже є: чорнило, евристика виду, заборона розпізнавання на порожніх і повторах, розбір колонки кодом, щити за стелею товщини і за галюцинаціями без чорнила.

Відкрито те, чого евристика не вміє: впевнені помилки на жирній сітці і блідому почерку. Туди піде крихітний класифікатор, а не друга універсальна модель. Практичний крок на цьому тижні — порахувати фантоми порожніх до і після вентиля hasInk / cellKind. Якщо різниця маленька, ви все ще годуєте розпізнавач сіткою.