Gambar ulang hanya dengan satu kurva tertutup

74

Terinspirasi oleh vi.sualize.us

Tujuan

Input adalah gambar skala abu-abu dan outputnya adalah gambar hitam putih. Gambar output hanya terdiri dari satu kurva tertutup (loop) yang tidak diizinkan untuk bersinggungan dengan dirinya sendiri atau menyentuh sendiri. Lebar garis harus konstan di seluruh gambar. Tantangannya di sini adalah menemukan algoritma untuk melakukannya. Output hanya harus mewakili gambar input, tetapi dengan kebebasan artistik. Resolusi tidak begitu penting tetapi rasio aspek harus tetap hampir sama.

Contoh

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Lebih banyak gambar uji

tidak ada gedung pencakar langit Einstein pemeriksa

cacat
sumber
2
Anda mungkin ingin menempatkan beberapa pembatasan pada resolusi relatif. Kalau tidak, orang hanya dapat meningkatkan resolusi secara signifikan (katakanlah faktor 32 atau sesuatu), dan kemudian ganti setiap piksel dengan blok 32x32 dengan intensitas rata-rata yang sesuai. Seharusnya cukup mudah untuk membuat semua blok terhubung dan mereka mengaturnya sedemikian rupa sehingga semuanya terhubung ke satu loop.
Martin Ender
1
Jika garis tidak dapat menyentuh dirinya sendiri, tidak ada daerah gelap, warna yang lebih gelap akan menjadi abu
edc65
1
@ Martin The width of the line shall be constant throughout the whole image.Tapi masih petunjuk yang berguna
edc65
2
@ edc65 Ya konstan, tetapi Anda masih dapat membuatnya lebih lebar dari satu piksel (terus-menerus) dalam hal ini Anda dapat memiliki dua bagian garis yang dipisahkan oleh satu piksel dan kemudian area itu akan lebih gelap dari intensitas rata-rata 50%.
Martin Ender
2
@githubphagocyte Terutama gambar harus dalam warna hitam dan putih, tetapi tidak masalah jika itu mengandung efek anti aliasing. Dan Anda harus mencoba untuk menghindari situasi ini menyentuh piksel secara diagonal, tetapi sekali lagi, jika ini terjadi hanya beberapa kali dalam gambar itu akan baik-baik saja, selama Anda tidak menggunakannya secara sistematis. Terima kasih atas masukannya. @ edc65: Ya, saya sadar akan hal itu, tujuannya adalah agar pemirsa masih dapat mengidentifikasi satu garis berbeda pada gambar (saat memperbesar).
flawr

Jawaban:

34

Java: Gaya dot matrix

Karena tidak ada yang menjawab pertanyaan, namun saya akan mencobanya. Pertama saya ingin mengisi kanvas dengan kurva Hilbert, tetapi pada akhirnya saya memilih pendekatan yang lebih sederhana:

gaya dot matrix mona lisa

Ini kodenya:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class LineArt extends JPanel {
    private BufferedImage ref;
    //Images are stored in integers:
    int[] images = new int[] {31, 475, 14683, 469339};
    int[] brightness = new int[] {200,170,120,0};

    public static void main(String[] args) throws Exception {
        new LineArt(args[0]);
    }

    public LineArt(String filename) throws Exception {
        ref = ImageIO.read(new File(filename));
        JFrame frame = new JFrame();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(ref.getWidth()*5, ref.getHeight()*5);
        this.setPreferredSize(new Dimension((ref.getWidth()*5)+20, (ref.getHeight()*5)+20));
        frame.add(new JScrollPane(this));
    }

    @Override
    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, getWidth(), getHeight());
        g2d.translate(10, 10);
        g2d.setColor(Color.BLACK);
        g2d.drawLine(0, 0, 4, 0);
        g2d.drawLine(0, 0, 0, ref.getHeight()*5);

        for(int y = 0; y<ref.getHeight();y++) {
            for(int x = 1; x<ref.getWidth()-1;x++) {
                int light = new Color(ref.getRGB(x, y)).getRed();
                int offset = 0;
                while(brightness[offset]>light) offset++;
                for(int i = 0; i<25;i++) {
                    if((images[offset]&1<<i)>0) {
                        g2d.drawRect((x*5)+i%5, (y*5)+(i/5), 0,0);
                    }
                }
            }
            g2d.drawLine(2, (y*5), 4, (y*5));
            g2d.drawLine((ref.getWidth()*5)-5, (y*5), (ref.getWidth()*5)-1, (y*5));
            if(y%2==0) {
                g2d.drawLine((ref.getWidth()*5)-1, (y*5), (ref.getWidth()*5)-1, (y*5)+4);
            } else {
                g2d.drawLine(2, (y*5), 2, (y*5)+4);
            }
        }
        if(ref.getHeight()%2==0) {
            g2d.drawLine(0, ref.getHeight()*5, 2, ref.getHeight()*5);
        } else {
            g2d.drawLine(0, ref.getHeight()*5, (ref.getWidth()*5)-1, ref.getHeight()*5);
        }
    }
}

Pembaruan : Sekarang ia menciptakan siklus, bukan hanya satu baris

Roy van Rijn
sumber
2
Solusi yang sangat bagus dan sederhana, saya tidak membayangkan cara menyelesaikannya tetapi terlihat hebat!
flawr
@DenDenDo menyarankan untuk merencanakan animasi aliran shortening kurva. Akan lebih bagus jika Anda bisa memberikan file teks (csv atau apa pun yang Anda inginkan) dengan koordinat semua poin conrner yang Anda gunakan dalam urutan yang benar. Saya membuat skrip matlab untuk menghitung animasi - tetapi tentu saja Anda juga bebas melakukannya sendiri =)
flawr
35

