Reproduire une image en utilisant des lignes

31

Écrivez un programme qui prend une image RVB en couleurs vraies I , le nombre maximal de lignes pour dessiner L , et la longueur minimale m et maximale M de chaque ligne. Une image de sortie O qui ressemble le plus possible comme I et est tiré à l' aide de L ou moins de lignes droites, qui ont toutes une longueur euclidienne entre m et M .

Chaque ligne doit être d'une couleur unie, avoir les deux extrémités dans les limites de O et être dessinée en utilisant l' algorithme de ligne de Bresenham (ce que la plupart des bibliothèques graphiques feront déjà pour vous). Les lignes individuelles ne peuvent avoir qu'une épaisseur de 1 pixel.

Toutes les lignes, même celles de longueur 0, doivent occuper au moins un pixel. Les lignes peuvent être tracées les unes sur les autres.

Avant de dessiner des lignes, vous pouvez initialiser l'arrière-plan de O à n'importe quelle couleur unie (qui peut dépendre de I ).

Détails

  • O devrait avoir les mêmes dimensions que je .
  • L sera toujours un entier non négatif. Il peut être supérieure à la surface de I .
  • m et M sont des nombres à virgule flottante non négatifs avec M > = m . La distance entre deux pixels est la distance euclidienne entre leurs centres. Si cette distance est inférieure à m ou supérieure à M , une ligne entre ces pixels n'est pas autorisée.
  • Les lignes ne doivent pas être anticrénelées.
  • L'opacité et l'alpha ne doivent pas être utilisés.
  • Votre programme ne devrait pas prendre plus d'une heure pour s'exécuter sur un ordinateur moderne décent sur des images avec moins d'un million de pixels et L moins de 10 000.

Images d'essai

Vous devez bien sûr nous montrer vos images de sortie les plus précises ou les plus intéressantes (ce qui, je pense, se produira lorsque L est compris entre 5% et 25% du nombre de pixels en I , et m et M sont environ un dixième de la taille diagonale).

Voici quelques images de test (cliquez pour les originaux). Vous pouvez également publier le vôtre.

Mona Lisa Cascade Nighthawks Nuit étoilée le pont du Golden Gate

Images plus simples:

Escaliers de Penroseruban de Möbius Courbe de Hilbert

C'est un concours de popularité. La soumission la plus votée gagne.

Remarques

  • Il peut être utile de laisser L dériver d'un pourcentage du nombre total de pixels dans I ainsi que d'une valeur absolue. par exemple, ce >>> imageliner I=img.png L=50% m=10 M=20serait la même chose que >>> imageliner I=img.png L=32 m=10 M=20s'il img.pngs'agissait d'une image de 8 x 8 pixels. Quelque chose de semblable pourrait être fait pour m et M . Ce n'est pas obligatoire.
  • Étant donné que les lignes ne peuvent pas sortir des limites, les lignes les plus longues possibles sera la longueur de la diagonale de I . Avoir M plus élevé que cela ne devrait cependant rien casser.
  • Naturellement, si m est 0 et L est supérieur ou égal au nombre de pixels dans I , O pourrait être identique à I en ayant une longueur 0 "lignes" à chaque emplacement de pixel. Ce comportement n'est pas requis.
  • On peut dire que la reproduction de la forme de I est plus importante que la reproduction de la couleur. Vous voudrez peut-être examiner la détection des contours .
Les passe-temps de Calvin
la source
Pour clarifier: les bibliothèques comme SimpleCV sont-elles autorisées? Et les réponses peuvent avoir n'importe quel choix pour I, L, m et M, y compris m = 0 et L = aire?
rationalis
@epicwisdom Oui, toutes les bibliothèques (sauf les choses qui font déjà spécifiquement cette tâche) sont autorisées. N'hésitez pas à utiliser les points clés, la détection des bords, peu importe. Votre algorithme devrait fonctionner pour tout choix valide de I , L , m , M , y compris m = 0 et L = zone. (Bien sûr, votre algorithme peut sembler mieux pour des réglages particuliers des paramètres.)
Calvin's Hobbies
Alors, par exemple, cet algorithme de bibliothèque particulier serait considéré comme une réponse invalide?
rationalis
@epicwisdom En fait, je permettrai cela et d'autres choses similaires. Il semble qu'il faudrait encore quelques ajustements intelligents pour créer une image à partir des lignes qu'elle vous donne.
Calvin's Hobbies
1
Les lignes doivent-elles avoir une épaisseur de 1?
aditsu

