C ++ - nieco losowe linie, a potem trochę
Najpierw kilka losowych linii
Pierwszy krok algorytmu losowo generuje linie, przyjmuje dla obrazu docelowego średnią liczbę pikseli wzdłuż tego, a następnie oblicza, czy zsumowany kwadrat odległości rgb wszystkich pikseli byłby mniejszy, gdybyśmy namalowali nową linię (i tylko go pomaluj, jeśli jest). Nowy kolor linii dla tego jest wybierany jako średnia kanałowa wartości rgb, z losowym dodawaniem -15 / + 15.
Rzeczy, które zauważyłem i które wpłynęły na wdrożenie:
- Kolor początkowy to średnia całego obrazu. Ma to na celu przeciwdziałanie śmiesznym efektom, takim jak zmiana na biały, a obszar jest czarny, wtedy już coś w postaci jasnozielonej linii jest lepiej widoczne, ponieważ jest bliżej czerni niż już biała.
- Przyjmowanie czystego średniego koloru linii nie jest tak dobre, ponieważ okazuje się, że nie jest w stanie wygenerować rozjaśnienia poprzez zastąpienie go późniejszymi liniami. Wykonanie małego przypadkowego odchylenia trochę pomaga, ale jeśli spojrzysz na gwiaździstą noc, nie powiedzie się, jeśli lokalny kontrast jest wysoki w wielu miejscach.
Eksperymentowałem z niektórymi liczbami, wybrałem L=0.3*pixel_count(I)
i wyszedłem m=10
i M=50
. Będzie produkować ładne wyniki zaczynając od około 0.25
aby 0.26
do liczby linii, ale wybrałem 0,3 mieć więcej miejsca dla dokładnych szczegółów.
W przypadku pełnowymiarowego obrazu złotej bramy spowodowało to malowanie 235929 linii (co zajęło tutaj aż 13 sekund). Pamiętaj, że wszystkie obrazy tutaj są wyświetlane w zmniejszonym rozmiarze i musisz je otworzyć w nowej karcie / pobrać je, aby wyświetlić pełną rozdzielczość.
Usuń niegodne
Następny krok jest dość drogi (dla 235 tys. Linii zajęło to około godziny, ale powinno to być w granicach „godziny dla 10 tys. Linii na 1 megapiksel”), ale jest to również nieco zaskakujące. Przechodzę przez wszystkie wcześniej malowane linie i usuwam te, które nie poprawiają obrazu. To pozostawia mnie w tym biegu z tylko 97347 liniami, które tworzą następujący obraz:
Prawdopodobnie trzeba je pobrać i porównać w odpowiedniej przeglądarce obrazów, aby wykryć większość różnic.
i zacznij od nowa
Teraz mam wiele linii, które mogę ponownie pomalować, aby mieć w sumie 235929. Niewiele do powiedzenia, więc oto obraz:
krótka analiza
Cała procedura wydaje się działać jak filtr rozmywający, wrażliwy na lokalny kontrast i rozmiary obiektów. Ciekawe jest również to, gdzie są malowane linie, więc program też je rejestruje (dla każdej linii kolor pikseli będzie o jeden stopień bielszy, na końcu kontrast zostanie zmaksymalizowany). Oto odpowiadające trzy powyżej kolorowe.
animacje
A ponieważ wszyscy uwielbiamy animacje, oto kilka animowanych gifów z całego procesu tworzenia mniejszego obrazu złotej bramy. Zauważ, że istnieje znaczące dithering z powodu formatu gif (a ponieważ twórcy formatów plików animacji w prawdziwych kolorach i producenci przeglądarek toczą wojnę o swoje ego, nie ma standardowego formatu dla animacji w prawdziwych kolorach, w przeciwnym razie mógłbym dodać plik .mng lub podobny ).
Trochę więcej
Zgodnie z życzeniem, oto niektóre wyniki innych obrazów (ponownie może być konieczne otwarcie ich w nowej karcie, aby nie skalować ich w dół)
Przyszłe myśli
Zabawa kodem może dać ciekawe odmiany.
- Wybierz kolor linii losowo, zamiast opierać się na średniej. Możesz potrzebować więcej niż dwóch cykli.
- Kod w pastebin również zawiera pewne pojęcie o algorytmie genetycznym, ale obraz jest już prawdopodobnie tak dobry, że zajęłoby zbyt wiele pokoleń, a ten kod jest zbyt wolny, aby pasował do reguły „jednej godziny”.
- Wykonaj kolejną rundę usuwania / odmalowywania, a nawet dwie ...
- Zmień limit miejsca, w którym można usunąć linie (np. „Musi poprawić obraz co najmniej N lepiej”)
Kod
To tylko dwie główne przydatne funkcje, cały kod nie mieści się tutaj i można je znaleźć na stronie http://ideone.com/Z2P6Ls
Te bmp
zajęcia raw
i raw_line
funkcja zrobić dostępu pikseli i linii odpowiednio w obiekcie, który można zapisać do formatu bmp (To tylko niektóre Hack leżące wokół i pomyślałem, że czyni to nieco niezależny od jakiejkolwiek biblioteki).
Format pliku wejściowego to 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]);
}
}
}