C ++ - etwas zufällige Zeilen und noch einige mehr
Zuerst ein paar zufällige Zeilen
Der erste Schritt des Algorithmus erzeugt zufällig Linien, nimmt für das Zielbild einen Durchschnitt der Pixel entlang dieser Linie und berechnet dann, ob das aufsummierte Quadrat der RGB-Abstände aller Pixel geringer wäre, wenn wir die neue Linie zeichnen würden (und nur malen, wenn es ist). Die Farbe der neuen Linien wird als kanalweiser Durchschnitt der RGB-Werte mit einer zufälligen Addition von -15 / + 15 gewählt.
Dinge, die mir aufgefallen sind und die Umsetzung beeinflusst haben:
- Die Anfangsfarbe ist der Durchschnitt des gesamten Bildes. Dies dient dazu, lustigen Effekten entgegenzuwirken, wenn man sie weiß macht und die Fläche schwarz ist, dann ist schon etwas wie eine hellgrüne Linie besser zu sehen, da sie näher an Schwarz liegt als die bereits weiße.
- Die Verwendung der reinen Durchschnittsfarbe für die Linie ist nicht so gut, da sich herausstellt, dass durch das Überschreiben durch spätere Linien keine Hervorhebungen erzeugt werden können. Eine kleine zufällige Abweichung hilft ein bisschen, aber wenn Sie in der Sternennacht schauen, schlägt dies fehl, wenn der lokale Kontrast an vielen Stellen hoch ist.
Ich experimentierte mit einigen Zahlen und wählte L=0.3*pixel_count(I)
und verließ m=10
und M=50
. Es wird gute Ergebnisse liefern, angefangen bei ungefähr 0.25
bis 0.26
zur Anzahl der Zeilen, aber ich habe 0,3 gewählt, um mehr Platz für genaue Details zu haben.
Für das vollständige Golden Gate-Bild wurden 235929 Linien gezeichnet (wofür es hier satte 13 Sekunden gedauert hat). Beachten Sie, dass alle Bilder hier in verkleinerter Größe angezeigt werden und Sie sie in einem neuen Tab öffnen / herunterladen müssen, um die volle Auflösung anzuzeigen.
Löschen Sie die Unwürdigen
Der nächste Schritt ist ziemlich teuer (für die 235k-Leitungen hat es ungefähr eine Stunde gedauert, aber das sollte durchaus innerhalb der Zeitanforderungen von "einer Stunde für 10k-Leitungen mit 1 Megapixel" liegen), ist aber auch ein bisschen überraschend. Ich gehe alle zuvor gemalten Linien durch und entferne diejenigen, die das Bild nicht verbessern. Dies lässt mich in diesem Lauf mit nur 97347 Zeilen, die das folgende Bild erzeugen:
Sie müssen sie wahrscheinlich in einem geeigneten Bildbetrachter herunterladen und vergleichen, um die meisten Unterschiede zu erkennen.
und von vorne anfangen
Jetzt habe ich viele Linien, die ich wieder malen kann, um insgesamt 235929 wieder zu haben. Nicht viel zu sagen, deshalb hier das Bild:
kurze Analyse
Der gesamte Vorgang scheint wie ein Unschärfefilter zu funktionieren, der auf lokalen Kontrast und Objektgrößen reagiert. Es ist aber auch interessant zu sehen, wo die Linien gezeichnet werden, so dass das Programm diese auch aufzeichnet (für jede Linie wird die Pixelfarbe einen Schritt weißer gemacht, am Ende wird der Kontrast maximiert). Hier sind die entsprechenden zu den drei oben farbigen.
Animationen
Und da wir alle Animationen lieben, finden Sie hier einige animierte Gifs des gesamten Prozesses für das kleinere Golden Gate-Bild. Beachten Sie, dass es aufgrund des GIF-Formats zu deutlichen Dithering-Effekten kommt (und da Entwickler von True-Color-Animationsdateiformaten und Browserhersteller im Streit um ihr Ego sind, gibt es kein Standardformat für True-Color-Animationen. Andernfalls hätte ich möglicherweise ein .mng oder ähnliches hinzugefügt ).
Etwas mehr
Wie gewünscht, hier einige Ergebnisse der anderen Bilder (möglicherweise müssen Sie sie erneut in einem neuen Tab öffnen, damit sie nicht verkleinert werden.)
Zukünftige Gedanken
Das Herumspielen mit dem Code kann einige interessante Variationen ergeben.
- Wählen Sie die Farbe der Linien nach dem Zufallsprinzip, anstatt auf dem Durchschnitt zu basieren. Möglicherweise benötigen Sie mehr als zwei Zyklen.
- Der Code im Pastebin enthält auch eine Idee eines genetischen Algorithmus, aber das Bild ist wahrscheinlich bereits so gut, dass es zu viele Generationen dauern würde, und dieser Code ist auch zu langsam, um in die Regel "eine Stunde" zu passen.
- Mach noch eine Runde Löschen / Repaint oder sogar zwei ...
- Ändern Sie die Grenze, an der Linien gelöscht werden können (z. B. "muss das Bild mindestens N besser machen").
Der Code
Dies sind nur die beiden wichtigsten nützlichen Funktionen. Der gesamte Code passt hier nicht hinein und kann unter http://ideone.com/Z2P6Ls gefunden werden
Die bmp
Klassen raw
und raw_line
Funktionen greifen auf Pixel bzw. Zeilen in einem Objekt zu, das im bmp-Format geschrieben werden kann (es war nur ein Hack, der herumlag, und ich dachte, das macht dies etwas unabhängig von jeder Bibliothek).
Das Eingabedateiformat ist 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]);
}
}
}