Помилка була в колірному просторі, а не в парсері
Кольорові клітинки на зображенні графіка перестають збігатися з вашими константами тієї миті, як зображення пройшло через месенджер. CIE-Lab витримує те, чого не витримує RGB.
Графік приходить зображенням. Кожна клітинка позначена кольором — один колір на тип зміни, — тож прочитати графік означає прочитати кольори. Перша версія робила очевидне: взяти піксель, порівняти трійку RGB з таблицею відомих кольорів, обрати найближчий.
На файлах, на яких ми тестували, це працювало. На файлах, які надсилали користувачі, — розсипалося.
Що ламається
Ніхто не надсилає оригінальний експорт. Люди роблять скриншот, пересилають його месенджером, а
месенджер перекодовує. Квантування JPEG зсуває значення, субдискретизація кольоровості розмиває межу
між сусідніми клітинками, а друга пересилка додає і те, й інше. Колір, який вийшов як #F4B183,
може прийти як #F2B57F: для людського ока — непомітно, для пошуку в таблиці — три канали мимо.
Можна розширити допуск, і саме тут пастка захлопується: розширите достатньо, щоб поглинути зсув, — і два кольори палітри почнуть претендувати на ті самі пікселі. Звузите достатньо, щоб їх розвести, — і половина справжніх клітинок не збігатиметься ні з чим.
Чому RGB — неправильна лінійка
Відстань у RGB вимірює, наскільки два кольори далекі як числа, а не наскільки вони різні на вигляд. Та сама числова відстань в одній частині простору — ледь помітний зсув, а в іншій — очевидна зміна. Тож «найближчий колір у RGB» відповідає на питання, якого ніхто не ставив.
CIE-Lab створювали саме для того питання, яке в нас є. Він побудований так, щоб евклідова відстань між двома кольорами приблизно відповідала тому, наскільки різними вони здаються людському оку. Артефакти стиснення налаштовані — навмисно — залишатися нижче порога людського сприйняття, а отже, вони залишаються малими і в Lab. Спотворення, яке RGB подає як великий числовий стрибок, у Lab виявляється невеликим.
Зміна була механічною: перевести і зразок, і палітру в Lab, класифікувати за найближчим сусідом уже там, а поріг відхилення лишити, щоб невідомий колір лишався невідомим, а не втискався в палітру.
Спершу треба знайти самі клітинки
Класифікація кольору має сенс лише тоді, коли відомо, де клітинка. Виявлення сітки тут морфологічне, а не модельне: ерозія вздовж однієї осі довгим тонким ядром лишає тільки горизонтальні відрізки, те саме вздовж іншої осі — вертикальні, перетин двох дає кандидатів у лінії, а далі координати кластеризують, щоб лінія завтовшки три пікселі згорнулася в один індекс.
Це негламурно і має властивість, якої жоден навчений детектор не дав нам задарма: коли воно падає, воно падає помітно й локально — одна відсутня лінія, а не правдоподібна сітка, зсунута на рядок.
Загальний висновок
Коли конвеєр нестабільний, а логіка читається правильно, помилка зазвичай у представленні, на яке ніхто не дивиться: колірний простір, кодування, часовий пояс, локаль, правило порівняння рядків. Код ніколи не був неправильним. Він вимірював правильну річ неправильною лінійкою.
Той парсер тепер працює на реальних завантаженнях користувачів у Telegram-боті, де кожен вхід перед надходженням пройшов щонайменше одне перестиснення.