C ++ - дещо випадкові лінії, а потім деякі
Спочатку кілька випадкових ліній
Перший крок алгоритму випадковим чином генерує рядки, бере для цільового зображення середнє значення пікселів вздовж цього, а потім обчислює, чи буде підсумований квадратний пробіл відстані всіх пікселів меншим, якби ми намалювали новий рядок (і фарбуйте лише, якщо є). Новий колір рядків для цього вибирається як середнє значення каналу значень rgb, з випадковим додаванням -15 / + 15.
Те, що я помітив і вплинув на реалізацію:
- Початковий колір - це середнє значення повного зображення. Це для протидії кумедним ефектам, як, наприклад, коли він стає білим, а область чорною, то вже щось як яскраво-зелена лінія видно краще, оскільки вона ближче до чорної, ніж вже біла.
- Прийняття чистого середнього кольору для рядка не настільки добре, оскільки виявляється, що він не може генерувати виділення, перезаписуючи їх пізнішими рядками. Невелике випадкове відхилення трохи допомагає, але якщо подивитися на зоряну ніч, це не вдається, якщо локальний контраст у багатьох місцях високий.
Я експериментував з деякими номерами, і вибрав L=0.3*pixel_count(I)
і пішов m=10
і M=50
. Це дасть приємні результати, починаючи з навколо0.25
з 0.26
за кількістю рядків, але я вибрав 0.3 , щоб мати більше можливостей для уточнення деталей.
Для повноцінного зображення золотих воріт це призвело до фарбування 235929 ліній (для яких тут знадобилося колосальні 13 секунд). Зауважте, що всі зображення тут відображаються у зменшеному розмірі, і вам потрібно відкрити їх на новій вкладці / завантажити їх, щоб переглянути повну роздільну здатність.
Стерти недостойне
Наступний крок - досить дорогий (для 235-лінійних рядків це зайняло близько години, але це повинно бути в межах "години на 10 к рядків на 1 мегапіксель" часу), але це також трохи дивно. Я проходжу всі попередньо пофарбовані лінії та видаляю ті, які не покращують зображення. Це залишає мене в цьому пробігу лише з 97347 рядками, які створюють таке зображення:
Можливо, вам потрібно завантажити та порівняти їх у відповідному переглядачі зображень, щоб виявити більшість відмінностей.
і почати заново
Зараз у мене дуже багато ліній, які я можу намалювати знову, щоб знову було 235929. Мало що сказати, так ось образ:
короткий аналіз
Вся процедура, здається, працює як фільтр, що розмивається, чутливий до локального контрасту та розмірів об'єкта. Але також цікаво побачити, куди намальовані лінії, тому програма записує їх також (для кожного рядка колір пікселів буде зроблений на крок білішим, в кінці контраст максимізований). Ось відповідні до трьох кольорових вище.
анімації
А оскільки ми всі любимо анімацію, ось кілька анімованих gif-файлів усього процесу для менших зображень золотих воріт. Зауважте, що внаслідок gif-формату спостерігається суттєве забарвлення (а оскільки творці справжніх форматів файлів анімації кольорів та виробники браузерів ведуть війну за свої его, немає стандартного формату для справжньої кольорової анімації, інакше я міг би додати .mng або подібне ).
Трошки більше
Як вимагається, ось деякі результати інших зображень (знову, можливо, вам доведеться відкрити їх у новій вкладці, щоб їх не було зменшено в масштабі)
Майбутні думки
Погравши з кодом, можна дати кілька цікавих варіантів.
- Виберіть колір ліній випадковим чином, а не на середньому. Можливо, вам знадобиться більше двох циклів.
- Код у пастбіні також містить деяке уявлення про генетичний алгоритм, але зображення, ймовірно, вже настільки добре, що зайняло б занадто багато поколінь, і цей код також занадто повільний, щоб вписатись у правило «однієї години».
- Зробіть ще один раунд стирання / перефарбовування, а то й двох ...
- Змініть межу, коли рядки можуть бути стерті (наприклад, "має покращити зображення, щоб не було кращого N")
Кодекс
Це лише дві основні корисні функції, весь код тут не вміщується, і їх можна знайти на веб-сторінці http://ideone.com/Z2P6L
У bmp
класах raw
іraw_line
функції роблять пікселі доступу і лінії відповідно в об'єкті , які можуть бути записані в формат BMP формату (Це було просто якимось - то хак валяється і я думав , що робить це кілька незалежного від будь-якої бібліотеки).
Формат вхідного файлу - 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]);
}
}
}