目次
前回、輪郭を検出することでキャラクタの位置を取得しようとしたが難しそうだったので、今回は背景除去をやってみる事にしました。
まずは簡単なフレーム差分法を試してみます。
これは、あるフレームを別のフレームから減算して大きな差分がある箇所を前景(それ以外を背景)とする方法です。
フレーム差分法をやってみる
以下の2枚の画像を使います。
画像2は画像1の数フレーム後の画像です。
2画像間の差分を計算する
コードは以下の通り。
実際に差分を計算しているのはCore.absdiff
val orig1 = Imgcodecs.imread("/1516.bmp") val orig2 = Imgcodecs.imread("/1520.bmp") val diff = Mat() Imgproc.cvtColor(orig1, orig1, Imgproc.COLOR_RGB2GRAY) Imgproc.cvtColor(orig2, orig2, Imgproc.COLOR_RGB2GRAY) Core.absdiff(orig1, orig2, diff) Imgproc.threshold(diff, diff, 15.0, 255.0, Imgproc.THRESH_BINARY) showWindow() showImage(diff)
差分を描画するとこんな感じになった。
黒が差分の少ない箇所で、白が差分の大きい箇所です。
キャラクタ付近が白の領域が多いのは分かるけどノイズも多い。
差分のノイズを除去する
モルフォロジー変換のオープニングとクロージングをする事で、小さなノイズを除去できるらしい。
白い領域に対して、収縮、膨張、膨張、収縮という変換を行うことで小さい領域を潰すテクニックらしい。Imgproc.erode
で膨張、Imgproc.dilate
で収縮する
val kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_RECT, Size(3.0, 3.0)) Imgproc.erode(diff, diff, kernel) Imgproc.dilate(diff, diff, kernel) Imgproc.dilate(diff, diff, kernel) Imgproc.erode(diff, diff, kernel)
ノイズ除去後はこんな感じになった。
たしかに小さな白い領域が消えてる。
輪郭を取得する
白い領域の輪郭を取得し、小さな輪郭は捨てます。Imgproc.findContours
で輪郭を取得している。
val contours = mutableListOf<MatOfPoint>() val hierarchy = Mat() Imgproc.findContours(diff, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE) val buffer = Mat(diff.height(), diff.width(), CvType.CV_8UC3) val q = 0.05 * diff.width() * diff.height() for (contour in contours) { if (Imgproc.contourArea(contour) > q) { Imgproc.drawContours(buffer, listOf(contour), 0, Scalar(255.0, 255.0, 255.0)) } }
輪郭を描画するとこんな感じになった。
なかなかいい感じにキャラクタの位置を抽出できてる気がする・・・!
輪郭を凸包に変換してみる
上記の輪郭は細かすぎる気がするので凸包に変換してみる。
ちなみに凸包とは、与えられた点をすべて包含する最小の多角形との事。convexHull
メソッドが使いにくくてラッパーを作った。
fun hull(contour: MatOfPoint): MatOfPoint { val indexes = MatOfInt() Imgproc.convexHull(contour, indexes) val contourList = contour.toList() val hullList = ArrayList<Point>() for (i in indexes.toList()) { hullList.add(contourList[i]) } val hull = MatOfPoint() hull.fromList(hullList) return hull }
こんな感じ。トゲトゲしかった輪郭がキュートになった。
輪郭をポリゴン近似してみる
こちらは、輪郭をポリゴン近似してみた。Imgproc.approxPolyDP
を使う
fun poly(contour: MatOfPoint): MatOfPoint { val polyf = MatOfPoint2f() val poly = MatOfPoint() val contourf = MatOfPoint2f() contour.convertTo(contourf, CvType.CV_32F) Imgproc.approxPolyDP(contourf, polyf, 7.0, true) polyf.convertTo(poly, CvType.CV_32S) return poly }
こんな感じ。
今日はここまで。
所感
輪郭周りのオペレーションは慣れてきた感がある。
前景が取得できる様になったので、どのキャラクターなのか?、何をしているのか?と言ったマッチングを考えなきゃいけない。
つかキャラクター同士が接触した時の背景除去が難しそう(差分が結合するので)
参考資料
Webエンジニアをやっています
UX/UIデザインからプログラミング、DB設計、SEO、インフラ構築など幅広く対応してます
PHP/PHPUnit/Laravel/Vue/Nuxt/Docker/Terraform
ご連絡はTwitterのDMまで。