全自動水玉コラ生成マシーン

聖夜なので表題のものを作った。

demo

https://github.com/onk/auto_circle_collage

processing で書いたアプリだけど、この記事の内容はほぼ OpenCV の話です。

仕組み

  1. 水着を自動認識して「隠す」とマーク
  2. 顔を自動認識して「見せる」とマーク
  3. マークに沿って円充填

水着領域の自動認識

最初のアプローチ

肌色認識

先人が大量に居た。RGB 色空間ではなく HSV 色空間を使うというのがコツなようだ。

HSV色空間 HSV色空間 - Wikipedia

HSV 色空間なら影になっている部分も抽出できる。

今回は Hue: 7..15 を肌色として定義した。

PImage detectHada() {
  // 作業用に hue で grayscale にする
  opencv.loadImage(img);
  opencv.useColor(HSB);
  opencv.setGray(opencv.getH().clone());
  // 肌色は 7..15 と定義。inRange で取り出す
  opencv.inRange(7, 15);

  // ノイズ除去 MORPH_OPEN
  opencv.erode();
  opencv.dilate();

  return opencv.getSnapshot();
}

影の部分も抽出できている様子

膨張・収縮

欲しいのは肌色領域ではなく水着領域なので、肌と肌の間の空間を選択する必要がある。

膨張 -> 収縮し、最初との差分を取ることで取得できるのではないかと考えた。

膨張・収縮で水着領域を選択

// 輪郭を広げて戻す
for (int i = 0; i < 10; i++) { opencv.dilate(); }
for (int i = 0; i < 10; i++) { opencv.erode(); }

が、膨張する際に水着領域を上手に隠せないパターンが多い。

問題はループで 10 回回しているところにあり、本来は膨張・収縮時にパラメータを渡して解決すべきである。 デフォルトのパラメータのまま無理やりループで代替したところ、望むような膨張効果が得られていない。

opencv-processing でパラメータを渡す方法を調べるのに時間がかかりそうだったので次善の策に頼ることにした。

彩度の高いもの=水着と仮定する

グラビアの水着はなぜか彩度の高いものが採用されていることが多いので「彩度の高いもの」という条件で水着が抽出できる。

いくつかパラメータを変えつつ試したが、

という絞り込みで、一定の条件の水着に対しては判定が効くようになった。

彩度で水着判定

void detectMizugi() {
  // 水着を彩度 180..255, 明度 8..247 と定義。inRange で取り出す
  opencv.loadImage(img);
  opencv.useColor(HSB);
  opencv.setGray(opencv.getS().clone());
  opencv.inRange(180, 255);
  PImage s = opencv.getSnapshot();

  opencv.loadImage(img);
  opencv.useColor(HSB);
  opencv.setGray(opencv.getB().clone());
  opencv.inRange(8, 247);
  PImage b = opencv.getSnapshot();

  // TODO: 無駄に PImage に一度変換して blend しているが
  // おそらく OpenCV のみで可能
  s.blend(b, 0, 0, width, height, 0, 0, width, height, MULTIPLY);

  opencv.loadImage(s);
  opencv.erode();
  opencv.dilate();

  ArrayList<Contour> contours = opencv.findContours();
  for (Contour contour : contours) {
    // 面積が小さすぎるものはノイズと判断して弾く
    if (contour.area() > 25) {
      mizugiAreaList.add(contour);
    }
  }
}

顔領域の自動認識

OpenCV に任せて 2 行で解決した。

opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE);
Rectangle[] faces = opencv.detect();

顔認識

デフォルトだと誤認識率が 30% ぐらいあるが、OpenCV に同梱されている分類器を複数重ね合わせて使う のように、認識率を上げる手段がまだあるようだ。

円充填

ここまでで「隠す領域」と「隠してはいけない領域」が抽出できたので、 いい感じに円で埋めていく作業をする。

等で勉強したのだが、美しい水玉コラを作るには

といった標準的なルールの他に

といったテクニックがあるようだ。

とはいえ「くびれを学習させた教師データ」なるものは存在しないので、右上、左上から先に開けることでなるべく肩が含まれることを期待するにとどめた。

ここでゲームで培った当たり判定の技術 (OBB と点の当たり判定、凸包と点の当たり判定等) が生きたんだが、 処理を簡便化するために水着領域を凸包にしたことで谷間を見せることができなくなってしまった。

凸包 (黄色の部分が当たり判定になるので谷間は必ず隠れてしまう)

ここは今後改善したい。

おまけ (曇りガラス効果)

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

と、いうことは「水着を取り除き」「ガウスボカし」で曇りガラス表現ができるのでは……!

曇りガラス表現

まとめ

OpenCV 超便利。

全自動で水玉を適用できるようになったので「動画に適用できるのでは?」とか「Chrome 拡張等で全 web ページに適用できる?」とか夢が広がっている。

また、人間が頭で考えている手順はだいたい自動化できるというのも再認識した。

偉大なる先人たち