Конструируем AutoDiver v1.0
AutoDiver v1.0
Сверхзадача автоматизации формулируется просто: всё, что может быть автоматизировано — должно быть автоматизировано (и рано или поздно — будет). На странный вопрос, который почему-то часто рано или поздно понурым упрёком всплывает в подобном контексте: "возможно ли автоматизировать творчество?", следует сразу ответить "а мы и не пытаемся".
Автоматизация касается лишь рутинных составляющих процесса, на которые в результате тратится на порядок меньше времени и, как следствие, творцу становится легче удержать в памяти искру озарения и запечатлеть её сияние.
Итак, какова одна из самых рутинных составляющих процесса рисования на ZX Spectrum?
(Впрочем, уверен, что, возможно, кто-то может относиться к ней, как одной из самых увлекательных и творческих частей: решение нетривиальной логической задачи, приносящее удовольствие.)
Я бы назвал самым рутинным процесс первоначальной подгонки цветного наброска под атрибутную сетку спектрума. И поручил бы решение этой задачи компьютеру.
Итак у нас есть цветной набросок 256x192 цвет-на-точку (совершенно случайно в палитре спектрума 8/15 цветов), нам нужно научить компьютер "подгонять" изображение под сетку (это задача №1), а потом определять хорош ли данный вариант подгонки или нет (это задача №2).
Как живой Diver подгоняет набросок под атрибутную сетку?
По наитию.
А как это делает Autodiver?
Для первой операции существует всего 8*8 = 64 варианта сдвига. (Если сдвинуть на 8 вниз или влево — смещение внутри атрибутной сетки будет таким же как исходное состояние картинки.)
Для второй операции имеет смысл масштабирование в пределах от 100% до 200%.
Вращение имеет смысл от -45 до +45 градусов (большие значения угла поворота — дадут нам повторы относительного расположения внутри сетки).
Отлично, в самом дотошном случае комбинация подобных преобразований даст нам 64 * 256 * 90 = 1474560 вариантов.
Не так уж и много.
Но на практике нам понадобится ещё меньше по следующим причинам: вращение исходника на -45 и +45 градусов не всегда приемлемо и на самом деле достаточно поворотов от -15 до +15, низкое разрешение также позволяет повысить шаг изменения угла поворота: для получения различимых результатов достаточно двух-трёх градусов.
Масштабирование в 2 раза также не всегда приемлемо и достаточно перебрать масштабы от 100% до 150% с таким же шагом 2-4 пикселя.
64 * 32 * 10 = 20480 вариантов.
И вот есть у нас двадцать тысяч картинок.
Как живой Diver выбирает какой из них лучше? Каковы критерии оценки?
Основная причина "ухудшения" картинки при накладывании спектрумовских ограничений — это потеря пикселей третьего цвета в знакоместе, ибо в одном знакоместе возможны только два.
Таким образом метрика очевидна: сумма потенциальных потерь в каждом знакоместе и будет искомой мерой.
В каждом знакоместе картинки мы считаем количество пикселей каждого цвета, сортируем цвета по убыванию распространённости и подсчитываем количество пикселей с третьего места по частоте: их мы потеряем при накладывании ограничений.
Затем суммируем "потери" и получаем рейтинг картинки.
Чем он меньше — тем меньше потерь, тем мы ближе к идеалу.
N.B. Кажется, что улучшить эту схему можно введя ещё одну сущность: маску значимости пикселей (по сути — это монохромная картинка с разрешением аналогичным исходной картинке, в которой пиксели соответствующие значащим из первой картинки — белые, а соответствующие незначащим из первой картинки — чёрные). С маской можно игнорировать потерю "незначащих" пикселей. Но на самом деле проще избавиться от незначащих пикселей непосредственно на исходной картинке.
Самым простым и мощным инструментом для пакетного преобразования картинок практически в любых исходных форматах является ImageMagick — он есть для всех основных платформ.
Описываемое выше преобразование с помощью ImageMagick будет выглядеть так:
convert source_image.png -gravity center -filter point -resize #{newsize} -rotate #{rotateangle} +repage -crop 256x192#{offsetx}#{offsety} -gravity forget -extent 256x192 target_image.png
где:
Например:
convert in.png -gravity center -filter point -resize 257x -rotate 8 +repage -crop 256x192+4+4 -gravity forget -extent 256x192+4+4 ./eval/in.pngr8s257x4y4.png
Для генерации 20 тысяч таких строк мы воспользуемся соответствующим генератором на ruby "gen.rb":
ruby gen.rb in.png 30 3 128 4 1 0
где:
Результатом работы генератора является командный .sh или .bat файл, который следует запустить для начала массовых преобразований над картинкой.
После выполнения массовых преобразований исходные картинки будут в каталоге ./eval (от evaluate) и ./eval_mask (если маска использовалась).
Оценка выполняется с помощью файла ev.rb:
ruby ev.rb
Результатом его работы являются файлы: result.csv (с результатами оценки всех картинок) и best.csv (c результатами, отсортированными по рейтингу) в каталоге ./best.
А также в каталог ./best копируется несколько лучших картинок (с лучшим рейтингом).
Надеюсь вы сможете по достоинству оценить вашего домашнего персонального автодайвера, готового прийти к вам на помощь и выбрать лучший вариант цветного наброска (лучше если он будет уже в спектрумовской палитре — автодайвер это оценит и порадуется). Надеюсь также этот инструмент освободит вас от рутины и вы больше времени сможете уделить творчеству.
Возможно вы сами доработаете автодайвера, научите более хитрым алгоритмам оценки, учитывающим цветовое расстояние между цветами в знакоместе, усреднённый цвет участков с дизерингом или что-нибудь ещё не менее увлекательное.
Исходники выложены тут: github.com/oisee/autodiver
Fork'айте, дорабатывайте, переписывайте на своих любимых языках программирования (первую часть «генератор» — можно легко переписать на .bat/.sh)
Спасибо за внимание =)
Q:Не убъёт ли autodiver GFX-сцену?
A:Нет, autodiver исключительный пацифист, также как autosiril, который за 4 года существования никого не только не убил, но и не оживил.
Q:Не является ли название autodiver — оскорбительным для Diver'а?
Отвечает Diver/4D:
A:Нет не является абсолютно. Мне даже польстило. Вообще то автодайвера мы придумали вместе с сирилом. Просто программер он, а не я. Поэтому он и написал его. {отя у меня есть прототип, который писал я сам в bat.
Q:Не является ли название autosiril — оскорбительным для Siril'а?
Отвечает Taylor S.:
A: shake, shake, shake, shake, shake.
Q:Как пользоваться этим вашим конвертором?
A:Autodiver, как и Diver против конверсий, а потому он ничего не конвертирует. Autodiver просто просматривает тысячи вариантов преобразованного изображения и рекомендует лучший. Тот, который потеряет наименьшее количество пикселей при накладывании ограничений атрибутной сетки.
A:Ок:
Сверхзадача автоматизации формулируется просто: всё, что может быть автоматизировано — должно быть автоматизировано (и рано или поздно — будет). На странный вопрос, который почему-то часто рано или поздно понурым упрёком всплывает в подобном контексте: "возможно ли автоматизировать творчество?", следует сразу ответить "а мы и не пытаемся".
Автоматизация касается лишь рутинных составляющих процесса, на которые в результате тратится на порядок меньше времени и, как следствие, творцу становится легче удержать в памяти искру озарения и запечатлеть её сияние.
Итак, какова одна из самых рутинных составляющих процесса рисования на ZX Spectrum?
(Впрочем, уверен, что, возможно, кто-то может относиться к ней, как одной из самых увлекательных и творческих частей: решение нетривиальной логической задачи, приносящее удовольствие.)
Я бы назвал самым рутинным процесс первоначальной подгонки цветного наброска под атрибутную сетку спектрума. И поручил бы решение этой задачи компьютеру.
Итак у нас есть цветной набросок 256x192 цвет-на-точку (совершенно случайно в палитре спектрума 8/15 цветов), нам нужно научить компьютер "подгонять" изображение под сетку (это задача №1), а потом определять хорош ли данный вариант подгонки или нет (это задача №2).
Задача №1
Как живой Diver подгоняет набросок под атрибутную сетку?
По наитию.
А как это делает Autodiver?
- Двигает картинку вверх-вниз и вправо-влево.
- Масштабирует картинку (увеличивает-уменьшает) в каких-то пределах.
- Вращает картинку.
Для первой операции существует всего 8*8 = 64 варианта сдвига. (Если сдвинуть на 8 вниз или влево — смещение внутри атрибутной сетки будет таким же как исходное состояние картинки.)
Для второй операции имеет смысл масштабирование в пределах от 100% до 200%.
Вращение имеет смысл от -45 до +45 градусов (большие значения угла поворота — дадут нам повторы относительного расположения внутри сетки).
Отлично, в самом дотошном случае комбинация подобных преобразований даст нам 64 * 256 * 90 = 1474560 вариантов.
Не так уж и много.
Но на практике нам понадобится ещё меньше по следующим причинам: вращение исходника на -45 и +45 градусов не всегда приемлемо и на самом деле достаточно поворотов от -15 до +15, низкое разрешение также позволяет повысить шаг изменения угла поворота: для получения различимых результатов достаточно двух-трёх градусов.
Масштабирование в 2 раза также не всегда приемлемо и достаточно перебрать масштабы от 100% до 150% с таким же шагом 2-4 пикселя.
64 * 32 * 10 = 20480 вариантов.
Задача №2
И вот есть у нас двадцать тысяч картинок.
Как живой Diver выбирает какой из них лучше? Каковы критерии оценки?
Основная причина "ухудшения" картинки при накладывании спектрумовских ограничений — это потеря пикселей третьего цвета в знакоместе, ибо в одном знакоместе возможны только два.
Таким образом метрика очевидна: сумма потенциальных потерь в каждом знакоместе и будет искомой мерой.
В каждом знакоместе картинки мы считаем количество пикселей каждого цвета, сортируем цвета по убыванию распространённости и подсчитываем количество пикселей с третьего места по частоте: их мы потеряем при накладывании ограничений.
Затем суммируем "потери" и получаем рейтинг картинки.
Чем он меньше — тем меньше потерь, тем мы ближе к идеалу.
N.B. Кажется, что улучшить эту схему можно введя ещё одну сущность: маску значимости пикселей (по сути — это монохромная картинка с разрешением аналогичным исходной картинке, в которой пиксели соответствующие значащим из первой картинки — белые, а соответствующие незначащим из первой картинки — чёрные). С маской можно игнорировать потерю "незначащих" пикселей. Но на самом деле проще избавиться от незначащих пикселей непосредственно на исходной картинке.
Практика
Генерация
Самым простым и мощным инструментом для пакетного преобразования картинок практически в любых исходных форматах является ImageMagick — он есть для всех основных платформ.
Описываемое выше преобразование с помощью ImageMagick будет выглядеть так:
convert source_image.png -gravity center -filter point -resize #{newsize} -rotate #{rotateangle} +repage -crop 256x192#{offsetx}#{offsety} -gravity forget -extent 256x192 target_image.png
где:
- #{new_size} — это новый размер
- #{rotate_angle} — угол поворота
- #{offsetx}#{offsety} — смещения по x и y
Например:
convert in.png -gravity center -filter point -resize 257x -rotate 8 +repage -crop 256x192+4+4 -gravity forget -extent 256x192+4+4 ./eval/in.pngr8s257x4y4.png
Для генерации 20 тысяч таких строк мы воспользуемся соответствующим генератором на ruby "gen.rb":
ruby gen.rb in.png 30 3 128 4 1 0
где:
- in.png — исходная картинка
- 30 — угол отклонения между первым и последним вариантом (то есть от -15 до +15)
- 3 — шаг угла вращения
- 128 — максимальный добавочный размер (256+128 = 384)
- 4 — шаг масштабирования
- 1 — необходимость перебора всех 64 смещений
- 0 — отсутствие маски (над маской, которая должна носить имя mask{sourcefile} — то есть в нашем случае maskin.png проделываются все те же операции вращения, смещения и масштабирования что и над исходной картинкой с тем лишь отличием, что маска преобразуется в монохром и складывается в каталог ./evalmask, а не ./eval, в дальнейшем маска используется при оценке).
Результатом работы генератора является командный .sh или .bat файл, который следует запустить для начала массовых преобразований над картинкой.
После выполнения массовых преобразований исходные картинки будут в каталоге ./eval (от evaluate) и ./eval_mask (если маска использовалась).
Оценка
Оценка выполняется с помощью файла ev.rb:
ruby ev.rb
Результатом его работы являются файлы: result.csv (с результатами оценки всех картинок) и best.csv (c результатами, отсортированными по рейтингу) в каталоге ./best.
А также в каталог ./best копируется несколько лучших картинок (с лучшим рейтингом).
Резюме
Надеюсь вы сможете по достоинству оценить вашего домашнего персонального автодайвера, готового прийти к вам на помощь и выбрать лучший вариант цветного наброска (лучше если он будет уже в спектрумовской палитре — автодайвер это оценит и порадуется). Надеюсь также этот инструмент освободит вас от рутины и вы больше времени сможете уделить творчеству.
Возможно вы сами доработаете автодайвера, научите более хитрым алгоритмам оценки, учитывающим цветовое расстояние между цветами в знакоместе, усреднённый цвет участков с дизерингом или что-нибудь ещё не менее увлекательное.
Исходники выложены тут: github.com/oisee/autodiver
Fork'айте, дорабатывайте, переписывайте на своих любимых языках программирования (первую часть «генератор» — можно легко переписать на .bat/.sh)
Спасибо за внимание =)
UPD1:
FAQQ:Не убъёт ли autodiver GFX-сцену?
A:Нет, autodiver исключительный пацифист, также как autosiril, который за 4 года существования никого не только не убил, но и не оживил.
Q:Не является ли название autodiver — оскорбительным для Diver'а?
Отвечает Diver/4D:
A:Нет не является абсолютно. Мне даже польстило. Вообще то автодайвера мы придумали вместе с сирилом. Просто программер он, а не я. Поэтому он и написал его. {отя у меня есть прототип, который писал я сам в bat.
Q:Не является ли название autosiril — оскорбительным для Siril'а?
Отвечает Taylor S.:
A: shake, shake, shake, shake, shake.
Q:Как пользоваться этим вашим конвертором?
A:Autodiver, как и Diver против конверсий, а потому он ничего не конвертирует. Autodiver просто просматривает тысячи вариантов преобразованного изображения и рекомендует лучший. Тот, который потеряет наименьшее количество пикселей при накладывании ограничений атрибутной сетки.
UPD2
Q:Примеры!A:Ок:
Исходник | Вариант, выбранный autodiver | bmp2scr |
---|---|---|
30 комментариев
Вот ещё парочка:
на вход которой поступает img — картинка, px и py — координаты знакоместа (в пикселях):
def eval_cell(img,px,py)
cell = img.get_pixels(px,py,8,8) # получаем массив пикселей 8x8 (64) по коодинатам px и py
rcell = cell.reduce(Hash.new(0)) {|a,b| a[b] += 1;a} #из массива конструируем словарь/хэш у которого ключ — это цвет пикселя(сам пиксель), а значение — количество этих пикселей в массиве
acell = rcell.values.sort.reverse[2,64] #получаем массив пикселей, ниже второго места по распространённости в нашем знакоместе — «потерянные» пиксели
bcell = acell != nil? acell: [0] # если таких не было (в знакоместе только один или два цвета) — то создаём массив из одного элемента, равный нулю.
err = bcell.inject(0, &:+) # суммируем элементы массива — получаем сумму «потерянных» пикселей в знакоместе
end
то есть, предположим у нас есть массив:
1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1
1,1,1,1,0,0,0,0
1,1,1,1,0,0,0,0
1,1,1,1,0,0,0,0
1,1,1,1,2,2,2,3
То на первом этапе получим хэш 1=>44, 0=>16, 2=>3, 3=>1
затем получим все, что ниже «второго места»: 3, 1
затем суммируем: 4.
«Штрафной рейтинг» знакоместа = 4.
Повторить 768 раз =)
А вот битовая (монохромная) маска повышенной значимости — поможет штрафовать сильнее за утерю пикселей повышенной значимости. Ну то есть если обмазать такой маской рот или глаза — штраф за их утерю будет выше. =)
Но тут важно понимать требования для запуска:
1) поставить ImageMagick,
2) поставить ruby,
3) поставить RMagick (для этого нужно [a] поставить DevKit)
Оценивать расхождение с оригиналом я пытался по размеру файла с xor-разницей, сохраняемой в оптимизированный PNG. Но в результате оценку произвожу на глаз, ориентируясь по наиболее важным элементам картинки. Фактически в BMP2SCR происходит то же самое, но наобум. Я лишь снизил для себя вероятность потери оптимальных вариантов.
1. Можно ли распознавать формы на оригинале и пододвигать их отдельно, не всей картинкой?
2. Если нет, то, может быть, тупо каждое знакоместо двигать отдельно? Результат на больших картинках может быть любопытным.
Вообще, это можно сделать уже сейчас, разделив исходник на объекты и просто обработав последовательность картинок.
Все эти идеи обсуждались в процессе мозгоштурма нового идеального графического редактора: оказалось что простой интнрактивной многослойности со слоями «цвет на точку» при моментальном перерендеринге в спекрум-атрибуты при любом изменении — достаточно для значительного упрощения рисования и снятия рутины.
Неограниченное количество слоёв со спектрум-палитрой в режиме «цвет на точку».
Каждый слой может независимо от остальных смещаться, двигаться и т.п. — редактироваться.
У каждого слоя есть «вес» при слиянии и рендеринге в спектрум режим: область просмотра которого всегда видна и доступна, любые изменения в любом из слоёв сразу отражаются там.
Еще можно учитывать смещение конкретных знакомест для коррекции приоритета смещения в соседних знакоместах. Типа, если мы верхнее знакоместо взяли со сдвигом на два пикселя вниз, то нижнее знакоместо с бОльшей вероятностью (при прочих равных? по какой-то количественной оценке?) должно выбрать смещение в ту же сторону.
Пытаемся считать из оригинала блок 8*8 по координатам в 14:30, 15:30, 16:30, 17:31, 18:31… 18:34.
Для каждого блока подсчитываем количество цветов и выбираем блок с наименьшим количеством/наиболее выгодно сочетаемыми цветами.
Для следующего знакоместа частично (!) учитываем уже рассчитанное направление смещения в соседних.
1) мы идём «в обратную сторону», то есть пляшем от спектрумовской атрибутной сетки.
2) выбираем стартовое знакоместо и подгоняем, двигая (всю?) картинку под него на +-2 пикселя (что даёт нам 5 вариантов по горизонтали и 5 по вертикали, итого: 25 вариантов).
3) переходим к следующему соседнему знакоместу и также подгоняем двигая всю картинку (25 вариантов).
5) перебираем так всю атрибутную сетку: для каждого знакоместа у нас есть массив лучших вариантов, отсортированных по убыванию.
6) самый главный этап: пытаемся для каждого знакоместа выбрать лучший вариант с учётом лучших вариантов для соседних знакомест (тут количество переборов растёт по экспоненте).
Да, получается 25 вариантов на каждое знакоместо — больше, наверное, смысла нет делать, будет сильно портиться геометрия оригинала.
Для такого метода, возможно, потребовалось бы все функции писать самому — отдельно сварганить такое через bmp2scr гипотетически возможно, но сложно.
велосипедметод 100% не универсален. С ним самая большая беда, что, пока не реализуешь, результат непредсказуем, а на реализацию надо потратить время всё же.