Odtwórz obraz za pomocą linii

31

Napisz program, który pobiera prawdziwy obraz RGB I , maksymalną liczbę linii do narysowania L oraz minimalną długość m i maksymalną długość M każdej linii. Wyjście obraz O , który wygląda jak najbardziej jak I i jest sporządzony za pomocą L lub mniej prostych linii, z których każdy ma długość od euklidesową m i M .

Każda linia musi mieć jeden jednolity kolor, mieć oba punkty końcowe w granicach O i być narysowana przy użyciu algorytmu linii Bresenhama (co większość bibliotek graficznych już dla ciebie zrobi). Pojedyncze linie mogą mieć grubość tylko 1 piksela.

Wszystkie linie, nawet te o długości 0, powinny zajmować co najmniej jeden piksel. Linie mogą być rysowane jeden na drugim.

Przed narysowaniem linii możesz zainicjować tło litery O dowolnym kolorem (zależnym od I ).

Detale

  • O powinien mieć takie same wymiary jak ja .
  • L zawsze będzie nieujemną liczbą całkowitą. To może być większy niż obszar I .
  • m i M są nieujemnymi liczb zmiennoprzecinkowych o K > = m . Odległość między dwoma pikselami to odległość euklidesowa między ich środkami. Jeśli ta odległość jest mniejsza niż m lub większa niż M , linia między tymi pikselami jest niedozwolona.
  • Linie nie powinny być antyializowane.
  • Nie należy używać krycia i alfa.
  • Twój program nie powinien zająć więcej niż godzinę, aby uruchomić się na przyzwoitym nowoczesnym komputerze na obrazach o mniej niż milion pikseli i L mniej niż 10.000.

Testuj obrazy

Powinieneś oczywiście pokazać nam swoje najdokładniejsze lub najciekawsze obrazy wyjściowe (które, jak się spodziewam, wystąpią, gdy L będzie między 5% a 25% liczby pikseli w I , a m i M są około jednej dziesiątej przekątnej).

Oto kilka zdjęć testowych (kliknij, aby zobaczyć oryginały). Możesz także opublikować własne.

Mona Lisa Wodospad Nighthawks Gwieździsta noc Most złotej bramy

Prostsze obrazy:

Schody PenrosePasek Mobiusa Krzywa Hilberta

To konkurs popularności. Zwycięskie zgłoszenie wygrywa.

Notatki

  • Pomocne może być pozwolenie na wyprowadzenie L z procentu całkowitej liczby pikseli w I, a także wartości bezwzględnej. np. >>> imageliner I=img.png L=50% m=10 M=20byłoby to to samo, >>> imageliner I=img.png L=32 m=10 M=20jakby to img.pngbył obraz 8 na 8 pikseli. Coś podobnego można zrobić dla m i M . Nie jest to wymagane.
  • Ponieważ linie nie może przejść poza boiskiem, jak najdłuższej linii będzie przekątnej długości I . Posiadanie M wyższego niż to nie powinno jednak niczego łamać.
  • Oczywiście, jeśli m wynosi 0, a L jest większa lub równa liczbie pikseli w I , O może być identyczne z I , mając długość 0 „linii” w każdym miejscu piksela. To zachowanie nie jest wymagane.
  • Prawdopodobnie odtworzenie kształtu I jest ważniejsze niż odtworzenie koloru. Możesz zajrzeć do wykrywania krawędzi .
Hobby Calvina
źródło
Wyjaśnienie: Czy biblioteki takie jak SimpleCV są dozwolone? A odpowiedzi mogą mieć dowolny wybór dla I, L, m i M, w tym m = 0 i L = obszar?
rationalis
@epicwisdom Tak, wszystkie biblioteki (oprócz rzeczy, które już wykonują to zadanie) są dozwolone. Możesz używać klawiszy, wykrywanie krawędzi, cokolwiek. Twój algorytm powinien działać dla wszystkich poprawnych wyborów I , L , m , M , włączając m = 0 i L = obszar. (Chociaż oczywiście Twój algorytm może wyglądać lepiej dla określonych ustawień parametrów.)
Calvin's Hobbies
Czy na przykład ten konkretny algorytm biblioteczny zostałby uznany za nieprawidłową odpowiedź?
rationalis
@epicwisdom Właściwie pozwolę na to i inne podobne rzeczy. Wygląda na to, że nadal trzeba by sprytnego ulepszenia, aby zrobić zdjęcie z linii, które ci daje.
Calvin's Hobbies
1
Czy linie muszą mieć grubość 1?
aditsu

