Створити ASCII ст


14

З огляду на чорно-біле зображення у будь-якому розумному форматі без втрат, як вхід, виведіть ASCII зображення, максимально наближене до вхідного зображення.

Правила

  • Можуть використовуватись лише канали ліній і ASCII байти 32-127.
  • Вхідне зображення буде обрізане, щоб не було сторонніх пробілів навколо зображення.
  • Подання повинні бути здатні виконати весь корпус балів за 5 хвилин.
  • Прийнятний лише необроблений текст; відсутні формати насиченого тексту.
  • Шрифт, використаний при оцінці, - 20-pt Linux Libertine .
  • Вихідний текстовий файл при перетворенні на зображення, як описано нижче, повинен мати ті ж розміри, що і вхідне зображення, у межах 30 пікселів в будь-якому вимірі.

Оцінка балів

Ці зображення будуть використані для оцінки:

Ви можете завантажити zipfile зображень тут .

Подання не повинні бути оптимізовані для цього корпусу; скоріше, вони повинні працювати для будь-яких 8 чорно-білих зображень подібних розмірів. Я залишаю за собою право змінювати зображення в корпусі, якщо я підозрюю, що матеріали оптимізовані для цих конкретних зображень.

Оцінка буде проведена за цим сценарієм:

#!/usr/bin/env python
from __future__ import print_function
from __future__ import division
# modified from http://stackoverflow.com/a/29775654/2508324
# requires Linux Libertine fonts - get them at https://sourceforge.net/projects/linuxlibertine/files/linuxlibertine/5.3.0/
# requires dssim - get it at https://github.com/pornel/dssim
import PIL
import PIL.Image
import PIL.ImageFont
import PIL.ImageOps
import PIL.ImageDraw
import pathlib
import os
import subprocess
import sys

PIXEL_ON = 0  # PIL color to use for "on"
PIXEL_OFF = 255  # PIL color to use for "off"

def dssim_score(src_path, image_path):
    out = subprocess.check_output(['dssim', src_path, image_path])
    return float(out.split()[0])

def text_image(text_path):
    """Convert text file to a grayscale image with black characters on a white background.

    arguments:
    text_path - the content of this file will be converted to an image
    """
    grayscale = 'L'
    # parse the file into lines
    with open(str(text_path)) as text_file:  # can throw FileNotFoundError
        lines = tuple(l.rstrip() for l in text_file.readlines())

    # choose a font (you can see more detail in my library on github)
    large_font = 20  # get better resolution with larger size
    if os.name == 'posix':
        font_path = '/usr/share/fonts/linux-libertine/LinLibertineO.otf'
    else:
        font_path = 'LinLibertine_DRah.ttf'
    try:
        font = PIL.ImageFont.truetype(font_path, size=large_font)
    except IOError:
        print('Could not use Libertine font, exiting...')
        exit()

    # make the background image based on the combination of font and lines
    pt2px = lambda pt: int(round(pt * 96.0 / 72))  # convert points to pixels
    max_width_line = max(lines, key=lambda s: font.getsize(s)[0])
    # max height is adjusted down because it's too large visually for spacing
    test_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    max_height = pt2px(font.getsize(test_string)[1])
    max_width = pt2px(font.getsize(max_width_line)[0])
    height = max_height * len(lines)  # perfect or a little oversized
    width = int(round(max_width + 40))  # a little oversized
    image = PIL.Image.new(grayscale, (width, height), color=PIXEL_OFF)
    draw = PIL.ImageDraw.Draw(image)

    # draw each line of text
    vertical_position = 5
    horizontal_position = 5
    line_spacing = int(round(max_height * 0.8))  # reduced spacing seems better
    for line in lines:
        draw.text((horizontal_position, vertical_position),
                  line, fill=PIXEL_ON, font=font)
        vertical_position += line_spacing
    # crop the text
    c_box = PIL.ImageOps.invert(image).getbbox()
    image = image.crop(c_box)
    return image

