2値画像処理

目的

ここまでで,色により領域を抽出する方法,書類に書かれた文字や図面の画像を背景と対象に分ける方法を学んだ.いずれも処理結果は2値画像となる.ここでは,2値画像に対する様々な処理について学ぶ.

説明

近傍・隣接・連結

近傍

近傍とは注目している画素と隣り合う画素の集合で,正方格子を用いて標本化されているディジタル画像では2種類の定義が用いられる.上下左右の4点だけの画素の集合を4近傍,斜め方向の点も加えた画素の集合を8近傍と呼ぶ.

隣接

2値画像で同じ値をもつ2つの画素が互いに4近傍に存在するとき,2つの画素は4隣接していると言い,互いに8近傍に存在するとき,8隣接していると言う.

連結

2値画像で同じ値をもつ2つの画素\(a, b\)が存在し,\(a\)から\(b\)まで4隣接の意味で接続しているとき,\(a\)と\(b\)は4連結していると言う.同様に,\(a\)から\(b\)まで8隣接の意味で接続しているとき,\(a\)と\(b\)は8連結していると言う.以下の図の場合,\(a\)と\(b\)は4連結しており,また,8連結している.\(a\)と\(c\)・\(b\)と\(c\)は4連結していないが,8連結している.

膨張・収縮処理

画像を2値化すると,小さな孔や溝,孤立点や突起ができることがある.このような2値画像の対象画素と背景画素の境界において,対象領域を1画素分大きくする膨張処理をすると,小さな孔や溝を除去できることがある.また,対象画素と背景画素の境界において,対象領域を1画素分小さくする収縮処理をすると,孤立点や突起を除去できることがある.2値画像を修正する場合,膨張してから収縮する,あるいは,収縮してから膨張するというように組み合わせて使用することが多い.

以下のような2値画像に対して,8近傍で膨張処理をし,その後8近傍で収縮処理をすると,孔や溝が除去できていることがわかる.

また,以下のような2値画像に対して,8近傍で収縮処理をし,その後8近傍で膨張処理をすると,余分な線が除去できていることがわかる.

OpenCVでは,黒い背景領域に白い対象領域があるとし,白い対象領域を膨張させる処理をdilate関数で,白い対象領域を収縮させる処理をerode関数で行う.ここでは,白い紙に黒い文字や図が書かれているとするため,対象領域と背景領域が逆転する.したがって,黒い対象領域を膨張させる処理をerode関数で,黒い対象領域を収縮させる処理をdilate関数で行うことになる.

膨張処理

OpenCVのerode関数を使用して,黒い対象領域を膨張させてみよう.

21行目で2値画像を読み込み,22行目から24行目で4近傍のカーネルを作成し,25行目でerode関数を呼び出し,4近傍の膨張処理を行っている.26行目から28行目で8近傍のカーネルを作成し,29行目で8近傍の膨張処理を行っている.erode関数のキーワード引数iterationsには繰り返し膨張処理を施す場合の回数を指定することができる.実行すると以下のように表示され,4近傍と8近傍で膨張処理が行われていることが確認できる.

収縮処理

OpenCVのdilate関数を使用して,黒い対象領域を収縮させるには以下のようにすればよい.

21行目で2値画像を読み込み,22行目から24行目で4近傍のカーネルを作成し,25行目でdilate関数を呼び出し,4近傍の収縮処理を行っている.26行目から28行目で8近傍のカーネルを作成し,29行目で8近傍の収縮処理を行っている.実行すると以下のように表示され,4近傍と8近傍で収縮処理が行われていることが確認できる.

ラベリング

2値画像に対して,連結している対象領域ごとに異なるラベルを付ける処理をラベリングと呼ぶ.OpenCVにはラベリングを行う関数が用意されており,黒い背景に白い対象領域がある2値画像に対して使用することができる.OpenCVを使用してラベリングをしてみよう.