Odpowiedzi:

21

C ++ - nieco losowe linie, a potem trochę

Najpierw kilka losowych linii

Pierwszy krok algorytmu losowo generuje linie, przyjmuje dla obrazu docelowego średnią liczbę pikseli wzdłuż tego, a następnie oblicza, czy zsumowany kwadrat odległości rgb wszystkich pikseli byłby mniejszy, gdybyśmy namalowali nową linię (i tylko go pomaluj, jeśli jest). Nowy kolor linii dla tego jest wybierany jako średnia kanałowa wartości rgb, z losowym dodawaniem -15 / + 15.

Rzeczy, które zauważyłem i które wpłynęły na wdrożenie:

  • Kolor początkowy to średnia całego obrazu. Ma to na celu przeciwdziałanie śmiesznym efektom, takim jak zmiana na biały, a obszar jest czarny, wtedy już coś w postaci jasnozielonej linii jest lepiej widoczne, ponieważ jest bliżej czerni niż już biała.
  • Przyjmowanie czystego średniego koloru linii nie jest tak dobre, ponieważ okazuje się, że nie jest w stanie wygenerować rozjaśnienia poprzez zastąpienie go późniejszymi liniami. Wykonanie małego przypadkowego odchylenia trochę pomaga, ale jeśli spojrzysz na gwiaździstą noc, nie powiedzie się, jeśli lokalny kontrast jest wysoki w wielu miejscach.

Eksperymentowałem z niektórymi liczbami, wybrałem L=0.3*pixel_count(I)i wyszedłem m=10i M=50. Będzie produkować ładne wyniki zaczynając od około 0.25aby 0.26do liczby linii, ale wybrałem 0,3 mieć więcej miejsca dla dokładnych szczegółów.

W przypadku pełnowymiarowego obrazu złotej bramy spowodowało to malowanie 235929 linii (co zajęło tutaj aż 13 sekund). Pamiętaj, że wszystkie obrazy tutaj są wyświetlane w zmniejszonym rozmiarze i musisz je otworzyć w nowej karcie / pobrać je, aby wyświetlić pełną rozdzielczość.

Usuń niegodne

Następny krok jest dość drogi (dla 235 tys. Linii zajęło to około godziny, ale powinno to być w granicach „godziny dla 10 tys. Linii na 1 megapiksel”), ale jest to również nieco zaskakujące. Przechodzę przez wszystkie wcześniej malowane linie i usuwam te, które nie poprawiają obrazu. To pozostawia mnie w tym biegu z tylko 97347 liniami, które tworzą następujący obraz:

Prawdopodobnie trzeba je pobrać i porównać w odpowiedniej przeglądarce obrazów, aby wykryć większość różnic.

i zacznij od nowa

Teraz mam wiele linii, które mogę ponownie pomalować, aby mieć w sumie 235929. Niewiele do powiedzenia, więc oto obraz:

wprowadź opis zdjęcia tutaj

krótka analiza

Cała procedura wydaje się działać jak filtr rozmywający, wrażliwy na lokalny kontrast i rozmiary obiektów. Ciekawe jest również to, gdzie są malowane linie, więc program też je rejestruje (dla każdej linii kolor pikseli będzie o jeden stopień bielszy, na końcu kontrast zostanie zmaksymalizowany). Oto odpowiadające trzy powyżej kolorowe.

animacje

A ponieważ wszyscy uwielbiamy animacje, oto kilka animowanych gifów z całego procesu tworzenia mniejszego obrazu złotej bramy. Zauważ, że istnieje znaczące dithering z powodu formatu gif (a ponieważ twórcy formatów plików animacji w prawdziwych kolorach i producenci przeglądarek toczą wojnę o swoje ego, nie ma standardowego formatu dla animacji w prawdziwych kolorach, w przeciwnym razie mógłbym dodać plik .mng lub podobny ).

Trochę więcej

Zgodnie z życzeniem, oto niektóre wyniki innych obrazów (ponownie może być konieczne otwarcie ich w nowej karcie, aby nie skalować ich w dół)

Przyszłe myśli

Zabawa kodem może dać ciekawe odmiany.

  • Wybierz kolor linii losowo, zamiast opierać się na średniej. Możesz potrzebować więcej niż dwóch cykli.
  • Kod w pastebin również zawiera pewne pojęcie o algorytmie genetycznym, ale obraz jest już prawdopodobnie tak dobry, że zajęłoby zbyt wiele pokoleń, a ten kod jest zbyt wolny, aby pasował do reguły „jednej godziny”.
  • Wykonaj kolejną rundę usuwania / odmalowywania, a nawet dwie ...
  • Zmień limit miejsca, w którym można usunąć linie (np. „Musi poprawić obraz co najmniej N lepiej”)

