顔認識してみるか・・・まずはモザイク、グレースケールに

 作成中の「ぞろぞろ」はマップに従いパーティクルで髭を生やすだけなのですが、何人かの顔を写して髭を生やしてみたところ、面長の方は髭が宙に浮いてしまうのです。枠に合わないのですね。
 丸顔専用のアプリにするか、横方向に拡縮させて位置を合わせようかとも思ったのですが、拡縮はタッチ位置のパーティクルをリセットするために座標変換が必要になってくるはずで厄介そう。
 
 そこで、顔認識に挑戦してみるかと、顔を撮影しているのは判っているのだから目鼻口輪郭を切り出そうかと、そう思い立ったわけです。
 こんなアプリに何故にそこまで、と思われるかも知れませんが、この挑戦が次のアプリにつながったりするのですね。
 
 手始めに写真をモザイク可してみました。
device-2015-01-31-203934
 ブロック内のRGB値をそれぞれ足し上げて、平均値を戻せばモザイクになります。

	private Bitmap mosaicBitmap(Bitmap bm, int dot){
		Bitmap bitmap = bm.copy(Bitmap.Config.ARGB_8888, true);
		final int width = bitmap.getWidth();
		final int height = bitmap.getHeight();
		int[] pixels = new int[width * height];
		int[] mosaic = new int[(width/dot)*(height/dot)];
		bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
		int r, g, b;
		int color;
		for(int i = 0 , mx = 0 ; i < height ; i += dot , mx++){
			for(int j = 0 , my = 0 ; j < width ; j += dot , my++){
				r = 0;
				g = 0;
				b = 0;
				for(int x = i ; x < i + dot ; x++){
					for(int y = j ; y < j + dot ; y++){
				        r += Color.red(pixels[x*width + y]);
				        g += Color.green(pixels[x*width + y]);
				        b += Color.blue(pixels[x*width + y]);
					}
				}
				color = Color.rgb(r/(dot*dot), g/(dot*dot), b/(dot*dot));
				mosaic[mx*(width/dot) + my] = color;
				for(int x = i ; x < i + dot ; x++){
					for(int y = j ; y < j + dot ; y++){
						pixels[x*width + y] = color;
					}
				}
			}
		}
		bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
		return bitmap;
	}

 今回は、モザイク画像を表示する必要もなく mosaic[] に色情報を格納しておけば足りるのですが、正しく動いているか確認するために Bitmap を返しています。
 
 そしてグレースケール化してみます。
device-2015-01-31-205058
 ピクセル毎にRGB値をNTSC系加重平均法で計算した値に置き換えています。

	private Bitmap grayScaleBitmap(Bitmap bm){
		Bitmap bitmap = bm.copy(Bitmap.Config.ARGB_8888, true);
		final int width = bitmap.getWidth();
		final int height = bitmap.getHeight();
		int[] pixels = new int[width * height];
		bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
		int r = 0;
		int g = 0;
		int b = 0;
		int color = 0;
		for(int i = 0 ; i < height ; i++){
			for(int j = 0 ; j < width ; j++){
		        r = Color.red(pixels[i*width + j]);
		        g = Color.green(pixels[i*width + j]);
		        b = Color.blue(pixels[i*width + j]);
				color = (int) (r * 0.298912 + g * 0.586611 + b * 0.114478);
				pixels[i*width + j] = Color.rgb(color, color, color);
			}
		}
		bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
		return bitmap;
	}

 mosaic[] をグレースケール化する肝心の処理は書いていなかったりします。画像表示は完全にいらん処理です。
 
 ちなみに AndEngine で Bitmap picture をグレーモザイクにするために、各メソッドから戻る Bitmap を予め作成してある BitmapTextureAtlas pictureTextureAtlas や ITextureRegion picture_region とする方法はこんなコードです。
 詳しいことは聞かないでください。

	public void test(int dot){
		final Bitmap bitmap = grayScaleBitmap(mosaicBitmap(picture, dot));
		if(pictureTextureAtlas != null){
			picture_region = null;
			pictureTextureAtlas.unload();
			pictureTextureAtlas = null;
		}
		this.pictureTextureAtlas = new BitmapTextureAtlas(activity.getTextureManager(), 1024, 1024, TextureOptions.BILINEAR);
		final IBitmapTextureAtlasSource baseTextureSource = new EmptyBitmapTextureAtlasSource(bitmap.getWidth(), bitmap.getHeight());
		final IBitmapTextureAtlasSource decoratedTextureAtlasSource = new BaseBitmapTextureAtlasSourceDecorator(baseTextureSource){
			@Override
			protected void onDecorateBitmap(Canvas pCanvas) throws Exception{
				pCanvas.drawBitmap(bitmap, 0, 0, this.mPaint);
			}
			@Override
			public BaseBitmapTextureAtlasSourceDecorator deepCopy(){
				return this;
			}
		};
		this.picture_region = BitmapTextureAtlasTextureRegionFactory.createFromSource(this.pictureTextureAtlas, decoratedTextureAtlasSource, 0, 0);
		this.pictureTextureAtlas.load();
	}

 
 さて、このグレーモザイク画像から目鼻口輪郭を認識することができるのか!?
 
 できない時には「ぞろぞろ」は丸顔専用アプリとなります。

