画像内を1画素毎では無く、ある領域毎にまとめて処理をしたい時があります。この時、画像内を任意のサイズのボックスで走査する必要があります。ここでは画像走査方法の一例を紹介します。
こんにちは。wat(@watlablog)です。
今回は画像処理の中でもかなり汎用的に使える内容として、画像を領域毎に走査する方法を紹介します!
画像走査の概要
画像走査とは?
画像走査とは、1枚の画像に対して横方向、縦方向に情報を抽出することを言います。
PythonのOpenCVやNumPyを使えば、特に意識せずとも画像の全ての情報は行列形式(ndarray)で取得することができます。
しかし、ある範囲を持った領域内の情報を使って画像処理を行う場合、自分で任意サイズの領域を設定して、その領域を少しずつずらしながら処理をしていくプログラムにしなければなりません。
ここでは領域の設定、領域の移動、処理した内容の上書きといった基本的な方法について説明していきます。
※ここで紹介する内容はネットの情報から探せなかった筆者が勝手に考えたものです。もしかしたらもっと効率的で一般的なやり方が存在するかも知れないのでご注意下さい。
OpenCVの関数とかにあるのかな?
もしご存知の方がいたら教えて欲しいです!
領域を設定する
具体的なコードを紹介する前に、概要を図解で説明していきます。
まず、任意の領域を設定する、という所ですが、イメージ図は以下の図の通りです。
幅\(w\), 高さ\(h\)の画像があるとします。この範囲の中に1つ、幅\(x\), 高さ\(y\)の領域を設定します。この領域の始点は\((x_{0}, y_{0})\)で、必ずしも(0, 0)である必要はありません。
横方向に領域をずらす
続いて、設定した領域を横方向にずらしていきます。ずらすと言っても、1画素毎にずらす必要も無く、任意の横方向ずらし幅\(x_{step}\)を使います。
画像の横幅を\(w\), 領域の横の大きさを\(x\)とすると、
「while \(x\) + (\(i\) * \(x_{step}\)) < \(w\):(\(i\)は整数)」
というwhileループの条件式がTrueである間分横にずらし続けることになります。
横方向の移動が終了したら縦方向に領域をずらす
横方向にずらし終わったら、つまり上記whileループの条件式がFalseになったら、今度は領域を縦方向にずらします。
そして、横方向の移動指標である\(i\)を0にリセットして画像の左端から走査し直します。
横の時と同じように、画像の高さを\(h\), 領域の縦の大きさを\(y\)とすると、
「while \(y\) + (\(j\) * \(y_{step}\)) < \(h\):(\(j\)は整数)」
というwhileループの条件式がTrueである間分縦にずらし続けることになります。
Pythonによる画像走査プログラム
画像を縦横に走査して画像処理する関数
では、早速Pythonコードを紹介します。
以下のコードが画像内に領域を設定し、横と縦に走査して画像処理をする関数です。
画像はグレースケールを対象としていますが、カラー画像を扱う場合は「img.shape[:3]」として下さい。
コードの説明は下記にコメントとして書きましたので、ご確認下さい。
whileループは縦の走査ループの中に横の走査ループを配置しています。説明は上でした通りです。
領域のサイズは画像の大きさに対して20分割という動的な与え方をしています。ずらし幅は領域のサイズと同一にしていますが、この値を領域サイズよりも小さくすればオーバーラップさせた走査が可能です。
横の走査ループの1つ1つで画像処理をすれば、ある領域毎に画像処理したことになります。この関数の入力は読み込んだ画像で、戻り値としては画像処理を行った画像になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
def scanning(img): h, w = img.shape[:2] # グレースケール画像のサイズ取得(カラーは3) x = int(w/20) # 領域の横幅 y = int(h/20) # 領域の高さ x_step = x # 領域の横方向へのずらし幅 y_step = y # 領域の縦方向へのずらし幅 x0 = 0 # 領域の初期値x成分 y0 = 0 # 領域の初期値y成分 j = 0 # 縦方向のループ指標を初期化 # 縦方向の走査を行うループ while y + (j * y_step) < h: i = 0 # 横方向の走査が終わる度にiを初期化 ys = y0 + (j * y_step) # 高さ方向の始点位置を更新 yf = y + (j * y_step) # 高さ方向の終点位置を更新 # 横方向の走査をするループ while x + (i * x_step) < w: roi = img[ys:yf, x0 + (i * x_step):x + (i * x_step)] # 元画像から領域をroiで抽出 # ここからが領域に対する画像処理 ave = np.mean(roi).astype("uint8") img[ys:yf, x0 + (i * x_step):x + (i * x_step)] = np.full(roi.shape, ave) # ここまでが領域に対する画像処理 i = i + 1 # whileループの条件がFalse(横方向の端になる)まで、iを増分 j = j + 1 # whileループの条件がFalse(縦方向の端になる)まで、jを増分 return img |
画像から興味領域(ROI)を抽出したり、元画像に処理結果を貼り付けたりしている部分には、「Python/OpenCVのROI抽出!領域の切り出しとコピー」で紹介した方法を使っていますので、もしその部分に不明点がありましたら是非こちらの記事も参照してみて下さい。
画像処理結果
今回処理する画像は以下の東京の渋谷MODIの建物です。
Google Earthからの提供です。
上記のコードは領域内の画素を平均化して、平均値を領域全体に適用するものです。
上記コードの実行結果は以下の図になります。
領域毎の平均値を、再度設定した領域全体に適用しているため画像の各画素が領域サイズに拡大して粗くなったような効果を出します。
横方向の最後や縦方向の最後は元画像が残っています。上記コードは基本だけで、「余り」の処理を書いていません。ループ内の処理を少し修正すれば、余りを出さない処理ができると思いますので、是非挑戦してみて下さい。
画像読み込みから表示までの全コード
参考までに、画像読み込みから処理後の画像表示までの全コードを以下に記載します。「sample.jpg」の画像ファイルへのパスを変更して実行してみて下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import cv2 import numpy as np from matplotlib import pyplot as plt def scanning(img): h, w = img.shape[:2] # グレースケール画像のサイズ取得(カラーは3) x = int(w/20) # 領域の横幅 y = int(h/20) # 領域の高さ x_step = x # 領域の横方向へのずらし幅 y_step = y # 領域の縦方向へのずらし幅 x0 = 0 # 領域の初期値x成分 y0 = 0 # 領域の初期値y成分 j = 0 # 縦方向のループ指標を初期化 # 縦方向の走査を行うループ while y + (j * y_step) < h: i = 0 # 横方向の走査が終わる度にiを初期化 ys = y0 + (j * y_step) # 高さ方向の始点位置を更新 yf = y + (j * y_step) # 高さ方向の終点位置を更新 # 横方向の走査をするループ while x + (i * x_step) < w: roi = img[ys:yf, x0 + (i * x_step):x + (i * x_step)] # 元画像から領域をroiで抽出 # ここからが領域に対する画像処理 ave = np.mean(roi).astype("uint8") img[ys:yf, x0 + (i * x_step):x + (i * x_step)] = np.full(roi.shape, ave) # ここまでが領域に対する画像処理 i = i + 1 # whileループの条件がFalse(横方向の端になる)まで、iを増分 j = j + 1 # whileループの条件がFalse(縦方向の端になる)まで、jを増分 return img path = 'sample.jpg' img = cv2.imread(path, 0) # 画像読み込み img = scanning(img) # ここからグラフ設定 # フォントの種類とサイズを設定する。 plt.rcParams['font.size'] = 14 plt.rcParams['font.family'] = 'Times New Roman' fig = plt.figure() ax1 = fig.add_subplot(111) # 画像をプロット ax1.imshow(img, cmap='gray') # 軸のラベルを設定する。 ax1.set_xlabel('x [pix]') ax1.set_ylabel('y [pix]') fig.tight_layout() plt.show() plt.close() |
まとめ
画像処理の基本である領域の設定、その領域を順次ずらして処理をしていく方法について紹介しました。
処理部分を変更すればあらゆる画像処理に応用することができます。
今回は画像処理初心者の筆者が思いついた方法を紹介しましたが、もしかするとさらに効率的な方法も存在するかも知れません。その時はまたブログで記事にしてみたいと思います。
画像全体を処理するのではなく、任意の領域毎に細かく処理することができるようになったぞ!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!
コメント