Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art?. Claude.. Claude. llm.. Claude. llm. pixel art.. Claude. llm. pixel art. SIGGRAPH.. Claude. llm. pixel art. SIGGRAPH. Swift.. Claude. llm. pixel art. SIGGRAPH. Swift. Алгоритмы.. Claude. llm. pixel art. SIGGRAPH. Swift. Алгоритмы. депикселизация.. Claude. llm. pixel art. SIGGRAPH. Swift. Алгоритмы. депикселизация. ии-агенты.. Claude. llm. pixel art. SIGGRAPH. Swift. Алгоритмы. депикселизация. ии-агенты. искусственный интеллект.. Claude. llm. pixel art. SIGGRAPH. Swift. Алгоритмы. депикселизация. ии-агенты. искусственный интеллект. Машинное обучение.. Claude. llm. pixel art. SIGGRAPH. Swift. Алгоритмы. депикселизация. ии-агенты. искусственный интеллект. Машинное обучение. Обработка изображений.
Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art? - 1

Лето, скоро отпуск — захотелось написать статью, которую просто кайф прочитать, и заодно попробовать что‑то новое. Для ИИ есть бенчмарки вроде HumanEval, где модель просят написать функцию на пару строк, есть задачи уровня «сделай мне todo‑лист на React». А что будет, если дать современным ИИ‑агентам по‑настоящему наукоёмкую задачу — реализовать алгоритм из статьи SIGGRAPH на Swift, без сторонних библиотек, — и потом честно сравнить, что получилось на выходе?

Для этого я взял алгоритм «Depixelizing Pixel Art» (Johannes Kopf, Dani Lischinski, SIGGRAPH 2011) — тот, который я когда‑то давно реализовывал на C++. Поставил одинаковую задачу реализовать на языке Swift разным агентам (Claude, Codex, Cursor, Cline, Antigravity, Kimi, Grok — на разных моделях). Условия просты — один промпт = одна реализация, без уточнений, указаний недочетов и итераций правок

Вот полный текст промпта:

Скрытый текст
## Задание

Ты выполняешь готовый план реализации алгоритма **«Depixelizing Pixel Art»**
(J. Kopf, D. Lischinski, SIGGRAPH 2011). Статья лежит рядом: `pixel.pdf`.
Следуй плану шаг за шагом, не пропускай шаги и не заменяй алгоритм другим.
Если шаг невозможно выполнить точно — реализуй указанное в нём упрощение и
зафиксируй отклонение в `solution/NOTES.md`.

### Шаг 0. Контракт

- Прочитай `config.txt`: `language` — язык реализации, `os` — целевая ОС
  (для кроссплатформенного языка игнорируется), `upscale` — целочисленный
  множитель увеличения.
- Весь код помести в папку `solution/`.
- Обработай **каждый** файл из `input/`. Результат сохрани в `output/`,
  заменив в имени суффикс `_input` на `_output`
  (`smw_boo_input.png` → `smw_boo_output.png`), формат PNG.
- Размер каждого результата: `(W*upscale) x (H*upscale)`, где `W x H` — размер оригинала.
- Запуск — одной командой; команду опиши в `solution/README.md`.

### Шаг 1. Подготовка проекта

- Модули: конфиг, ввод/вывод изображений, граф похожести, эвристики,
  перестройка ячеек, сплайны, оптимизация, рендеринг, точка входа.
- Зависимости — только базовые: чтение/запись PNG и математика.
  **Запрещены** готовые реализации апскейла/векторизации (hqx, xBR, Scale2x/ScaleNx,
  EPX, 2xSaI, potrace, функции масштабирования из opencv/PIL как итоговый результат).
- Загрузка изображения в RGB. Если есть альфа-канал: полностью прозрачные
  пиксели считай отдельным самостоятельным «цветом».

### Шаг 2. Граф похожести (статья, §3.2, начало)