Réponses:

21

C ++ - des lignes quelque peu aléatoires, puis certaines

D'abord quelques lignes aléatoires

La première étape de l'algorithme génère aléatoirement des lignes, prend pour l'image cible une moyenne des pixels le long de celle-ci, puis calcule si le carré résumé des distances spatiales RVB de tous les pixels serait inférieur si nous peignions la nouvelle ligne (et peignez-le seulement, si c'est le cas). La nouvelle couleur des lignes est choisie comme la moyenne des valeurs RVB par canal, avec une addition aléatoire de -15 / + 15.

Choses que j'ai remarquées et influencé la mise en œuvre:

  • La couleur initiale est la moyenne de l'image complète. C'est pour contrer des effets amusants comme lorsque vous la rendez blanche et que la zone est noire, alors déjà quelque chose comme une ligne verte brillante est mieux vue, car elle est plus proche du noir que celle déjà blanche.
  • Prendre la couleur moyenne pure pour la ligne n'est pas si bon qu'il s'avère impossible de générer des reflets en étant écrasés par des lignes ultérieures. Faire un petit écart aléatoire aide un peu, mais si vous regardez la nuit étoilée, cela échoue si le contraste local est élevé à de nombreux endroits.

J'expérimentais avec quelques chiffres, et j'ai choisi L=0.3*pixel_count(I)et quitté m=10et M=50. Il produira des résultats agréables à partir autour 0.25de 0.26pour le nombre de lignes, mais j'ai choisi 0,3 pour avoir plus de place pour plus de détails précis.

Pour l'image de la porte dorée en taille réelle, cela a entraîné 235929 lignes à peindre (pour lesquelles il a fallu 13 secondes ici). Notez que toutes les images ici sont affichées en taille réduite et vous devez les ouvrir dans un nouvel onglet / les télécharger pour afficher la pleine résolution.

Effacez l'indigne

L'étape suivante est assez chère (pour les lignes de 235k, cela a pris environ une heure, mais cela devrait être bien dans le délai de "une heure pour 10k lignes sur 1 mégapixel"), mais c'est aussi un peu surprenant. Je passe en revue toutes les lignes précédemment peintes et je supprime celles qui ne rendent pas l'image meilleure. Cela me laisse dans cette course avec seulement 97347 lignes qui produisent l'image suivante:

Vous devrez probablement les télécharger et les comparer dans une visionneuse d'images appropriée pour repérer la plupart des différences.

et recommencer

Maintenant, j'ai beaucoup de lignes que je peux peindre à nouveau pour avoir à nouveau un total de 235929. Pas grand chose à dire, voici donc l'image:

entrez la description de l'image ici

brève analyse

L'ensemble de la procédure semble fonctionner comme un filtre flou sensible au contraste local et à la taille des objets. Mais il est également intéressant de voir où les lignes sont peintes, donc le programme les enregistre également (pour chaque ligne, la couleur des pixels sera rendue plus blanche, à la fin le contraste est maximisé). Voici les correspondants aux trois couleurs ci-dessus.

animations

Et puisque nous aimons tous les animations, voici quelques gifs animés de tout le processus pour la petite image du Golden Gate. Notez qu'il y a un tramage important en raison du format gif (et puisque les créateurs de formats de fichiers d'animation en vraies couleurs et les fabricants de navigateurs sont en guerre pour leurs ego, il n'y a pas de format standard pour les animations en vraies couleurs, sinon j'aurais pu ajouter un .mng ou similaire ).

Un peu plus

Comme demandé, voici quelques résultats des autres images (encore une fois, vous devrez peut-être les ouvrir dans un nouvel onglet pour ne pas les réduire)

Pensées futures

Jouer avec le code peut donner quelques variations intéressantes.

  • Choisissez la couleur des lignes au hasard au lieu d'être basé sur la moyenne. Vous pourriez avoir besoin de plus de deux cycles.
  • Le code dans le pastebin contient également une idée d'un algorithme génétique, mais l'image est probablement déjà si bonne qu'elle prendrait trop de générations, et ce code est également trop lent pour s'insérer dans la règle "d'une heure".
  • Faites un autre cycle d'effacement / repeinture, ou même deux ...
  • Changer la limite de l'endroit où les lignes peuvent être effacées (par exemple "doit rendre l'image au moins N meilleure")

