動画の解説を参照
今回は画像内にある建物などのエリアの輪郭を検出します。
そのために使うのはピクセルの明るさだけなので、処理を軽くするためにグレースケール化します。
以下のコードは前回の課題1と全く同じものです。
import cv2
import numpy as np
from google.colab import files
file = files.upload()
filename = next(iter(file))
img = cv2.imread(filename)
h, w = img.shape[:2]
new_w = 800
new_h = int(h * (new_w / w))
img1 = cv2.resize(img, (new_w, new_h))
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
cv2.imwrite('ex1.png', img1)
(次の項以降で各ピクセルの明るさの情報を使うため、ここでは劣化のないPNG形式で保存しています)
元画像の選び方についての注意
今回確認したいことは主に「エリアの輪郭の検出」ですが、2番目の項目では縦方向・横方向のエッジを取り出します。
そのため、元画像には
周囲と明確に明るさが異なるエリアがあり、その輪郭にほぼ鉛直・ほぼ水平な線があって、それなりに解像度が大きいものを使ってください。
例えばこのような画像です。
(出力画像もこのページに掲載されているので、課題の元画像としてこの画像は使わないでください)
上記の太枠の説明の条件を満たす元画像を元にして、横幅が800ピクセルのグレースケール画像 ex1.pngを出力してください。
例
動画の解説を参照
このようなマスクを用意して
グレースケール画像のそれぞれのピクセルで、「そのピクセルを囲む9個のピクセルの明るさとマスクの値をかけて足し合わせる」という処理を行うと、
| エリア |
計算結果 |
| 青枠 |
負の値 |
| 緑枠 |
0 |
| 赤枠 |
正の値 |
のようになります。そこで、その計算結果の絶対値を明るさとする画像を作ると右の図のようになります。

OpenCVでは「Sobel」(ソーベル) 関数でこの処理ができます。第1引数が画像の変数、第2引数が出力画像の形式、第3, 4引数が明度の勾配を取得する方向、第5引数はカーネルのサイズです。
img2_X = cv2.Sobel(img1, cv2.CV_64F, 1, 0, ksize=3)
これを
X方向のソーベルフィルタといいます。
結果としてこのような画像が得られます。
X方向の明度の勾配がある (左右に隣り合ったピクセルどうしの明るさが異なる) 部分が白くなるので、結果として縦向きのエッジが白くなります。
Sobel関数の第3, 4引数を「0, 1」にすると、縦方向の明度の勾配を取得します。
これを
Y方向のソーベルフィルタといいます。
img2_Y = cv2.Sobel(img1, cv2.CV_64F, 0, 1, ksize=3)
対応するマスクは
です。
この場合は、Y方向の明度の勾配がある (上下に隣り合ったピクセルどうしの明るさが異なる) 部分が白くなるので、結果として横向きのエッジが白くなります。
img2_X = cv2.Sobel(img1, cv2.CV_64F, 1, 0, ksize=3)
img2_Y = cv2.Sobel(img1, cv2.CV_64F, 0, 1, ksize=3)
cv2.imwrite('ex2_X.png', img2_X)
cv2.imwrite('ex2_Y.png', img2_Y)
課題1で作成したグレースケール画像を元にして、X方向・Y方向のソーベルフィルタをかけた画像 ex2_X.png, ex2_Y.png を出力してください。
※ ex2_X.pngでは縦方向、ex2_Y.pngでは横方向のエッジが白くなるはずです。出力された画像を比較して確認してください。
動画の解説を参照
X方向のソーベルフィルタでは縦方向のエッジが白くなりますが、横方向のエッジはそうなりません。
Y方向のソーベルフィルタではその逆になります。
そこで、それぞれの出力画像の「明度を2乗して足して平方根をとる」という処理を行えば、どの向きのエッジも白くなります。
単独のソーベルフィルタでは薄く見えていた斜めのエッジも縦や横と同じ明るさになります。
※「sqrt」は numpyモジュールの関数で、平方根を求めるものです。
※「**」は累乗の演算子です。
img3 = np.zeros((new_h, new_w), dtype=np.uint8)
for j in range(new_h):
for i in range(new_w):
img3[j, i] = np.sqrt(img2_X[j, i] ** 2 + img2_Y[j, i] ** 2)
cv2.imwrite('ex3.png', img3)
動画の解説を参照
前項の方法で得られる画像でとりだされるエッジには太さがあり、完全な白ではないピクセルが含まれます。
Canny法では、この線の中心線を取り出せます。
その線は完全な白で、太さ1になります。
img4 = cv2.Canny(img1, ?, ?)
第1引数が画像の変数、第2引数が最小閾値、第3引数が最大閾値です。
最大閾値は最小閾値より大きい値にする必要があります。
最小閾値~最大閾値の範囲を広くすると多くのエッジが検出されるようになります。
img4 = cv2.Canny(img1, ?, ?)
cv2.imwrite('ex4.png', img4)