「カラフルフロア」が注目アプリに「全ての姫君は~」を公認していただきました。

 いつもお世話になっていますアンドロイダーさんにパズルゲーム「カラフルフロア」を注目アプリdメニューの新着アプリで紹介していただきました。
 「全ての姫君はわたしのものだ!」も公認していただき、ホントお世話になりっぱなしです。
 
 少しでもダウンロード数が増えればいいなあ・・(今月二度目のつぶやき)。

AndEngineのParticleSystemとTMXTiledMapを使う

 作成中のアプリ「ぞろぞろ」の髭はパーティクルで生成しています。
 パラメータを設定しておけば自動でぞろぞろ生えてくるので便利なのですが、顔の部分部分で髭の濃淡を付けるのには、それぞれのパラメータが必要です。
 
 そこで、480×800 の画面を8ピクセル方形の 60×100 に分割して顔のガイドラインに沿って右5段階、左5段階の濃淡を付けるマップを作成しました。右と左とに分けたのは、髭の生える方向が違うから。
 

		int[] particles		= {25, 20, 15, 10,  5,  5, 10, 15, 20, 25}; // 密度
		float[] scaleXMax 	= {0.2f, 0.15f, 0.15f, 0.1f, 0.1f, 0.1f, 0.1f, 0.15f, 0.15f, 0.2f}; // 太さ
		float[] scaleYMax 	= {0.35f, 0.3f, 0.25f, 0.2f, 0.15f, 0.15f, 0.2f, 0.25f, 0.3f, 0.35f}; // 長さ

		int id;
		for(int i = 0 ; i < 60 ; i++){
			for(int j = 0 ; j < 100; j++){
				// TMXTiledMap faceMap から設定値を読み込む
				id = faceMap.getTMXLayers().get(0).getTMXTile(i, j).getGlobalTileID();
				if(id > 0){ // id = 0 は設定なし

					// エミッターで位置と大きさを設定 RectangleParticleEmitter(float pCenterX, float pCenterY, float pWidth, float pHeight)
					final RectangleParticleEmitter pe = new RectangleParticleEmitter((i*8)+4, ((100-j)*8)+4, 8, 8);

					// サンプルと同じ BatchedPseudoSpriteParticleSystem を使用
					// BatchedPseudoSpriteParticleSystem(IParticleEmitter pParticleEmitter, float pRateMinimum, float pRateMaximum, int pParticlesMaximum, ITextureRegion pTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager)
					BatchedPseudoSpriteParticleSystem ps = new BatchedPseudoSpriteParticleSystem(pe, 10, 30, particles[id-1], resourcesManager.higeParticleTexture_region, vbom);

					// setBlendFunction(int pBlendFunctionSource, int pBlendFunctionDestination) ・・わからん・・
					ps.setBlendFunction(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE);

					// カラー初期値 ColorParticleInitializer<Entity>(float pMinRed, float pMaxRed, float pMinGreen, float pMaxGreen, float pMinBlue, float pMaxBlue)
					ps.addParticleInitializer(new ColorParticleInitializer<Entity>(0.1f, 0.2f, 0.1f, 0.2f, 0.1f, 0.2f));

					// 透明度初期値 AlphaParticleInitializer.AlphaParticleInitializer<Entity>(float pMinAlpha, float pMaxAlpha)
					ps.addParticleInitializer(new AlphaParticleInitializer<Entity>(0.5f, 0.8f));

					// 回転範囲 RotationParticleInitializer.RotationParticleInitializer<Entity>(float pMinRotation, float pMaxRotation) 右と左で振り分け
					if(id <= 5) ps.addParticleInitializer(new RotationParticleInitializer<Entity>(175.0f, 215.0f));
					else 		ps.addParticleInitializer(new RotationParticleInitializer<Entity>(155.0f, 195.0f));

					// 寿命 ExpireParticleInitializer<Entity>(float pLifeTime)
					ps.addParticleInitializer(new ExpireParticleInitializer<Entity>(240f));

					// スケール変化 ScaleParticleModifier<Entity>(float pFromTime, float pToTime, float pFromScaleX, float pToScaleX, float pFromScaleY, float pToScaleY)
					ps.addParticleModifier(new ScaleParticleModifier&amp;lt;Entity&amp;gt;(0f, 90f, 0.0f, scaleXMax[id-1], 0.0f, scaleYMax[id-1]));

					// 透明度変化 AlphaParticleModifier<Entity>(float pFromTime, float pToTime, float pFromAlpha, float pToAlpha)
					ps.addParticleModifier(new AlphaParticleModifier<Entity>(0f, 90f, 0.5f, 0.8f));
					ps.setTag(j*100 + i + TAG_PS);
					attachChild(ps);

					// 停止しておく setParticlesSpawnEnabled(boolean pParticlesSpawnEnabled)
					ps.setParticlesSpawnEnabled(false);
				}
			}
		}

 こうなった。
device-2015-01-18-201812
 真正面からの写真なら、もっとうまくフィットします。
 画面タッチした部分のパーティクルシステムをリセットして髭剃りにも対応したので
device-2015-01-18-201910
リンカーン風にもできます。
 
 あぁ、メモリが・・。

こりゃ怒られるわな

 作成中のアプリ「ぞろぞろ」は落語の「ぞろぞろ」に因んでいるので、髭が生えます。
 
 テストがてら、ただの円形アウトラインにパーティクルで髭を生やしてみました。
 device-2015-01-12-235004 device-2015-01-12-234428
 位置を調整していけばイイ感じになりそうです。
 
 「PAKUTASO」さんのロイヤリティフリー・加工OKの写真素材だけど怒られそうだね。