Le code

Ce ne sont que les deux principales fonctions utiles, le code entier ne tient pas ici et peut être trouvé à http://ideone.com/Z2P6Ls

Les bmpclasses rawet la raw_linefonction accèdent respectivement aux pixels et aux lignes dans un objet qui peut être écrit au format bmp (c'était juste un hack qui traînait et je pensais que cela le rendait quelque peu indépendant de toute bibliothèque).

Le format du fichier d'entrée est 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
la source
+1, très agréable en effet. Avez-vous des résultats pour les autres images de test?
Nathaniel
1
@Nathaniel: J'en ai ajouté. Les images "simples" ne sont pas intéressantes car la récréation est presque parfaite au pixel près.
PlasmaHH
17

Java - lignes aléatoires

Une solution très basique qui dessine des lignes aléatoires et calcule pour elles la couleur moyenne de l'image source. La couleur d'arrière-plan est définie sur la couleur moyenne source.

L = 5000, m = 10, M = 50

entrez la description de l'image ici

L = 10000, m = 10, M = 50

entrez la description de l'image ici

MODIFIER

J'ai ajouté un algorithme génétique qui gère une population de lignées. À chaque génération, nous ne conservons que les 50% des meilleures lignes, supprimons les autres et générons au hasard de nouvelles lignes. Les critères de maintien des lignes sont:

  • leur distance aux couleurs de l'image source est petite
  • le nombre d'intersections avec d'autres lignes (le plus petit sera le mieux)
  • leur longueur (plus c'est long, mieux c'est)
  • leur angle avec le plus proche voisin (le plus petit sera le mieux)

À ma grande déception, l'algorithme ne semble pas vraiment améliorer la qualité de l'image :-( seules les lignes deviennent plus parallèles.

Première génération (5000 lignes)

entrez la description de l'image ici

Dixième génération (5000 lignes)

entrez la description de l'image ici

Jouer avec les paramètres

entrez la description de l'image icientrez la description de l'image icientrez la description de l'image ici

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
la source
Enfin, quelqu'un a répondu: D J'aimerais voir plus d'exemples.
Calvin's Hobbies
@Calvin Sure. En ce moment je travaille sur l'amélioration de l'algorithme en gardant une population de lignées, et en éliminant par exemple les 20% de pire, et en recréant de nouvelles (une sorte d'algorithme génétique)
Arnaud
J'avais quelque chose comme ça en tête, mais pas le temps de l'écrire. Dans l'attente de l'alg génétique. résultats :)
aditsu
Vous souhaitez peut-être supprimer le critère d'angle plus petit? Pourquoi tu l'as mis? L'image d'origine semble bonne même si les lignes n'ont pas un petit angle d'intersection.
juste la moitié du
@justhalf Done. J'ai ajouté le critère d'angle pour tenter de simuler le pinceau du peintre.
Arnaud
9

C - lignes droites

Une approche de base en C qui fonctionne sur des fichiers ppm. L'algorithme essaie de placer des lignes verticales avec une longueur de ligne optimale pour remplir tous les pixels. La couleur d'arrière-plan et les couleurs de ligne sont calculées comme une valeur moyenne de l'image d'origine (la médiane de chaque canal de couleur):

#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

entrez la description de l'image ici

urzeit
la source
6

Python 3 basé sur "des lignes quelque peu aléatoires, puis certaines", plus la détection des bords sobel.

le code peut théoriquement fonctionner indéfiniment (donc je peux l'exécuter du jour au lendemain pour le plaisir), mais il enregistre sa progression, donc toutes les images sont prises à partir de la marque 1-10 min.

Il lit d'abord l'image, puis utilise la détection des bords sobel pour trouver l'angle de tous les bords, afin de s'assurer que les lignes n'empiètent pas sur une autre couleur. Une fois qu'une ligne de la longueur aléatoire à l'intérieur (lengthmin, lengthmax) est définie, elle teste ensuite pour voir si elle contribue à l'image globale. Bien que les lignes plus petites soient meilleures, je règle la longueur de la ligne de 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')

gothique americain

Escher

Magenta
la source