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=10
y M=50
. Se va a producir resultados agradables a partir de alrededor 0.25
de 0.26
para 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:
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 bmp
clases raw
y la raw_line
funció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]);
}
}
}