はじめに
この記事では入力画像から18匹のポケモンの画像を用いてモザイクアートを自動作成する方法を紹介します。
事前準備
- ポケモン図鑑から18匹(aqua,black,blue,fuchsia,silver,green,lime,maroon,navy,olive,purple,red,gray,teal, white,yellow,beige,orangeに色が近いポケモン)を選ぶ。
- photoshopなどを使って背景色をつけたら40×40にサイズを調整する。
処理の大まかな流れ
もっと賢い方法あるかもしれませんが、、
- 入力画像を40×40に分割
PhotoMosaic.java
- 1でできた画像の色平均をとる
GetAverage.java
- 2の色平均をもとに減色
ReduceColor.java
- 3で選ばれた色のポケモンを配置
PhotoMosaic.java
という感じで実装してみます。
実装
- 画像の読み込み/書き出し部分は省略します。
PhotoMosaic.java
import java.awt.image.*;
import java.awt.Graphics;
public class PhotoMosaic {
static BufferedImage execute(BufferedImage before_image) {
int width = before_image.getWidth();
int height = before_image.getHeight();
BufferedImage after_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics g = after_image.getGraphics();
BufferedImage image[] = new BufferedImage[18];
image[0] = JpegFileReader.read("image/white.png");
image[1] = JpegFileReader.read("image/fuchsia.png");
image[2] = JpegFileReader.read("image/lime.png");
image[3] = JpegFileReader.read("image/blue.png");
image[4] = JpegFileReader.read("image/black.png");
image[5] = JpegFileReader.read("image/maroon.png");
image[6] = JpegFileReader.read("image/olive.png");
image[7] = JpegFileReader.read("image/silver.png");
image[8] = JpegFileReader.read("image/red.png");
image[9] = JpegFileReader.read("image/green.png");
image[10] = JpegFileReader.read("image/navy.png");
image[11] = JpegFileReader.read("image/yellow.png");
image[12] = JpegFileReader.read("image/aqua.png");
image[13] = JpegFileReader.read("image/gray.png");
image[14] = JpegFileReader.read("image/purple.png");
image[15] = JpegFileReader.read("image/teal.png");
image[16] = JpegFileReader.read("image/beige.png");
image[17] = JpegFileReader.read("image/orange.png");
// 切り取った画像を入れておく配列を宣言
BufferedImage[][] split_image = new BufferedImage[width/40][height/40];
for ( int i=0; i*40<width; i++ ) {
for ( int j=0; j*40<height; j++ ) {
// 元の画像を40*40に切り取る
split_image[i][j] = before_image.getSubimage( i*40, j*40, 40, 40 );
// 切り取った画像の色平均をとる
int color_num = ReduceColor.detectColor(GetAverage.execute(split_image[i][j]));
g.drawImage(image[color_num], i*40, j*40, null);
}
}
g.dispose();
return after_image;
}
}
GetAverage.java
import java.awt.image.*;
import java.awt.Color;
public class GetAverage{
static int[] execute(BufferedImage image){
int R = 0, G = 0, B = 0;
// Average[0]->Red, Average[1]->Green, Average[2]->Blue
int Average[] = new int[3];
// 画素のRGB値をとって合計を求める
for (int i=0; i<40; i++ ) {
for (int j=0; j<40; j++ ) {
int tmp_color = image.getRGB(i,j);
Color color = new Color(tmp_color);
R += color.getRed();
G += color.getGreen();
B += color.getBlue();
}
}
// 画素の平均値を求める
Average[0] = (int)R/1600;
Average[1] = (int)G/1600;
Average[2] = (int)B/1600;
return Average;
}
}
ReduceColor.java
public class ReduceColor{
static int detectColor(int[] Average){
int R = getNearestValue(Average[0]);
int G = getNearestValue(Average[1]);
int B = getNearestValue(Average[2]);
// 変換する画像の配列の添え字を入れる配列
int[][][] v = new int[256][256][256];
v[64][64][64] = 4; // black
v[64][64][128] = 10; // navy
v[64][64][192] = 3; // blue
v[64][64][255] = 3; // blue
v[64][128][64] = 9; // green
v[64][128][128] = 15; // teal
v[64][128][192] = 15; // teal
v[64][128][255] = 3; // blue
v[64][192][64] = 2; // lime
v[64][192][128] = 15; // teal
v[64][192][192] = 12; // aqua
v[64][192][255] = 3; // blue
v[64][255][64] = 2; // lime
v[64][255][128] = 2; // lime
v[64][255][192] = 12; // aqua
v[64][255][255] = 12; // aqua
v[128][64][64] = 5; // maroon
v[128][64][128] = 14; // purple
v[128][64][192] = 14; // purple
v[128][64][255] = 3; // blue
v[128][128][64] = 6; // olive
v[128][128][128] = 13;// gray
v[128][128][192] = 3; // blue
v[128][128][255] = 3; // blue
v[128][192][64] = 9; // green
v[128][192][128] = 2; // lime
v[128][192][192] = 12;// aqua
v[128][192][255] = 12;// aqua
v[128][255][64] = 2; // lime
v[128][255][128] = 2; // lime
v[128][255][192] = 2; // lime
v[128][255][255] = 12;// aqua
v[192][64][64] = 8; // red
v[192][64][128] = 1; // fuchsia
v[192][64][192] = 1; // fuchsia
v[192][64][255] = 14; // purple
v[192][128][64] = 17; // orange
v[192][128][128] = 16;// beige
v[192][128][192] = 1; // fuchsia
v[192][128][255] = 1; // fuchsia
v[192][192][64] = 11; // yellow
v[192][192][128] = 11; // yellow
v[192][192][192] = 0; // white
v[192][192][255] = 12;// aqua
v[192][255][64] = 12; // aqua
v[192][255][128] = 2; // lime
v[192][255][192] = 0; // white
v[192][255][255] = 12;// aqua
v[255][64][64] = 8; // red
v[255][64][128] = 8; // red
v[255][64][192] = 1; // fuchsia
v[255][64][255] = 1; // fuchsia
v[255][128][64] = 17; // orange
v[255][128][128] = 16;// beige
v[255][128][192] = 1; // fuchsia
v[255][128][255] = 1; // fuchsia
v[255][192][64] = 11; // yellow
v[255][192][128] = 11;// yellow
v[255][192][192] = 16;// beige
v[255][192][255] = 16;// beige
v[255][255][64] = 11; // yellow
v[255][255][128] = 11;// yellow
v[255][255][192] = 0; // white
v[255][255][255] = 0; // white
return v[R][G][B];
}
public static int getNearestValue(int v){
int num=0; // 配列の添え字
int difference; // 配列値-RGB値vの絶対値
int[] list = {64,128,192,192};
// System.out.println("vの値は"+v);
difference = Math.abs( list[0] - v );
for ( int i = 1; i < list.length; i++ ) {
if ( Math.abs( list[i] - v ) < difference ) {
num = i;
difference = Math.abs( list[i] - v );
}
}
return list[num];
}
}
結果
- 減色するのが結構難しかった。特に水色だと白だと判断されてうまくいかなかった。
- 赤系は割といい感じにできた。
- 18色しかないので風景写真は厳しい。