2018年6月10日日曜日

【Processing】遺伝的アルゴリズムを使った画像生成 Part1

★うさぎの画像を遺伝的アルゴリズムで作ってみた


昨年度、遺伝的アルゴリズムの勉強をしていて、デザインに使えそうなサンプルがないかと調べている最中に見つけたのが下のリンク先のサイトです。


モナリザの画像を遺伝的アルゴリズムで解析し、多角形の色や透明度、大きさを重ね合わせて入力画像に近づけていくという仕組みです。

ただしソースがJavascriptで作られており、ブラウザーの種類やVersionによって動いたり動かなかったりで開発には不向きなので、Processingで同じアルゴリズムを再現して見ました。

中身を解読してわかったのは、突然変異のみを使った、簡易的な遺伝的アルゴリズムですね。多角形の大きさとか色をランダムに作って重ねていくので、ある程度処理が進むとそこからさきは膨大な時間がかかります。

何に使えるかはよくわかりませんが、ネタとして興味のある人は触ってみてください。

<注意点>
・入力画像は200x200の解像度で、sketchフォルダ直下に置きます。
・画像の名前はrabit.jpgとなってますので、画像の名前を合わせるか、sketchの該当箇所のjpeg名を変更してください。
以下、ソースです↓
PImage input_image;
String IMG_INIT ="rabit.jpg"; //入力画像のファイル名
int IWIDTH = 0;//画像のピクセル幅
int IHEIGHT = 0;//画像のピクセル高さ
int COUNTER_TOTAL = 0;//計算の回数
int MAX_SHAPES = 50;//一度に計算する多角形の個数
int MAX_POINTS = 6;//多角形の点の数
int CHANGED_SHAPE_INDEX = 0;//突然変異のとき、50個の多角形のうち一つを指定する番号
int CHANGED_POINT_INDEX = 0;//突然変異のとき、6個の点のうち一つを指定する番号
int count = 1;//スクリーンショットの連番用変数
boolean flag = true;//ENTERキーのON/OFF判定スイッチ
int ms = 0;//経過時間
int hr;//時間差分
int sum1,sum2;//停止時間、再開時間の合計値
/*配列*/
Integer[][] dna_test_point = new Integer[12][MAX_SHAPES];
Integer[][] dna_best_point = new Integer[12][MAX_SHAPES];
Integer[][] dna_test_color = new Integer[4][MAX_SHAPES];
Integer[][] dna_best_color = new Integer[4][MAX_SHAPES];
color[] data_input;//入力画面のピクセル情報を格納する配列
color[] data_test;//TEST画面のピクセル情報を格納する配列
ArrayList<Integer> stop_time = new ArrayList<Integer>();
ArrayList<Integer> start_time = new ArrayList<Integer>();
/*評価関数*/
Float FITNESS_MAX = 999923404.0;//初期値
Float FITNESS_TEST = FITNESS_MAX;//TEST画面の評価関数
Float FITNESS_BEST = FITNESS_MAX;//BEST画面の評価関数
Float FITNESS_BEST_NORMALIZED = 0.0; // pixel match: 0% worst - 100% best
int NORM_COEF = 0; // maximum distance between black and white images
/*フォント*/
PFont font;
String[] fontList = PFont.list();//パソコン内部のフォントリストを作成
void setup(){
size(800,300);//ウィンドウサイズ
background(0);//背景を黒色
smooth();
frameRate(60);
/*文字の表示*/
font = createFont("Arial",15); //フォントリストの中から選ぶ この際に最大サイズを決める
textFont(font); //設定したフォントを使用
textSize(20); //サイズを最終決定
textAlign(CENTER);
text("Input_Image",150,270);
text("Update_Image",400,270);
text("Test_Image",650,270);
/*入力画像の表示*/
input_image = loadImage(IMG_INIT);//入力画像の読み込み
image(input_image, 50, 50); //画像の左上を(50,100)に指定して表示
IWIDTH = input_image.width;//入力画像の幅を取得
IHEIGHT = input_image.height;//入力画像の高さを取得
//SUBPIXELS = IWIDTH*IHEIGHT*DEPTH;
NORM_COEF = IWIDTH*IHEIGHT*3*255;
/*入力画像のピクセル情報の取得*/
input_image.loadPixels();//入力画像のピクセル情報を取得
data_input = new color[IWIDTH*IHEIGHT];//色情報を格納するカラー型配列を作成
for (int i = 0; i < IWIDTH*IHEIGHT; i++) {//入力画像のカラー情報を格納(RGBA)
data_input[i] = input_image.pixels[i];
}
/*CANVASの作成*/
stroke(125);
strokeWeight(2);
fill(0,0,0);
rect(300,50,IWIDTH,IHEIGHT);//BEST画面
rect(550,50,IWIDTH,IHEIGHT);//TEST画面
/*初期のDNAを作成*/
init_dna(dna_test_point,dna_test_color);
init_dna(dna_best_point,dna_best_color);
/*TESTからBESTにDNAをコピーして同じ状態にする*/
copyDNA_point(dna_test_point,dna_best_point);
copyDNA_color(dna_test_color,dna_best_color);
/*TEST画面とBEST画面に初期DNAを描画*/
drawDNA_test(dna_test_point,dna_test_color);
drawDNA_best(dna_best_point,dna_best_color);
}
/*キーを押したときの処理*/
void keyPressed(){
if(key == ENTER){//ENTERキーを押したら処理を中断
if(flag == true){
stop_time.add(millis());//停止時間をアレイリストに格納
noLoop();//drawを停止
flag = false;
}
else if(flag == false){//ENTERキーをもう一度押したら処理を再開
start_time.add(millis());//再開時間をアレイリストに格納
count_time();//時間差分を計算
flag = true;
loop();//再開
}
}
if(key == 'p'){//スペースキーを押したら画面を保存
String path = count + ".jpg";
save(path);
count++;
}
}
void count_time(){
sum1 = 0;//初期化
sum2 = 0;//初期化
for(int i = 0 ; i < stop_time.size();i++){//停止時間の和
sum1 += (stop_time.get(i));
}
for(int j = 0 ; j < start_time.size();j++){//再開時間の和
sum2 += (start_time.get(j));
}
hr =sum2 - sum1;//差分をとる
}
void draw(){
ms = millis() - hr;//経過時間を取得
int s = (ms/1000)%60;//秒
int m = floor(ms/60000)%60;//分
int h = floor(ms/(60000*60))%60;//時
/*テキストの表示*/
textAlign(CORNER);
fill(0,0,0);//黒
rect(0,0,700,49);//表示をいったん消す
fill(255,255,255);//文字の色
text("Fitness : " + FITNESS_BEST_NORMALIZED + "%" , 5, 20);//文字
text("Count : " + COUNTER_TOTAL , 170, 20);//文字
text("Elapsed_Time : " + h + "h " + m + "m " + s + "s " , 400, 20);//文字
evolve();
}
/*初期DNAの作成*/
void init_dna(Integer[][] pt,Integer[][] col){
for (int x = 0; x < MAX_SHAPES; x++){//0から49まで
for (int y = 0; y < MAX_POINTS; y++){
pt[y][x] = round(random(1)*IWIDTH);//6個のランダムな点のX座標をpoints配列に格納
}
for (int y = MAX_POINTS; y < MAX_POINTS*2; y++){
pt[y][x] = round(random(1)*IHEIGHT);//6個のランダムな点のY座標をpoints配列に格納
}
color c = color(round(255*(random(1))),round(255*(random(1))),round(255*(random(1))),round(0.001*255));//一番最初の50個のDNAの色
//color c = color(255,255,255,round(0.001*255));//黒
col[0][x] = int(red(c));
col[1][x] = int(green(c));
col[2][x] = int(blue(c));
col[3][x] = int(alpha(c));
}
}
/*DNAの点をコピー*/
void copyDNA_point(Integer[][] dna_from, Integer[][] dna_to){
for (int x = 0; x < MAX_SHAPES; x++){
for (int y = 0; y < MAX_POINTS*2; y++){//6個のランダムな点の座標をpoints配列に格納
dna_to[y][x] = dna_from[y][x];
}
}
}
/*DNAの色をコピー*/
void copyDNA_color(Integer[][] dna_from,Integer[][] dna_to){
for(int x=0;x<MAX_SHAPES;x++){//0から49まで
dna_to[0][x] = dna_from[0][x];
dna_to[1][x] = dna_from[1][x];
dna_to[2][x] = dna_from[2][x];
dna_to[3][x] = dna_from[3][x];
}
}
/*TEST画面を描画*/
void drawDNA_test(Integer[][] pt,Integer[][] col){
noStroke();//線なし
for(int x=0;x<MAX_SHAPES;x++) {//0から49まで
fill(col[0][x],col[1][x],col[2][x],col[3][x]);
pushMatrix();
translate(550,50);
beginShape();
for(int y = 0;y<MAX_POINTS;y++){
vertex(pt[y][x],pt[y+MAX_POINTS][x]);
}
endShape(CLOSE);
popMatrix();
}
}
/*BEST画面を描画*/
void drawDNA_best(Integer[][] pt,Integer[][] col){
noStroke();//線なし
for(int x=0;x<MAX_SHAPES;x++) {//0から49まで
fill(col[0][x],col[1][x],col[2][x],col[3][x]);
pushMatrix();
translate(300,50);
beginShape();
for(int y = 0;y<MAX_POINTS;y++){
vertex(pt[y][x],pt[y+MAX_POINTS][x]);
}
endShape(CLOSE);
popMatrix();
}
}
/**/
void evolve(){
mutateDNA(dna_test_point,dna_test_color);//初期DNA(50個の多角形)に対し、変更(突然変異)を加えていく
drawDNA_test(dna_test_point,dna_test_color);//TEST画面の描画
compute_fitness();//評価関数の計算
if(FITNESS_TEST < FITNESS_BEST){//FITNESSがより小さくなったら
pass_gene_mutation_pt(dna_test_point,dna_best_point,CHANGED_SHAPE_INDEX);//TEST画面からBEST画面に50個の点の座標データをコピー
pass_gene_mutation_color(dna_test_color,dna_best_color,CHANGED_SHAPE_INDEX);//TEST画面からBEST画面に色データをコピー
FITNESS_BEST = FITNESS_TEST;//FITBESS_BESTを更新
drawDNA_best(dna_best_point,dna_best_color);//BEST画面の描画
FITNESS_BEST_NORMALIZED = round(1000*(1.0-(FITNESS_BEST/ NORM_COEF)))/10.0; //評価関数の割合を計算 小数点一桁にするため、10倍の四捨五入してから10で除算
}
else{//FITNESSが小さくならないときは
pass_gene_mutation_pt(dna_best_point,dna_test_point,CHANGED_SHAPE_INDEX);//BEST画面からTEST画面にコピー
pass_gene_mutation_color(dna_best_color,dna_test_color,CHANGED_SHAPE_INDEX);//BEST画面からTEST画面にコピー
}
COUNTER_TOTAL++;
}
/*点の座標データをコピー*/
void pass_gene_mutation_pt(Integer[][] dna_from,Integer[][] dna_to,int gene_index){
for (int y = 0; y < MAX_POINTS*2; y++){//6個のランダムな点の座標をpoints配列に格納
dna_to[y][gene_index] = dna_from[y][gene_index];
}
}
/*色データをコピー*/
void pass_gene_mutation_color(Integer[][] dna_from,Integer[][] dna_to,int gene_index){
dna_to[0][gene_index] = dna_from[0][gene_index];//red
dna_to[1][gene_index] = dna_from[1][gene_index];//green
dna_to[2][gene_index] = dna_from[2][gene_index];//blue
dna_to[3][gene_index] = dna_from[3][gene_index];//alpha
}
/*突然変異*/
void mutateDNA(Integer[][] pt, Integer[][] col){
CHANGED_SHAPE_INDEX = round((MAX_SHAPES)*random(1))-1;
if(CHANGED_SHAPE_INDEX < 0){CHANGED_SHAPE_INDEX = 0;}
Float rourette = random(2);//突然変異のときに用いるサイコロを振る
if(rourette<1){
if(rourette<0.25){
col[0][CHANGED_SHAPE_INDEX] = round(255*random(1));//redを置き換え
}
else if(rourette < 0.5){
col[1][CHANGED_SHAPE_INDEX] = round(255*random(1));//greenを置き換え
}
else if(rourette < 0.75){
col[2][CHANGED_SHAPE_INDEX] = round(255*random(1));//blueを置き換え
}
else if(rourette < 1.0){
col[3][CHANGED_SHAPE_INDEX] = round(255*random(1));//alphaを置き換え
}
}
else{
CHANGED_POINT_INDEX = round((MAX_POINTS)*random(1))-1;
if(CHANGED_POINT_INDEX < 0){CHANGED_POINT_INDEX = 0;}
if(rourette<1.5){
pt[CHANGED_POINT_INDEX][CHANGED_SHAPE_INDEX] = round(IWIDTH*random(1));//x座標を置き換え
}
else{
pt[CHANGED_POINT_INDEX*2][CHANGED_SHAPE_INDEX] = round(IHEIGHT*random(1));//y座標を置き換え
}
}
}
/*評価関数を計算*/
void compute_fitness(){
data_test = new color[IWIDTH*IHEIGHT];
loadPixels();//ウィンドウ全体のピクセル情報を取得
int fitness_red = 0;//赤色の差分
int fitness_green = 0;//緑色の差分
int fitness_blue = 0;//青色の差分
//int fitness_alpha = 0;//アルファチャンネルの差分
/*TEST画面のピクセル情報を抽出*/
for(int y = 50 ; y < 50+IHEIGHT;y++){
for(int x = 550;x < 550+IWIDTH ; x++){
data_test[(y-50)*IWIDTH + (x-550)] = get(x,y);
}
}
/*入力画像とTEST画面のピクセル情報の差分を計算*/
for(int k = 0 ; k < IWIDTH*IHEIGHT; k++){
fitness_red += abs(red(data_input[k]) - red(data_test[k]));
fitness_green += abs(green(data_input[k]) - green(data_test[k]));
fitness_blue += abs(blue(data_input[k]) - blue(data_test[k]));
//fitness_alpha += abs(alpha(data_input[k]) - alpha(data_test[k]));
}
//FITNESS_TEST = float(fitness_red + fitness_blue + fitness_green + fitness_alpha);//差分を格納
FITNESS_TEST = float(fitness_red + fitness_blue + fitness_green);//差分を格納
}

0 件のコメント:

コメントを投稿

【Grasshopper_110】縦長長方形の範囲内で大きさを変化させる

今回は、縦長の四角形にひし形を配置し、真ん中より少し上の位置を基準に大きさを変化させるサンプルを作ってみました。 四角形の枠に近いところを同じ大きさに揃えるところが今回のポイントです。