if __name__ == '__main__':
    compare_dir = pathlib.PurePath(sys.argv[1])
    corpus_dir = pathlib.PurePath(sys.argv[2])
    images = []
    scores = []
    for txtfile in os.listdir(str(compare_dir)):
        fname = pathlib.PurePath(sys.argv[1]).joinpath(txtfile)
        if fname.suffix != '.txt':
            continue
        imgpath = fname.with_suffix('.png')
        corpname = corpus_dir.joinpath(imgpath.name)
        img = text_image(str(fname))
        corpimg = PIL.Image.open(str(corpname))
        img = img.resize(corpimg.size, PIL.Image.LANCZOS)
        corpimg.close()
        img.save(str(imgpath), 'png')
        img.close()
        images.append(str(imgpath))
        score = dssim_score(str(corpname), str(imgpath))
        print('{}: {}'.format(corpname, score))
        scores.append(score)
    print('Score: {}'.format(sum(scores)/len(scores)))

Процедура підрахунку балів:

  1. Запустіть подання для кожного зображення корпусу, вивівши результати у .txtфайли з тим самим стовбуром, що і файл корпусу (робиться вручну).
  2. Перетворіть кожен текстовий файл у зображення PNG, використовуючи 20-кратний шрифт, обрізаючи пробіл.
  3. Змініть розмір отриманого зображення до розмірів вихідного зображення, використовуючи повторне виділення Lanczos.
  4. Порівняйте кожне текстове зображення з вихідним зображенням, використовуючи dssim.
  5. Виведіть бал dssim для кожного текстового файлу.
  6. Виведіть середній бал.

Структурна схожість (показник, за допомогою якого dssimобчислюють бали) - це метрика, заснована на зорі людини та ідентифікації об'єктів у зображеннях. Простіше кажучи: якщо два зображення будуть схожі на людину, вони (мабуть) матимуть низьку оцінку dssim.

Виграшним буде подання з найнижчою середньою оцінкою.

пов'язані


6
"Чорно-біле", як у "нуль / один" або скільки рівнів сірого?
Луїс Мендо

2
@DonMuesli 0 і 1.
Мего

Чи можете ви пояснити, що ви маєте на увазі під "Виведення результатів у .txtфайли"? Чи повинен текст програми виводити текст, який потрапляє у файл, або ми повинні виводити файл безпосередньо?
DanTheMan

@DanTheMan Будь-яка прийнятна. Якщо ви виходите на STDOUT, однак, результат буде потрібно перенаправляти у файл для цілей вибору.
Mego

Чи не слід вказати обмеження для вирішення? В іншому випадку ми могли б створити, скажімо, зображення символів розміром 10000 на 10000, яке, коли, зменшивши його розмір, буде цілком відповідати оригінальним зображенням, а окремі символи були б нерозбірливими крапками. Розмір шрифту не має значення, чи є вихідне зображення величезним.
DavidC

Відповіді:


6

Java, оцінка 0,57058675

Це насправді я вперше роблю маніпуляції із зображеннями, так що це якось незручно, але я думаю, що це вийшло нормально.

Я не міг змусити dssim працювати на своїй машині, але мені вдалося зробити зображення за допомогою PIL.

Цікаво, що шрифт говорить мені на Java, що кожен із символів, які я використовую, має ширину 6. Ви бачите, що в моїй програмі FontMetrics::charWidthє 6всі персонажі, якими я користувався. {}Логотип виглядає досить пристойно в моно шрифт. Але чомусь рядки насправді не вирівнюються у повному текстовому файлі. Я звинувачую лігатури. (І так, я повинен використовувати правильний шрифт.)

З односхилим шрифтом:

                                                                                      .
                         .,:ff:,                                                   ,:fff::,.
                ,ff .fIIIIIf,                                                         .:fIIIIIf.:f:.
            .,:III: ,ff::                       ..,,            ,,..                      ,:fff, IIII.,
          :IIf,f:,:fff:,                  .:fIIIIIII.          .IIIIIIIf:.                 .,:fff:,ff IIf,
       ,.fIIIf,:ffff,                   ,IIIIIII:,,.            .,,:IIIIIII.                  .:ffff:,IIII,:.
     ,III.::.,,,,,.                     IIIIII:                      ,IIIIII                     ,,,,,.,:,:IIf
     IIIII :ffIIf,                      IIIIII,                      .IIIIII                      :IIIf:,.IIIIf.
  ,II,fIf.:::,..                        IIIIII,                      .IIIIII                       ..,:::,,If::II
  IIIIf.  ,:fII:                       .IIIIII,                      .IIIIII.                       IIff:.  :IIII:
 ::IIIIf:IIIf: .                  ,::fIIIIIII,                        ,fIIIIIIf::,                   ,ffIII,IIIIf,,
