Reproduzieren Sie ein Bild mit Linien

31

Schreiben Sie ein Programm, das ein farbechtes RGB-Bild I , die maximale Anzahl der zu zeichnenden Linien L und die minimale m- und maximale M- Länge jeder Linie aufnimmt . Geben Sie ein Bild O aus , das so gut wie möglich wie I aussieht und mit L oder weniger geraden Linien gezeichnet wird, die alle eine euklidische Länge zwischen m und M haben .

Jede Linie muss einfarbig sein, beide Endpunkte in den Grenzen von O haben und mit dem Linienalgorithmus von Bresenham gezeichnet werden (was die meisten Grafikbibliotheken bereits für Sie tun werden). Einzelne Zeilen können nur 1 Pixel dick sein.

Alle Zeilen, auch die mit der Länge 0, sollten mindestens ein Pixel einnehmen. Linien können übereinander gezeichnet werden.

Bevor Sie Linien zeichnen, können Sie den Hintergrund von O mit einer beliebigen Farbe initialisieren (die von I abhängen kann ).

Einzelheiten

  • O sollte die gleichen Abmessungen haben wie ich .
  • L wird immer eine nichtnegative ganze Zahl sein. Es kann größer als die Fläche sein , ich .
  • m und M sind nichtnegative Gleitkommazahlen mit M > = m . Der Abstand zwischen zwei Pixeln ist der euklidische Abstand zwischen ihren Mittelpunkten. Wenn dieser Abstand kleiner als m oder größer als M ist , ist eine Linie zwischen diesen Pixeln nicht zulässig.
  • Die Leitungen sollten nicht vorgespannt sein.
  • Deckkraft und Alpha sollten nicht verwendet werden.
  • Es sollte nicht länger als eine Stunde dauern, bis Ihr Programm auf einem anständigen modernen Computer mit Bildern mit weniger als einer Million Pixel und L weniger als 10.000 ausgeführt wird.

Bilder testen

Sie sollten uns natürlich Ihre genauesten oder interessantesten Ausgabebilder zeigen (was ich erwarte, wenn L zwischen 5% und 25% der Pixelanzahl in I liegt und m und M ungefähr ein Zehntel der Diagonalengröße betragen).

Hier sind einige Testbilder (für Originale klicken). Sie können auch Ihre eigenen posten.

Mona Lisa Wasserfall Nighthawks Sternenklare Nacht Golden Gate Bridge

Einfachere Bilder:

Penrose-TreppeMöbiusband Hilbert-Kurve

Dies ist ein Beliebtheitswettbewerb. Die Einsendung mit den höchsten Stimmen gewinnt.

Anmerkungen

  • Es kann hilfreich sein, L aus einem Prozentsatz der gesamten Pixel in I sowie einem absoluten Wert ableiten zu lassen . zB >>> imageliner I=img.png L=50% m=10 M=20wäre das gleiche, als >>> imageliner I=img.png L=32 m=10 M=20wäre img.pnges ein 8 mal 8 Pixel großes Bild. Ähnliches könnte für m und M getan werden . Dies ist nicht erforderlich.
  • Da Linien nicht über Grenzen hinausgehen können, ist die Diagonallänge von I die längste mögliche Linie . Ein höheres M sollte jedoch nichts kaputt machen.
  • Wenn m 0 ist und L größer oder gleich der Anzahl von Pixeln in I ist , könnte O natürlich mit I identisch sein, indem die Länge 0 "Zeilen" an jeder Pixelstelle vorliegt. Dieses Verhalten ist nicht erforderlich.
  • Wahrscheinlich ist die Reproduktion der Form von I wichtiger als die Reproduktion der Farbe. Möglicherweise möchten Sie sich mit der Kantenerkennung befassen .
Calvins Hobbys
quelle
Zur Verdeutlichung: Sind Bibliotheken wie SimpleCV erlaubt? Und Antworten können eine beliebige Wahl für I, L, m und M haben, einschließlich m = 0 und L = Fläche?
Rationalis
@epicwisdom Ja, alle Bibliotheken (mit Ausnahme von Dingen, die diese Aufgabe bereits ausführen) sind zulässig. Fühlen Sie sich frei, um Schlüsselpunkte, Kantenerkennung, was auch immer zu verwenden. Ihr Algorithmus sollte für alle gültigen Auswahlen von I , L , m , M , einschließlich m = 0 und L = area funktionieren . (Natürlich kann Ihr Algorithmus für bestimmte Einstellungen der Parameter besser aussehen.)
Calvins Hobbys
Dann würde zum Beispiel dieser spezielle Bibliotheksalgorithmus als ungültige Antwort betrachtet werden?
Rationalis
@epicwisdom Eigentlich werde ich das und ähnliche Dinge zulassen. Es sieht so aus, als würde es noch einige clevere Anpassungen erfordern, um ein Bild aus den Linien zu machen, die es Ihnen gibt.
Calvins Hobbys
1
Müssen die Linien eine Stärke von 1 haben?
Aditsu

