Reproduzir uma imagem usando linhas

31

Escreva um programa que obtenha uma imagem RGB colorida I , o número máximo de linhas para desenhar L e o comprimento mínimo e máximo M de cada linha. Saída uma imagem ó que a aparência tanto quanto possível, como I e é desenhado utilizando L ou menos linhas rectas, todos os quais têm comprimento Euclidiana entre m e M .

Cada linha deve ter uma cor sólida, ter os dois pontos de extremidade nos limites de O e ser desenhada usando o algoritmo de linha de Bresenham (o que a maioria das bibliotecas gráficas já fará por você). As linhas individuais podem ter apenas 1 pixel de espessura.

Todas as linhas, mesmo as de comprimento 0, devem ocupar pelo menos um pixel. Linhas podem ser desenhadas umas sobre as outras.

Antes de desenhar qualquer linha, você pode inicializar o fundo de O com qualquer cor sólida (que pode depender de I ).

Detalhes

  • O deve ter as mesmas dimensões que eu .
  • L sempre será um número inteiro não negativo. Ele pode ser maior do que a área de I .
  • m e M são números de ponto flutuante não-negativos com M > = m . A distância entre dois pixels é a distância euclidiana entre seus centros. Se essa distância for menor que m ou maior que M , uma linha entre esses pixels não será permitida.
  • As linhas não devem ter antialias.
  • Opacidade e alfa não devem ser usados.
  • Seu programa não deve demorar mais de uma hora para ser executado em um computador moderno decente em imagens com menos de um milhão de pixels e L inferior a 10.000.

Imagens de teste

Você deve mostrar-nos, naturalmente, as suas imagens de saída precisas ou interessantes a maioria (que eu espero vai ocorrer quando L é entre 5% e 25% do número de pixels em I , e m e M são cerca de um décimo do tamanho diagonal).

Aqui estão algumas imagens de teste (clique para originais). Você também pode postar o seu próprio.

Monalisa Cascata Nighthawks Noite estrelada Ponte Golden Gate

Imagens mais simples:

Escadas de PenroseMobius strip Curva de Hilbert

Este é um concurso de popularidade. A inscrição mais votada vence.

Notas

  • Pode ser útil permitir que L seja derivado de uma porcentagem do total de pixels em I , bem como de um valor absoluto. por exemplo, >>> imageliner I=img.png L=50% m=10 M=20seria a mesma coisa que >>> imageliner I=img.png L=32 m=10 M=20se img.pngfosse uma imagem de 8 por 8 pixels. Algo semelhante poderá ser feito para m e M . Isso não é necessário.
  • Desde linhas não pode sair dos limites, as linhas mais longas possíveis será o comprimento diagonal de I . Ter M maior que isso não deve quebrar nada.
  • Naturalmente, se m for 0 e L for maior ou igual ao número de pixels em I , O poderá ser idêntico a I por ter 0 "linhas" de comprimento em cada localização de pixel. Esse comportamento não é necessário.
  • Indiscutivelmente, reproduzir a forma de I é mais importante do que reproduzir a cor. Você pode querer examinar a detecção de borda .
Passatempos de Calvin
fonte
Para esclarecer: Bibliotecas como o SimpleCV são permitidas? E as respostas podem ter alguma escolha para I, L, me M, incluindo m = 0 e L = área?
racionalis
@epicwisdom Sim, todas as bibliotecas (exceto as que já fazem especificamente essa tarefa) são permitidas. Sinta-se livre para usar pontos-chave, detecção de borda, o que for. Seu algoritmo deve funcionar para qualquer opção válida de I , L , m , M , incluir m = 0 e L = área. (Embora, claro, o seu algoritmo pode parecer melhor para afinações específicas dos parâmetros.)
de Calvino Hobbies
Então, por exemplo, esse algoritmo específico da biblioteca seria considerado uma resposta inválida?
racionalis
@epicwisdom Na verdade, vou permitir isso e outras coisas semelhantes. Parece que ainda precisaria de alguns ajustes inteligentes para criar uma imagem fora das linhas que ela fornece.
Passatempos de Calvin
1
As linhas precisam ter espessura 1?
Aditsu 17/08/14