Python: Hilbert curve ( 373 361)

Saya memutuskan untuk menggambar kurva Hilbert dengan variabel granularitas tergantung pada intensitas gambar:

import pylab as pl
from scipy.misc import imresize, imfilter
import turtle

# load image
img = pl.flipud(pl.imread("face.png"))

# setup turtle
levels = 8
size = 2**levels
turtle.setup(img.shape[1] * 4.2, img.shape[0] * 4.2)
turtle.setworldcoordinates(0, 0, size, -size)
turtle.tracer(1000, 0)

# resize and blur image
img = imfilter(imresize(img, (size, size)), 'blur')

# define recursive hilbert curve
def hilbert(level, angle = 90):
    if level == 0:
        return
    if level == 1 and img[-turtle.pos()[1], turtle.pos()[0]] > 128:
        turtle.forward(2**level - 1)
    else:
        turtle.right(angle)
        hilbert(level - 1, -angle)
        turtle.forward(1)
        turtle.left(angle)
        hilbert(level - 1, angle)
        turtle.forward(1)
        hilbert(level - 1, angle)
        turtle.left(angle)
        turtle.forward(1)
        hilbert(level - 1, -angle)
        turtle.right(angle)

# draw hilbert curve
hilbert(levels)
turtle.update()

Sebenarnya saya berencana untuk membuat keputusan pada tingkat detail yang berbeda, seperti "Tempat ini sangat cerah, saya akan menghentikan rekursi dan pindah ke blok berikutnya!". Tetapi mengevaluasi intensitas gambar secara lokal yang mengarah ke gerakan besar sangat tidak akurat dan terlihat jelek. Jadi saya berakhir dengan hanya memutuskan apakah akan melewati level 1 atau menggambar lingkaran Hilbert yang lain.

Ini adalah hasil gambar uji pertama:

hasil

Berkat @githubphagocy renderingnya cukup cepat (menggunakan turtle.tracer). Jadi saya tidak perlu menunggu sepanjang malam untuk hasilnya dan bisa pergi ke tempat tidur saya yang memang layak. :)


Beberapa kode golf

@ flawr: "program singkat"? Anda belum melihat versi golfnya! ;)

Jadi hanya untuk bersenang-senang:

from pylab import*;from scipy.misc import*;from turtle import*
i=imread("f.p")[::-1];s=256;h=i.shape;i=imfilter(imresize(i,(s,s)),'blur')
setup(h[1]*4.2,h[0]*4.2);setworldcoordinates(0,0,s,-s);f=forward;r=right
def h(l,a=90):
 x,y=pos()
 if l==1and i[-y,x]>128:f(2**l-1)
 else:
  if l:l-=1;r(a);h(l,-a);f(1);r(-a);h(l,a);f(1);h(l,a);r(-a);f(1);h(l,-a);r(a)
h(8)

( 373 361 karakter. Tapi itu akan selamanya karena saya menghapus turte.tracer(...)perintah!)


Animasi oleh flawr

flawr: Algoritme saya sedikit dimodifikasi dengan apa yang dikatakan @DenDenDo kepada saya: Saya harus menghapus beberapa poin di setiap iterasi karena konvergensi akan melambat secara drastis. Itu sebabnya kurva akan memotong sendiri.

masukkan deskripsi gambar di sini

Falko
sumber
1
Bagus sekali! Jika Anda ingin berjalan lebih cepat, cobalah screen.tracer(0)bukan turtle.speed(0). Anda mungkin perlu membuat instance layar di awal, tetapi jika itu adalah satu-satunya contoh layar semua kura-kura Anda akan secara otomatis ditugaskan untuk itu. Kemudian hanya screen.update()di bagian akhir untuk menampilkan hasilnya. Saya kagum dengan perbedaan kecepatan ketika saya pertama kali menemukan ini ...
trichoplax
Saya benar-benar terkejut bahwa Anda dapat melakukannya dalam program yang begitu singkat! Tapi bagaimanapun, selamat! fractals ftw =)
flawr
@DenDenDo menyarankan untuk merencanakan animasi aliran shortening kurva. Akan lebih bagus jika Anda bisa memberikan file teks (csv atau apa pun yang Anda inginkan) dengan koordinat semua poin conrner yang Anda gunakan dalam urutan yang benar. Saya membuat skrip matlab untuk menghitung animasi - tetapi tentu saja Anda juga bebas melakukannya sendiri =)
flawr
@ flawr: Ini dia.
Falko
Jadi, inilah kodenya: pastebin.com/wTcwb0nm
flawr
32

Python 3.4 - Masalah Salesman Bepergian

Program ini membuat gambar bingung dari aslinya:

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Untuk setiap piksel hitam, sebuah titik dihasilkan secara acak di dekat pusat piksel dan titik-titik ini diperlakukan sebagai masalah salesman keliling . Program ini menyimpan file html yang berisi gambar SVG secara berkala ketika mencoba mengurangi panjang lintasan. Jalan mulai berpotongan sendiri dan secara bertahap menjadi kurang dari beberapa jam. Akhirnya jalan tidak lagi berpotongan:

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

'''
Traveling Salesman image approximation.
'''

import os.path

from PIL import Image   # This uses Pillow, the PIL fork for Python 3.4
                        # https://pypi.python.org/pypi/Pillow

from random import random, sample, randrange, shuffle
from time import perf_counter