20行目で8bitグレースケール画像を読み込み,21行目で2値化している.OpenCVでは黒い背景に白い対象領域がある2値画像に対してラベリングを行うことになるため,22行目で白黒(背景領域と対象領域)を反転させている.24行目のようにconnectedComponents関数を呼び出すと,連結している対象領域の数labels_countと対象領域にラベル番号が付与された配列label_imageが返される.label_imageの背景領域には0のラベルが,連結している対象領域には1, 2, 3, …といったラベルが付けられる.26, 27行目でlabel_imageを3チャンネル画像に変換し,28行目で,出力画像として解像度が入力画像と同じである3チャンネル画像を作っている.29行目で各ラベルの色を表すBGR値を指定し,30行目から32行目で出力画像の各領域に指定したBGR値を格納している.実行すると以下のように表示され,連結している対象領域ごとに同じラベルが振られていることがわかる.

OpenCVではラベリングされた各領域の情報を取得することができる.

24行目のように,connectedComponentsWithStats関数を呼び出すと,各領域の情報dataと各領域の重心の座標centersを得ることができる.dataは,各領域の左上の座標(x,y)と横幅wと高さhと面積areaから成っている.34行目から38行目で,各領域に対して矩形と重心を描画し,面積を表示している.実行すると以下のように表示され,各領域の面積・矩形・重心が求められていることがわかる.

輪郭検出

2値画像の連結した対象領域に対して輪郭を検出してみよう.

20行目で8bitグレースケール画像を読み込み,21行目で2値化している.結果をわかりやすくするために,22行目から25行目で対象領域を膨張させ,26行目で白黒を反転させている.28行目のようにfindContours関数を呼び出すと,輪郭線の情報contoursと階層構造の情報hierarchyが返される.29行目で2値画像をカラー画像に変換し,30行目のようにdrawContours関数を呼び出すと,2値画像上に輪郭線を描画することができる.実行すると以下のように表示され,各領域の外側の輪郭が検出できていることがわかる.

対象領域の外側だけでなく,内側の輪郭も検出さいたい場合には以下のようにすればよい.

28行目のように,第2引数にcv2.RETR_LISTを指定してfindContours関数を呼び出すと,内側の輪郭も検出することができる.実行すると以下のように表示され,各領域の輪郭が検出できていることがわかる.

OpenCVでは輪郭検出した各領域の情報を取得することができる.

28行目で外側の輪郭を検出し,29,30行目で2値画像をカラー画像に変換し,輪郭線を描画する際の色を指定している.31行目で各領域の輪郭に対して処理をしている.32行目から34行目のように記述することで,各領域の重心を求めることができる.35行目のように記述することで各領域の面積を,36行目のように記述することで各領域の周囲長を求めることができる.37行目でそれらを表示し,38行目で輪郭線を,39行目で重心を描画している.実行すると以下のように表示され,各領域の重心・面積・周囲長が求められていることがわかる.

内側の輪郭に対する情報も取得するには,28行目のように第2引数にcv2.RETR_LISTを指定してfindContours関数を呼び出せばよい.

実行すると以下のように表示され,内側の輪郭の領域に対しても重心・面積・周囲長が求められていることがわかる.

課題

課題0

2値画像に対して4近傍で膨張処理をした後に収縮処理をせよ.

課題1

2値画像に対して8近傍で膨張処理をした後に収縮処理をせよ.

課題2

2値画像に対して4近傍で収縮処理をした後に膨張処理をせよ.

課題3

2値画像に対して8近傍で収縮処理をした後に膨張処理をせよ.

課題4

2値画像に対してラベリングせよ.

課題5

2値画像に対してラベリングし,各領域の面積・矩形・重心を求めよ.

課題6

2値画像に対して外側の輪郭を検出せよ.

課題7

2値画像に対して外側と内側の輪郭を検出せよ.

課題8

2値画像に対して外側の輪郭を検出し,各領域の重心・面積・周囲長を求めよ.

課題9

2値画像に対して外側と内側の輪郭を検出し,各領域の重心・面積・周囲長を求めよ.