目次

一人クリスマスハッカソンとは

説明しよう!
一人クリスマスハッカソンとは、クリスマス・イブからクリスマスの期間のうち任意の時間を利用して、一人で行うハッカソンである!
参加者は、集中的にサービスやソフトウェアなどを作り上げるんだ!

前回はゴールデンウィークで、一人GWハッカソンと称して、一次元定常状態のシュレーディンガー方程式の解を求めました。
前々回は一人年越しハッカソンということで、三重振り子のシミュレーションをやりました。

今回やったこと

二年半前に顔を検出して顔を置き換える雑コラソフトを作ったのですが、それは検出した場所にただ重ねるだけのソフトウェアでした。
今回は、そのソフトを改良して、もう少し頑張った感のある雑コラを目指しました。

成果

ソースコードやWindows向けの実行ファイル(動く保証はない)はここに置いてあります。
0V/Face-Exchanger: 顔を認識して他の画像を重ねる雑コラ製造ソフト

UI周りや基幹のシステムは二年半前のままなので、結構お察しな部分はありますが、目をつぶってやってください。

DEMO

たくさんの人と

ゴリラフェイスを組み合わせて

こうなります

  • UI

  • 画像

  • ほかにも

まとめ

何度か一部の作業の手動化も考えましたが、なんとか自動でやりきりました。結構大変でした。
顔が正面向いているとうまくいくんですが、横向いたり傾いたりしていると結構シビアな感じになります。
あと顔検出の精度上、誤検出もたまにします。
とまあ、クオリティはあまり高くないですが、楽しかったので良しとします。
まだ改良したい部分も多いですが、ひとまずクリスマスも終わりなのでここで一段落ということで。
それでは皆さん、良いお年を!

技術的な話

以下は技術的な蛇足です。

実装

実装は C# にて行いました。WPF アプリケーションとして作成してあります。多分 MVVM の思想におおよそ従っているはずです(ガワを作ったのが二年以上前なので自信がない)。

依存関係

画像処理ライブラリとしては、OpenCV の C# ラッパーライブラリである OpenCvSharp を用いました。
OpenCV のバージョンは 3.1 です。

仕組み

顔検出
OpenCV 備え付けのカスケード分類器を用いて顔を検出しています。
FaceExchanger/Model/TargetDetector.cs の中のこういうのが書いてあるところです。これだけで検出自体は終わってしまいます。便利ですね。


Cv2.CvtColor(srcMat, grayMat, ColorConversionCodes.BGR2GRAY);
Cv2.EqualizeHist(grayMat, grayMat);
var faces = Cascade.DetectMultiScale(grayMat);

view raw

facedetect.cs

hosted with ❤ by GitHub

ここで出てきた Cascade は CascadeClassifier クラスのインスタンスで、最初にこのインスタンスを作る際、検出したい物体(ここでは顔)の分類器をコンストラクタの引数にとって読み込ませておいてあります。

傾き認識
この方法の顔検出だと、顔の傾きが認識できないので、別の方法を用いて傾きを認識してやる必要があります。

最初のアプローチとして、適応的閾(いき)値処理(適応的二値化処理などと呼ばれることも)をして画像を二値化(白黒にすること)して顔の輪郭を取り、その輪郭を楕円でフィッティングすることで傾きを検出しようとしました。
しかし、このアプローチだと顔と首の境界が曖昧であったり、顔の輪郭とは関係のないノイズが多く混ざったりといったことが原因でうまくいきませんでした。また、楕円ではなく、輪郭にフィットする最小の矩形を取る方法も行いましたが、同様の理由で駄目でした。

  • 適応的閾値処理とは
    ある画像を二値化する際、通常は「明度がある値(この値を閾値と呼びます)以上なら白、未満なら黒とする」といった方法がとられます。この時、影などがあるとその周辺が全部黒くなるなど、画像全体の明るさの違いによってうまく二値化できないことが多いです。適応的閾値処理では、画像の各部分についてあるアルゴリズムにもとづいて閾値を動的に変更することで、明度などの変化に強い閾値処理を可能にしています。

次に肌色領域を先に抽出することで余計なノイズを減らそうとしましたが、人によって肌の色がかなり違うこと、首や肌色に近い別の物体がノイズになってしまうことが原因で断念しました。

その次に考えたのは、注目する点を顔の内部のみにして、顔の輪郭を取るのではなく、鼻や目などの情報を元に傾きを認識する方法でした。
ここでも先程と同様に輪郭を検出し、楕円や矩形をフィッティングさせることで傾きを出そうとしましたが、目や鼻以外にも顔の影などの情報が邪魔してうまくいきませんでした。

行き詰まって OpenCV のソースコードを眺めていたら、各種データを集めたフォルダの中に目を検出するカスケード分類器を見つけました。
これを用いて、検出された顔の領域で目の検出を行い、検出された目の中心を結んで、その結んだ線と水平方向の傾きを取ることで顔の傾きをおおよそ判定することに成功しました。
また、目の位置がわかったので、顔の中心を若干補正することができるようになりました。

顔を重ねる
顔を重ねる際は、2003年にPatrick Perezらが提案した、Poisson Image Editing と呼ばれる手法を用いました。OpenCvSharp の OpenCV 対応バージョンがまだ 2.* だけだと思っていて、最初はこのアルゴリズムを自分で実装したのですが、Nuget で別のパッケージとして 3.* 対応版が配布されていることに気づいたので、 3.0 から使えるようになった cv::seamlessClone (OpenCvSharp では Cv2.SeamlessClone)を利用しました。そっちのほうが圧倒的に早いので。

Poisson Image Editing の仕組みとしては、以下の多元連立一次方程式を解くことで適切な画素値(解)を得ています。

昔に Word でこの式を入力したやつがあって、それを流用して楽をしたかったので画像になっています。すみません。
これの説明を長々と書こうかと思ったのですが、多分良い文献はあると思うのでググってください。おしまい。