def make_line_picture(image_filename):
    '''Save SVG image of closed curve approximating input image.'''
    input_image_path = os.path.abspath(image_filename)
    image = Image.open(input_image_path)
    width, height = image.size
    scale = 1024 / width
    head, tail = os.path.split(input_image_path)
    output_tail = 'TSP_' + os.path.splitext(tail)[0] + '.html'
    output_filename = os.path.join(head, output_tail)
    points = generate_points(image)
    population = len(points)
    save_dither(points, image)
    grid_cells = [set() for i in range(width * height)]
    line_cells = [set() for i in range(population)]
    print('Initialising acceleration grid')
    for i in range(population):
        recalculate_cells(i, width, points, grid_cells, line_cells)
    while True:
        save_svg(output_filename, width, height, points, scale)
        improve_TSP_solution(points, width, grid_cells, line_cells)


def save_dither(points, image):
    '''Save a copy of the dithered image generated for approximation.'''
    image = image.copy()
    pixels = list(image.getdata())
    pixels = [255] * len(pixels)
    width, height = image.size
    for p in points:
        x = int(p[0])
        y = int(p[1])
        pixels[x+y*width] = 0
    image.putdata(pixels)
    image.save('dither_test.png', 'PNG')


def generate_points(image):
    '''Return a list of points approximating the image.

    All points are offset by small random amounts to prevent parallel lines.'''
    width, height = image.size
    image = image.convert('L')
    pixels = image.getdata()
    points = []
    gap = 1
    r = random
    for y in range(2*gap, height - 2*gap, gap):
        for x in range(2*gap, width - 2*gap, gap):
            if (r()+r()+r()+r()+r()+r())/6 < 1 - pixels[x + y*width]/255:
                        points.append((x + r()*0.5 - 0.25,
                                       y + r()*0.5 - 0.25))
    shuffle(points)
    print('Total number of points', len(points))
    print('Total length', current_total_length(points))
    return points


def current_total_length(points):
    '''Return the total length of the current closed curve approximation.'''
    population = len(points)
    return sum(distance(points[i], points[(i+1)%population])
               for i in range(population))


def recalculate_cells(i, width, points, grid_cells, line_cells):
    '''Recalculate the grid acceleration cells for the line from point i.'''
    for j in line_cells[i]:
        try:
            grid_cells[j].remove(i)
        except KeyError:
            print('grid_cells[j]',grid_cells[j])
            print('i',i)
    line_cells[i] = set()
    add_cells_along_line(i, width, points, grid_cells, line_cells)
    for j in line_cells[i]:
        grid_cells[j].add(i)


def add_cells_along_line(i, width, points, grid_cells, line_cells):
    '''Add each grid cell that lies on the line from point i.'''
    population = len(points)
    start_coords = points[i]
    start_x, start_y = start_coords
    end_coords = points[(i+1) % population]
    end_x, end_y = end_coords
    gradient = (end_y - start_y) / (end_x - start_x)
    y_intercept = start_y - gradient * start_x
    total_distance = distance(start_coords, end_coords)
    x_direction = end_x - start_x
    y_direction = end_y - start_y
    x, y = start_x, start_y
    grid_x, grid_y = int(x), int(y)
    grid_index = grid_x + grid_y * width
    line_cells[i].add(grid_index)
    while True:
        if x_direction > 0:
            x_line = int(x + 1)
        else:
            x_line = int(x)
            if x_line == x:
                x_line = x - 1
        if y_direction > 0:
            y_line = int(y + 1)
        else:
            y_line = int(y)
            if y_line == y:
                y_line = y - 1
        x_line_intersection = gradient * x_line + y_intercept
        y_line_intersection = (y_line - y_intercept) / gradient
        x_line_distance = distance(start_coords, (x_line, x_line_intersection))
        y_line_distance = distance(start_coords, (y_line_intersection, y_line))
        if (x_line_distance > total_distance and
            y_line_distance > total_distance):
            break
        if x_line_distance < y_line_distance:
            x = x_line
            y = gradient * x_line + y_intercept
        else:
            y = y_line
            x = (y_line - y_intercept) / gradient
        grid_x = int(x - (x_direction < 0) * (x == int(x)))
        grid_y = int(y - (y_direction < 0) * (y == int(y)))
        grid_index = grid_x + grid_y * width
        line_cells[i].add(grid_index)


def improve_TSP_solution(points, width, grid_cells, line_cells,
                         performance=[0,0,0], total_length=None):
    '''Apply 3 approaches, allocating time to each based on performance.'''
    population = len(points)
    if total_length is None:
        total_length = current_total_length(points)

    print('Swapping pairs of vertices')
    if performance[0] == max(performance):
        time_limit = 300
    else:
        time_limit = 10
    print('    Aiming for {} seconds'.format(time_limit))
    start_time = perf_counter()
    for n in range(1000000):
        swap_two_vertices(points, width, grid_cells, line_cells)
        if perf_counter() - start_time > time_limit:
            break
    time_taken = perf_counter() - start_time
    old_length = total_length
    total_length = current_total_length(points)
    performance[0] = (old_length - total_length) / time_taken
    print('    Time taken', time_taken)
    print('    Total length', total_length)
    print('    Performance', performance[0])

    print('Moving single vertices')
    if performance[1] == max(performance):
        time_limit = 300
    else:
        time_limit = 10
    print('    Aiming for {} seconds'.format(time_limit))
    start_time = perf_counter()
    for n in range(1000000):
        move_a_single_vertex(points, width, grid_cells, line_cells)
        if perf_counter() - start_time > time_limit:
            break
    time_taken = perf_counter() - start_time
    old_length = total_length
    total_length = current_total_length(points)
    performance[1] = (old_length - total_length) / time_taken
    print('    Time taken', time_taken)
    print('    Total length', total_length)
    print('    Performance', performance[1])

    print('Uncrossing lines')
    if performance[2] == max(performance):
        time_limit = 60
    else:
        time_limit = 10
    print('    Aiming for {} seconds'.format(time_limit))
    start_time = perf_counter()
    for n in range(1000000):
        uncross_lines(points, width, grid_cells, line_cells)
        if perf_counter() - start_time > time_limit:
            break
    time_taken = perf_counter() - start_time        
    old_length = total_length
    total_length = current_total_length(points)
    performance[2] = (old_length - total_length) / time_taken
    print('    Time taken', time_taken)
    print('    Total length', total_length)
    print('    Performance', performance[2])