:IIf:::    .,fI:                  IIIIIIIII:                            :IIIIIIIIf                  If:,    .::fIIf
 IIIIII, :IIIIf                     .,:IIIIIIf                        fIIIIII:,.                    ,IIIII. fIIIII:
 ,:IIIII ff:,   f,                      IIIIII,                      .IIIIII                      f.  .::f::IIIIf,.
 fIf::,,     ,fIII                      IIIIII,                      .IIIIII                     :III:      ,,:fII.
  fIIIIIIf, :IIIIf   ,                  IIIIII,                      .IIIIII                 .,  ,IIIII. :fIIIIII,
   .:IIIIIII,ff,    :II:                IIIIIIf                      fIIIIII               .fII.   .:ff:IIIIIIf,
     :fffff:,      IIIIIf   ,            :IIIIIIIfff            fffIIIIIII:           ..   IIIII:      ::fffff,
      .fIIIIIIIf:, fIIII,   ,IIf,           ,:ffIIII.          .IIIIff:,          .:fII    fIIII,.:ffIIIIIII:
         ,fIIIIIIIIIf:,     ,IIIII:  .,::,                               .,::,  .IIIIII      ::fIIIIIIIIf:.
             :fffffff,      .fIIIII,   .IIIIIf:                     ,:fIIII:    IIIIII:       :fffffff,
              .:fIIIIIIIIIIIIffffI:      IIIIIIII.                :IIIIIII:     .fIffffIIIIIIIIIIII:,
                   ,:fIIIIIIIIIIIf,       .:fIIIII               ,IIIIIf,        :IIIIIIIIIIIff,.
                         .:ffffffffIIIIIIIIIIIfff:.              ,ffffIIIIIIIIIIIfffffff:,
                             .,:ffIIIIIIIIIIIIIIIIf,   .,,,,.  .:fIIIIIIIIIIIIIIIIff:,.
                                       ....... .,,:fffff:.,:fffff:,.  .......
                                    ..,,:fffIIIIf:,.            .,:fIIIIff::,,..
                                   .IIIIIf:,.                          .,:fIIIII
                                     f,                                      ,f

Після запуску його через інструмент зображення:

{} логотип

У всякому разі, ось фактичний код.

//package cad97;

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;

public final class AsciiArt {

    private static final Font LINUX_LIBERTINE = new Font("LinLibertine_DRah", Font.PLAIN, 20);
    private static final FontMetrics LL_METRICS = Toolkit.getDefaultToolkit().getFontMetrics(LINUX_LIBERTINE);
    // Toolkit::getFontMetrics is deprecated, but that's the only way to get FontMetrics without an explicit Graphics environment.
    // If there's a better way to get the widths of characters, please tell me.

    public static void main(String[] args) throws IOException {
        File jar = new java.io.File(AsciiArt.class.getProtectionDomain().getCodeSource().getLocation().getPath());
        if (args.length != 1) {
            String jarName = jar.getName();
            System.out.println("Usage: java -jar " + jarName + " file");
        } else {
            File image = new File(args[0]);
            try (InputStream input = new FileInputStream(image)) {
                String art = createAsciiArt(ImageIO.read(input), LINUX_LIBERTINE, LL_METRICS);
                System.out.print(art); // If you want to save as a file, change this.
            } catch (FileNotFoundException fnfe) {
                System.out.println("Unable to find file " + image + ".");
                System.out.println("Please note that you need to pass the full file path.");
            }
        }
    }

    private static String createAsciiArt(BufferedImage image, Font font, FontMetrics metrics) {
        final int height = metrics.getHeight();
        final Map<Character,Integer> width = new HashMap<>();
        for (char c=32; c<127; c++) { width.put(c, metrics.charWidth(c)); }

        StringBuilder art = new StringBuilder();

        for (int i=0; i<=image.getHeight(); i+=height) {
            final int tempHeight = Math.min(height, image.getHeight()-i);
            art.append(createAsciiLine(image.getSubimage(0, i, image.getWidth(), tempHeight), width));
        }

        return art.toString();
    }

