Reproduce una imagen usando líneas

31

Escriba un programa que tome una imagen RGB en color verdadero I , el número máximo de líneas para dibujar L y la longitud mínima m y máxima M de cada línea. Salida a la imagen O que las miradas tanto como sea posible como I y se dibuja con L o menos líneas rectas, todos los cuales tienen longitud euclidiana entre m y M .

Cada línea debe ser de un color sólido, tener ambos puntos finales en los límites de O y dibujarse utilizando el algoritmo de línea de Bresenham (que la mayoría de las bibliotecas de gráficos ya lo harán por usted). Las líneas individuales solo pueden tener 1 píxel de grosor.

Todas las líneas, incluso las de longitud 0, deben ocupar al menos un píxel. Las líneas pueden dibujarse una encima de la otra.

Antes de dibujar cualquier línea, puede inicializar el fondo de O a cualquier color sólido (que puede depender de I ).

Detalles

  • O debe tener las mismas dimensiones que I .
  • L siempre será un entero no negativo. Puede que sea mayor que el área de I .
  • m y M son números de coma flotante no negativos con M > = m . La distancia entre dos píxeles es la distancia euclidiana entre sus centros. Si esta distancia es menor que m o mayor que M , entonces no se permite una línea entre esos píxeles.
  • Las líneas no deben estar suavizadas.
  • La opacidad y el alfa no deben usarse.
  • Su programa no debería demorar más de una hora en ejecutarse en una computadora moderna decente en imágenes con menos de un millón de píxeles y L menos de 10,000.

Imágenes de prueba

Por supuesto, debe mostrarnos sus imágenes de salida más precisas o interesantes (que espero que ocurran cuando L es entre 5% y 25% del número de píxeles en I , ym y M son alrededor de una décima parte del tamaño diagonal).

Aquí hay algunas imágenes de prueba (haga clic para ver los originales). También puede publicar el suyo.

Mona Lisa Cascada Nighthawks Noche estrellada Puente de puerta de oro

Imágenes más simples:

Escaleras Penrosecinta de Moebius Curva de Hilbert

Este es un concurso de popularidad. La presentación más votada gana.

Notas

  • Puede ser útil dejar que L se derive de un porcentaje del total de píxeles en I , así como de un valor absoluto. por ejemplo >>> imageliner I=img.png L=50% m=10 M=20, sería lo mismo que >>> imageliner I=img.png L=32 m=10 M=20si img.pngfuera una imagen de 8 por 8 píxeles. Algo similar se podría hacer para m y M . Esto no es requerido.
  • Ya que las líneas no pueden salir de los límites, las líneas más largas posibles serán la longitud diagonal de I . Sin embargo, tener M más alto que esto no debería romper nada.
  • Naturalmente, si m es 0 y L es mayor o igual que el número de píxeles en I , O podría ser idéntico a I al tener una longitud de 0 "líneas" en cada ubicación de píxeles. Este comportamiento no es obligatorio.
  • Podría decirse que reproducir la forma de I es más importante que reproducir el color. Es posible que desee examinar la detección de bordes .
Pasatiempos de Calvin
fuente
Para aclarar: ¿ Se permiten bibliotecas como SimpleCV ? ¿Y las respuestas pueden tener alguna opción para I, L, my M, incluyendo m = 0 y L = área?
racionalis
@epicwisdom Sí, todas las bibliotecas (excepto las cosas que ya realizan específicamente esta tarea) están permitidas. Siéntase libre de usar puntos clave, detección de bordes, lo que sea. Su algoritmo debería funcionar para cualquier elección válida de I , L , m , M , incluyendo m = 0 y L = área. (Aunque, por supuesto, su algoritmo puede verse mejor para ajustes particulares de los parámetros).
Calvin's Hobbies
Entonces, por ejemplo, ¿ este algoritmo de biblioteca en particular se consideraría una respuesta no válida?
racionalis
@epicwisdom En realidad permitiré eso y otras cosas similares. Parece que todavía necesitaría algunos ajustes inteligentes para hacer una imagen de las líneas que te da.
Aficiones de Calvin
1
¿Las líneas deben tener espesor 1?
aditsu

Respuestas:

21

C ++: líneas algo aleatorias y algo más

Primero algunas líneas aleatorias

El primer paso del algoritmo genera líneas al azar, toma para la imagen objetivo un promedio de los píxeles a lo largo de este, y luego calcula si el cuadrado sumado de las distancias espaciales rgb de todos los píxeles sería menor si pintamos la nueva línea (y solo píntalo, si es así). El nuevo color de las líneas para esto se elige como el promedio de los valores rgb, con una adición aleatoria de -15 / + 15.

Cosas que noté e influyeron en la implementación:

  • El color inicial es el promedio de la imagen completa. Esto es para contrarrestar los efectos divertidos, como cuando se hace blanco, y el área es negra, entonces algo como una línea verde brillante se ve mejor, ya que está más cerca del negro que la ya blanca.
  • Tomar el color promedio puro para la línea no es tan bueno, ya que resulta ser incapaz de generar resaltados al ser sobrescrito por líneas posteriores. Hacer una pequeña desviación aleatoria ayuda un poco, pero si observas la noche estrellada, falla si el contraste local es alto en muchos lugares.

Estaba experimentando con algunos números, y elegí L=0.3*pixel_count(I)y me fui m=10y M=50. Se va a producir resultados agradables a partir de alrededor 0.25de 0.26para el número de líneas, pero optó por 0,3 para tener más espacio para los detalles precisos.

Para la imagen de la puerta dorada de tamaño completo, esto resultó en 235929 líneas para pintar (para lo cual tomó 13 segundos aquí). Tenga en cuenta que todas las imágenes aquí se muestran en tamaño reducido y debe abrirlas en una nueva pestaña / descargarlas para ver la resolución completa.

Borrar lo indigno

El siguiente paso es bastante costoso (para las líneas de 235k tomó aproximadamente una hora, pero eso debería estar dentro del requisito de "una hora para líneas de 10k en 1 megapíxel"), pero también es un poco sorprendente. Reviso todas las líneas pintadas previamente y elimino las que no mejoran la imagen. Esto me deja en esta carrera con solo 97347 líneas que producen la siguiente imagen:

Probablemente necesite descargarlos y compararlos en un visor de imágenes apropiado para detectar la mayoría de las diferencias.

y empezar de nuevo

Ahora tengo muchas líneas que puedo pintar nuevamente para tener un total de 235929 nuevamente. No hay mucho que decir, así que aquí está la imagen:

ingrese la descripción de la imagen aquí

análisis corto

Todo el procedimiento parece funcionar como un filtro borroso que es sensible al contraste local y al tamaño de los objetos. Pero también es interesante ver dónde se pintan las líneas, por lo que el programa también las registra (para cada línea, el color del píxel se hará un paso más blanco, al final se maximiza el contraste). Aquí están los correspondientes a los tres colores anteriores.

animaciones

Y como a todos nos encantan las animaciones, aquí hay algunos gifs animados de todo el proceso para la imagen más pequeña de Golden Gate. Tenga en cuenta que existe un gran dithering debido al formato gif (y dado que los creadores de formatos de archivos de animación en color verdadero y los fabricantes de navegadores están en guerra por sus egos, no hay un formato estándar para animaciones en color verdadero, de lo contrario podría haber agregado un .mng o similar )

Algo mas

Según lo solicitado, aquí hay algunos resultados de las otras imágenes (de nuevo, es posible que deba abrirlas en una nueva pestaña para no reducirlas)

Pensamientos futuros

Jugar con el código puede dar algunas variaciones interesantes.

  • Elija el color de las líneas al azar en lugar de basarse en el promedio. Es posible que necesite más de dos ciclos.
  • El código en el pastebin también contiene alguna idea de un algoritmo genético, pero la imagen probablemente ya es tan buena que tomaría muchas generaciones, y este código también es demasiado lento para ajustarse a la regla de "una hora".
  • Haga otra ronda de borrado / repintado, o incluso dos ...
  • Cambie el límite de dónde se pueden borrar las líneas (p. Ej., "Debe mejorar la imagen para que N sea mejor")

El código

Estas son solo las dos funciones útiles principales, el código completo no cabe aquí y se puede encontrar en http://ideone.com/Z2P6Ls

Las bmpclases rawy la raw_linefunción acceden a píxeles y líneas, respectivamente, en un objeto que se puede escribir en formato bmp (fue solo un truco y pensé que eso lo hace algo independiente de cualquier biblioteca).

El formato del archivo de entrada es 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
fuente
+1, muy agradable de hecho. ¿Tiene resultados para las otras imágenes de prueba?
Nathaniel
1
@Nathaniel: He agregado algunos. Las imágenes "simples" no son interesantes porque la recreación es casi perfecta en píxeles.
PlasmaHH
17

Java - líneas aleatorias

Una solución muy básica que dibuja líneas aleatorias y calcula para ellas el color promedio de la imagen de origen. El color de fondo se establece en el color promedio de origen.

L = 5000, m = 10, M = 50

ingrese la descripción de la imagen aquí

L = 10000, m = 10, M = 50

ingrese la descripción de la imagen aquí

EDITAR

He agregado un algoritmo genético que maneja una población de líneas. En cada generación, conservamos solo el 50% de las mejores líneas, descartamos las demás y generamos nuevas al azar. Los criterios para mantener las líneas son:

  • su distancia a los colores de la imagen de origen es pequeña
  • El número de intersecciones con otras líneas (cuanto más pequeño, mejor)
  • su longitud (cuanto más larga mejor)
  • su ángulo con el vecino más cercano (cuanto más pequeño, mejor)

Para mi gran decepción, el algoritmo realmente no parece mejorar la calidad de la imagen :-( solo las líneas se vuelven más paralelas.

Primera generación (5000 líneas)

ingrese la descripción de la imagen aquí

Décima generación (5000 líneas)

ingrese la descripción de la imagen aquí

Jugando con parámetros

ingrese la descripción de la imagen aquíingrese la descripción de la imagen aquíingrese la descripción de la imagen aquí

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
fuente
Finalmente alguien respondió: D Me encantaría ver más ejemplos.
Aficiones de Calvin
@Calvin Claro. En este momento estoy trabajando para mejorar el algoritmo manteniendo una población de líneas y eliminando, por ejemplo, el 20% peor, y volviendo a generar otras nuevas (algún tipo de algoritmo genético)
Arnaud
Tenía algo así en mente, pero no tenía tiempo para escribirlo. Mirando hacia adelante a la genética alg. resultados :)
aditsu
¿Quizás quiera eliminar el criterio de ángulo más pequeño? ¿Por qué lo pones? La imagen original se ve bien aunque las líneas no tienen un ángulo de intersección pequeño.
justhalf
@justhalf Listo. He agregado el criterio de ángulo en un intento de simular el pincel de pintor.
Arnaud
9

C - líneas rectas

Un enfoque básico en C que opera en archivos ppm. El algoritmo intenta colocar líneas verticales con una longitud de línea óptima para llenar todos los píxeles. El color de fondo y los colores de línea se calculan como un valor promedio de la imagen original (la mediana de cada canal de color):

#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

ingrese la descripción de la imagen aquí

urzeit
fuente
6

Python 3 basado en "líneas algo aleatorias y algo más", además de detección de bordes sobel.

teóricamente, el código puede ejecutarse para siempre (por lo que puedo ejecutarlo durante la noche por diversión), pero registra su progreso, por lo que todas las imágenes se toman de la marca de 1-10 min.

Primero lee la imagen, y luego usa la detección de bordes sobel para encontrar el ángulo de todos los bordes, para asegurarse de que las líneas no traspasen otro color. Una vez que se establece una línea de la longitud aleatoria dentro de (lengthmin, lengthmax), se prueba para ver si contribuye a la imagen general. Si bien las líneas más pequeñas son mejores, configuro la longitud de la línea de 10 a 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')

gótico americano

Escher

Magenta
fuente