きよぽん=サンのブログ

プログラミングを中心に。C++.Java,Perlなど

python + opencv で肌色を検出して褐色にするだけ その3

おはこんばんちわ

 

褐色シリーズ(たぶん)最終回です。

前回、前々回とそれぞれ肌色を検出する方法を紹介してみました。

python + opencv で肌色を検出して褐色にするだけ - きよぽん=サンのブログ

python + opencv で肌色を検出して褐色にするだけ その2 - きよぽん=サンのブログ

 

今回は、前回の面による肌色検出を利用して、グラフカットを利用します。

グラフカットは前景領域(ここでは肌色領域)を抽出する手法です。

 

グラフカット

説明するより入出力を見てもらったほうが早い。

f:id:kiyoponb:20150222025723j:plain

Interactive Foreground Extraction using GrabCut Algorithm — OpenCV 3.0.0-dev documentation から引用

 左側の画像と青色の四角の線が入力、右側の画像が出力です。

グラフカットを使うと、割と綺麗に前景の物体領域が得られます。

 

提案手法

  1. 面を利用して肌色と思われる領域をとる(前回記事を参照
  2. 1で得られた領域を前景領域、それ以外の領域を背景領域としてマスク画像を作成
  3. グラフカットの適用

 

結果

前回、前々回と同じ画像を使わせていただきます。絵師さまに感謝

左が入力画像で、右が出力画像

f:id:kiyoponb:20150222031408j:plain

 

マスクをかけた画像

f:id:kiyoponb:20150222031614j:plain

眉と髪の間の肌色が検出漏れだが、かなり綺麗に切り取れるようになった。

 

まとめ

グラフカット先輩イケメン

 

ソースコード全体

gistに載せておく

肌色検出_面_グラフカット

 

ちょっと解説

def mask_from_contours(im_shape, fg_contours, bg_contours):
    mask = np.zeros(im_shape, np.uint8)
    mask.fill(cv2.GC_PR_BGD)
    for contour in fg_contours:
        cv2.drawContours(mask, [contour], 0, cv2.GC_FGD, -1)
    for contour in bg_contours:
        cv2.drawContours(mask, [contour], 0, cv2.GC_BGD, -1)
    return mask

画像サイズからマスクを作成し、すべての点を背景と思われる画素としてラべリング

肌色と思われる領域は前景としてラべリング

肌色と思われないと事は背景としてラべリング

 

グラフカットの際に利用されるマスク画像の各画素には、

  • 背景画素 cv2.GC_BGD
  • 背景と思われる画素 cv2.GC_PR_BGD
  • 前景画素 cv2.GC_FGD
  • 前景と思われる画素 cv2.GC_PR_FGD

の4パターンをラべリングできる。

 

    # graph cut
    mask = mask_from_contours(image.shape[:2], skin_contours, bg_contours)
    bgd_model = np.zeros((1, 65), np.float64)
    fgd_model = np.zeros((1, 65), np.float64)

    mask, bgd_model, fgd_model = cv2.grabCut(image, mask, None, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_MASK)
    gc_mask = np.where((mask==2)|(mask==0), 0, 1).astype('uint8')

マスクを利用したグラフカットを適用し、結果のマスクを0,1に変換

 

+α

今回の記事では、面によって肌色と類似した領域を前景、

肌色とは類似していない領域を背景、どちらでもない場合は背景と思われる画素としています。

 

今回は面によって検出した領域だけの結果をメインに紹介しましたが、

点で検出した肌色領域、面で検出した肌色領域の2つからグラフカットのためのマスク画像を作成することで、

2つの検出手法の良いところを吸収できる可能性があります。

(短所も吸収するばあいもしかり)

パラメータをいじる根気があれば、おそらくコチラのほうが良い結果がでそう

 

コードはgistに載せておきます。

点と面の肌色領域推定結果を用いたグラフカットによる肌色検出

 

それではノシ

python + opencv で肌色を検出して褐色にするだけ その2

その2 ~面との出会い~

 

おはこんばんにちわ

前回、「書かせてもらった肌色を褐色にする」を別手法でアプローチします


python + opencv で肌色を検出して褐色にするだけ - きよぽん=サンのブログ

 

提案手法

ざっくり言うと、肌色領域を面で検出する。

詳しく言うと、

  1. 顔領域のヒストグラムを計算し、肌色のヒストグラムと定義
  2. 輪郭線を抽出し、各輪郭線内の画素のヒストグラムを計算
  3. 1と2を比較して類似した所を肌色とする

以上

 

前回のおさらい

前回は

  1. 顔検出して、顔領域の中心点を肌色として定義
  2. 肌色に類似した点を画像中から全探索

という手法にて肌色を検出し、褐色に染め上げました。

 

左が元画像、右が変換後

f:id:kiyoponb:20150215205728j:plain

 

点による検出の問題点

  • ノイズがのる
  • 顔領域の中心座標が肌色として定義される

点による検出の際のマスク画像はこんな感じ

f:id:kiyoponb:20150215205906j:plain

白い部分が肌色として検出された画素

腰回りとかにノイズがのってるのがわかる

 

面で肌色を検出してみる

前処理に手間がかかるが、次のようなフローで肌色を検出

  1. 顔検出
  2. 顔領域からヒストグラムの計算
  3. 画像全域からエッジ検出
  4. 輪郭線の抽出
  5. 輪郭線内の画素からヒストグラムを計算
  6. 2と5のヒストグラムを比較、類似しているものは肌色の面とする
  7. 肌色として得られた輪郭線内の画素を褐色に染める

  8. やーうぇい

 

結果はこうなる

f:id:kiyoponb:20150219002839j:plain

 

見た目はあんまり変わっていないが、マスク画像はこうなる

f:id:kiyoponb:20150219000444j:plain

 

点で検出した際のノイズはなくなった!

が、輪郭線の周辺で検出できていない肌色の画素が出てきてしまった。。。

 

まとめ

点も面も一長一短だ

 

コード全体

前回は環境書くのわすれてました

python3.4 + opencv3.0 beta + win です

肌色検出 面ver

 

ちょっと解説

  1. 顔検出
  2. 顔領域からヒストグラムの計算
  3. 画像全域からエッジ検出
  4. 輪郭線の抽出
  5. 輪郭線内の画素からヒストグラムを計算
  6. 2と5のヒストグラムを比較、類似しているものは肌色の面とする
  7. 肌色として得られた輪郭線内の画素を褐色に染める

  8. やーうぇい

1は前回の記事を見ていただくことにして、2~6を解説

(とはいってもopencvの関数の説明になるだけです

 

2. 顔領域からヒストグラムの計算

def calc_face_histgram_normalized(im, faces, padding=0):
    im_hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)

    hsv_histgrams = []
    for i, face in enumerate(faces):
        origin_x, origin_y, width, height = face
        area = (height - padding*2)*(width - padding*2)
        roi_image = im_hsv[(origin_y + padding):(origin_y + height - padding), (origin_x + padding):(origin_x + width - padding)]
        cv2.imshow('win', roi_image)
        cv2.waitKey(1000)

        h_h = cv2.calcHist([roi_image], [0], None, [180], [0, 180], None, 0)
        h_s = cv2.calcHist([roi_image], [1], None, [256], [0, 256], None, 0)
        h_v = cv2.calcHist([roi_image], [2], None, [256], [0, 256], None, 0)

        # normalization and append
        if area == 0:
            area = 1
        hsv_histgrams.append([h_h/area, h_s/area, h_v/area])

    return hsv_histgrams

顔検出の結果は、左上の頂点座標とwidth, heightが返ってくるので、その領域のヒストグラムを作成。正規化しておく

paddingってうのはパラメータの一つです

髪の領域などを少なくしたいときに増やすと良い感じ

 

3. 画像全域からエッジ検出

def canny_edges(im):
    edge_im = cv2.Canny(im, 100, 200)
    kernel = np.ones((3, 3), np.uint8)
    opening_im = cv2.morphologyEx(edge_im, cv2.MORPH_CLOSE, kernel)
    return opening_im

CannyエッジClosing処理でdone

 

4. 輪郭線の抽出

def detect_contours(im):
    closing_im = canny_edges(im)
    ret, thresh = cv2.threshold(closing_im, 127, 255, 0)
    height, width = thresh.shape[:2]
    thresh[0:3, 0:width-1] = 255
    thresh[height-3:height-1, 0:width-1] = 255
    thresh[0:height-1, 0:3] = 255
    thresh[0:height-1, width-3:width-1] = 255
    _, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    return contours

Cloging処理した画像を2値化する。

その際に、画像に3pxの枠をつくっておく

輪郭線の取得はopencv2と3で返ってくる値の数が違うので注意

 

5. 輪郭線内の画素からヒストグラムを計算

def mask_from_contour(im_shape, contour):
    mask = np.zeros(im_shape, np.uint8)
    cv2.drawContours(mask, [contour], 0, 255, -1)
    return mask
def calc_contour_histgram_normalized(im, contour):
    mask = mask_from_contour(im.shape, contour)
    area = cv2.contourArea(contour)
    im_hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
    im_mask = cv2.inRange(mask, 10, 255)

    h_h = cv2.calcHist([im_hsv], [0], im_mask, [180], [0, 180], None, 0)
    h_s = cv2.calcHist([im_hsv], [1], im_mask, [256], [0, 256], None, 0)
    h_v = cv2.calcHist([im_hsv], [2], im_mask, [256], [0, 256], None, 0)

    # normalization and return
    if area == 0:
        area = 1
    return [h_h/area, h_s/area, h_v/area]

 

輪郭線からマスク画像を作って(mask_from_contour)、ヒストグラムを計算

輪郭線の領域を取得しておいて、正規化しておく

 

6. ヒストグラムを比較、類似しているものは肌色の面とする

    faces = detect_faces(image)
    face_hists = calc_face_histgram_normalized(image, faces, padding=60)
    contours = detect_contours(image)
    skin_contours = []

    for index, contour in enumerate(contours):
        hists = calc_contour_histgram_normalized(image, contour)
        for face_hist in face_hists:
            h_dist = cv2.compareHist(face_hist[0], hists[0], 1)
            s_dist = cv2.compareHist(face_hist[1], hists[1], 1)
            v_dist = cv2.compareHist(face_hist[2], hists[2], 1)
            if abs(h_dist + s_dist + v_dist) < thresh:
                print(h_dist + s_dist + v_dist)
                print('is face')
                skin_contours.append(contour)

cv2.compareHistの3つめの引数は比較する手法を表す識別子で

0:相関、1:カイ2乗、2:交差、3:Bhattacharyya 距離

それぞれで類似の指標が異なるので注意

abs(h_dist + s_dist + v_dist) < thresh

の条件式で肌色かどうか判定してマス

 

 

長くなりましたが、今回はこれくらいで

あと1回だけつづきます(たぶん

python + opencv で肌色を検出して褐色にするだけ

の簡単な作業をしてみた。

考え方

  1. 肌色領域のマスク画像を生成
  2. マスク画像から肌色部分を褐色にする
  3. 簡単

 

やってみる

肌色領域の検出

歴代の先人達がよしなにやってくれている

顔検出に必要なxmlを拝借し、ぱろすけさんが書いたコードを参考にするとできる

 

マスク画像から肌色部部分を褐色にする

画素値をいじる簡単なお仕事

def blown_skin_mask(im, mask):
    height, width = im.shape[:2]
    hsv_im = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
    for y in range(height):
        for x in range(width):
            if (mask[y, x, 0]):
                h = hsv_im[y, x, 0]
                s = hsv_im[y, x, 1]
                if (h < 8):
                    hsv_im[y, x, 0] = h + 180 - 8
                else:
                    hsv_im[y, x, 0] -= 8
                if (s < 212):
                    hsv_im[y, x, 1] += 43
                else:
                    hsv_im[y, x, 1] = 255
    return cv2.cvtColor(hsv_im, cv2.COLOR_HSV2BGR)

元画像と肌色領域のマスク画像をもらって、マスクの部分だけHSVをいじる

HSVの値はお好みの褐色具合で、個人的には (h,s,v)=(15,180,230)くらいが健康的な褐色で好き

 

コード全体

gistに載せておいた

detect_skin_and_blown.md

 

やってみる

次の画像に試してみる

f:id:kiyoponb:20150215205705j:plain

 

 結果を並べてみる

f:id:kiyoponb:20150215205728j:plain

 

肌色領域がまだまだ改善できそうな感じです

 

今回はここらへんで終わり

カッショクゴキゲンヨウジョ!!