C ++ - linhas um tanto aleatórias e algumas
Primeiro algumas linhas aleatórias
A primeira etapa do algoritmo gera linhas aleatoriamente, leva para a imagem de destino uma média dos pixels ao longo disso e calcula se o quadrado resumido das distâncias do espaço rgb de todos os pixels seria menor se pintássemos a nova linha (e apenas pinte, se for). A cor das novas linhas para isso é escolhida como a média sábia do canal dos valores rgb, com uma adição aleatória de -15 / + 15.
Coisas que eu notei e influenciei a implementação:
- A cor inicial é a média da imagem completa. Isso é para combater efeitos engraçados, como quando a cor é branca, e a área é preta; então, algo como uma linha verde brilhante já é visto melhor, pois está mais perto do preto do que a já branca.
- Tomar a cor média pura da linha não é tão bom, pois acaba sendo incapaz de gerar realces sendo substituído por linhas posteriores. Fazer um pequeno desvio aleatório ajuda um pouco, mas se você olhar a noite estrelada, ele falhará se o contraste local for alto em muitos lugares.
Eu estava experimentando alguns números e escolhi L=0.3*pixel_count(I)
e saí m=10
e M=50
. Ela irá produzir resultados agradáveis a partir de cerca 0.25
de 0.26
para o número de linhas, mas eu escolhi 0,3 a ter mais espaço para detalhes precisos.
Para a imagem em tamanho real do golden gate, isso resultou em 235929 linhas para pintar (pelas quais demoraram 13 segundos aqui). Observe que todas as imagens aqui são exibidas em tamanho reduzido e você precisa abri-las em uma nova guia / fazer o download delas para visualizar a resolução completa.
Apague os indignos
O próximo passo é bastante caro (para as linhas de 235k demorou cerca de uma hora, mas isso deve estar dentro do requisito de tempo "uma hora para 10k linhas em 1 megapixel"), mas também é um pouco surpreendente. Passo por todas as linhas pintadas anteriormente e removo aquelas que não melhoram a imagem. Isso me deixa nesta corrida com apenas 97347 linhas que produzem a seguinte imagem:
Você provavelmente precisará fazer o download e compará-los em um visualizador de imagens apropriado para identificar a maioria das diferenças.
e começar de novo
Agora eu tenho muitas linhas que posso pintar novamente para ter um total de 235929 novamente. Não há muito a dizer, então aqui está a imagem:
breve análise
Todo o procedimento parece funcionar como um filtro de desfoque sensível ao contraste local e ao tamanho dos objetos. Mas também é interessante ver onde as linhas são pintadas; portanto, o programa também as grava (para cada linha, a cor do pixel será tornada um passo mais branca, no final, o contraste é maximizado). Aqui estão os correspondentes aos três coloridos acima.
animações
E como todos nós amamos animações, aqui estão alguns gifs animados de todo o processo para a imagem menor do portão de ouro. Observe que há um pontilhamento significativo devido ao formato gif (e como os criadores de formatos de arquivos de animação em cores verdadeiras e os fabricantes de navegadores estão em guerra por seus egos, não há um formato padrão para animações em cores verdadeiras, caso contrário, eu poderia ter adicionado um .mng ou semelhante )
Um pouco mais
Conforme solicitado, aqui estão alguns resultados das outras imagens (novamente, você pode abri-las em uma nova guia para não reduzi-las)
Pensamentos futuros
Brincar com o código pode dar algumas variações interessantes.
- Escolha a cor das linhas aleatoriamente, em vez de basear-se na média. Você pode precisar de mais de dois ciclos.
- O código no pastebin também contém alguma idéia de um algoritmo genético, mas a imagem provavelmente já é tão boa que levaria muitas gerações, e esse código também é muito lento para se ajustar à regra de "uma hora".
- Faça outra rodada de apagar / repintar ou até duas ...
- Altere o limite de onde as linhas podem ser apagadas (por exemplo, "deve melhorar a imagem para que N não seja melhor")
O código
Essas são apenas as duas principais funções úteis, o código inteiro não se encaixa aqui e pode ser encontrado em http://ideone.com/Z2P6Ls
As bmp
classes raw
e a raw_line
função acessam pixels e linhas, respectivamente, em um objeto que pode ser gravado no formato bmp (eram apenas alguns truques e achei que isso tornava isso um pouco independente de qualquer biblioteca).
O formato do arquivo de entrada é 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]);
}
}
}