def swap_two_vertices(points, width, grid_cells, line_cells):
    '''Attempt to find a pair of vertices that reduce length when swapped.'''
    population = len(points)
    for n in range(100):
        candidates = sample(range(population), 2)
        befores = [(candidates[i] - 1) % population
                   for i in (0,1)]
        afters = [(candidates[i] + 1) % population for i in (0,1)]
        current_distance = sum((distance(points[befores[i]],
                                         points[candidates[i]]) +
                                distance(points[candidates[i]],
                                         points[afters[i]]))
                               for i in (0,1))
        (points[candidates[0]],
         points[candidates[1]]) = (points[candidates[1]],
                                   points[candidates[0]])
        befores = [(candidates[i] - 1) % population
                   for i in (0,1)]
        afters = [(candidates[i] + 1) % population for i in (0,1)]
        new_distance = sum((distance(points[befores[i]],
                                     points[candidates[i]]) +
                            distance(points[candidates[i]],
                                     points[afters[i]]))
                           for i in (0,1))
        if new_distance > current_distance:
            (points[candidates[0]],
             points[candidates[1]]) = (points[candidates[1]],
                                       points[candidates[0]])
        else:
            modified_points = tuple(set(befores + candidates))
            for k in modified_points:
                recalculate_cells(k, width, points, grid_cells, line_cells)
            return


def move_a_single_vertex(points, width, grid_cells, line_cells):
    '''Attempt to find a vertex that reduces length when moved elsewhere.'''
    for n in range(100):
        population = len(points)
        candidate = randrange(population)
        offset = randrange(2, population - 1)
        new_location = (candidate + offset) % population
        before_candidate = (candidate - 1) % population
        after_candidate = (candidate + 1) % population
        before_new_location = (new_location - 1) % population
        old_distance = (distance(points[before_candidate], points[candidate]) +
                        distance(points[candidate], points[after_candidate]) +
                        distance(points[before_new_location],
                                 points[new_location]))
        new_distance = (distance(points[before_candidate],
                                 points[after_candidate]) +
                        distance(points[before_new_location],
                                 points[candidate]) +
                        distance(points[candidate], points[new_location]))
        if new_distance <= old_distance:
            if new_location < candidate:
                points[:] = (points[:new_location] +
                             points[candidate:candidate + 1] +
                             points[new_location:candidate] +
                             points[candidate + 1:])
                for k in range(candidate - 1, new_location, -1):
                    for m in line_cells[k]:
                        grid_cells[m].remove(k)
                    line_cells[k] = line_cells[k - 1]
                    for m in line_cells[k]:
                        grid_cells[m].add(k)
                for k in ((new_location - 1) % population,
                          new_location, candidate):
                    recalculate_cells(k, width, points, grid_cells, line_cells)
            else:
                points[:] = (points[:candidate] +
                             points[candidate + 1:new_location] +
                             points[candidate:candidate + 1] +
                             points[new_location:])
                for k in range(candidate, new_location - 3):
                    for m in line_cells[k]:
                        grid_cells[m].remove(k)
                    line_cells[k] = line_cells[k + 1]
                    for m in line_cells[k]:
                        grid_cells[m].add(k)
                for k in ((candidate - 1) % population,
                          new_location - 2, new_location - 1):
                    recalculate_cells(k, width, points, grid_cells, line_cells)
            return


def uncross_lines(points, width, grid_cells, line_cells):
    '''Attempt to find lines that are crossed, and reverse path to uncross.'''
    population = len(points)
    for n in range(100):
        i = randrange(population)
        start_1 = points[i]
        end_1 = points[(i + 1) % population]
        if not line_cells[i]:
            recalculate_cells(i, width, points, grid_cells, line_cells)
        for cell in line_cells[i]:
            for j in grid_cells[cell]:
                if i != j and i != (j+1)%population and i != (j-1)%population:
                    start_2 = points[j]
                    end_2 = points[(j + 1) % population]
                    if are_crossed(start_1, end_1, start_2, end_2):
                        if i < j:
                            points[i + 1:j + 1] = reversed(points[i + 1:j + 1])
                            for k in range(i, j + 1):
                                recalculate_cells(k, width, points, grid_cells,
                                                  line_cells)
                        else:
                            points[j + 1:i + 1] = reversed(points[j + 1:i + 1])
                            for k in range(j, i + 1):
                                recalculate_cells(k, width, points, grid_cells,
                                                  line_cells)
                        return


