Skip to content

About

Converter of AI "pixel" art into real pixel art with a palette in Aseprite.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

img2pixels

Превращает «ИИ-пиксель-арт» в высоком разрешении — картинку, на которой пиксельная сетка только нарисована, — в настоящий пиксель-арт,
где один логический пиксель равен одному пикселю файла.

Слева исходник 1024×1024, справа результат 42×42

слева исходник 1024×1024, справа результат — честные 42×42

python img2pixels.py art.png --all

Только numpy и Pillow. Ничего больше ставить не нужно.


В чём подвох

Сетка в таких картинках неравномерная. У примера выше реальные шаги блоков идут 22, 25, 24, 24, 26, 28, 21… при среднем 24 — разброс ±15%.

Ровный гребень туда не ложится в принципе: расхождение с настоящими границами блоков доходит до 5 px при шаге 24, а к дальнему краю оси сетка уезжает почти на треть ячейки. На глаз это выглядит как «поплывшие» детали — у персонажа один глаз просто исчезает.

исходник, равномерная сетка, эластичная сетка

исходник  ·  равномерная сетка  ·  эластичная сетка

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

найденная эластичная сетка


Как это работает

1. Грубая оценка шага. По периодичности краёв: профиль границ сворачивается по модулю пробного шага, что даёт амплитуду и фазу для всех фаз сразу. Нужна только чтобы задать диапазон перебора — опираться на неё нельзя, на примере выше она ошибается вдвое.

2. Стоимость полосы. Для полосы пикселей [a, a+L) считается, насколько она неоднородна внутри каждого среза. У правильной полосы каждая строка почти постоянна; у съехавшей — содержит границу блока и разваливается на два цвета. Через префиксные суммы стоимость всех положений всех ширин берётся за один проход.

3. Динамическое программирование. Ищется разбиение оси ровно на N полос с минимальной суммарной стоимостью и мягким штрафом за отклонение от среднего шага. Перебор N даёт кривую цены, и штраф делает её U-образной: при N меньше истинного полосы вынужденно накрывают по два блока, при большем — режут блоки пополам. Минимум приходится ровно на истинное число ячеек.

4. Две страховки. Покрытие краёв ловит кратное измельчение сетки — при нём половина границ стоит внутри блоков, где никакого края нет. Условие «пиксель квадратный» подтягивает ту ось, у которой минимум цены оказался пологим: узкий спрайт с большими полями иначе сбивает оценку по короткой стороне.

5. Правка съехавших пикселей. Цвет ячейки берётся не из её серединки. Из первичной выборки собирается палитра спрайта, а затем цвет каждой ячейки переголосовывается по всей её площади: каждый пиксель исходника голосует за ближайший цвет палитры. Ячейка, которой при неровной сетке в серединку попала кромка соседа, всё равно получает цвет большинства. На примере выше так переголосовывается около четверти ячеек.


Насколько это работает

Метрика — tests/evaluate.py. Генератор tests/make_synth.py растягивает спрайт из ограниченной палитры неравномерной сеткой (jitter до 15%), затем размывает, ресайзит нецелым коэффициентом и жмёт в JPEG. Эталон известен точно, поэтому считается доля пикселей, восстановленных с точностью до 3.0 в Lab.

равномерная сетка эластичная
восстановлено пикселей, настроечный набор 76.9% 95.3%
восстановлено пикселей, отложенный набор 84.2% 92.0%
размер угадан, настроечный набор 19% 88%
размер угадан, отложенный набор 17% 100%
расхождение с исходником, Lab 5.76 2.63
доля испорченных ячеек 9.9% 3.4%

Отложенный набор порождён другим сидом и в подборе параметров не участвовал.

python tests/make_synth.py -o tests/synth -n 16 --seed 1234
python tests/evaluate.py

Что сохраняет

Файл Что это
ИМЯ_px.png результат 1:1 — то, ради чего всё затевалось
ИМЯ_px_x8.png превью с nearest-увеличением, чтобы разглядеть
ИМЯ.aseprite индексный спрайт, палитра ровно из цветов арта
ИМЯ_indexed.png indexed PNG с той же палитрой
ИМЯ.gpl палитра для Aseprite / GIMP / Photoshop
ИМЯ_grid.png исходник с наложенной найденной сеткой — для проверки

.aseprite пишется напрямую по спецификации формата: установленный Aseprite для этого не нужен. Файл открывается сразу в индексном режиме, без дефолтных 256 оттенков — только те цвета, что реально есть в арте (плюс слот прозрачности, если включён -t).


Использование

python img2pixels.py [ФАЙЛЫ_ИЛИ_ПАПКИ...] [опции]

