python + opencv で肌色を検出して褐色にするだけ その2
その2 ~面との出会い~
おはこんばんにちわ
前回、「書かせてもらった肌色を褐色にする」を別手法でアプローチします
python + opencv で肌色を検出して褐色にするだけ - きよぽん=サンのブログ
提案手法
ざっくり言うと、肌色領域を面で検出する。
詳しく言うと、
以上
前回のおさらい
前回は
- 顔検出して、顔領域の中心点を肌色として定義
- 肌色に類似した点を画像中から全探索
という手法にて肌色を検出し、褐色に染め上げました。
左が元画像、右が変換後
点による検出の問題点
- ノイズがのる
- 顔領域の中心座標が肌色として定義される
点による検出の際のマスク画像はこんな感じ
白い部分が肌色として検出された画素
腰回りとかにノイズがのってるのがわかる
面で肌色を検出してみる
前処理に手間がかかるが、次のようなフローで肌色を検出
- 顔検出
- 顔領域からヒストグラムの計算
- 画像全域からエッジ検出
- 輪郭線の抽出
- 輪郭線内の画素からヒストグラムを計算
- 2と5のヒストグラムを比較、類似しているものは肌色の面とする
-
肌色として得られた輪郭線内の画素を褐色に染める
- やーうぇい
結果はこうなる
見た目はあんまり変わっていないが、マスク画像はこうなる
点で検出した際のノイズはなくなった!
が、輪郭線の周辺で検出できていない肌色の画素が出てきてしまった。。。
まとめ
点も面も一長一短だ
コード全体
前回は環境書くのわすれてました
python3.4 + opencv3.0 beta + win です
ちょっと解説
- 顔検出
- 顔領域からヒストグラムの計算
- 画像全域からエッジ検出
- 輪郭線の抽出
- 輪郭線内の画素からヒストグラムを計算
- 2と5のヒストグラムを比較、類似しているものは肌色の面とする
-
肌色として得られた輪郭線内の画素を褐色に染める
- やーうぇい
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
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回だけつづきます(たぶん