    private static String createAsciiLine(BufferedImage image, Map<Character,Integer> charWidth) {
        if (image.getWidth()<6) return "\n";
        /*
        I'm passing in the charWidth Map because I could use it, and probably a later revision if I
        come back to this will actually use non-6-pixel-wide characters. As is, I'm only using the
        6-pixel-wide characters for simplicity. They are those in this set: { !,./:;I[\]ft|}
        */
        assert charWidth.get(' ') == 6; assert charWidth.get('!') == 6;
        assert charWidth.get(',') == 6; assert charWidth.get('.') == 6;
        assert charWidth.get('/') == 6; assert charWidth.get(':') == 6;
        assert charWidth.get(';') == 6; assert charWidth.get('I') == 6;
        assert charWidth.get('[') == 6; assert charWidth.get('\\') == 6;
        assert charWidth.get(']') == 6; assert charWidth.get('f') == 6;
        assert charWidth.get('t') == 6; assert charWidth.get('|') == 6;

        // Measure whiteness of 6-pixel-wide sample
        Raster sample = image.getData(new Rectangle(6, image.getHeight()));
        int whiteCount = 0;
        for (int x=sample.getMinX(); x<sample.getMinX()+sample.getWidth(); x++) {
            for (int y=sample.getMinY(); y<sample.getMinY()+sample.getHeight(); y++) {
                int pixel = sample.getPixel(x, y, new int[1])[0];
                whiteCount += pixel==1?0:1;
            }
        }

        char next;

        int area = sample.getWidth()*sample.getHeight();

        if (whiteCount > area*0.9) {
            next = ' ';
        } else if (whiteCount > area*0.8) {
            next = '.';
        } else if (whiteCount > area*0.65) {
            next = ',';
        } else if (whiteCount > area*0.5) {
            next = ':';
        } else if (whiteCount > area*0.3) {
            next = 'f';
        } else {
            next = 'I';
        }

        return next + createAsciiLine(image.getSubimage(charWidth.get(','), 0, image.getWidth()-sample.getWidth(), image.getHeight()), charWidth);
    }

}

Збірка:

  • Переконайтеся , що у вас є JDK встановлений
  • Переконайтеся, що контейнер JDK знаходиться на вашій PATH (для мене це C:\Program Files\Java\jdk1.8.0_91\bin)
  • Збережіть файл як AsciiArt.java
  • javac AsciiArt.java
  • jar cvfe WhateverNameYouWant.jar AsciiArt AsciiArt.class

Використання:, java -jar WhateverNameYouWant.jar C:\full\file\path.pngдрукує до STDOUT

ПОТРІБНО вимагає збереження вихідного файла з глибиною 1 біт, а зразок для білого пікселя 1 .

Оцінка результатів:

corp/board.png: 0.6384
corp/Doppelspalt.png: 0.605746
corp/down.png: 1.012326
corp/img2.png: 0.528794
corp/pcgm.png: 0.243618
corp/peng.png: 0.440982
corp/phi.png: 0.929552
corp/text2image.png: 0.165276
Score: 0.57058675

1
Запустіть, -eaщоб увімкнути твердження. Це не змінить поведінку (за винятком, можливо, сповільнить її невелику кількість), оскільки твердження спрацьовують при збої програми, коли вони оцінюють falseі всі ці твердження проходять.
CAD97

Ах, я пропустив, що ви видалили декларацію про пакет. Це працює зараз. Я зарахую це, коли сьогодні отримаю кілька хвилин.
Мего

Вихід для board.png чомусь лише 4 рядки: gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f . Насправді, весь результат видається передчасно скороченим, коли я його запускаю, за винятком логотипу PPCG.
Мего

@Mego Я думаю, що це має відношення до висоти шрифту (24 px у звіті FontMetrics). Я змінив цикл рядків, щоб він помилявся на стороні однієї занадто багато ліній, а не однієї занадто мало, і це має працювати зараз. (дошка 5 ліній)
CAD97

Як правило, цей алгоритм бореться з меншими зображеннями, оскільки (на його думку) всі символи мають ширину 6 пікселів і висоту 24 пікселів, і все, на що він дивиться, - скільки пікселів включено у цьому суперпікселі.
CAD97
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.