C ++ - linee un po 'casuali e poi alcune
Prima alcune righe casuali
Il primo passo dell'algoritmo genera casualmente linee, assume per l'immagine di destinazione una media dei pixel lungo questa e quindi calcola se il quadrato sommato delle distanze spaziali rgb di tutti i pixel sarebbe inferiore se disegnassimo la nuova linea (e dipingilo solo, se lo è). Il nuovo colore delle linee per questo viene scelto come media saggia del canale dei valori rgb, con un'aggiunta casuale di -15 / + 15.
Cose che ho notato e influenzato l'implementazione:
- Il colore iniziale è la media dell'immagine completa. Questo per contrastare effetti divertenti come quando lo rende bianco, e l'area è nera, quindi già qualcosa di meglio si vede una linea verde brillante, poiché è più vicina al nero di quella già bianca.
- Prendere il colore medio puro per la linea non è così buono in quanto risulta incapace di generare riflessi venendo sovrascritto da linee successive. Fare una piccola deviazione casuale aiuta un po ', ma se guardi la notte stellata, fallisce se il contrasto locale è elevato in molti punti.
Stavo sperimentando alcuni numeri, e ho scelto, L=0.3*pixel_count(I)
lasciato m=10
e M=50
. Esso produrrà buoni risultati a partire da circa 0.25
per 0.26
per il numero di linee, ma ho scelto 0.3 di avere più spazio per i dettagli precisi.
Per l'immagine della porta d'oro a grandezza naturale, ciò ha comportato la 233529 linee da dipingere (per le quali ci sono voluti 13 secondi qui). Nota che tutte le immagini qui sono visualizzate in dimensioni ridotte e devi aprirle in una nuova scheda / scaricarle per visualizzare la risoluzione completa.
Cancella l'indegno
Il passo successivo è piuttosto costoso (per le 235k linee ci sono voluti circa un'ora, ma dovrebbe essere ben entro il tempo "un'ora per 10k linee su 1 megapixel"), ma è anche un po 'sorprendente. Passo attraverso tutte le linee precedentemente dipinte e rimuovo quelle che non migliorano l'immagine. Questo mi lascia in questa corsa con solo 97347 righe che producono la seguente immagine:
Probabilmente dovrai scaricarli e confrontarli in un visualizzatore di immagini appropriato per individuare la maggior parte delle differenze.
e ricominciare da capo
Ora ho molte linee che posso dipingere di nuovo per avere di nuovo un totale di 235929. Non c'è molto da dire, quindi ecco l'immagine:
breve analisi
L'intera procedura sembra funzionare come un filtro di sfocatura sensibile al contrasto locale e alle dimensioni degli oggetti. Ma è anche interessante vedere dove sono disegnate le linee, quindi anche il programma le registra (per ogni linea, il colore dei pixel sarà reso un passo più bianco, alla fine il contrasto è massimizzato). Ecco quelli corrispondenti ai tre colorati sopra.
animazioni
E poiché tutti amiamo le animazioni, ecco alcune gif animate dell'intero processo per l'immagine del cancello dorato più piccolo. Si noti che esiste un significativo dithering a causa del formato gif (e poiché i creatori di formati di file di animazione a colori reali e i produttori di browser sono in guerra sui loro ego, non esiste un formato standard per le animazioni a colori reali, altrimenti avrei potuto aggiungere un .mng o simile ).
Alcuni di più
Come richiesto, ecco alcuni risultati delle altre immagini (potrebbe essere necessario aprirle di nuovo in una nuova scheda per non ridimensionarle)
Pensieri futuri
Giocare con il codice può dare alcune variazioni interessanti.
- Scegli il colore delle linee in modo casuale anziché basarsi sulla media. Potrebbero essere necessari più di due cicli.
- Il codice nel pastebin contiene anche qualche idea di un algoritmo genetico, ma l'immagine è probabilmente già così buona che richiederebbe troppe generazioni e questo codice è anche troppo lento per adattarsi alla regola di "un'ora".
- Fai un altro giro di cancellazione / riverniciatura, o anche due ...
- Modifica il limite di cancellazione delle linee (ad es. "Deve migliorare l'immagine per non N")
Il codice
Queste sono solo le due principali funzioni utili, l'intero codice non si adatta qui e può essere trovato su http://ideone.com/Z2P6Ls
Le bmp
classi raw
e le raw_line
funzioni accedono rispettivamente ai pixel e alle linee in un oggetto che può essere scritto nel formato bmp (era solo un po 'di hack in giro e ho pensato che lo rende in qualche modo indipendente da qualsiasi libreria).
Il formato del file di input è 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]);
}
}
}