Kod

To tylko dwie główne przydatne funkcje, cały kod nie mieści się tutaj i można je znaleźć na stronie http://ideone.com/Z2P6Ls

Te bmpzajęcia rawi raw_linefunkcja zrobić dostępu pikseli i linii odpowiednio w obiekcie, który można zapisać do formatu bmp (To tylko niektóre Hack leżące wokół i pomyślałem, że czyni to nieco niezależny od jakiejkolwiek biblioteki).

Format pliku wejściowego to PPM

std::pair<bmp,std::vector<line>>  paint_useful( const bmp& orig, bmp& clone, std::vector<line>& retlines, bmp& layer, const std::string& outprefix, size_t x, size_t y )
{
        const size_t pixels = (x*y);
        const size_t lines = 0.3*pixels;
//      const size_t lines = 10000;

//      const size_t start_accurate_color = lines/4;

        std::random_device rnd;

        std::uniform_int_distribution<size_t> distx(0,x-1);
        std::uniform_int_distribution<size_t> disty(0,y-1);
        std::uniform_int_distribution<size_t> col(-15,15);
        std::uniform_int_distribution<size_t> acol(0,255);

        const ssize_t m = 1*1;
        const ssize_t M = 50*50;

        retlines.reserve( lines );

        for (size_t i = retlines.size(); i < lines; ++i)
        {
                size_t x0;
                size_t x1;

                size_t y0;
                size_t y1;

                size_t dist = 0;
                do
                {
                        x0 = distx(rnd);
                        x1 = distx(rnd);

                        y0 = disty(rnd);
                        y1 = disty(rnd);

                        dist = distance(x0,x1,y0,y1);
                }
                while( dist > M || dist < m );

                std::vector<std::pair<int32_t,int32_t>> points = clone.raw_line_pixels(x0,y0,x1,y1);

                ssize_t r = 0;
                ssize_t g = 0;
                ssize_t b = 0;

                for (size_t i = 0; i < points.size(); ++i)
                {
                        r += orig.raw(points[i].first,points[i].second).r;
                        g += orig.raw(points[i].first,points[i].second).g;
                        b += orig.raw(points[i].first,points[i].second).b;
                }

                r += col(rnd);
                g += col(rnd);
                b += col(rnd);

                r /= points.size();
                g /= points.size();
                b /= points.size();

                r %= 255;
                g %= 255;
                b %= 255;

                r = std::max(ssize_t(0),r);
                g = std::max(ssize_t(0),g);
                b = std::max(ssize_t(0),b);

//              r = acol(rnd);
//              g = acol(rnd);
//              b = acol(rnd);

//              if( i > start_accurate_color )
                {
                        ssize_t dp = 0; // accumulated distance of new color to original
                        ssize_t dn = 0; // accumulated distance of current reproduced to original
                        for (size_t i = 0; i < points.size(); ++i)
                        {
                                dp += rgb_distance(
                                                                                orig.raw(points[i].first,points[i].second).r,r,
                                                                                orig.raw(points[i].first,points[i].second).g,g,
                                                                                orig.raw(points[i].first,points[i].second).b,b
                                                                        );

                                dn += rgb_distance(
                                                                                clone.raw(points[i].first,points[i].second).r,orig.raw(points[i].first,points[i].second).r,
                                                                                clone.raw(points[i].first,points[i].second).g,orig.raw(points[i].first,points[i].second).g,
                                                                                clone.raw(points[i].first,points[i].second).b,orig.raw(points[i].first,points[i].second).b
                                                                        );

                        }

                        if( dp > dn ) // the distance to original is bigger, use the new one
                        {
                                --i;
                                continue;
                        }
                        // also abandon if already too bad
//                      if( dp > 100000 )
//                      {
//                              --i;
//                              continue;
//                      }
                }

                layer.raw_line_add(x0,y0,x1,y1,{1u,1u,1u});
                clone.raw_line(x0,y0,x1,y1,{(uint32_t)r,(uint32_t)g,(uint32_t)b});
                retlines.push_back({ (int)x0,(int)y0,(int)x1,(int)y1,(int)r,(int)g,(int)b});

                static time_t last = 0;
                time_t now = time(0);
                if( i % (lines/100) == 0 )
                {
                        std::ostringstream fn;
                        fn << outprefix + "perc_" << std::setw(3) << std::setfill('0') << (i/(lines/100)) << ".bmp"; 
                        clone.write(fn.str());
                        bmp lc(layer);
                        lc.max_contrast_all();
                        lc.write(outprefix + "layer_" + fn.str());
                }

                if( (now-last) > 10 )
                {
                        last = now;
                        static int st = 0;
                        std::ostringstream fn;
                        fn << outprefix + "inter_" << std::setw(8) << std::setfill('0') << i << ".bmp";
                        clone.write(fn.str());

                        ++st;
                }
        }
        clone.write(outprefix + "clone.bmp");
        return { clone, retlines };
}


void erase_bad( std::vector<line>& lines, const bmp& orig )
{
        ssize_t current_score = evaluate(lines,orig);

        std::vector<line> newlines(lines);

        uint32_t deactivated = 0;
        std::cout << "current_score = " << current_score << "\n";
        for (size_t i = 0; i < newlines.size(); ++i)
        {
                newlines[i].active = false;
                ssize_t score = evaluate(newlines,orig);
                if( score > current_score )
                {
                        newlines[i].active = true;
                }
                else
                {
                        current_score = score;
                        ++deactivated;
                }
                if( i % 1000 == 0 )
                {
                        std::ostringstream fn;
                        fn << "erase_" << std::setw(6) << std::setfill('0') << i << ".bmp";
                        bmp tmp(orig);
                        paint(newlines,tmp);
                        tmp.write(fn.str());
                        paint_layers(newlines,tmp);
                        tmp.max_contrast_all();
                        tmp.write("layers_" + fn.str());
                        std::cout << "\r i = " << i << std::flush;
                }
        }
        std::cout << "\n";
        std::cout << "current_score = " << current_score << "\n";
        std::cout << "deactivated = " << deactivated << "\n";


        bmp tmp(orig);

        paint(newlines,tmp);
        tmp.write("newlines.bmp");
        lines.clear();
        for (size_t i = 0; i < newlines.size(); ++i)
        {
                if( newlines[i].is_active() )
                {
                        lines.push_back(newlines[i]);
                }
        }
}
Plazma
źródło
+1, naprawdę bardzo fajnie. Czy masz wyniki dla innych zdjęć testowych?
Nathaniel
1
@Nanielaniel: Dodałem trochę. „Proste” obrazy są nieciekawe, ponieważ odtwarzanie jest prawie w pikselach idealne.
PlasmaHH
17

Java - losowe linie

Bardzo podstawowe rozwiązanie, które rysuje losowe linie i oblicza dla nich średni kolor obrazu źródłowego. Kolor tła jest ustawiony na średni kolor źródłowy.

L = 5000, m = 10, M = 50

wprowadź opis zdjęcia tutaj

L = 10000, m = 10, M = 50

wprowadź opis zdjęcia tutaj

EDYTOWAĆ

Dodałem algorytm genetyczny, który obsługuje populację linii. W każdym pokoleniu zachowujemy tylko 50% najlepszych linii, usuwamy pozostałe i generujemy losowo nowe. Kryteria utrzymania linii są:

  • ich odległość do kolorów obrazu źródłowego jest niewielka
  • liczba skrzyżowań z innymi liniami (im mniejsza, tym lepsza)
  • ich długość (im dłuższa, tym lepsza)
  • ich kąt z najbliższym sąsiadem (im mniejszy, tym lepszy)

Ku mojemu wielkiemu rozczarowaniu algorytm tak naprawdę nie poprawia jakości obrazu :-( po prostu linie stają się bardziej równoległe.

Pierwsza generacja (5000 linii)

wprowadź opis zdjęcia tutaj

Dziesiąta generacja (5000 linii)

wprowadź opis zdjęcia tutaj

Zabawa z parametrami

wprowadź opis zdjęcia tutajwprowadź opis zdjęcia tutajwprowadź opis zdjęcia tutaj

package line;

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

import javax.imageio.ImageIO;

import snake.Image;

public class Lines {

    private final static int NB_LINES = 5000;
    private final static int MIN_LENGTH = 10;
    private final static int MAX_LENGTH = 50;

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


        int [] bgColor = {0, 0, 0};
        int avgRed = 0, avgGreen = 0, avgBlue = 0, count = 0;
        for (int y = 0; y < src.getHeight(); y++) {
            for (int x = 0; x < src.getWidth(); x++) {
                int colsrc = src.getRGB(x, y);
                avgRed += colsrc & 255;
                avgGreen += (colsrc >> 8) & 255;
                avgBlue += (colsrc >> 16) & 255;
                count++;
            }
        }

        bgColor[0] = avgBlue/count; bgColor[1] = avgGreen/count; bgColor[2] = avgRed/count;
        for (int y = 0; y < src.getHeight(); y++) {
            for (int x = 0; x < src.getWidth(); x++) {
                dest.getRaster().setPixel(x, y, bgColor);
            }
        }
        List<List<Point>> lines = new ArrayList<List<Point>>();
        Random rand = new Random();
        for (int i = 0; i < NB_LINES; i++) {
            int length = rand.nextInt(MAX_LENGTH - MIN_LENGTH) + MIN_LENGTH;
            double ang = rand.nextDouble() * Math.PI;
            int lx = (int)(Math.cos(ang) * length); // can be negative or positive
            int ly = (int)(Math.sin(ang) * length); // positive only
            int sx = rand.nextInt(dest.getWidth() -1 - Math.abs(lx));
            int sy = rand.nextInt(dest.getHeight() - 1- Math.abs(ly));
            List<Point> line;
            if (lx > 0) {
                line = line(sx, sy, sx+lx, sy+ly);
            } else {
                line = line(sx+Math.abs(lx), sy, sx, sy+ly);
            }
            lines.add(line);    
        }

        // render the picture
        int [] color = {0, 0, 0};
        for (List<Point> line : lines) {

            avgRed = 0; avgGreen = 0; avgBlue = 0;
            count = 0;
            for (Point p : line) {
                int colsrc = src.getRGB(p.x, p.y);
                avgRed += colsrc & 255;
                avgGreen += (colsrc >> 8) & 255;
                avgBlue += (colsrc >> 16) & 255;
                count++;
            }
            avgRed /= count; avgGreen /= count; avgBlue /= count;
            color[0] = avgBlue; color[1] = avgGreen; color[2] = avgRed;
            for (Point p : line) {
                dest.getRaster().setPixel(p.x, p.y, color);
            }

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

    }

    private static List<Point> line(int x0, int y0, int x1, int y1) {
        List<Point> points = new ArrayList<Point>();
        int deltax = x1 - x0;
        int deltay = y1 - y0;
        int tmp;
        double error = 0;       
        double deltaerr = 0;
        if (Math.abs(deltax) >= Math.abs(deltay)) {
            if (x0 > x1) { // swap the 2 points
                tmp = x0; x0 = x1; x1 = tmp;
                tmp = y0; y0 = y1; y1 = tmp;
                deltax = - deltax; deltay = -deltay;
            }
            deltaerr = Math.abs (((double)deltay) / deltax); 
            int y = y0;
            for (int x = x0; x <= x1; x++) {
                points.add(new Point(x, y));
                error += deltaerr;
                if (error >= 0.5) {
                    if (y0 < y1) y++; else y--;
                    error -= 1.0;
                }
            }
        } else {
            if (y0 > y1) { // swap the 2 points
                tmp = x0; x0 = x1; x1 = tmp;
                tmp = y0; y0 = y1; y1 = tmp;
                deltax = - deltax; deltay = -deltay;
            }
            deltaerr = Math.abs (((double)deltax) / deltay);   // Assume deltay != 0 (line is not horizontal),
            int x = x0;
            for (int y = y0; y <= y1; y++) {
                points.add(new Point(x, y));
                error += deltaerr;
                if (error >= 0.5) {
                    if (x0 < x1) x++; else x--;
                    error -= 1.0;
                }
            }
        }
        return points;
    }
}
Arnaud
źródło
W końcu ktoś odpowiedział: D Chciałbym zobaczyć więcej przykładów.
Calvin's Hobbies
@Calvin Sure. Obecnie pracuję nad ulepszeniem algorytmu poprzez utrzymanie populacji linii i wyeliminowanie np. 20% gorszych i ponowne wygenerowanie nowych (jakiś rodzaj algorytmu genetycznego)
Arnaud
Miałem na myśli coś takiego, ale nie miałem czasu na pisanie. Czekam na genetyczny alg. wyniki :)
aditsu
Być może chcesz usunąć kryterium mniejszego kąta? Dlaczego to położyłeś? Oryginalny obraz wygląda dobrze, chociaż linie nie mają małego kąta przecięcia.
justhalf
@justhalf Gotowe. Dodałem kryterium kąta, próbując zasymulować pędzel malarza.
Arnaud,
9

C - linie proste

