Tạo nghệ thuật ASCII


14

Đưa ra một hình ảnh đen trắng ở bất kỳ định dạng lossless hợp lý nào làm đầu vào, nghệ thuật ASCII đầu ra càng gần với hình ảnh đầu vào càng tốt.

Quy tắc

  • Chỉ các nguồn cấp dữ liệu và các byte ASCII 32-127 có thể được sử dụng.
  • Hình ảnh đầu vào sẽ được cắt để không có khoảng trắng bên ngoài xung quanh hình ảnh.
  • Đệ trình phải có thể hoàn thành toàn bộ kho điểm trong vòng dưới 5 phút.
  • Chỉ văn bản thô được chấp nhận; không có định dạng văn bản phong phú.
  • Phông chữ được sử dụng trong tính điểm là Linux Libertine 20 pt .
  • Tệp văn bản đầu ra, khi được chuyển đổi thành hình ảnh như được mô tả bên dưới, phải có cùng kích thước với hình ảnh đầu vào, trong phạm vi 30 pixel ở cả hai chiều.

Chấm điểm

Những hình ảnh này sẽ được sử dụng để chấm điểm:

Bạn có thể tải về một zipfile của hình ảnh ở đây .

Đệ trình không nên được tối ưu hóa cho kho văn bản này; thay vào đó, chúng nên hoạt động cho bất kỳ 8 hình ảnh đen trắng có kích thước tương tự. Tôi bảo lưu quyền thay đổi hình ảnh trong kho văn bản nếu tôi nghi ngờ việc gửi đang được tối ưu hóa cho những hình ảnh cụ thể này.

Việc chấm điểm sẽ được thực hiện thông qua kịch bản này:

#!/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)))

Quy trình chấm điểm:

  1. Chạy đệ trình cho mỗi hình ảnh kho văn bản, xuất kết quả cho .txtcác tệp có cùng gốc với tệp kho văn bản (được thực hiện thủ công).
  2. Chuyển đổi từng tệp văn bản thành hình ảnh PNG, sử dụng phông chữ 20 điểm, cắt xén khoảng trắng.
  3. Thay đổi kích thước hình ảnh kết quả theo kích thước của hình ảnh gốc bằng cách sử dụng mô hình lại Lanczos.
  4. So sánh từng hình ảnh văn bản với hình ảnh gốc bằng cách sử dụng dssim.
  5. Xuất điểm dssim cho mỗi tệp văn bản.
  6. Xuất điểm trung bình.

Độ tương tự về cấu trúc (số liệu dssimtính toán điểm số) là một số liệu dựa trên tầm nhìn của con người và nhận dạng đối tượng trong hình ảnh. Nói một cách dễ hiểu: nếu hai hình ảnh trông giống con người, chúng sẽ (có thể) có điểm thấp dssim.

Bài dự thi sẽ là bài nộp có điểm trung bình thấp nhất.

liên quan


6
"Đen trắng" như trong "không / một" hay có bao nhiêu mức xám?
Luis Mendo

2
@DonMuesli 0 và 1.
Mego

Bạn có thể làm rõ ý của bạn bằng cách "Xuất kết quả ra .txttệp" không? Văn bản đầu ra của chương trình sẽ được dẫn đến một tập tin hay chúng ta nên xuất một tập tin trực tiếp?
DanTheMan

@DanTheMan Hoặc là chấp nhận được. Nếu bạn xuất ra STDOUT, đầu ra sẽ cần phải được chuyển hướng vào một tệp cho mục đích ghi điểm.
Mego

Bạn không nên chỉ định các ràng buộc độ phân giải? Mặt khác, chúng ta có thể tạo ra một hình ảnh 10000 x 10000 ký tự, khi được thu nhỏ lại, sẽ khớp với các hình ảnh gốc khá chặt chẽ và các ký tự riêng lẻ sẽ là các chấm không thể đọc được. Cỡ chữ không quan trọng nếu hình ảnh đầu ra rất lớn.
DavidC

Câu trả lời:


6

Java, điểm 0,57058675

Đây thực sự là lần đầu tiên tôi thực hiện thao tác hình ảnh nên hơi khó xử nhưng tôi nghĩ nó đã ổn.

Tôi không thể làm cho dssim hoạt động trên máy của mình, nhưng tôi đã có thể tạo hình ảnh bằng PIL.

Thật thú vị, phông chữ cho tôi biết trong Java rằng mỗi ký tự tôi đang sử dụng có chiều rộng 6. Bạn có thể thấy rằng trong chương trình của tôi FontMetrics::charWidth6dành cho tất cả các nhân vật mà tôi đã sử dụng. Các {}biểu tượng trông khá tốt trong một phông chữ monospace. Nhưng vì một số lý do, các dòng không thực sự xếp hàng trong tệp văn bản đầy đủ. Tôi đổ lỗi cho dây chằng. (Và vâng, tôi nên sử dụng đúng phông chữ.)

Trong phông chữ đơn cách:

                                                                                      .
                         .,: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

Sau khi chạy nó thông qua công cụ hình ảnh:

{} Logo

Dù sao, đây là mã thực tế.

//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);
    }

}

Biên dịch:

  • Hãy chắc chắn rằng bạn đã cài đặt JDK
  • Hãy chắc chắn rằng thùng JDK nằm trên PATH của bạn (đối với tôi là nó C:\Program Files\Java\jdk1.8.0_91\bin)
  • Lưu tệp dưới dạng AsciiArt.java
  • javac AsciiArt.java
  • jar cvfe WhateverNameYouWant.jar AsciiArt AsciiArt.class

Sử dụng: java -jar WhateverNameYouWant.jar C:\full\file\path.png , in ra STDOUT

YÊU CẦU tệp nguồn được lưu với độ sâu 1 bit và mẫu cho pixel trắng sẽ là 1 .

Đầu ra chấm điểm:

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
Chạy với -eađể kích hoạt xác nhận. Nó sẽ không thay đổi hành vi (ngoại trừ có thể làm chậm một lượng nhỏ) vì các xác nhận hoạt động bằng cách không thực hiện chương trình khi chúng đánh giá falsevà tất cả các xác nhận này đều vượt qua.
CAD97

Ahh, tôi đã bỏ lỡ rằng bạn đã gỡ bỏ khai báo gói. Nó hoạt dộng bây giờ. Tôi sẽ ghi điểm khi tôi nhận được một vài phút ngày hôm nay.
Mego

Đầu ra cho board.png chỉ dài 4 dòng vì một số lý do: gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f . Trên thực tế, tất cả các đầu ra dường như bị cắt sớm khi tôi chạy nó, ngoại trừ logo PPCG.
Mego

@Mego Tôi nghĩ rằng nó phải làm với chiều cao của phông chữ (24 px theo báo cáo FontMetrics). Tôi đã thay đổi vòng lặp dòng để nó bị lỗi ở một bên của quá nhiều dòng chứ không phải quá ít và nó sẽ hoạt động ngay bây giờ. (bảng là 5 dòng)
CAD97

Như một quy tắc, thuật toán này vật lộn với các hình ảnh nhỏ hơn, vì (nó nghĩ) tất cả các ký tự đều rộng 6px và cao 24px, và tất cả những gì nó nhìn vào là có bao nhiêu pixel được bật trong siêu pixel đó.
CAD97
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.