Пролог
Эта тема время от времени всплывает здесь, в Stack Overflow, но обычно удаляется из-за плохо написанного вопроса. Я видел много таких вопросов, а затем молчание со стороны OP (обычно низкая репутация), когда запрашивается дополнительная информация. Время от времени, если вводная информация для меня достаточно хороша, я решаю ответить ответом, и обычно он получает несколько голосов в день, пока активен, но затем через несколько недель вопрос удаляется / удаляется, и все начинается с начало. Поэтому я решил написать эти вопросы и ответы, чтобы я мог напрямую ссылаться на такие вопросы, не переписывая ответ снова и снова ...
Другая причина в том, что этот мета-поток нацелен на меня, поэтому, если вы получили дополнительную информацию, не стесняйтесь комментировать.
Вопрос
Как преобразовать растровое изображение в искусство ASCII с помощью C ++ ?
Некоторые ограничения:
- полутоновые изображения
- использование шрифтов с одинарным интервалом
- сохраняя простоту (не используя слишком сложные вещи для начинающих программистов)
Вот связанная страница Википедии в формате ASCII (спасибо @RogerRowland).
Здесь похожий лабиринт на преобразование ASCII Art Q&A.
Ответы:
Есть и другие подходы к преобразованию изображений в ASCII art, которые в основном основаны на использовании монотонных шрифтов . Для простоты я придерживаюсь только основ:
На основе интенсивности пикселей / области (затенение)
Этот подход обрабатывает каждый пиксель области пикселей как одну точку. Идея состоит в том, чтобы вычислить среднюю интенсивность серой шкалы этой точки и затем заменить ее символом с интенсивностью, достаточно близкой к вычисленной. Для этого нам понадобится список используемых персонажей, каждый с заранее вычисленной интенсивностью. Назовем это персонажем
map
. Чтобы быстрее выбрать, какой персонаж лучше всего подходит для какой интенсивности, есть два способа:Карта характера с линейно распределенной интенсивностью
Поэтому мы используем только символы, у которых есть разница в интенсивности на одном и том же шаге. Другими словами, при сортировке по возрастанию:
Кроме того, когда наш персонаж
map
отсортирован, мы можем вычислить его непосредственно по интенсивности (поиск не требуется).Карта характера произвольно распределенной интенсивности
Итак, у нас есть набор используемых персонажей и их интенсивность. Нам нужно найти интенсивность, наиболее близкую к интенсивности.
intensity_of(dot)
Итак, снова, если мы отсортировалиmap[]
, мы можем использовать двоичный поиск, в противном случае нам понадобитсяO(n)
цикл илиO(1)
словарь минимального расстояния поиска . Иногда для простоты характерmap[]
можно рассматривать как линейно распределенный, вызывая небольшое гамма-искажение, обычно невидимое в результате, если вы не знаете, что искать.Преобразование на основе интенсивности также отлично подходит для изображений в оттенках серого (а не только для черно-белых). Если вы выберете точку как один пиксель, результат станет большим (один пиксель -> один символ), поэтому для больших изображений вместо этого выбирается область (умноженная на размер шрифта), чтобы сохранить соотношение сторон и не увеличивать слишком сильно.
Как это сделать:
В качестве персонажа
map
вы можете использовать любые символы, но результат улучшается, если у персонажа есть пиксели, равномерно распределенные по области символа. Для начала можно использовать:char map[10]=" .,:;ox%#@";
отсортированы по убыванию и претендуют на линейное распределение.
Таким образом, если интенсивность пикселя / области равна,
i = <0-255>
то заменяющий символ будетmap[(255-i)*10/256];
Если
i==0
тогда пиксель / область черный, еслиi==127
тогда пиксель / область серые, и еслиi==255
тогда пиксель / область белые. Вы можете экспериментировать с разными персонажами внутриmap[]
...Вот мой древний пример на C ++ и VCL:
Вам нужно заменить / игнорировать VCL, если вы не используете среду Borland / Embarcadero .
mm_log
это памятка, в которой выводится текстbmp
это входное растровое изображениеAnsiString
это строка типа VCL, индексированная с 1, а не с 0, какchar*
!!!Это результат: Пример изображения с небольшой интенсивностью NSFW
Слева находится изображение ASCII art (размер шрифта 5 пикселей), а справа входное изображение увеличено в несколько раз. Как видите, на выходе больше пиксель -> символ. Если вы используете большие области вместо пикселей, тогда масштаб будет меньше, но, конечно, результат будет менее приятным визуально. Этот подход очень легко и быстро кодировать / обрабатывать.
Когда вы добавляете более сложные вещи, например:
Затем вы можете обрабатывать более сложные изображения с лучшими результатами:
Вот результат в соотношении 1: 1 (увеличьте масштаб, чтобы увидеть символы):
Конечно, при выборке площади вы теряете мелкие детали. Это изображение того же размера, что и в первом примере с областями:
Расширенный пример изображения с небольшой интенсивностью NSFW
Как видите, это больше подходит для изображений большего размера.
Подгонка символов (гибрид между штриховкой и сплошным рисунком ASCII)
Этот подход пытается заменить область (без точек в один пиксель) символом с аналогичной интенсивностью и формой. Это приводит к лучшим результатам даже при использовании шрифтов большего размера по сравнению с предыдущим подходом. С другой стороны, этот подход, конечно, немного медленнее. Есть и другие способы сделать это, но основная идея состоит в том, чтобы вычислить разницу (расстояние) между областью изображения (
dot
) и визуализированным символом. Вы можете начать с наивной суммы абсолютной разницы между пикселями, но это приведет к не очень хорошим результатам, потому что даже сдвиг на один пиксель сделает расстояние большим. Вместо этого вы можете использовать корреляцию или другие показатели. Общий алгоритм почти такой же, как и в предыдущем подходе:Таким образом , равномерно разделить изображение на (полутоновые) прямоугольных областях точки «s
в идеале с тем же соотношением сторон, что и отображаемые символы шрифта (это сохранит соотношение сторон. Не забывайте, что символы обычно немного перекрываются по оси x)
Вычислите интенсивность каждой области (
dot
)Замените его персонажем персонажа
map
с наиболее близкой интенсивностью / формойКак мы можем вычислить расстояние между символом и точкой? Это самая сложная часть этого подхода. Экспериментируя, я выработал компромисс между скоростью, качеством и простотой:
Разделите область персонажа на зоны
map
).i=(i*256)/(xs*ys)
.Обработка исходного изображения в прямоугольных областях
Это результат для размера шрифта = 7 пикселей.
Как видите, результат визуально приятен даже при использовании большего размера шрифта (предыдущий пример подхода был с размером шрифта 5 пикселей). Размер выходного изображения примерно такой же, как у входного изображения (без увеличения). Лучшие результаты достигаются, потому что символы ближе к исходному изображению не только по интенсивности, но и по общей форме, и поэтому вы можете использовать более крупные шрифты и при этом сохранять детали (до определенного момента, конечно).
Вот полный код приложения преобразования на основе VCL:
Это простая форма application (
Form1
) с единственнымTMemo mm_txt
в ней. Он загружает изображение,"pic.bmp"
а затем в зависимости от разрешения выбирает, какой подход использовать для преобразования в текст, который сохраняется"pic.txt"
и отправляется в заметку для визуализации.Для тех, у кого нет VCL, игнорируйте материал VCL и заменяйте его
AnsiString
любым строковым типом, который у вас есть, а такжеGraphics::TBitmap
любым имеющимся у вас классом растрового изображения или изображения с возможностью доступа к пикселям.Очень важно отметить, что здесь используются настройки
mm_txt->Font
, поэтому убедитесь, что вы установили:Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
чтобы это работало правильно, иначе шрифт не будет обрабатываться как одинарный. Колесо мыши просто изменяет размер шрифта вверх / вниз, чтобы увидеть результаты для разных размеров шрифта.
[Ноты]
3x3
вместо этого какую-нибудь сетку .Сравнение
Наконец, вот сравнение двух подходов к одному и тому же входу:
Зеленая точка , выделенные изображения сделаны с подъездным # 2 и красными с # 1 , всеми по размеру шрифта в шесть пикселей. Как вы можете видеть на изображении лампочки, подход с учетом формы намного лучше (даже если №1 сделан на исходном изображении с 2-кратным увеличением).
Классное приложение
Читая сегодняшние новые вопросы, я получил представление о классном приложении, которое захватывает выбранную область рабочего стола и непрерывно передает ее конвертеру ASCIIart и просматривает результат. После часа написания кода все готово, и я настолько доволен результатом, что мне просто нужно добавить его сюда.
ОК, приложение состоит всего из двух окон. Первое главное окно - это в основном мое старое окно конвертера без выбора изображения и предварительного просмотра (в нем есть все, что указано выше). В нем есть только предварительный просмотр и настройки преобразования ASCII. Второе окно представляет собой пустую форму с прозрачной внутри для выбора области захвата (никакой функциональности).
Теперь по таймеру я просто захватываю выделенную область формой выбора, передаю ее в преобразование и просматриваю ASCIIart .
Таким образом, вы заключаете область, которую хотите преобразовать, в окно выбора и просматриваете результат в главном окне. Это может быть игра, вьювер и т.д. Выглядит это так:
Так что теперь я могу смотреть даже видео в ASCIIart для развлечения. Некоторые действительно хороши :).
Если вы хотите попробовать реализовать это в GLSL , взгляните на это:
источник
3x3
зон и сравнить DCT, но я думаю, это сильно снизит производительность.