全自動水玉コラ画像・動画生成方法 python+opencv



PythonとOpenCVを使って水着と顔を自動で検出して曇りガラス(すりガラス)風のセクシーな画像・動画を生成します。

OpenCVは顔の検出や特徴抽出等のパッケージも揃っているため、比較的容易に画像処理を試す&実装することができます。

スポンサードリンク

水玉コラとは何か?

水玉コラは、日本で生まれ2010年を中心にインターネット上で話題になった技術です。

人間には、見えない部分を補完する能力があります(プレグナンツの法則)。その能力を利用して、写真中の水着部分を水玉模様の中に隠すことで、その部分を人間に想像させ、実際とは異なる知覚を与えています。

今回は、その中でも特に美しいとされる「曇りガラス表現」による静止画・動画の水玉コラを自動生成してみます。

手法

より美しい水玉コラの自動生成に必要な機能は次のとおりです[1][2]。

これらを意識しながら、具体的には次のような画像処理・計算を行い自動生成を行っています。

HSV色空間(HSBとも呼ばれます)

HSV表色系は明度(V:Value、B:Brightness)、彩度(S:Saturation)、色相(H:Hue)で表されます。

水着や肌部分を抽出するために画像をRGB 色空間から、 HSV 色空間に変換します。

これにより、画像の明るさによらず肌を色相(H)の値のみで識別できます。

肌色領域の抽出

人間の肌の色は色相(H)0~30の間が多いため、その範囲に閾値を設定してやれば肌領域を検出することが可能です。

ここでは、次の範囲を肌領域として検出しています。

    hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
    # 取得する肌領域の範囲を指定する
    lower = np.array([0, 60, 80])
    upper = np.array([30,255,255])

    # 指定した領域に基づいたマスク画像の生成
    dst = cv2.inRange(hsv, lower, upper)

多くの画像で実験しましたが、この閾値は大半の画像で有効のようです。

隠す領域選択

肌領域の抽出同様、水着は彩度の高いものが採用されていることが多いので「彩度の高いもの」を水着として抽出していきます。

def swimsuit(src):

    hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
    # 取得する色の範囲を指定する
    lower = np.array([  0,180, 8])
    upper = np.array([255,255,247])

    # 指定した色に基づいたマスク画像の生成
    dst = cv2.inRange(hsv, lower, upper)
    return dst

多くの画像で試したところ、一定の条件の水着に対しては検出可能ですが、水着の色により閾値の調整が必要な事があります。

顔領域の選択

顔領域の検出は、デフォルトで付いているCascade検出器を利用すると数行で記載できます。

    cascade_path = './haarcascades/haarcascade_frontalface_default.xml'
    cascade  = cv2.CascadeClassifier(cascade_path)
    facerect  = cascade.detectMultiScale(img_gray, scaleFactor=1.11, minNeighbors=1, minSize=(80, 80))

    if len(facerect) > 0:
        for rect in facerect:
            # 枠の左上、枠の幅高
            print(rect[0],",",rect[1], ",",rect[2], ",", rect[3])

一般的に誤認識率は 約30% 程度ありますが、次のように認識率を上げることも可能です。

水着モデルは顔が正面を向いている画像が多く顔の検出率が高かったので、今回は特に何もしていません。

スポンサードリンク

隠さない領域の選択(水玉模様の生成)

次の手順で水玉を自動生成します。

上記を満たすために、「すでに水玉領域となっている領域」「水着領域」の衝突判定が必要です。

水着の輪郭部分の抽出

まず、衝突判定のために水着部分の輪郭の座標を求めます。

衝突判定の計算を簡単にするために、(処理時間はかかりますが)輪郭上の全点の情報を保持しています。

