きよぽん=サンのブログ

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

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回だけつづきます(たぶん