- Узел на каждый пиксель, рёбра ко всем 8 соседям (включая диагонали).
- Переведи цвета в YUV. Ребро удаляется, если цвета «непохожи»:
  `|dY| > 48/255` **или** `|dU| > 7/255` **или** `|dV| > 6/255`.
- Самопроверка: на однотонной картинке граф остаётся полным;
  на шахматной доске 1x1 остаются только диагональные рёбра внутри каждого цвета.

### Шаг 3. Разрешение пересекающихся диагоналей (§3.2)

Для каждого блока 2x2, в котором присутствуют обе диагонали:

- Если блок полностью связан (все четыре пикселя взаимно похожи) —
  удали обе диагонали: это плоско закрашенная область.
- Иначе подсчитай голоса трёх эвристик за каждую из диагоналей:
  - **Кривые.** Если диагональ — часть цепочки узлов валентности 2, она
    принадлежит «кривой». Вычисли длины двух кривых, проходящих через две
    диагонали; голос за более длинную, вес = разность длин.
  - **Разреженные пиксели.** В окне 8x8 с центром в блоке сравни размеры
    компонент связности, к которым подключены концы каждой диагонали.
    Голос за диагональ компоненты **меньшего** размера (разреженный цвет —
    передний план), вес = разность размеров компонент.
  - **Острова.** Если у одного из концов диагонали валентность 1, её удаление
    создаст одинокий пиксель-«остров». Голос за сохранение этой диагонали
    с фиксированным весом **5**.
- Оставь диагональ с большей суммой весов; при равенстве удали обе.
- Результат: планарный граф.
- Самопроверка: визуализируй граф для `smw_boo_input.png` и сравни со схемой
  Figure 3(c) статьи.

### Шаг 4. Перестройка пиксельных ячеек (§3.2, конец)

- Построй **упрощённую обобщённую диаграмму Вороного**: проход окном 3x3 по
  графу, подстановка готовых шаблонов формы ячеек (формы перечислимы, потому
  что граф планарен).
- Координаты узлов ячеек квантованы к **четвертям пикселя**.
- Схлопни узлы валентности 2 для упрощения диаграммы.
- Самопроверка: похожие пиксели, соседние по диагонали, теперь делят общее
  ребро ячейки; сравни с Figure 3(d).

### Шаг 5. Извлечение сплайнов (§3.3)

- Ребро ячейки **видимое**, если цвета по его сторонам различны
  (критерий из шага 2).
- Последовательности видимых рёбер, проходящие через узлы валентности 2
  (по видимым рёбрам), преврати в **квадратичные B-сплайны**;
  контрольные точки — узлы ячеек.
- T-стыки (три видимых ребра в одном узле): классифицируй каждое ребро как
  «теневое» (shading), если YUV-расстояние цветов с двух сторон `<= 100/255`,
  иначе «контурное». Если теневое ровно одно — соедини два контурных в один
  сплайн. Иначе соедини пару рёбер с углом, ближайшим к 180 градусам.
  Конец третьей кривой спроецируй на продолжающуюся кривую (B-сплайн не
  интерполирует контрольные точки).
- Самопроверка: отрисуй сплайны поверх сетки, сравни с Figure 3(e).

### Шаг 6. Оптимизация кривых (§3.4)

- Минимизируй сумму поузловых энергий `E = Es + Ep`:
  - `Es` — гладкость: интеграл `|kappa| ds` (модуль кривизны) по участку кривой,
    на который влияет узел; считай численно, сэмплированием с фиксированным шагом.
  - `Ep` — позиция: `||p - p_hat||^4`, где `p_hat` — исходное положение узла
    (четвёртая степень: свободно в малом радиусе, резкий штраф дальше).
- Релаксация: случайный обход узлов; для каждого пробуй несколько случайных
  смещений в малом радиусе, оставляй лучшее. Несколько итераций.
  **Зафиксируй seed** — результат должен быть воспроизводим.
- Углы: найди в перестроенном графе ячеек шаблоны углов (Figure 7, плюс их
  повороты и отражения) и исключи участки кривой между узлами шаблона из
  интеграла гладкости — намеренно острые углы сглаживать нельзя.