Antworten:

21

C ++ - etwas zufällige Zeilen und noch einige mehr

Zuerst ein paar zufällige Zeilen

Der erste Schritt des Algorithmus erzeugt zufällig Linien, nimmt für das Zielbild einen Durchschnitt der Pixel entlang dieser Linie und berechnet dann, ob das aufsummierte Quadrat der RGB-Abstände aller Pixel geringer wäre, wenn wir die neue Linie zeichnen würden (und nur malen, wenn es ist). Die Farbe der neuen Linien wird als kanalweiser Durchschnitt der RGB-Werte mit einer zufälligen Addition von -15 / + 15 gewählt.

Dinge, die mir aufgefallen sind und die Umsetzung beeinflusst haben:

  • Die Anfangsfarbe ist der Durchschnitt des gesamten Bildes. Dies dient dazu, lustigen Effekten entgegenzuwirken, wenn man sie weiß macht und die Fläche schwarz ist, dann ist schon etwas wie eine hellgrüne Linie besser zu sehen, da sie näher an Schwarz liegt als die bereits weiße.
  • Die Verwendung der reinen Durchschnittsfarbe für die Linie ist nicht so gut, da sich herausstellt, dass durch das Überschreiben durch spätere Linien keine Hervorhebungen erzeugt werden können. Eine kleine zufällige Abweichung hilft ein bisschen, aber wenn Sie in der Sternennacht schauen, schlägt dies fehl, wenn der lokale Kontrast an vielen Stellen hoch ist.

Ich experimentierte mit einigen Zahlen und wählte L=0.3*pixel_count(I)und verließ m=10und M=50. Es wird gute Ergebnisse liefern, angefangen bei ungefähr 0.25bis 0.26zur Anzahl der Zeilen, aber ich habe 0,3 gewählt, um mehr Platz für genaue Details zu haben.

Für das vollständige Golden Gate-Bild wurden 235929 Linien gezeichnet (wofür es hier satte 13 Sekunden gedauert hat). Beachten Sie, dass alle Bilder hier in verkleinerter Größe angezeigt werden und Sie sie in einem neuen Tab öffnen / herunterladen müssen, um die volle Auflösung anzuzeigen.

Löschen Sie die Unwürdigen

Der nächste Schritt ist ziemlich teuer (für die 235k-Leitungen hat es ungefähr eine Stunde gedauert, aber das sollte durchaus innerhalb der Zeitanforderungen von "einer Stunde für 10k-Leitungen mit 1 Megapixel" liegen), ist aber auch ein bisschen überraschend. Ich gehe alle zuvor gemalten Linien durch und entferne diejenigen, die das Bild nicht verbessern. Dies lässt mich in diesem Lauf mit nur 97347 Zeilen, die das folgende Bild erzeugen:

Sie müssen sie wahrscheinlich in einem geeigneten Bildbetrachter herunterladen und vergleichen, um die meisten Unterschiede zu erkennen.

und von vorne anfangen

Jetzt habe ich viele Linien, die ich wieder malen kann, um insgesamt 235929 wieder zu haben. Nicht viel zu sagen, deshalb hier das Bild:

Bildbeschreibung hier eingeben

kurze Analyse

Der gesamte Vorgang scheint wie ein Unschärfefilter zu funktionieren, der auf lokalen Kontrast und Objektgrößen reagiert. Es ist aber auch interessant zu sehen, wo die Linien gezeichnet werden, so dass das Programm diese auch aufzeichnet (für jede Linie wird die Pixelfarbe einen Schritt weißer gemacht, am Ende wird der Kontrast maximiert). Hier sind die entsprechenden zu den drei oben farbigen.

Animationen

Und da wir alle Animationen lieben, finden Sie hier einige animierte Gifs des gesamten Prozesses für das kleinere Golden Gate-Bild. Beachten Sie, dass es aufgrund des GIF-Formats zu deutlichen Dithering-Effekten kommt (und da Entwickler von True-Color-Animationsdateiformaten und Browserhersteller im Streit um ihr Ego sind, gibt es kein Standardformat für True-Color-Animationen. Andernfalls hätte ich möglicherweise ein .mng oder ähnliches hinzugefügt ).

Etwas mehr

Wie gewünscht, hier einige Ergebnisse der anderen Bilder (möglicherweise müssen Sie sie erneut in einem neuen Tab öffnen, damit sie nicht verkleinert werden.)

Zukünftige Gedanken

Das Herumspielen mit dem Code kann einige interessante Variationen ergeben.

  • Wählen Sie die Farbe der Linien nach dem Zufallsprinzip, anstatt auf dem Durchschnitt zu basieren. Möglicherweise benötigen Sie mehr als zwei Zyklen.
  • Der Code im Pastebin enthält auch eine Idee eines genetischen Algorithmus, aber das Bild ist wahrscheinlich bereits so gut, dass es zu viele Generationen dauern würde, und dieser Code ist auch zu langsam, um in die Regel "eine Stunde" zu passen.
  • Mach noch eine Runde Löschen / Repaint oder sogar zwei ...
  • Ändern Sie die Grenze, an der Linien gelöscht werden können (z. B. "muss das Bild mindestens N besser machen").