Podstawowe podejście w C, które działa na plikach ppm. Algorytm próbuje umieścić pionowe linie o optymalnej długości, aby wypełnić wszystkie piksele. Kolor tła i kolory linii są obliczane jako średnia wartość oryginalnego obrazu (mediana każdego kanału kolorów):

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define SIGN(x) ((x > 0) ? 1 : (x < 0) ? -1 : 0)
#define MIN(x, y) ((x > y) ? y : x)
#define MAX(x, y) ((x > y) ? x : y)

typedef struct {
    size_t width;
    size_t height;

    unsigned char *r;
    unsigned char *g;
    unsigned char *b;
} image;

typedef struct {
    unsigned char r;
    unsigned char g;
    unsigned char b;
} color;

void init_image(image *data, size_t width, size_t height) {
    data->width = width;
    data->height = height;
    data->r = malloc(sizeof(data->r) * data->width * data->height);
    data->g = malloc(sizeof(data->g) * data->width * data->height);
    data->b = malloc(sizeof(data->b) * data->width * data->height);
}

#define BUFFER_LEN 1024
int load_image(const char *filename, image* data) {
    FILE *f = fopen(filename, "r");
    char buffer[BUFFER_LEN];          /* read buffer */
    size_t max_value;
    size_t i;
    fgets(buffer, BUFFER_LEN, f);
    if (strncmp(buffer, "P3", 2) != 0) {
        printf("File begins with %s instead of P3\n", buffer);
        return 0;
    }

    fscanf(f, "%u", &data->width);
    fscanf(f, "%u", &data->height);
    fscanf(f, "%u", &max_value);
    assert(max_value==255);

    init_image(data, data->width, data->height);

    for (i = 0; i < data->width * data->height; i++) {
        fscanf(f, "%hhu", &(data->r[i]));
        fscanf(f, "%hhu", &(data->g[i]));
        fscanf(f, "%hhu", &(data->b[i]));
    }
    fclose(f);

    printf("Read %zux%zu pixels from %s.\n", data->width, data->height, filename);
}

int write_image(const char *filename, image *data) {
    FILE *f = fopen(filename, "w");
    size_t i;
    fprintf(f, "P3\n%zu %zu\n255\n", data->width, data->height);
    for (i = 0; i < data->width * data->height; i++) {
        fprintf(f, "%hhu %hhu %hhu ", data->r[i], data->g[i], data->b[i]);
    }
    fclose(f);
}

unsigned char average(unsigned char *data, size_t data_len) {
    size_t i;
    size_t j;
    size_t hist[256];

    for (i = 0; i < 256; i++) hist[i] = 0;
    for (i = 0; i < data_len; i++) hist[data[i]]++;
    j = 0;
    for (i = 0; i < 256; i++) {
        j += hist[i];
        if (j >= data_len / 2) return i;
    }
    return 255;
}

void set_pixel(image *data, size_t x, size_t y, unsigned char r, unsigned char g, unsigned char b) {
    data->r[x + data->width * y] = r;
    data->g[x + data->width * y] = g;
    data->b[x + data->width * y] = b;
}

color get_pixel(image *data, size_t x, size_t y) {
    color ret;
    ret.r = data->r[x + data->width * y];
    ret.g = data->g[x + data->width * y];
    ret.b = data->b[x + data->width * y];
    return ret;
}

void fill(image *data, unsigned char r, unsigned char g, unsigned char b) {
    size_t i;
    for (i = 0; i < data->width * data->height; i++) {
        data->r[i] = r;
        data->g[i] = g;
        data->b[i] = b;
    }
}

void line(image *data, size_t x1, size_t y1, size_t x2, size_t y2, unsigned char r, unsigned char g, unsigned char b) {
    size_t x, y, t, pdx, pdy, ddx, ddy, es, el;
    int dx, dy, incx, incy, err;

    dx=x2-x1;
    dy=y2-y1;
    incx=SIGN(dx);
    incy=SIGN(dy);
    if(dx<0) dx=-dx;
    if(dy<0) dy=-dy;
    if (dx>dy) {
        pdx=incx;
        pdy=0;
        ddx=incx;
        ddy=incy;
        es=dy;
        el=dx;
    } else {
        pdx=0;
        pdy=incy;
        ddx=incx;
        ddy=incy;
        es=dx;
        el=dy;
    }
    x=x1;
    y=y1;
    err=el/2;
    set_pixel(data, x, y, r, g, b);

    for(t=0; t<el; t++) {
        err -= es;
        if(err<0) {
            err+=el;
            x+=ddx;
            y+=ddy;
        } else {
            x+=pdx;
            y+=pdy;
        }
        set_pixel(data, x, y, r, g, b);
    }
}