def are_crossed(start_1, end_1, start_2, end_2):
    '''Return True if the two lines intersect.'''
    if end_1[0]-start_1[0] and end_2[0]-start_2[0]:
        gradient_1 = (end_1[1]-start_1[1])/(end_1[0]-start_1[0])
        gradient_2 = (end_2[1]-start_2[1])/(end_2[0]-start_2[0])
        if gradient_1-gradient_2:
            intercept_1 = start_1[1] - gradient_1 * start_1[0]
            intercept_2 = start_2[1] - gradient_2 * start_2[0]        
            x = (intercept_2 - intercept_1) / (gradient_1 - gradient_2)
            if (x-start_1[0]) * (end_1[0]-x) > 0 and (x-start_2[0]) * (end_2[0]-x) > 0:
                return True


def distance(point_1, point_2):
    '''Return the Euclidean distance between the two points.'''
    return sum((point_1[i] - point_2[i]) ** 2 for i in (0, 1)) ** 0.5


def save_svg(filename, width, height, points, scale):
    '''Save a file containing an SVG path of the points.'''
    print('Saving partial solution\n')
    with open(filename, 'w') as file:
        file.write(content(width, height, points, scale))


def content(width, height, points, scale):
    '''Return the full content to be written to the SVG file.'''
    return (header(width, height, scale) +
            specifics(points, scale) +
            footer()
            )


def header(width, height,scale):
    '''Return the text of the SVG header.'''
    return ('<?xml version="1.0"?>\n'
            '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"\n'
            '    "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n'
            '\n'
            '<svg width="{0}" height="{1}">\n'
            '<title>Traveling Salesman Problem</title>\n'
            '<desc>An approximate solution to the Traveling Salesman Problem</desc>\n'
            ).format(scale*width, scale*height)


def specifics(points, scale):
    '''Return text for the SVG path command.'''
    population = len(points)
    x1, y1 = points[-1]
    x2, y2 = points[0]
    x_mid, y_mid = (x1 + x2) / 2, (y1 + y2) / 2
    text = '<path d="M{},{} L{},{} '.format(x1, y1, x2, y2)
    for i in range(1, population):
        text += 'L{},{} '.format(*points[i])
    text += '" stroke="black" fill="none" stroke-linecap="round" transform="scale({0},{0})" vector-effect="non-scaling-stroke" stroke-width="3"/>'.format(scale)
    return text


def footer():
    '''Return the closing text of the SVG file.'''
    return '\n</svg>\n'


if __name__ == '__main__':
    import sys
    arguments = sys.argv[1:]
    if arguments:
        make_line_picture(arguments[0])
    else:
        print('Required argument: image file')

Program ini menggunakan 3 pendekatan berbeda untuk meningkatkan solusi, dan mengukur kinerja per detik untuk masing-masing. Waktu yang dialokasikan untuk masing-masing pendekatan disesuaikan untuk memberikan sebagian besar waktu untuk pendekatan apa pun yang berkinerja terbaik pada waktu itu.

Awalnya saya mencoba menebak berapa proporsi waktu yang dialokasikan untuk masing-masing pendekatan, tetapi ternyata pendekatan mana yang paling efektif sangat bervariasi selama proses berlangsung, sehingga membuat perbedaan besar untuk tetap menyesuaikan secara otomatis.

Tiga pendekatan sederhana adalah:

  1. Pilih dua poin secara acak dan tukarkan mereka jika ini tidak menambah panjang total.
  2. Pilih satu titik secara acak dan offset acak di sepanjang daftar poin dan pindahkan jika panjangnya tidak bertambah.
  3. Pilih garis secara acak dan periksa apakah ada garis lain yang melewatinya, membalikkan bagian mana pun dari jalur yang menyebabkan persilangan.

Untuk pendekatan 3 digunakan kisi, mendaftar semua garis yang melewati sel tertentu. Daripada harus memeriksa setiap baris pada halaman untuk persimpangan, hanya mereka yang memiliki sel grid yang sama diperiksa.


Saya mendapat ide untuk menggunakan masalah salesman keliling dari posting blog yang saya lihat sebelum tantangan ini diposting, tetapi saya tidak bisa melacaknya ketika saya memposting jawaban ini. Saya percaya gambar dalam tantangan diproduksi menggunakan pendekatan salesman keliling juga, dikombinasikan dengan semacam jalur smoothing untuk menghilangkan tikungan tajam.

Saya masih tidak dapat menemukan posting blog tertentu tetapi saya sekarang telah menemukan referensi ke makalah asli di mana Mona Lisa digunakan untuk menunjukkan masalah salesman keliling .

Implementasi TSP di sini adalah pendekatan hybrid yang saya coba untuk bersenang-senang untuk tantangan ini. Saya belum membaca makalah terkait saat saya memposting ini. Pendekatan saya sangat lambat dibandingkan. Perhatikan bahwa gambar saya di sini menggunakan kurang dari 10.000 poin, dan membutuhkan waktu berjam-jam untuk cukup konvergen sehingga tidak ada garis silang. Contoh gambar di tautan ke makalah menggunakan 100.000 poin ...

Sayangnya sebagian besar tautan tampaknya sudah mati sekarang, tetapi makalah "TSP Art" oleh Craig S Kaplan & Robert Bosch 2005 masih berfungsi dan memberikan gambaran menarik tentang berbagai pendekatan.