# 輪郭検知(swimsuit_bw:水着部分の2値画像)
def contour_detection(swimsuit_bw):

    # 輪郭を抽出(衝突判定の手抜きのため、cv2.CHAIN_APPROX_SIMPLEは使わない)
    img, contours, hierarchy = cv2.findContours(swimsuit_bw, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    contours.sort(key=cv2.contourArea, reverse=True)
    for i in range(0, len(contours)):
        if True:
           # 凸包の取得
           approx = cv2.convexHull(contours[i])
           # 輪郭の領域を計算
           area = cv2.contourArea(approx)
        else:
           area = cv2.contourArea(contours[i])

        # ノイズ(小さすぎる領域)と全体の輪郭(大きすぎる領域)を除外
        if area < 1e2 or 1e5 < area:
           continue

    return contours, hierarchy

円と線の衝突判定で実装する場合は、CHAIN_APPROX_SIMPLEを使って下さい(こちらが一般的だと思います)。

円と点の衝突判定

円と点の衝突は、点が円の中に含まれているかを判定します。これは、円の半径\(r\)と点までの距離が円の半径以下であれば衝突となります。

上記が次の式を満たす場合は衝突しています。


\({(x_{p}-x_{c})^2} + {(y_{p}-y_{c})^2} \leqq {r^2}\)


円と円の衝突判定

円と円の衝突は、2つの円の中心点間の距離が2円の半径よりも短ければ衝突しています。

上記が次の式を満たす場合には衝突しています。

\({(x_{c1}-x_{c2})^2} + {(y_{c1}-y_{c2})^2} \leqq {(r_{1}+r_{2})^2}\)


水着部分の肌色化とガウシアンフィルタ

今回は「すりガラス風」の水玉コラを作ることを目的としているので、最後の処理が必要です。

水着と判別された部分を肌の色で塗りつぶす

最初は肌の部分からランダムに抽出して肌の色として塗りつぶしていました。

しかし、極端に濃くなったり薄くなったりするため、最終的には固定色で塗りつぶしています。

    # 肌色画素を与える(RGBでなくBGRとなっている事に注意)
    skin_img = cv2.rectangle(skin_img,(0,0),(width,height),(115,144,200),-1)
    # 肌の色の水着を生成する swim_bw:水着部分だけ抽出した2値画像
    dst = cv2.bitwise_and(skin_img, skin_img, mask=swim_bw)

すりガラス効果(ガウシアンフィルタ)を適用する

    dst = cv2.GaussianBlur(src, (51, 51), 70)

動画への適用

静止画での水玉コラ自動作成は、何人かがトライしています[1][2]。

各フレームで上記の手法を適用して動画を作成すれば動画でも水玉コラが実現しますが、次のことに注意する必要があります。

顔検知が1フレームでも見落としがあれば、ボカし対象になるため違和感を感じる動画になります。

また水玉部分も毎フレーム毎に場所が変更してしまうと、チラつきが出てしまいます。

このため、最初見つけた顔の位置・水玉位置をオプティカルフローを用いて追跡することで、軽減しています。

           for m in range(0, len(gFeature)):
               features_prev = np.array([[[gFeature[m][0], gFeature[m][1]]]], np.float32)

               # 前フレーム、現フレーム、前フレームでの点の位置、画像ピラミッドの最大レベル数
               features, status, err = cv2.calcOpticalFlowPyrLK( \
                                                    gray_prev, \
                                                    gray_next, \
                                                    features_prev, \
                                                    None, \
                                                    winSize = (20, 20), \
                                                    maxLevel = 3, \
                                                    criteria = CRITERIA, \
                                                    flags = 0)
               # フレームに有効な特徴点を描画
               if features is not None:
                  for feature in features:
                      gFeature[m][0] = feature[0][0]
                      gFeature[m][1] = feature[0][1]
               else:
                  del gFeature[m]

その他

他のサイト上で書かれている内容に関する考察です。

OpenCV の inpaint

inpaint を用いると「選択領域を取り除き、付近のピクセルを利用していい感じに埋める」ということができます。

    # 写真に意図せず写りこんでしまった物体等を取り除き、それによって欠損した領域を自動修復
    # cv2.INPAINT_NS:Navier-Stokes based method
    # cv2.INPAINT_TELEA:Method by Alexandru Telea
    dst = cv2.inpaint(src, dst, 3, cv2.INPAINT_TELEA)

ですが、水着領域を自動修復するのは大きすぎて難しいようです。

処理時間もかかるため、今回は利用していません。

水玉コラを動画で実験している先人

すりガラス風ではありませんが、[2]の文献を参考にリアルタムでも動画処理できるように実装している人もいました。

上記の実装は、リアルタイムでは動作していません。

高速化するには次のような作り直しが必要です。

今回は実装完了までの期間を短縮することが目的だったので、Python&OpenCVを用いています。

参考サイト・研究

静止画かつ原色水玉の水玉コラを作成する場合は次の参考サイト・研究が役立ちます。

スポンサードリンク