- Допустимое упрощение: вместо гармонических отображений для узлов, не
  лежащих на сплайнах, — локальное усреднение соседей или пропуск
  перепозиционирования (выбор зафиксируй в NOTES.md).
- Самопроверка: «лесенка» на длинных дугах исчезает, сравни с Figure 6(b)→(c).

### Шаг 7. Рендеринг (§3.5)

- Растеризуй результат в `(W*upscale) x (H*upscale)`.
- Эталонный способ: цвет точки — взвешенное среднее цветов ячеек с усечёнными
  гауссовыми влияниями (`sigma = 1`, радиус 2 ячейки), размещёнными в центроидах
  ячеек; влияние **не распространяется через контурные сплайны** (edge-aware).
- Допустимое упрощение: залить каждую область, ограниченную сплайнами, цветом
  её ячеек, с антиалиасингом по границам (суперсэмплинг не менее 4x).
- **Запрещено**: nearest neighbor / bilinear / bicubic как итоговый способ
  масштабирования.

### Шаг 8. Прогон и самопроверка

- Прогони все файлы из `input/`.
- Проверь: все выходные файлы существуют, размеры верны, повторный запуск
  даёт идентичный результат.
- Сравни выходы с эталонами в статье: `smw_dolphin` — Figure 1,
  `smw_boo` — Figure 3(f), `invaders_03` — Figure 9, `sma_toad` — Figure 9,
  `win31_keyboard` — Figure 9, `smw2_yoshi_*` — Figure 9 (Yoshi).
- Все отклонения от плана зафиксируй в `solution/NOTES.md`.

### Критерии готовности

- [ ] В `output/` лежит результат для каждого входного файла, размеры `(W,H) * upscale`.
- [ ] Контуры гладкие, без «лесенки»; тонкие линии не разорваны; одиночные
      пиксели (глаза персонажей) не потеряны.
- [ ] Язык реализации соответствует `config.txt`; запуск одной командой по `solution/README.md`.
- [ ] Повторный запуск даёт байт-в-байт тот же результат.

Оценивать результаты я «пригласил» тоже три разных ИИ — Claude, Codex и Antigravity (Gemini).

Что за задача и почему она сложная

«Depixelizing Pixel Art» — это алгоритм, который восстанавливает намерение художника: где у спрайта непрерывный контур, где тонкая диагональная линия в один пиксель, а где одиночная точка-блик, которую нельзя терять.

Figure 3 из статьи Kopf & Lischinski: (a) вход, (b) граф похожести, (c) граф после разрешения диагоналей, (d) перестроенные ячейки, (e) сплайны, (f) финальный рендер.

Figure 3 из статьи Kopf & Lischinski: (a) вход, (b) граф похожести, (c) граф после разрешения диагоналей, (d) перестроенные ячейки, (e) сплайны, (f) финальный рендер.

Оценка на верность производилась по этим пяти этапам:

  1. Граф похожести. 8-связность, сравнение пикселей в пространстве YUV с порогами из статьи (48/255 по яркости, 7/255 и 6/255 по цветности).

  2. Разрешение диагоналей. В полносвязном блоке 2×2 надо выбрать одну из двух диагоналей по трём эвристикам (продолжение кривых, разреженные пиксели с окном 8×8, «острова» с весом 5); ничья — удалить обе.

  3. Перестройка ячеек. Упрощённая обобщённая диаграмма Вороного, узлы квантуются к четвертям пикселя.

  4. Сплайны. Видимые рёбра превращаются в квадратичные B-сплайны; отдельно обрабатываются T-стыки (теневые/контурные рёбра, порог 100/255, угол ближе к 180°).

  5. Оптимизация кривых. Минимизация энергии «гладкость + позиция», при этом узловые шаблоны углов из сглаживания исключаются (углы должны остаться углами).

Входной спрайт smw_boo, 18×18 пикселей.

Входной спрайт smw_boo, 18×18 пикселей.

