45
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「コワすぎ」るカメラアプリ1~歪む顔編~【Unity × OpenCV × Dlib】

Last updated at Posted at 2016-08-18

※(2017/04/27)公開しているデモアプリをUnityの新しいバージョンでビルドしたものに変更
(WebGL対応については「OpenCV for Unity」AssetのWebGL対応がやって来たヤァ!ヤァ!ヤァ!を参照してください)

はじめに

巷のイケてる若者たちの間で「Snapchat」や「Snow」のようなリアルタイムで映像加工するカメラアプリが大流行していると噂で聞きました。(犬になったり、顔を交換するアレ)
乗るしかない このビッグウェーブに!って事で、UnityとOpneCVとDlibを利用して同種のカメラアプリのサンプルを作成してみたいと思います。

お盆の時期なのでテーマは「ホラー」でいきます。
「コワすぎ」るカメラアプリ2~地獄だぞ編~【Unity × OpenCV × Dlib】とのシリーズ記事です)

元ネタ

映像加工の元ネタ映像は白石晃士監督作品の「戦慄怪奇ファイル コワすぎ!史上最恐の劇場版」のこのシーン。

qiita_kowasugirucamera1_1.jpg
ディレクター工藤が率いるコワすぎスタッフと一緒に「タタリ村」に撮影に行ったら霊体(?)に憑依されてしまい静止状態のまま顔が徐々に歪んでゆくゾンビアイドルの小明さん。
一緒にいたAD市川が背後から声をかけた瞬間に口から謎の白い手が飛び出してくるという非常に印象的なカットです。

制作のポイント

カメラに映る人物の口が半開きになったタイミングで表情を固定して、顔に貼りつけたまま時間経過によって徐々に歪ませるという表現を目標にして制作を進めます。
(歪みの度合いが最大になった後は顔が激しく動いたタイミングで元の顔に戻る)

qiita_kowasugirucamera1_2.jpg

まずはFaceRig無しでも中の人(二次元)になりたい!【Unity × OpenCV × Dlib × Live2D】と同様の手順で「OpenCV for Unity」と「Dlib FaceLandmark Detector」をプロジェクトにセットアップしておきます。

OpenCVで顔検出+トラッキング~顔領域の切り出し~貼り付け

ここの一連の処理は、「OpenCV for Unity」のpublisherのgithubで公開されているコードFaceSwapperSampleの大部分を参考にさせてもらいました。

Dlibで検出した顔器官の頂点群情報から顔の向きと口の開き具合を算出し、映像内の人物の顔が正面を向いていて、かつ口が半開き状態の時に顔領域を切り出して保持しておきます。
次フレームからは切り出しておいた顔画像を同一人物の顔の輪郭にフィットさせるアフィン変換を施してから重ねることで人物の表情を固定します。

Shaderを使って顔領域を歪ませる

切り出した顔領域画像を歪ませる処理はShaderで行います。
ここで公開されているFlowMapシェーダーを参考にして、専用の歪みシェーダーを作成しました。
FlowMapと呼ばれる移動方向と移動量が格納されたテクスチャを基にUV座標に変位を与えることで画像を歪ませることが出来ます。

flowmap_sample.png
(画像の中心部をねじるように変形させるFlowMapテクスチャの例)

DistortionShader.shader
Shader "Custom/DistortionShader"
{
    Properties
    {
        _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        // Flow
        _FlowMap ("Flow Map", 2D) = "white" {}
        _Flow  ("Flow", Range(0,1)) = 0
        _Trans ("Trans", Range(0,1)) = 0

        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    }
 
    SubShader
    {
        Tags
        { 
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }
 
        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ PIXELSNAP_ON
            #include "UnityCG.cginc"
             
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
 
            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
                half2 texcoord1 : TEXCOORD1;
            };
             
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _FlowMap;
            float4 _FlowMap_ST;

            float _Flow;
            float _Trans;
            fixed4 _Color;
 
            v2f vert(appdata_t IN)
            {
                v2f OUT;
 
                OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
                OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _MainTex);
                OUT.texcoord1 = TRANSFORM_TEX(IN.texcoord, _FlowMap);
                OUT.color = IN.color * _Color;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif
 
                return OUT;
            }
 
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed3 flowDir = tex2D(_FlowMap, IN.texcoord1) * 2.0f - 1.0f;
                flowDir *= _Flow;
                half3 tex0 = tex2D(_MainTex, IN.texcoord + flowDir.xy);
                fixed4 c = float4(tex0, 1 - _Trans) * IN.color;

                c.rgb *= c.a;
                return c;
            }
            ENDCG
        }
    }
}

合成

カメラ画像全体を表示しているQuad上に顔領域だけのQuad(歪み処理済み)をレイヤーのように手前に重ねることで、カメラ画面上で画像を合成しています。
qiita_kowasugirucamera1_3.jpg

デモアプリ(歪む顔編、地獄だぞ編共通)

※要求するカメラ入力サイズによってシーンをを切り替えて試せるように(640x480 or 320x240)デモアプリを修正しました。

  • Android (Unity5.5.1p4)

  • WebGL_asm.jp (Unity5.6.0f3)(WebGL対応のブラウザで実行可能)

  • WebGL_webassembly (Unity5.6.0f3)(WebGL対応かつwebassemblyが有効なブラウザならより高速に実行可能)

動作画面

映像素材:MovieTips

(動画はiPhone6実機からキャプチャして8fpsのGIF動画に変換したもの)

KowasugiruWarpCamera_cap2.gif
おわかりいただけただろうか、徐々に歪んでゆく女性の顔に注目して観てほしい。
KowasugiruWarpCamera_cap1_2.gif
KowasugiruWarpCamera_cap1.gif
歪みのパターンは5種類あります。

KowasugiruWarpCamera_cap3.gif
(画面上にデバッグ情報を表示したもの。画面左上に表示されるのは表情固定のために切り取った顔画像)

#まとめ

自分の顔とはいえ表情が固定されたまま顔に貼りついていると、それだけでも結構気持ち悪いです。歪むと尚更ホラーになります。
洗面所の鏡と入れ替えて設置してあったらドッキリ企画として面白いかもしれません。
元ネタ映像の禍々しさを少しは再現できたのではないかと思います。

続編記事>>「コワすぎ」るカメラアプリ2~地獄だぞ編~【Unity × OpenCV × Dlib】

関連記事

45
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?