C ++ - несколько случайных строк, а затем некоторые
Сначала несколько случайных строк
Первый шаг алгоритма случайным образом генерирует линии, берет для целевого изображения среднее значение пикселей вдоль этого и затем вычисляет, будет ли сумма квадратов пространственных расстояний rgb всех пикселей меньше, если мы нарисуем новую линию (и только покрась, если есть). Цвет новых линий для этого выбирается как среднее по каналу значений 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 (и поскольку создатели форматов файлов анимации истинного цвета и производители браузеров воюют из-за своего эго, стандартного формата для анимации истинного цвета не существует, в противном случае я мог бы добавить .mng или аналогичный ).
Еще немного
В соответствии с запросом приведены некоторые результаты других изображений (опять же, возможно, вам придется открыть их на новой вкладке, чтобы не уменьшать их масштаб)
Будущие мысли
Игра с кодом может дать некоторые интересные вариации.
- Выбирайте цвет линий случайным образом, а не на основе среднего. Вам может понадобиться более двух циклов.
- Код в pastebin также содержит некоторое представление о генетическом алгоритме, но изображение, вероятно, уже настолько хорошее, что потребуется слишком много поколений, и этот код также слишком медленный, чтобы соответствовать правилу «один час».
- Сделайте еще один раунд стереть / перекрасить, или даже два ...
- Измените предел того, где строки могут быть стерты (например, «должен сделать изображение как минимум N лучше»)
Код
Это всего лишь две основные полезные функции, весь код здесь не помещается и может быть найден на http://ideone.com/Z2P6Ls.
Эти 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]);
}
}
}