trichoplax
sumber
1
Wow, itu sangat bagus =) (Jika Anda ingin saya melakukan animasi aliran pemendek kurva juga hanya menyediakan csv atau sesuatu yang serupa dengan daftar koordinat titik yang
diatur
@ flawr, terima kasih! Adapun daftar koordinat titik yang dipesan, hampir 10.000 poin untuk wajah mona lisa. Itu akan mendekati 100.000 poin untuk gambar yang lebih besar. Itu sebabnya saya belum memposting teks SVG di sini ... :)
trichoplax
Yah Anda bisa menggunakan pastebin.com atau yang serupa, tapi saya tidak ingin memaksa Anda, itu keputusan Anda (saya tidak pandai Python =)
flawr
@ flawr Saya tidak ingin Anda harus menunggu berjam-jam agar program dapat berjalan. Saya tidak akan menambahkan animasi aliran ke jawaban saya, tetapi jika Anda ingin poin untuk diri sendiri, beri tahu saya dan saya dapat menemukan tempat untuk mempostingnya ...
trichoplax
Saya tidak akan pernah memiliki ide TSP untuk hal seperti itu! Dapatkan upvote!
sergiol
24

Java - Osilasi

Program ini menggambar jalur tertutup dan menambahkan osilasi yang amplitudo dan frekuensinya didasarkan pada kecerahan gambar. "Sudut" jalur tidak memiliki osilasi untuk memastikan jalur tidak berpotongan dengan sendirinya.

masukkan deskripsi gambar di sini

package trace;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import snake.Image;

public class Main5 {


    private final static int MULT = 3;
    private final static int ROWS = 80; // must be an even number
    private final static int COLS = 40;