color average_line(image *data, size_t x1, size_t y1, size_t x2, size_t y2) {
    size_t x, y, t, pdx, pdy, ddx, ddy, es, el;
    int dx, dy, incx, incy, err;
    color ret;
    color px;
    size_t i;
    size_t j;
    size_t hist_r[256];
    size_t hist_g[256];
    size_t hist_b[256];
    size_t data_len = 0;

    for (i = 0; i < 256; i++) {
        hist_r[i] = 0;
        hist_g[i] = 0;
        hist_b[i] = 0;
    }

    dx=x2-x1;
    dy=y2-y1;
    incx=SIGN(dx);
    incy=SIGN(dy);
    if(dx<0) dx=-dx;
    if(dy<0) dy=-dy;
    if (dx>dy) {
        pdx=incx;
        pdy=0;
        ddx=incx;
        ddy=incy;
        es=dy;
        el=dx;
    } else {
        pdx=0;
        pdy=incy;
        ddx=incx;
        ddy=incy;
        es=dx;
        el=dy;
    }
    x=x1;
    y=y1;
    err=el/2;
    px = get_pixel(data, x, y);
    hist_r[px.r]++;
    hist_g[px.g]++;
    hist_b[px.b]++;
    data_len++;

    for(t=0; t<el; t++) {
        err -= es;
        if(err<0) {
            err+=el;
            x+=ddx;
            y+=ddy;
        } else {
            x+=pdx;
            y+=pdy;
        }
        px = get_pixel(data, x, y);
        hist_r[px.r]++;
        hist_g[px.g]++;
        hist_b[px.b]++;
        data_len++;
    }

    j = 0;
    for (i = 0; i < 256; i++) {
        j += hist_r[i];
        if (j >= data_len / 2) {
            ret.r = i;
            break;
        }
    }
    j = 0;
    for (i = 0; i < 256; i++) {
        j += hist_g[i];
        if (j >= data_len / 2) {
            ret.g = i;
            break;
        }
    }
    j = 0;
    for (i = 0; i < 256; i++) {
        j += hist_b[i];
        if (j >= data_len / 2) {
            ret.b = i;
            break;
        }
    }
    return ret;
}


void lines(image *source, image *dest, size_t L, float m, float M) {
    size_t i, j;
    float dx;
    float mx, my;
    float mm = MAX(MIN(source->width * source->height / L, M), m);
    unsigned char av_r = average(source->r, source->width * source->height);
    unsigned char av_g = average(source->g, source->width * source->height);
    unsigned char av_b = average(source->b, source->width * source->height);
    fill(dest, av_r, av_g, av_b);
    dx = (float)source->width / L;
    mx = 0;
    my = mm / 2;
    for (i = 0; i < L; i++) {
        color avg;
        mx += dx;
        my += (source->height - mm) / 8;
        if (my + mm / 2 > source->height) {
            my = mm / 2 + ((size_t)(my + mm / 2) % (size_t)(source->height - mm));
        }
        avg = average_line(source, mx, my - mm / 2, mx, my + mm / 2);
        line(dest, mx, my - mm / 2, mx, my + mm / 2, avg.r, avg.g, avg.b);
    }
}

int main(int argc, char *argv[]) {
    image source;
    image dest;
    size_t L;
    float m;
    float M;

    load_image(argv[1], &source);
    L = atol(argv[2]);
    m = atof(argv[3]);
    M = atof(argv[4]);

    init_image(&dest, source.width, source.height);
    lines(&source, &dest, L, m, M);


    write_image(argv[5], &dest);
}

L = 5000, m = 10, M = 50

L = 5000, m = 10, M = 50

L = 5000, m = 10, M = 50

L = 5000, m = 10, M = 50

L = 100000, m = 10, M = 50

wprowadź opis zdjęcia tutaj

urzeit
źródło
6

Python 3 - oparty na „nieco losowych liniach, a potem niektórych” oraz wykrywaniu krawędzi sobel.

kod teoretycznie może działać wiecznie (więc mogę uruchomić go dla zabawy), ale rejestruje jego postęp, więc wszystkie zdjęcia są robione od 1 do 10 minut.

Najpierw odczytuje obraz, a następnie używa detekcji krawędzi sobel, aby znaleźć kąt wszystkich krawędzi, aby mieć pewność, że linie nie przekroczą innego koloru. Po ustawieniu linii o losowej długości w ciągu (lengthmin, lengthmax), następnie sprawdza, czy przyczynia się do ogólnego obrazu. Podczas gdy mniejsze linie są lepsze, ustawiam długość linii na 10-50.