Der Code

Dies sind nur die beiden wichtigsten nützlichen Funktionen. Der gesamte Code passt hier nicht hinein und kann unter http://ideone.com/Z2P6Ls gefunden werden

Die bmpKlassen rawund raw_lineFunktionen greifen auf Pixel bzw. Zeilen in einem Objekt zu, das im bmp-Format geschrieben werden kann (es war nur ein Hack, der herumlag, und ich dachte, das macht dies etwas unabhängig von jeder Bibliothek).

Das Eingabedateiformat ist 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]);
                }
        }
}
PlasmaHH
quelle
+1, in der Tat sehr schön. Haben Sie Ergebnisse für die anderen Testbilder?
Nathaniel
1
@ Nathaniel: Ich habe einige hinzugefügt. Die "einfachen" Bilder sind uninteressant, weil die Erholung fast pixelgenau ist.
PlasmaHH
17

Java - zufällige Zeilen

Eine sehr einfache Lösung, die zufällige Linien zeichnet und für sie die durchschnittliche Farbe des Quellbilds berechnet. Die Hintergrundfarbe wird auf die durchschnittliche Quellfarbe eingestellt.

L = 5000, m = 10, M = 50

Bildbeschreibung hier eingeben

L = 10000, m = 10, M = 50

Bildbeschreibung hier eingeben

BEARBEITEN

Ich habe einen genetischen Algorithmus hinzugefügt, der eine Population von Linien verarbeitet. Bei jeder Generation behalten wir nur die 50% besten Zeilen bei, lassen die anderen fallen und generieren zufällig neue. Die Kriterien für das Beibehalten der Linien sind:

  • ihr Abstand zu den Quellbildfarben ist gering
  • die Anzahl der Schnittpunkte mit anderen Linien (je kleiner desto besser)
  • ihre Länge (je länger desto besser)
  • ihr Winkel zum nächsten Nachbarn (je kleiner desto besser)

Zu meiner großen Enttäuschung scheint der Algorithmus die Bildqualität nicht wirklich zu verbessern :-( nur die Linien werden paralleler.

Erste Generation (5000 Zeilen)

Bildbeschreibung hier eingeben

Zehnte Generation (5000 Zeilen)

Bildbeschreibung hier eingeben

Mit Parametern spielen

Bildbeschreibung hier eingebenBildbeschreibung hier eingebenBildbeschreibung hier eingeben

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
quelle
Schließlich antwortete jemand: D Ich würde gerne weitere Beispiele sehen.
Calvins Hobbys
@ Calvin Sicher. Im Moment arbeite ich daran, den Algorithmus zu verbessern, indem ich eine Population von Linien behalte und z. B. die 20% schlechteren eliminiere und neue (eine Art genetischer Algorithmus) generiere
Arnaud,
Ich hatte so etwas im Sinn, aber keine Zeit, es zu schreiben. Ich freue mich auf die genetische Alge. Ergebnisse :)
aditsu
Vielleicht möchten Sie das kleinere Winkelkriterium entfernen? Warum hast du es gesagt? Das Originalbild sieht gut aus, obwohl die Linien keinen kleinen Schnittwinkel haben.
Nur die Hälfte des
@justhalf Done. Ich habe das Winkelkriterium hinzugefügt, um den Malpinsel zu simulieren.
Arnaud
9

C - gerade Linien

Ein grundlegender Ansatz in C, der ppm-Dateien verarbeitet. Der Algorithmus versucht, vertikale Linien mit optimaler Linienlänge zu platzieren, um alle Pixel zu füllen. Die Hintergrundfarbe und die Linienfarben werden als Durchschnittswert des Originalbilds berechnet (der Median jedes Farbkanals):

#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

Bildbeschreibung hier eingeben

urzeit
quelle
6

Python 3-basiert auf "etwas zufälligen Linien und etwas mehr" plus Sobel-Kantenerkennung.

Der Code kann theoretisch für immer laufen (also kann ich ihn zum Spaß über Nacht laufen lassen), aber er zeichnet seinen Fortschritt auf, sodass alle Bilder von der 1-10-Minuten-Marke aufgenommen werden.

Es liest zuerst das Bild und ermittelt dann mithilfe der Sobel-Kantenerkennung den Winkel aller Kanten, um sicherzustellen, dass die Linien nicht in eine andere Farbe übergehen. Sobald eine Zeile der zufälligen Länge innerhalb (lengthmin, lengthmax) festgelegt ist, wird geprüft, ob sie zum Gesamtbild beiträgt. Während kleinere Linien besser sind, stelle ich die Linienlänge von 10-50 ein.

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')

Amerikanische Gotik

Escher

Magenta
quelle