幽霊になるだって?
記事の概要
本記事では、画像処理技術の一つであるアルファブレンディングを用いて、幽霊っぽい画像を生成する。
動機
授業でアルファブレンディングを習った時、「あ、これ幽霊っぽい画像作れるな」と思ったのがきっかけ。面白そうだったのでやることにした。
開発環境
使用言語:C++ 4.2.1(ちょっと自信ない)
統合開発環境:Xcode 9.4.1
ライブラリ:Opencv
アルファブレンディングとは
本題へ入る前に技術について説明しておく。
アルファブレンディングとは、重み付き平均を利用して画像を混ぜ合わせることである。
アルファブレンディング公式
出力画像をg、入力画像1をf0、入力画像2をf1、重み付き平均係数をα(0 <= α <= 1)とすると、公式は以下のようになる。
g = α * f0 + (1 - α) * f1
重みであるαの値を大きくすれば、f0の成分量が増加し、入力画像f0の面影が強くなる。
反対にαの重みを小さくすれば、(1 - α)の値が大きくなり、入力画像f1の面影が強くなる。
日常生活で使用されているところを考えてみた。
考えてみると、これが結構使用されていることに気づく。
まず第一に思いついたのが、みなさんおなじみのパワーポイント。アニメーションの「フェードイン」や、「フロートイン」にはおそらく、アルファブレンディングが使われている。何もない画面をf0、テキストボックスをf1とし、重みの値αを時間に沿って増加させることにより、浮かび上がる感じになるのだろう。
次は見聞によるものだが、映画だ。昔の映画では場面切り替えの際にそうした手法がよく使われていたらしい。
映画から派生して考えてみると、アニメなどでも結構使われているかもしれないことに気づいた。高速移動するちっこいピンクの悪魔とか、残像を残して敵を翻弄する主人公とか。
最後はノベルゲームだ。筆者は最近友人から数年前のノベルゲームを貸してもらったのだが、キャラクターのポーズが切り替わる時にこれが使われている。
探してみてわかったが、これは結構生活に馴染んでいる技術なのかもしれない。
幽霊になろう ~実践~
材料集め
材料として用意したのは以下の画像二つだ。
画像1
画像2
画像はiPhoneを写真立てに固定して撮影。
顔出しはちょっとNGなので、適当にあったラムちぬいぐるみの画像を貼り付けた。
今回は幽霊っぽい画像を目指すので、後ろの背景は固定しなければならない。
そうしないと、画像をアルファブレンディングした時に、背景もだぶってしまう。
対象だけをだぶらせる場合は、背景が全く同じになるように写真を撮ろう。
コードを書く
コードは以下のようになった。
// Copyright © 2018年 furukawa rights reserved.
# include <iostream>
# include <opencv2/opencv.hpp>
using namespace std;
int main(int argc, const char * argv[]) {
cv::Mat me_img = cv::imread(画像1, 1);
cv::Mat non_me_img = cv::imread(画像2, 1);
cv::Mat dst_img = cv::Mat(non_me_img.size(),CV_8UC3);
if(me_img.empty() || non_me_img.empty()){
cout << "File is not opened." << endl;
return -1;
}
double ALPHA = 0.3;
for(int i=0; i<me_img.rows; i++){
for(int j=0; j<me_img.cols; j++){ //公式 g = αf + (1 - α )
cv::Vec3b me_color = me_img.at<cv::Vec3b>(i,j);
cv::Vec3b non_me_color = non_me_img.at<cv::Vec3b>(i,j);
//αブレンディング
double b_result = ALPHA * (double)me_color[0] + (1 - ALPHA) * (double)non_me_color[0];
double g_result = ALPHA * (double)me_color[1] + (1 - ALPHA) * (double)non_me_color[1];
double r_result = ALPHA * (double)me_color[2] + (1 - ALPHA) * (double)non_me_color[2];
dst_img.at<cv::Vec3b>(i,j)[0] = b_result;
dst_img.at<cv::Vec3b>(i,j)[1] = g_result;
dst_img.at<cv::Vec3b>(i,j)[2] = r_result;
if(abs(331-1208)/2 < i ){
ALPHA = ALPHA - 0.0000005;
if(ALPHA < 0) ALPHA = 0;
}
}
}
cv::imshow("a", me_img);
cv::imshow("b", non_me_img);
cv::imshow("dst_img",dst_img);
cv::imwrite(出力画像名, dst_img);
cv::waitKey();
return 0;
}
出力結果
いい感じに幽霊っぽくなれたのではないだろうか。
プログラムの説明
以下のコードに疑問を覚えた方がいた時のために、少し補足説明しておく。
if(abs(331-1208)/2 < i ){
ALPHA = ALPHA - 0.0000005;
if(ALPHA < 0) ALPHA = 0;
}
一つ目の条件式は、腰あたりの画素の値になったら、値を小さくし始めることを示している。
二つ目のifはアルファがゼロ以下になった時、マイナスになってしまわないようにしている。
ただ画像をアルファブレンディングしただけでは、おばけっぽさが足りないので、このようにした。
ちなみに、どうやって対象(この場合は僕)の頭と、足のy座標をとったのかというと、元画像RGB値の合計値とアルファブレンディング後の画像RGB値合計との距離がある閾値を超えた場合に、y座標の値を保存する、というプログラムを用いた。
要望があれば、こちらのプログラムについても、期末テストの後に載せようと思う。
興味があったらやってみてほしい。
まとめ
- 公式は g = α * f0 + (1 - α) * f1
- アルファブレンディングは結構日常生活に馴染んだ技術。
- だぶらせる対象が一つだけだった場合は、カメラを固定することが結構重要。