Вот как выглядит вход — 18×18 пикселей, на которые без увеличения и не взглянешь:

Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art? - 4

А вот эталон, к которому все стремились, — Figure 3(f) из статьи: гладкий контур, два глаза (правый — характерный «крючок»), намеренно «волнистый» розовый рот и мягкая тень.

А судьи кто? Главный тест всего бенчмарка

Оценка шла по 100-балльной шкале, пять категорий: контракт (25), верность алгоритму (30), визуальное качество (20), инженерное качество (15), процесс (10).

Но самое полезное в проверке оказалось почти тривиальным. Это тест на даунсэмплинг:

Взять выход 288×288, усреднить его обратно до 18×18 методом BOX, сравнить с оригиналом. Если средняя дельта около нуля — значит каждый блок 16×16 в выходе постоянен и выход практически полностью состоит из однородных блоков. А это и есть обычный nearest-neighbour, то есть депикселизации не было вообще.

import numpy as np
from PIL import Image

up = 16
src = Image.open("input/smw_boo_input.png").convert("RGB")
out = Image.open("output/smw_boo_output.png").convert("RGB")
ds  = out.resize(src.size, Image.BOX)          # свернуть выход обратно к 18×18
d   = np.abs(np.float32(src) - np.float32(ds)).mean()
print(f"средняя дельта: {d:.1f}")             # 10–25 — норма; 0.0 — это resize

Десять строк Python — и стало видно то, что не видно при беглом просмотре «красивых» исходников. Нормальное сглаживание «съедает» углы и даёт дельту в районе 10–25. Ноль означает, что модель просто растянула картинку.

Модель / агент

Размер

Δ к входу

Пикселей ≠ NN

Что это значит

Claude Fable 5

288²

24.2

39.98%

норма, есть сглаживание

Codex 5.5

288²

40.2 → 21.3 при v-flip

47.17%

обработка есть, но кадр перевёрнут

Cursor Auto

288²

27.8

25.77%

обработка есть, но рендер битый

Kimi Code 2.7

288²

42.7

31.24%

обработка есть (рендер по Вороному) сплайны в рендере не участвуют

Claude Sonnet 4.6

288²

19.0

13.55%

норма, есть сглаживание

Antigravity (Gemini 3.5 Flash)

288²

0.0

0.00%

чистый nearest ×16

Cline + DeepSeek v4 Pro

288²

0.0

0.00%

чистый nearest ×16

Cline + Qwen 3.7 Max

288²

0.0

0.00%

nearest ×16

Grok

4608²

0.0

1.85%

nearest ×256 — и неверный размер

Все агенты Интересные особенности и впечатления:

  • Fable — идеальный результат (его разберем далее)

  • Codex честно выполнил все инструкции и получил перевернутое изображение (помню, когда я реализовывал этот алгоритм, также перевернутое изображение)

  • Kimi Code 2.7 высадил 5-часовой лимит за один промпт, застрял в петле из постоянных улучшений

  • Antigravity — отправился гуглить реализацию на питоне, открыл браузер и начал серфить

  • DeepSeek v4 Pro — прогон стоил всего $0,08 на токены

Общий зачёт (Оценка ИИ)

Ниже — сводка. Поскольку оценивали три разных ИИ-судьи (Claude, Codex, Antigravity/Gemini), я привожу баллы каждого: расхождения сами по себе любопытны (об этом — в разделе про судей).

Модель / агент

Claude

Codex

Gemini

Вердикт

🥇

Claude Fable 5

97

97

100

Отлично

🥈

Sonnet 4.6

81

85

88

Хорошо

🥉

Codex 5.5

85

83

84

Хорошо (кадр перевёрнут)

4

Kimi Code 2.7

76

78

80

Хорошо — зачтено (рендер по Вороному)

5

Cursor Auto

68

66

81.5

Удовлетворительно

Antigravity (Gemini 3.5 Flash)

44

47

Не зачтено (nearest)

Cline + DeepSeek v4 Pro

53

47