from random import randint, uniform
import json
from PIL import Image, ImageDraw, ImageFilter
import math
k=(-1,0,1,-2,0,2,-1,0,1)
k1=(-1,-2,-1,0,0,0,1,2,1)
population=[]
lengthmin=10
lengthmax=50
number_lines=10**8
im=Image.open('0.png')
[x1,y1]=im.size
dx=0
class drawer():
    def __init__(self,genome,score,filename):
        self.genome=genome
        self.score=score
        self.filename=filename
    def initpoint(self,g1):
        g2=self.genome
        im=Image.open('0.png')
        im1=im.filter(ImageFilter.Kernel((3,3),k,1,128))
        im2=im.filter(ImageFilter.Kernel((3,3),k1,1,128))
        im1=im1.filter(ImageFilter.GaussianBlur(radius=4))
        im2=im2.filter(ImageFilter.GaussianBlur(radius=4))
        for x in range(0,number_lines):
            if(x%10**4==0):
                print(x*100/number_lines)
                self.save()
                g1.save('1.png')
            (x,y)=(randint(0,x1-1),randint(0,y1-1))
            w=im1.getpixel((x,y))[0]-128
            z=im2.getpixel((x,y))[0]-128
            w=int(w)
            z=int(z)
            W=(w**2+z**2)**0.5
            if(W!=0):
                w=(w/W)*randint(lengthmin,lengthmax)
                z=(z/W)*randint(lengthmin,lengthmax)
                (w,z)=(z,w)
                (a,b)=(x+w,y+z)
                a=int(a)
                b=int(b)
                x=int(x)
                y=int(y)
                if(a>=x1):
                    a=x1-1
                if(b>=y1):
                    b=y1-1
                if(a<0):
                    a=0
                if(b<0):
                    b=0
                if(x>=x1):
                    x=x1-1
                if(y>=y1):
                    y=y1-1
                if(x<0):
                    x=0
                if(y<0):
                    y=0
                C=[0,0,0]
                D=0
                E=0
                F=0
                G=0
                W=((x-a)**2+(y-b)**2)**0.5
                if(W!=0):
                    for Z in range(0,int(W)):
                        w=(Z/W)
                        (c,d)=((w*x+(1-w)*a),(w*y+(1-w)*b))
                        c=int(c)
                        d=int(d)
                        C[0]+=im.getpixel((c,d))[0]
                        C[1]+=im.getpixel((c,d))[1]
                        C[2]+=im.getpixel((c,d))[2]
                    C[0]/=W
                    C[1]/=W
                    C[2]/=W
                    C[0]=int(C[0])
                    C[1]=int(C[1])
                    C[2]=int(C[2])
                    for Z in range(0,int(W)):
                        w=(Z/W)
                        (c,d)=((w*x+(1-w)*a),(w*y+(1-w)*b))
                        c=int(c)
                        d=int(d)
                        E=0
                        D=0
                        D+=(g1.getpixel((c,d))[0]-im.getpixel((c,d))[0])**2
                        D+=(g1.getpixel((c,d))[1]-im.getpixel((c,d))[1])**2
                        D+=(g1.getpixel((c,d))[2]-im.getpixel((c,d))[2])**2
                        F+=D**0.5
                        E+=(im.getpixel((c,d))[0]-C[0])**2
                        E+=(im.getpixel((c,d))[1]-C[1])**2
                        E+=(im.getpixel((c,d))[2]-C[2])**2
                        G+=E**0.5
                    #print((G/W,F/W))
                    if(G<F):
                        for Z in range(0,int(W)):
                            w=(Z/W)
                            (c,d)=((w*x+(1-w)*a),(w*y+(1-w)*b))
                            c=int(c)
                            d=int(d)
                            g1.putpixel((c,d),(int(C[0]),int(C[1]),int(C[2])))
                        g2.append((x,y,a,b,int(C[0]%256),int(C[1]%256),int(C[2]%256)))
        return(g1)
    def import_file(self):
        with open(self.filename, 'r') as infile:
            self.genome=json.loads(infile.read())
        print(len(self.genome))
    def save(self):
        with open(self.filename, 'w') as outfile:
            data = json.dumps(self.genome)
            outfile.write(data)
population.append(drawer([],0,'0.txt'))
G=0
g1=Image.new('RGB',(x1,y1),'black')
g1=population[0].initpoint(g1)
g1.save('1.png')

amerykański gotyk

Escher

Magenta
źródło