見出し画像

GoCVでモルフォロジー変換してみた

こんにちは。
いきなりですが皆さんは今、

  • 一つのファイル上に散在する黒い(あるいは白い)領域を、Golangで、可変の均等な幅で拡大させる方法を探している。

        例えば、↓のような画像の黒い領域を、

        Golangで四方に1pxずつ拡大して↓のようにしたい。

  • そんなとき、画像処理に詳しい多忙な上司から「"モルフォロジー変換"が良いんじゃない?」という未知のワードを含んだ手短な助言をもらう。

  • 「モルフォロジー変換」そのものについてはネットの情報を漁って何となく理解できたけれど、GoCVの参考情報は少なく、PythonとOpenCVで書かれた記事をGolangとGoCV用に読み替えるのに時間がかかっている…。

このような状況の真っ只中にいませんか?

特に、Golang初心者かつ画像処理も初心者の場合は、まさに上記のような状況に陥りがちなのではないでしょうか。(かく言う私がそうでした。。)

そこで今回は、少しでもそのような方のお役に立てればと思い、GoCVにおけるモルフォロジー変換と、そのための前処理として実行されることの多い「二値化処理」について、自分が得た知識を共有させていただきます。

本記事がGoCVでモルフォロジー変換を実装する際の取っ掛かりとなることで、少しでも読んでくださった方の作業効率向上に寄与できれば幸いです。

※なお、そもそもの「モルフォロジー変換」や「膨張」「収縮」とは、に関しては本記事では触れていません。OpenCVのこちらのサイトがとても分かりやすかったので参考にしてみてください。

二値化処理

本記事では分かりやすさのために↓のような10×10サイズのPGM画像を例にとってお話します。
こちらはまだ何も処理を加えていないオリジナルの状態です。

example.pgm

モルフォロジー変換は主に二値画像を対象とします。
しっかり二値化しておかないと、対象画像に対して想定通りの処理を加えるのが難しくなってしまいます。
そのため、膨張や収縮の前準備として、画像を白と黒で二値化します。

二値化するコードの例を以下に示します。

package main

import (
	"gocv.io/x/gocv"
)

const White = 255
const Black = 0

func main() {
    // 画像ファイルの読み込み
	mat := gocv.IMRead("./example.pgm", gocv.IMReadGrayScale)
	defer mat.Close()

    // 二値化
	thresholdMat := gocv.NewMat()
	gocv.Threshold(mat, &thresholdMat, Black, White, gocv.ThresholdBinary)
	defer thresholdMat.Close()

    // 画像ファイルの保存
	gocv.IMWrite("thresholdImg.pgm", thresholdMat)
}

結果は、、、

thresholdImg.pgm

想定通り、グレーだった部分を白に変換できていますね。

なお、Threshold関数の詳細はGoCV公式のこちらで解説されています。
今回はグレー部分を白に変換しましたが、Threshold関数に渡す値によっては黒に変換されるように設定したり、白黒反転させることもできます。

膨張処理

では、二値化した画像に対して膨張(Dilation)処理を加えてみます。
今回は例として白い部分を四方に1pxずつ膨張させます。

package main

import (
	"image"

	"gocv.io/x/gocv"
)

func main() {
   // 画像ファイルの読み込み
	thresholdMat := gocv.IMRead("./thresholdImg.pgm", gocv.IMReadGrayScale)
	defer thresholdMat.Close()

    // 膨張(Dilation)
	dilatedMat := gocv.NewMat()
	defer dilatedMat.Close()

	kernel := gocv.GetStructuringElement(0, image.Point{3, 3})
	gocv.Dilate(thresholdMat, &dilatedMat, kernel)

    // 画像ファイルの保存
	gocv.IMWrite("dilatedImg.pgm", dilatedMat)
}

本コードの処理を適用後のPGMファイルは↓のようになります。

dilatedImg.pgm

モルフォロジー変換では構造的要素(カーネル)を使用します。

GoCVにおいて、構造的要素はGetStructuringElement関数で生成できます。第一引数は構造的要素の形状、第二引数は構造的要素のサイズです。

構造的要素の形状は、

  • MorphRect(矩形):0

  • MorphCross(十字):1

  • MorphEllipse(楕円):2

のいずれか一つから選択できます。

膨張処理は、画像に対してこの構造的要素をスライドさせて、構造的要素内に画素値が1(白)の画素が一つでも含まれれば、注目画素(そのときの構造的要素の中心)の画素値を1(白)にするイメージです。

上の例では、カーネルを3×3の正方形にすることで、画像の白い部分を四方に1pxずつ膨張させています。

収縮処理

今度は、膨張処理後の画像に対して収縮(Erosion)処理を加えてみます。

package main

import (
	"image"

	"gocv.io/x/gocv"
)

func main() {
    // 画像ファイルの読み込み
	dilatedMat := gocv.IMRead("./dilatedImg.pgm", gocv.IMReadGrayScale)
	defer dilatedMat.Close()

    // 収縮(Erosion)
	erodedMat := gocv.NewMat()
	defer erodedMat.Close()

	kernel := gocv.GetStructuringElement(0, image.Point{3, 3})
	gocv.Erode(dilatedMat, &erodedMat, kernel)

    // 画像ファイルの保存
	gocv.IMWrite("erodedImg.pgm", erodedMat)
}

結果は、、、

erodedImg.pgm

白い部分が収縮しています。
また、膨張処理で用いたのと同じ形状・サイズの構造的要素を使ったので、膨張処理を加える前の状態に戻っています。

個人的には、収縮処理は膨張処理の対象色が白黒反転したものと理解しておくと良いと思います。

ちなみに、本記事の冒頭で私の体験談として挙げた以下の変換処理も、上記の収縮処理のコードと全く同じコードで実現できました。

さいごに

今回、色々と触ってみて、モルフォロジー変換は構造的要素(カーネル)の挙動の理解が肝だと感じました。
GoCVでモルフォロジー変換を行う際はぜひ参考にしてみてください!