Respostas:

21

C ++ - linhas um tanto aleatórias e algumas

Primeiro algumas linhas aleatórias

A primeira etapa do algoritmo gera linhas aleatoriamente, leva para a imagem de destino uma média dos pixels ao longo disso e calcula se o quadrado resumido das distâncias do espaço rgb de todos os pixels seria menor se pintássemos a nova linha (e apenas pinte, se for). A cor das novas linhas para isso é escolhida como a média sábia do canal dos valores rgb, com uma adição aleatória de -15 / + 15.

Coisas que eu notei e influenciei a implementação:

  • A cor inicial é a média da imagem completa. Isso é para combater efeitos engraçados, como quando a cor é branca, e a área é preta; então, algo como uma linha verde brilhante já é visto melhor, pois está mais perto do preto do que a já branca.
  • Tomar a cor média pura da linha não é tão bom, pois acaba sendo incapaz de gerar realces sendo substituído por linhas posteriores. Fazer um pequeno desvio aleatório ajuda um pouco, mas se você olhar a noite estrelada, ele falhará se o contraste local for alto em muitos lugares.

Eu estava experimentando alguns números e escolhi L=0.3*pixel_count(I)e saí m=10e M=50. Ela irá produzir resultados agradáveis a partir de cerca 0.25de 0.26para o número de linhas, mas eu escolhi 0,3 a ter mais espaço para detalhes precisos.

Para a imagem em tamanho real do golden gate, isso resultou em 235929 linhas para pintar (pelas quais demoraram 13 segundos aqui). Observe que todas as imagens aqui são exibidas em tamanho reduzido e você precisa abri-las em uma nova guia / fazer o download delas para visualizar a resolução completa.

Apague os indignos

O próximo passo é bastante caro (para as linhas de 235k demorou cerca de uma hora, mas isso deve estar dentro do requisito de tempo "uma hora para 10k linhas em 1 megapixel"), mas também é um pouco surpreendente. Passo por todas as linhas pintadas anteriormente e removo aquelas que não melhoram a imagem. Isso me deixa nesta corrida com apenas 97347 linhas que produzem a seguinte imagem:

Você provavelmente precisará fazer o download e compará-los em um visualizador de imagens apropriado para identificar a maioria das diferenças.

e começar de novo

Agora eu tenho muitas linhas que posso pintar novamente para ter um total de 235929 novamente. Não há muito a dizer, então aqui está a imagem:

insira a descrição da imagem aqui

breve análise

Todo o procedimento parece funcionar como um filtro de desfoque sensível ao contraste local e ao tamanho dos objetos. Mas também é interessante ver onde as linhas são pintadas; portanto, o programa também as grava (para cada linha, a cor do pixel será tornada um passo mais branca, no final, o contraste é maximizado). Aqui estão os correspondentes aos três coloridos acima.

animações

E como todos nós amamos animações, aqui estão alguns gifs animados de todo o processo para a imagem menor do portão de ouro. Observe que há um pontilhamento significativo devido ao formato gif (e como os criadores de formatos de arquivos de animação em cores verdadeiras e os fabricantes de navegadores estão em guerra por seus egos, não há um formato padrão para animações em cores verdadeiras, caso contrário, eu poderia ter adicionado um .mng ou semelhante )

Um pouco mais

Conforme solicitado, aqui estão alguns resultados das outras imagens (novamente, você pode abri-las em uma nova guia para não reduzi-las)

Pensamentos futuros

Brincar com o código pode dar algumas variações interessantes.

  • Escolha a cor das linhas aleatoriamente, em vez de basear-se na média. Você pode precisar de mais de dois ciclos.
  • O código no pastebin também contém alguma idéia de um algoritmo genético, mas a imagem provavelmente já é tão boa que levaria muitas gerações, e esse código também é muito lento para se ajustar à regra de "uma hora".
  • Faça outra rodada de apagar / repintar ou até duas ...
  • Altere o limite de onde as linhas podem ser apagadas (por exemplo, "deve melhorar a imagem para que N não seja melhor")

O código

Essas são apenas as duas principais funções úteis, o código inteiro não se encaixa aqui e pode ser encontrado em http://ideone.com/Z2P6Ls

As bmpclasses rawe a raw_linefunção acessam pixels e linhas, respectivamente, em um objeto que pode ser gravado no formato bmp (eram apenas alguns truques e achei que isso tornava isso um pouco independente de qualquer biblioteca).

O formato do arquivo de entrada é 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
fonte
+1, muito bom mesmo. Você tem resultados para as outras imagens de teste?
Nathaniel #
1
@ Nathaniel: eu adicionei alguns. As imagens "simples" são desinteressantes porque a recreação é quase perfeita em pixels.
PlasmaHH
17

Java - linhas aleatórias

Uma solução muito básica que desenha linhas aleatórias e calcula para elas a cor média da imagem de origem. A cor do plano de fundo é definida como a cor média de origem.

L = 5000, m = 10, M = 50

insira a descrição da imagem aqui

L = 10000, m = 10, M = 50

insira a descrição da imagem aqui

EDITAR

Eu adicionei um algoritmo genético que lida com uma população de linhas. A cada geração, mantemos apenas as 50% melhores linhas, descartamos as outras e geramos novas aleatoriamente. Os critérios para manter as linhas são:

  • a distância das cores da imagem de origem é pequena
  • o número de interseções com outras linhas (quanto menor, melhor)
  • seu comprimento (quanto maior, melhor)
  • seu ângulo com o vizinho mais próximo (quanto menor, melhor)

Para minha grande decepção, o algoritmo não parece realmente melhorar a qualidade da imagem :-( apenas as linhas estão ficando mais paralelas.

Primeira geração (5000 linhas)

insira a descrição da imagem aqui

Décima geração (5000 linhas)

insira a descrição da imagem aqui

Brincando com parâmetros

insira a descrição da imagem aquiinsira a descrição da imagem aquiinsira a descrição da imagem aqui

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
fonte
Finalmente alguém respondeu: D Adoraria ver mais exemplos.
Hobbies de Calvin
@Calvin Sure. Agora eu estou trabalhando para melhorar o algoritmo, mantendo uma população de linhas, e eliminando, por exemplo, os 20% piores, e re-gerando novos (algum tipo de algoritmo genético)
Arnaud
Eu tinha algo em mente, mas não havia tempo para escrevê-lo. Ansioso para o alg genético. resultados :)
aditsu
Talvez você queira remover o critério de ângulo menor? Por que você colocou isso? A imagem original parece boa, embora as linhas não tenham um pequeno ângulo de interseção.
justhalf
@justhalf Done. Adicionei o critério de ângulo na tentativa de simular o pincel.
Arnaud
9

C - linhas retas

Uma abordagem básica em C que opera em arquivos ppm. O algoritmo tenta colocar linhas verticais com o comprimento ideal para preencher todos os pixels. A cor do plano de fundo e as cores da linha são calculadas como um valor médio da imagem original (a mediana de cada canal de cores):

#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

insira a descrição da imagem aqui

urzeit
fonte
6

O Python 3 é baseado em "linhas um tanto aleatórias e depois em algumas", além de detecção de borda sobel.

teoricamente, o código pode ser executado para sempre (para que eu possa executá-lo durante a noite por diversão), mas registra seu progresso, para que todas as imagens sejam tiradas da marca de 1 a 10 minutos.

Ele primeiro lê a imagem e, em seguida, usa a detecção de arestas sobel para encontrar o ângulo de todas as arestas, para garantir que as linhas não invadam outra cor. Depois que uma linha do comprimento aleatório dentro (lengthmin, lengthmax) é definida, ela é testada para verificar se contribui com a imagem geral. Enquanto linhas menores são melhores, eu defino o comprimento da linha 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
fonte