    public static void main(String[] args) throws IOException {
        BufferedImage src = ImageIO.read(Image.class.getClassLoader().getResourceAsStream("input.png"));
        BufferedImage dest = new BufferedImage(src.getWidth()*MULT, src.getHeight()*MULT, BufferedImage.TYPE_INT_RGB);

        int [] white = {255, 255, 255};
        for (int y = 0; y < dest.getHeight(); y++) {
            for (int x = 0; x < dest.getWidth(); x++) {
                dest.getRaster().setPixel(x, y, white);
            }
        }
        for (int j = 0; j < ROWS; j++) {
            if (j%2 == 0) {
                for (int i = j==0 ? 0 : 1; i < COLS-1; i++) {
                    drawLine(dest, src, (i+.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (i+1.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS,
                            i > 1 && i < COLS-2);
                }

                drawLine(dest, src, (COLS-.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (COLS-.5)*dest.getWidth()/COLS, (j+1.5)*dest.getHeight()/ROWS, false);
            } else {
                for (int i = COLS-2; i >= (j == ROWS - 1 ? 0 : 1); i--) {
                    drawLine(dest, src, (i+.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (i+1.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS,
                            i > 1 && i < COLS-2);
                }
                if (j < ROWS-1) {
                    drawLine(dest, src, (1.5)*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, (1.5)*dest.getWidth()/COLS, (j+1.5)*dest.getHeight()/ROWS, false);
                }
            }
            if (j < ROWS-1) {
                drawLine(dest, src, 0.5*dest.getWidth()/COLS, (j+.5)*dest.getHeight()/ROWS, 0.5*dest.getWidth()/COLS, (j+1.5)*dest.getHeight()/ROWS, false);
            }
        }
        ImageIO.write(dest, "png", new File("output.png"));
    }

    private static void drawLine(BufferedImage dest, BufferedImage src, double x1, double y1, double x2, double y2, boolean oscillate) {
        int [] black = {0, 0, 0};

        int col = smoothPixel((int)((x1*.5 + x2*.5) / MULT), (int)((y1*.5+y2*.5) / MULT), src);
        int fact = (255 - col) / 32;
        if (fact > 5) fact = 5;
        double dx = y1 - y2;
        double dy = - (x1 - x2);
        double dist = 2 * (Math.abs(x1 - x2) + Math.abs(y1 - y2)) * (fact + 1);
        for (int i = 0; i <= dist; i++) {
            double amp = oscillate ? (1 - Math.cos(fact * i*Math.PI*2/dist)) * 12 : 0;
            double x = (x1 * i + x2 * (dist - i)) / dist;
            double y = (y1 * i + y2 * (dist - i)) / dist;
            x += dx * amp / COLS;
            y += dy * amp / ROWS;
            dest.getRaster().setPixel((int)x, (int)y, black);
        }
    }

    public static int smoothPixel(int x, int y, BufferedImage src) {
        int sum = 0, count = 0;
        for (int j = -2; j <= 2; j++) {
            for (int i = -2; i <= 2; i++) {
                if (x + i >= 0 && x + i < src.getWidth()) {
                    if (y + j >= 0 && y + j < src.getHeight()) {
                        sum += src.getRGB(x + i, y + j) & 255;
                        count++;
                    }
                }
            }
        }
        return sum / count;
    }
}

Di bawah ini algoritma yang sebanding yang didasarkan pada spiral. ( Saya tahu jalannya tidak menutup dan itu pasti berpotongan , saya hanya mempostingnya demi seni :-)

masukkan deskripsi gambar di sini

Arnaud
sumber
Saya sangat suka efek visual spiral!
Will
Saya juga, terima kasih sudah berbagi! (Jika mau, Anda juga dapat membuat daftar titik jalur yang dipesan dan saya akan melihat apakah saya dapat melakukan animasi dengan yang satu ini juga =)
flawr
@github Terima kasih atas komentar konstruktif Anda.
Arnaud
1
+1 dari saya - cocok dengan aturan sekarang, dan saya suka transisi mulus yang diberikan oleh frekuensi yang berubah.
trichoplax
21

Java - Jalur Rekursif

Saya mulai dari jalur tertutup 2x3. Saya memindai setiap sel jalur dan membaginya menjadi sub-jalur 3x3 baru. Saya mencoba setiap kali untuk memilih sub-path 3x3 yang "mirip" gambar aslinya. Saya ulangi proses di atas 4 kali.

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Ini kodenya:

package divide;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;

import snake.Image;

public class Divide {

    private final static int MULT = 3;
    private final static int ITERATIONS = 4;

    public static void main(String[] args) throws IOException {
        BufferedImage src = ImageIO.read(Image.class.getClassLoader().getResourceAsStream("input.png"));
        BufferedImage dest = new BufferedImage(src.getWidth() * MULT, src.getHeight() * MULT, BufferedImage.TYPE_INT_RGB);
        for (int y = 0; y < src.getHeight() * MULT; y++) {
            for (int x = 0; x < src.getWidth() * MULT; x++) {
                dest.getRaster().setPixel(x, y, new int [] {255, 255, 255});
            }
        }
        List<String> tab = new ArrayList<String>();
        tab.add("rg");
        tab.add("||"); 
        tab.add("LJ");

        for (int k = 1; k <= ITERATIONS; k++) {
            boolean choose = k>=ITERATIONS-1;
            // multiply size by 3
            tab = iterate(src, tab, choose);
            // fill in the white space - if needed
            expand(src, tab, " r", " L", "r-", "L-", choose);
            expand(src, tab, "g ", "J ", "-g", "-J", choose);
            expand(src, tab, "LJ", "  ", "||", "LJ", choose);
            expand(src, tab, "  ", "rg", "rg", "||", choose);
            expand(src, tab, "L-J", "   ", "| |", "L-J", choose);
            expand(src, tab, "   ", "r-g", "r-g", "| |", choose);
            expand(src, tab, "| |", "| |", "Lg|", "rJ|", choose);
            expand(src, tab, "--", "  ", "gr", "LJ", choose);
            expand(src, tab, "  ", "--", "rg", "JL", choose);
            expand(src, tab, "| ", "| ", "Lg", "rJ", choose);
            expand(src, tab, " |", " |", "rJ", "Lg", choose);

            for (String s : tab) {
                System.out.println(s);
            }
            System.out.println();
        }

        for (int j = 0; j < tab.size(); j++) {
            String line = tab.get(j);
            for (int i = 0; i < line.length(); i++) {
                char c = line.charAt(i);
                int xleft = i * dest.getWidth() / line.length();
                int xright = (i+1) * dest.getWidth() / line.length();
                int ytop = j * dest.getHeight() / tab.size();
                int ybottom = (j+1) * dest.getHeight() / tab.size();
                int x = (xleft + xright) / 2;
                int y = (ytop + ybottom) / 2;
                if (c == '|') {
                    drawLine(dest, x, ytop, x, ybottom);
                }
                if (c == '-') {
                    drawLine(dest, xleft, y, xright, y);
                }
                if (c == 'L') {
                    drawLine(dest, x, y, xright, y);
                    drawLine(dest, x, y, x, ytop);
                }
                if (c == 'J') {
                    drawLine(dest, x, y, xleft, y);
                    drawLine(dest, x, y, x, ytop);
                }
                if (c == 'r') {
                    drawLine(dest, x, y, xright, y);
                    drawLine(dest, x, y, x, ybottom);
                }
                if (c == 'g') {
                    drawLine(dest, x, y, xleft, y);
                    drawLine(dest, x, y, x, ybottom);
                }
            }

        }

        ImageIO.write(dest, "png", new File("output.png"));

    }


    private static void drawLine(BufferedImage dest, int x1, int y1, int x2, int y2) {
        int dist = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
        for (int i = 0; i <= dist; i++) {
            int x = (x1*(dist - i) + x2 * i) / dist;
            int y = (y1*(dist - i) + y2 * i) / dist;
            dest.getRaster().setPixel(x, y, new int [] {0, 0, 0});
        }
    }

    private static void expand(BufferedImage src, List<String> tab, String p1, String p2, String r1, String r2, boolean choose) {
        for (int k = 0; k < (choose ? 2 : 1); k++) {
            while (true) {
                boolean again = false;
                for (int j = 0; j < tab.size() - 1; j++) {
                    String line1 = tab.get(j);
                    String line2 = tab.get(j+1);
                    int baseScore = evaluateLine(src, j, tab.size(), line1) + evaluateLine(src, j+1, tab.size(), line2);
                    for (int i = 0; i <= line1.length() - p1.length(); i++) {
                        if (line1.substring(i, i + p1.length()).equals(p1)
                                && line2.substring(i, i + p2.length()).equals(p2)) {
                            String nline1 = line1.substring(0,  i) + r1 + line1.substring(i + p1.length());
                            String nline2 = line2.substring(0,  i) + r2 + line2.substring(i + p2.length());
                            int nScore = evaluateLine(src, j, tab.size(), nline1) + evaluateLine(src, j+1, tab.size(), nline2);
                            if (!choose || nScore > baseScore) {
                                tab.set(j, nline1);
                                tab.set(j+1, nline2);
                                again = true;
                                break;
                            }
                        }
                    }
                    if (again) break;
                }
                if (!again) break;
            }
            String tmp1 = r1;
            String tmp2 = r2;
            r1 = p1;
            r2 = p2;
            p1 = tmp1;
            p2 = tmp2;
        }
    }

    private static int evaluateLine(BufferedImage src, int j, int tabSize, String line) {
        int [] color = {0, 0, 0};
        int score = 0;
        for (int i = 0; i < line.length(); i++) {
            char c = line.charAt(i);
            int x = i*src.getWidth() / line.length();
            int y = j*src.getHeight() / tabSize;
            src.getRaster().getPixel(x, y, color);
            if (c == ' ' && color[0] >= 128) score++;
            if (c != ' ' && color[0] < 128) score++;
        }
        return score;
    }



    private static List<String> iterate(BufferedImage src, List<String> tab, boolean choose) {
        int [] color = {0, 0, 0};
        List<String> tab2 = new ArrayList<String>();
        for (int j = 0; j < tab.size(); j++) {
            String line = tab.get(j);
            String l1 = "", l2 = "", l3 = "";
            for (int i = 0; i < line.length(); i++) {
                char c = line.charAt(i);
                List<String []> candidates = replace(c);
                String [] choice = null;
                if (choose) {

                    int best = 0;
                    for (String [] candidate : candidates) {
                        int bright1 = 0;
                        int bright2 = 0;
                        for (int j1 = 0; j1<3; j1++) {
                            int y = j*3+j1;
                            for (int i1 = 0; i1<3; i1++) {
                                int x = i*3+i1;
                                char c2 = candidate[j1].charAt(i1);
                                src.getRaster().getPixel(x*src.getWidth()/(line.length()*3), y*src.getHeight()/(tab.size()*3), color);
                                if (c2 != ' ') bright1++;
                                if (color[0] > 128) bright2++;
                            }
                        }
                        int score = Math.abs(bright1 - bright2);
                        if (choice == null || score > best) {
                            best = score;
                            choice = candidate;
                        }

                    }
                } else {
                    choice = candidates.get(0);
                }
                //String [] r = candidates.get(rand.nextInt(candidates.size()));
                String [] r = choice;
                l1 += r[0];
                l2 += r[1];
                l3 += r[2];
            }
            tab2.add(l1);
            tab2.add(l2);
            tab2.add(l3);
        }
        return tab2;
    }

    private static List<String []> replace(char c) {
        if (c == 'r') {
            return Arrays.asList(
                    new String[] {
                    "r-g",
                    "| L",
                    "Lg "},
                    new String[] {
                    "   ",
                    " r-",
                    " | "}, 
                    new String[] {
                    "   ",
                    "r--",
                    "Lg "}, 
                    new String[] {
                    " rg",
                    " |L",
                    " | "},
                    new String[] {
                    "   ",
                    "  r",
                    " rJ"});            
        } else if (c == 'g') {
            return Arrays.asList(
                    new String[] {
                    "r-g",
                    "J |",
                    " rJ"},                 
                    new String[] {
                    "   ",
                    "-g ",
                    " | "},
                    new String[] {
                    "   ",
                    "--g",
                    " rJ"},
                    new String[] {
                    "rg ",
                    "J| ",
                    " | "},
                    new String[] {
                    "   ",
                    "g  ",
                    "Lg "});
        } else if (c == 'L') {
            return Arrays.asList(
                    new String[] {
                    "rJ ",
                    "| r",
                    "L-J"},
                    new String[] {
                    " | ",
                    " L-",
                    "   "},
                    new String[] {
                    "rJ ",
                    "L--",
                    "   "},
                    new String[] {
                    " | ",
                    " |r",
                    " LJ"},
                    new String[] {
                    " Lg",
                    "  L",
                    "   "});
        } else if (c == 'J') {
            return Arrays.asList(
                    new String[] {
                    " Lg",
                    "g |",
                    "L-J"},
                    new String[] {
                    " | ",
                    "-J ",
                    "   "},
                    new String[] {
                    " Lg",
                    "--J",
                    "   "},
                    new String[] {
                    " | ",
                    "g| ",
                    "LJ "},
                    new String[] {
                    "rJ ",
                    "J  ",
                    "   "});
        } else if (c == '-') {
            return Arrays.asList(
                    new String[] {
                    " rg",
                    "g|L",
                    "LJ "},
                    new String[] {
                    "rg ",
                    "J|r",
                    " LJ"},
                    new String[] {
                    "   ",
                    "---",
                    "   "},
                    new String[] {
                    "r-g",
                    "J L",
                    "   "},
                    new String[] {
                    "   ",
                    "g r",
                    "L-J"},
                    new String[] {
                    "rg ",
                    "JL-",
                    "   "},
                    new String[] {
                    " rg",
                    "-JL",
                    "   "},                 
                    new String[] {
                    "   ",
                    "gr-",
                    "LJ "},
                    new String[] {
                    "   ",
                    "-gr",
                    " LJ"}                                      
                    );                      
        } else if (c == '|') {
            return Arrays.asList(
                    new String[] {
                    " Lg",
                    "r-J",
                    "Lg "},
                    new String[] {
                    "rJ ",
                    "L-g",
                    " rJ"},
                    new String[] {
                    " | ",
                    " | ",
                    " | "},
                    new String[] {
                    " Lg",
                    "  |",
                    " rJ"},
                    new String[] {
                    "rJ ",
                    "|  ",
                    "Lg "},
                    new String[] {
                    " Lg",
                    " rJ",
                    " | "},
                    new String[] {
                    " | ",
                    " Lg",
                    " rJ"},
                    new String[] {
                    "rJ ",
                    "Lg ",
                    " | "},
                    new String[] {
                    " | ",
                    "rJ ",
                    "Lg "}                  
                    );
        } else {
            List<String []> ret = new ArrayList<String []>();
            ret.add(
                    new String[] {
                    "   ",
                    "   ",
                    "   "});
            return ret;
        }

    }
}
Arnaud
sumber
2
Ini sepertinya salah satu solusi paling inovatif sejauh ini! +1 untuk Batman =)
flawr
Saya suka yang ini.
trichoplax