Ястремський

Помилка була в колірному просторі, а не в парсері

Кольорові клітинки на зображенні графіка перестають збігатися з вашими константами тієї миті, як зображення пройшло через месенджер. CIE-Lab витримує те, чого не витримує RGB.

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

На файлах, на яких ми тестували, це працювало. На файлах, які надсилали користувачі, — розсипалося.

Що ламається

Ніхто не надсилає оригінальний експорт. Люди роблять скриншот, пересилають його месенджером, а месенджер перекодовує. Квантування JPEG зсуває значення, субдискретизація кольоровості розмиває межу між сусідніми клітинками, а друга пересилка додає і те, й інше. Колір, який вийшов як #F4B183, може прийти як #F2B57F: для людського ока — непомітно, для пошуку в таблиці — три канали мимо.

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

Чому RGB — неправильна лінійка

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

CIE-Lab створювали саме для того питання, яке в нас є. Він побудований так, щоб евклідова відстань між двома кольорами приблизно відповідала тому, наскільки різними вони здаються людському оку. Артефакти стиснення налаштовані — навмисно — залишатися нижче порога людського сприйняття, а отже, вони залишаються малими і в Lab. Спотворення, яке RGB подає як великий числовий стрибок, у Lab виявляється невеликим.

Зміна була механічною: перевести і зразок, і палітру в Lab, класифікувати за найближчим сусідом уже там, а поріг відхилення лишити, щоб невідомий колір лишався невідомим, а не втискався в палітру.

Спершу треба знайти самі клітинки

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

Це негламурно і має властивість, якої жоден навчений детектор не дав нам задарма: коли воно падає, воно падає помітно й локально — одна відсутня лінія, а не правдоподібна сітка, зсунута на рядок.

Загальний висновок

Коли конвеєр нестабільний, а логіка читається правильно, помилка зазвичай у представленні, на яке ніхто не дивиться: колірний простір, кодування, часовий пояс, локаль, правило порівняння рядків. Код ніколи не був неправильним. Він вимірював правильну річ неправильною лінійкою.

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