Без аргументов обрабатывает текущую папку. На Windows можно просто перетащить файлы или папку на img2pixels.bat.

python img2pixels.py                     # всё в текущей папке
python img2pixels.py art.png             # одну картинку
python img2pixels.py pics -o готово      # всю папку, результат в ./готово
python img2pixels.py art.png --all       # все форматы + отладочная сетка
python img2pixels.py art.png -t -a       # прозрачный фон + .aseprite
python img2pixels.py art.png -s 64x64    # принудительный размер
python img2pixels.py art.png -c 16       # сжать палитру до 16 цветов

Основное

Опция Что делает
-o, --outdir куда складывать результат (по умолчанию ./out)
-p, --preview N превью с увеличением в N раз, 0 — не сохранять (по умолчанию 8)
-a, --aseprite сохранить .aseprite
-i, --indexed сохранить indexed PNG
-g, --palette сохранить палитру .gpl
-d, --debug сохранить исходник с наложенной сеткой
--all всё сразу: -a -i -g -d
-t, --transparent сделать связный с краями фон прозрачным
-c, --colors N сжать палитру до N цветов (k-means в Lab)
-s, --size ШxВ задать итоговый размер вручную
-q, --quiet не печатать подробности
-help полная справка со всеми параметрами

Остальные параметры (--lam, --span, --merge, --vote-inset, --cell, --no-square, --no-repair и прочие) трогать обычно не нужно — смотрите -help.

Если результат не тот

Программа печатает найденный размер и разброс шага. sd в районе 1–2 px при шаге 24 — норма для ИИ-артов; sd больше трети шага означает, что сетку не нашли.

  1. Запустите с -d и посмотрите ИМЯ_grid.png: линии должны совпадать с границами блоков.
  2. Размер вдвое больше нужного — поднимите --lam (сетка станет ровнее) или задайте -s вручную.
  3. Сетка «гуляет» сильнее, чем в исходнике — тоже --lam вверх.
  4. Сетка не поспевает за неровным исходником — --lam вниз, до 1.0.
  5. Потерялись одиночные пиксели-детали — уменьшите --vote-inset до 0 или отключите правку через --no-repair.

Ограничения

  • Картинка должна быть растянутой. Если на входе уже готовый пиксель-арт 1:1, находить нечего: минимальный шаг ограничен --min-cell (2.5 px).
  • Если исходник не пиксель-арт, а обычная иллюстрация с пиксельной текстурой, под текстурой нет структуры блоков. Результат честно отражает текстуру, но полноценным спрайтом не станет, и размер определяется неустойчиво — задавайте -s руками.
  • Предполагается, что сетка покрывает картинку целиком, без полей: границы ставятся от края до края. Обрезанный не по ячейке арт лучше подрезать.
  • Побайтовое совпадение цвета с оригиналом — 6–17%: JPEG сдвигает цвета на 1–3 единицы, и палитра восстанавливается с этой погрешностью. Визуально это незаметно (порог 3.0 в Lab), но побитовой точности нет.

Установка

git clone https://github.com/AnatomikPerq/img2pixels
cd img2pixels
pip install -r requirements.txt

Python 3.10+, numpy, Pillow.


In English

img2pixels converts high-resolution "AI pixel art" — images where the pixel grid is merely painted on — into real pixel art at 1:1 resolution.

The catch is that these grids are not uniform: in the sample above the real block widths run 22, 25, 24, 24, 26, 28, 21… around a mean of 24. A regular comb drifts by a third of a cell across the image and destroys fine detail.

So the grid is recovered elastically. For every candidate strip of pixels the tool measures how non-uniform that strip is within each scanline — a correct strip is nearly constant per row, a misaligned one contains a block boundary and splits into two colours. Dynamic programming then finds the partition of each axis into exactly N strips minimising total cost, with a soft penalty for deviating from the mean step. Sweeping N yields a U-shaped cost curve whose minimum lands on the true cell count. Edge coverage guards against picking a multiple of the real grid, and a square-pixel assumption fixes the axis whose minimum is shallow.

Cell colours are decided by an area vote against the sprite palette rather than by sampling the cell centre, which repairs cells that drifted off their block.

Measured on synthetic data with known ground truth: 92.0% of pixels recovered and 100% correct output size on a held-out set, versus 84.2% / 17% for a uniform grid. Outputs 1:1 PNG, a nearest-neighbour preview, indexed PNG, a .gpl palette, and a native .aseprite file written directly from the format spec — indexed, with a palette containing exactly the colours used.

Requires only numpy and Pillow. Run python img2pixels.py -help.

About

Converter of AI "pixel" art into real pixel art with a palette in Aseprite.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages