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=10
et M=50
. Il produira des résultats agréables à partir autour 0.25
de 0.26
pour 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:
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 bmp
classes raw
et la raw_line
fonction 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]);
}
}
}