先日記事に書いたとおり最近「紙の可能性」を模索しています。
その取り組みのなかで、現在iPhoneアプリを開発しています。詳細は今度明らかにしますが、今日はそのなかで使う素材の準備にかかわる話です。

そのアプリでは、松下ひろこさんが作ったモチーフを使うのですが、使うためには素材を写真撮影し、切り取る作業が必要になります。通常はPhotoshopの機能などを使って、ある程度効率化しつつ手作業で切っていくのですが、ちょっと想像するに大変そうな予感。。。

そこで、今回はせっかくプログラマーの僕がいるので、切り抜く処理をある程度自動化する仕組みを作ることにしました!プログラムしてる間に手で切り抜け!という声がすこし聞こえてきそうですが。。。

クロマキーで切り取り

背景をアルファで抜いたような切り取りを手軽にするといえば、クロマキーですよね。なので最初はこれで試しました。

craftrobo

事前に松下さんから渡してもらっていたこの写真で試します。背景が緑色なので、この色を抜くという処理をします。

スクリーンショット 2013-09-17 23.27.00

とりあえず実験なので、QuartzComposerというソフトを使います。これはAppleが配布しているソフトで、画像処理などが簡単に扱えるというものです。詳しくはこことか見てください。

今回の実験は、CoreImageFilterというパッチを使って作っていきます。元の画像をCoreImageFilterに記述したエフェクトでクロマキー処理します。GLSLでCoreImageのFilterをカスタムできるというような感じのやつです。ここで書いたものは、CoreImageFileterとしてネイティブ環境でそのまま使えます。またQuartzComposerのパッチ自体をフィルターとしてネイティブアプリ内で使うことも出来るので、どっちでもOKです。

スクリーンショット 2013-09-17 23.30.38

kernel vec4 coreImageKernel(sampler image, __color color, float max, float min, float shadowAlpha)
{
	vec4 imageColor;
	vec4 dColor;
	float dist;
	float minn;
	vec4 shadowColor;

	minn = min<0.0?0.0:min>1.0?1.0:min;
	minn = max*minn;
	imageColor = sample(image, samplerCoord(image));
	dColor = imageColor - color;
	dist = dColor.r*dColor.r + dColor.g*dColor.g + dColor.b*dColor.b;

	shadowColor = vec4(0.0,0.0,0.0,0.0);
	imageColor = dist<max?shadowColor:imageColor;
	imageColor.a = dist<max?(dist<minn?0.0:((dist-minn)/(max-minn)))*shadowAlpha:1.0;

	return imageColor;
}

imageに元の画像を入れます。colorは抜きたい色のサンプル。maxは、抜きたい色をベースに実際に抜く範囲の数値です。指定した色と全く同じ色しか抜けないと全然使えないので、ある程度ずれても良いようにするのですが、そのズレ量という意味です。minは、影を生かしたかったので作ったパラメータです。値を1.0より下げていくと影として採用する範囲が増えていきます。shadowAlphaは、影として使う範囲を強制的に加える透明度です。(説明が意味不明かもしれませんが。。)

とにかく結果です。

Resultクロマキー

結構うまく抜けた!?ん、よく見ると駄目ですね。。。

zoom

モチーフ自体に落ちている影まで抜けてしまっています。これは、モチーフの影が緑の紙に落ちているので、そこを抜くように範囲指定すると必然的に必要な部分まで抜けてしまったという感じです。撮影の工夫である程度回避できそうな気もしますが、なんか心配です。

この際別の方法も検討してみます。

バックライトのクロマキー方式

背景に影が落ちたのが問題だったので、思い切って液晶画面を背景に使います。自発光の緑を背景にしたら影が出ないだろうという思いつきです。

MacBookの画面にテスト用のモチーフを両面テープで貼ってテストしました。

green

良い感じに光ってますねー。先ほどのプログラムでテストした結果です。

resultGreen

今度はモチーフ内の影は残っています。うまくいったように見えます。が、よく見るとモチーフが緑がかってますね。。。背景が光ってることで、紙を貫通して緑色が出てしまったようです。これも画面からの距離など工夫したら解決できそうな気もしますが、なんかちょっと惜しいです。

この際なので、別の方法も試します。

背景を変える

クロマキーと比べると面倒なやり方ですが、2枚の写真から背景を認識する方式です。具体的には以下のような2枚の写真を使います。

black white

これは、先ほどと同様にMacBookの画面を使っています。黒を表示してる状態と白を表示してる状態です。カメラは3脚に固定し、フォーカス固定、絞りとシャッタースピードも固定します。

で、この2枚の画像の差を計算し、許容範囲を指定すると背景が抜けるんじゃないかという考えです。

プログラムはこんな感じ。

mask2

make maskの中身

kernel vec4 coreImageKernel(sampler baseImage, sampler lightImage, float threshold)
{
	vec4 baseColor;
	vec4 lightColor;
	vec4 dColor;
	vec4 distColor;
	float dist;

	baseColor = sample(baseImage, samplerCoord(baseImage));
	lightColor = sample(lightImage, samplerCoord(lightImage));

	dColor = baseColor - lightColor;
	distColor = vec4(dColor.r*dColor.r, dColor.g*dColor.g, dColor.b*dColor.b, 1.0);
	dist = sqrt(distColor.r + distColor.g + distColor.b);

	baseColor.r = baseColor.g = baseColor.b = dist<threshold?1.0:0.0;
	baseColor.a = dist<threshold?1.0:0.0;

	return baseColor;
}

mask alphaの中身

kernel vec4 coreImageKernel(sampler baseImage, sampler maskImage, float cutLevel)
{
	vec4 baseColor;
	vec4 maskColor;
	float alpha;

	baseColor = sample(baseImage, samplerCoord(baseImage));
	maskColor = sample(maskImage, samplerCoord(maskImage));

	alpha = (maskColor.r + maskColor.g + maskColor.b)/3.0;
	alpha = alpha<cutLevel?0.0:(alpha-cutLevel)/(1.0-cutLevel);
	baseColor.a = alpha;

	return baseColor;
}

処理的には、2画像の差からマスク画像を作成します。ただし、白か黒の2値画像なので、エッジがまずいです。そこで、ぼかしをすこしだけかけて、エッジを柔らかにし、そのマスク画像を使って背景黒の画像を切り取ります。モチーフに当たった照明のみで自然な色になっているはずです。最後に、マスク画像を元にドロップシャドウを落としてみました。

結果です。
result2Image.png

今度は、内部の影もきれいだし、色も自然です。
なかなか自然にできました。良い感じ。

アプリ化

残念ながらまだQuarzComposerで動かしたところで止まっています。ですが、今度一眼カメラの操作と画面の色の変化を含め自動化するアプリを作ってみようと思います。OSX用のアプリ。うまくできたら、配布しようかと思います。

一眼カメラの制御は、PTPを使って実現できるので比較的簡単です。マックとカメラをUSBでつないで制御します。あとはQuartzComposerのパッチをそのままFilterとしてアプリに組み込み処理を自動化したら完成です。撮影と同時に画像ができ上がるので、結構便利かも?