python + opencv で肌色を検出して褐色にするだけ その3
おはこんばんちわ
褐色シリーズ(たぶん)最終回です。
前回、前々回とそれぞれ肌色を検出する方法を紹介してみました。
python + opencv で肌色を検出して褐色にするだけ - きよぽん=サンのブログ
python + opencv で肌色を検出して褐色にするだけ その2 - きよぽん=サンのブログ
今回は、前回の面による肌色検出を利用して、グラフカットを利用します。
グラフカットは前景領域(ここでは肌色領域)を抽出する手法です。
グラフカット
説明するより入出力を見てもらったほうが早い。
Interactive Foreground Extraction using GrabCut Algorithm — OpenCV 3.0.0-dev documentation から引用
左側の画像と青色の四角の線が入力、右側の画像が出力です。
グラフカットを使うと、割と綺麗に前景の物体領域が得られます。
提案手法
- 面を利用して肌色と思われる領域をとる(前回記事を参照
- 1で得られた領域を前景領域、それ以外の領域を背景領域としてマスク画像を作成
- グラフカットの適用
結果
前回、前々回と同じ画像を使わせていただきます。絵師さまに感謝
左が入力画像で、右が出力画像
マスクをかけた画像
眉と髪の間の肌色が検出漏れだが、かなり綺麗に切り取れるようになった。
まとめ
グラフカット先輩イケメン
ソースコード全体
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
画像サイズからマスクを作成し、すべての点を背景と思われる画素としてラべリング
肌色と思われる領域は前景としてラべリング
肌色と思われないと事は背景としてラべリング
グラフカットの際に利用されるマスク画像の各画素には、
の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 で肌色を検出して褐色にするだけ - きよぽん=サンのブログ
提案手法
ざっくり言うと、肌色領域を面で検出する。
詳しく言うと、
以上
前回のおさらい
前回は
- 顔検出して、顔領域の中心点を肌色として定義
- 肌色に類似した点を画像中から全探索
という手法にて肌色を検出し、褐色に染め上げました。
左が元画像、右が変換後
点による検出の問題点
- ノイズがのる
- 顔領域の中心座標が肌色として定義される
点による検出の際のマスク画像はこんな感じ
白い部分が肌色として検出された画素
腰回りとかにノイズがのってるのがわかる
面で肌色を検出してみる
前処理に手間がかかるが、次のようなフローで肌色を検出
- 顔検出
- 顔領域からヒストグラムの計算
- 画像全域からエッジ検出
- 輪郭線の抽出
- 輪郭線内の画素からヒストグラムを計算
- 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回だけつづきます(たぶん
python + opencv で肌色を検出して褐色にするだけ
の簡単な作業をしてみた。
考え方
- 肌色領域のマスク画像を生成
- マスク画像から肌色部分を褐色にする
- 簡単
やってみる
肌色領域の検出
歴代の先人達がよしなにやってくれている
顔検出に必要な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に載せておいた
やってみる
次の画像に試してみる
結果を並べてみる
肌色領域がまだまだ改善できそうな感じです
今回はここらへんで終わり
カッショクゴキゲンヨウジョ!!