Не зачтено (nearest)

Cline + Qwen 3.7 Max

40

42

Не зачтено (nearest)

Grok

38

19

Не зачтено (nearest ×256)

А вот как это выглядит глазами — та самая картинка, ради которой стоит читать статью. Восемь результатов одного и того же призрака Boo:

Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art? - 5
Kimi Code 2.7

Kimi Code 2.7

Разница видна невооружённым глазом

Мой разбор призёров

Claude Fable — Однозначный лидер

Единственная реализация, которая одновременно и хорошо выглядит, и почти буквально покрывает все пять этапов алгоритма. Корректные YUV-пороги, все три эвристики диагоналей, четверть-пиксельные ячейки, квадратичные B-сплайны, полная логика T-стыков, шаблоны углов из Figure 7

Отдельно порадовала инженерия: фиксированный seed (SplitMix64), собственный PNG-кодер ради байт-в-байт воспроизводимости (два запуска → идентичный SHA-256), многопоточный рендер и режим --debug, выгружающий промежуточные этапы. Эти выгрузки можно прямо сопоставить со статьёй:

Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art? - 7
Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art? - 8

2. Перестроенные ячейки Вороного, узлы квантованы к ¼ пикселя (Figure 3d).

Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art? - 9

3. Сплайны на видимых рёбрах (Figure 3e): «лесенка» становится кривыми.

Нечестный обзор ИИ-агентов. Кто действительно смог реализовать Depixelizing Pixel Art? - 10

4. Финальный рендер — практически эталон.

Codex — моё второе место

По исходникам это одна из самых полных реализаций: B1–B5 разнесены по модулям, есть self-test графа, T-стыки с проекцией по 32 точкам сплайна, защита тонких компонент, гауссов рендер и три диагностических картинки.

финальный PNG перевёрнут по вертикали (рассогласование «начала отсчёта Y» между чтением и записью буфера) — поэтому все «ИИ-судьи» снизили баллы

Claude Sonnet

Результат: контур получился заметно гранёным — сплайны не убрали «лесенку», а превратили её в многоугольные фасетки; глаза стали ромбовидными, рот потерял волну. Плюс из-за ошибок округления на границах полигонов появились микро-зазоры (полупрозрачные пиксели). Цветовая верность при этом хорошая.

Kimi Code 2.7 —маньяк перфекционист

Мой приз зрительских симпатий, модель действительно старалась, когда я увидел, что потрачено 20% 5-часового лимита — я улыбнулся, 50% — я уже ждал “ошеломляющий” результат, и дальше с упоением наблюдал как заполняется 100%

Результат оказался далёк от идеала, но именно Kimi показал самое интересное агентное поведение: вместо решения задачи он начал бесконечно улучшать собственное решение и в итоге сжёг весь доступный лимит.

Выводы

  1. Claude Fable — чистый победитель. Единственный результат уровня эталона статьи, ещё и с диагностикой этапов и байт-в-байт воспроизводимостью. Пример когда модель понимает задачу, а не имитирует понимание

  2. Главный вывод: для AI-агентов нужен численный критерий. Это вероятностные системы, и статическое ревью «красивого кода» не показывает, работает ли алгоритм на самом деле.

  3. Много кода — не всегда работающий результат. Antigravity и Cline+DeepSeek написали полноценный каркас алгоритма с графами и сплайнами — и из-за одной логической ошибки (схлопывание коллинеарных вершин) всё выродилось в просто ресайз. Сквозные smoke-тесты на синтетике обязательны прямо в цикле генерации.

  4. LLM-судьи воспроизводимы ровно настолько, насколько объективны критерии. По числам — консенсус, по «вкусу» — разброс в 15 баллов. Это работающий рецепт для любого, кто строит автоматическую оценку на LLM.

Если кто-то захочет прогнать этот же тест на Kotlin, Rust, C#, Zig или любом другом языке — вот репозиторий с полным промптом, исходными артефактами и структурой, которую я использовал для эксперимента.

Автор: PALiarMo

Источник