勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

OpenCVやってみる-8. エッジ検出

OpenCVチュートリアル通りに進めます。
今回の内容はシール点数集計に役立ちそうです。

画像の勾配 — OpenCV-Python Tutorials 1 documentation

Canny法によるエッジ検出 — OpenCV-Python Tutorials 1 documentation


Sobelフィルタ、Laplacianフィルタ

Sobelフィルタ、Laplacianフィルタはいずれもカーネルと画像の畳み込み演算を行い、画像中の輝度勾配の大きさを示す画像を生成します。

Sobelフィルタは、画像の縦方向、横方向それぞれの輝度勾配を計算します。

【画像処理】ソーベルフィルタの原理・特徴・計算式 | 西住工房

Image Filtering — opencv 2.2 (r4295) documentation

Laplacianフィルタは、Laplacian演算子に相当する演算を行います。

【画像処理】ラプラシアンフィルタの原理・特徴・計算式 | 西住工房

これらを、春のパン祭り画像に作用させてみます。
やることは、チュートリアルのまんまです。

import cv2
import numpy as np
img1 = cv2.imread('harupan_200317_1.jpg', cv2.IMREAD_GRAYSCALE)
img1 = cv2.resize(img1, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_AREA)

img1_laplacian = cv2.Laplacian(img1, cv2.CV_64F)
img1_sobelx = cv2.Sobel(img1, cv2.CV_64F, 1, 0, ksize=5)
img1_sobely = cv2.Sobel(img1, cv2.CV_64F, 0, 1, ksize=5)

from matplotlib import pyplot as plt
plt.subplot(2,2,1), plt.imshow(img1, cmap='gray'), plt.title('img1'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2), plt.imshow(img1_laplacian, cmap='gray'), plt.title('laplacian'), plt.xticks([]), plt.yti
plt.subplot(2,2,3), plt.imshow(img1_sobelx, cmap='gray'), plt.title('sobelx'), plt.xticks([]), plt.yticks([]
plt.subplot(2,2,4), plt.imshow(img1_sobely, cmap='gray'), plt.title('sobely'), plt.xticks([]), plt.yticks([]
plt.show()

f:id:nokixa:20210428232742p:plain

ラプラシアンだけわかりにくくなってしまった…
これだけで表示してみます。

plt.imshow(img1_laplacian, cmap='gray'), plt.title('laplacian'), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20210429001516p:plain:w400

カーネルサイズを大きくしてやってみます。

img1_laplacian2 = cv2.Laplacian(img1, cv2.CV_64F, ksize=5)
plt.imshow(img1_laplacian2, cmap='gray'), plt.title('laplacian2'), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20210429001805p:plain:w400

エッジがはっきりしました。
一応横に並べて表示。

plt.subplot(1,2,1), plt.imshow(img1_laplacian, cmap='gray'), plt.title('laplacian'), plt.xticks([]), plt.yti
plt.subplot(1,2,2), plt.imshow(img1_laplacian2, cmap='gray'), plt.title('laplacian2'), plt.xticks([]), plt.y
plt.show()

f:id:nokixa:20210429002200p:plain:w400

画像の解像度やエッジの幅に合わせてカーネルサイズも調整する必要がある?
あらかじめノイズ除去や平滑化しておく必要もある?
解像度の変更も必要か?

解像度を縦横それぞれ半分にしてラプラシアンフィルタをかけてみると、元の画像でカーネルサイズ5にしたのと似たような結果が得られました。

img2 = cv2.imread('harupan_200317_1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.resize(img2, None, fx=0.05, fy=0.05, interpolation=cv2.INTER_AREA)
img2_laplacian = cv2.Laplacian(img2, cv2.CV_64F)
plt.imshow(img2_laplacian, cmap='gray'), plt.title('laplacian'), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20210429002801p:plain:w400


Canny法

Canny法によるエッジ検出もやってみます。

Canny法によるエッジ検出 — OpenCV-Python Tutorials 1 documentation

ざっくり言うと、つながっているエッジをうまく検出するようなアルゴリズムのようです。
結果は2値になるのか?

cv2.Canny()関数には2つのエッジ強度閾値の引数があります。
どれくらいに設定すればいいのか調べてみます。

エッジ画像を作る

Canny法では、5x5のガウシアンフィルタをかけ、2軸方向のSobelフィルタをかけてエッジ画像(勾配の強度)を作ります。
これと同じことをやってみます。

img1_gaussian = cv2.GaussianBlur(img1, (5,5), 0)
img1_1 = cv2.GaussianBlur(img1, (5,5), 0)
img1_2 = cv2.Sobel(img1_1, cv2.CV_64F, 1, 0, ksize=3)
img1_3 = cv2.Sobel(img1_1, cv2.CV_64F, 0, 1, ksize=3)
img1_4 = np.sqrt(img1_2**2 + img1_3**2)
plt.imshow(img1_4, cmap='gray'), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20210501215155p:plain:w400

いい感じにエッジが見えています。
前までのエッジ画像では正負の値があったためか、ベースがグレーで、エッジのところが黒または白となっていましたが、今回は勾配強度の絶対値を取っているので、ベースが黒でエッジが白となっています。

このエッジ画像のヒストグラムを見てみます。

まずは値の範囲を確認。

>>> np.max(img1_4)
411.0985283359696
>>> np.min(img1_4)
0.0

cv2.calcHist()関数を使おうと思いますが、画素値データ型はuint8かfloat32のみとのことなので、変換してから使います。

img1_5 = img1_4.astype(np.float32)
hist = cv2.calcHist([img1_5], [0], None, [500], [0,500])
plt.plot(hist)
plt.show()

f:id:nokixa:20210501225336p:plain:w400

ピークの出ているのがバックグラウンドの黒でしょうか。
値100、200、300ぐらいで2値化してみると、

ret, img1_th1 = cv2.threshold(img1_5, 100, 255, cv2.THRESH_BINARY)
ret, img1_th2 = cv2.threshold(img1_5, 200, 255, cv2.THRESH_BINARY)
ret, img1_th3 = cv2.threshold(img1_5, 300, 255, cv2.THRESH_BINARY)
plt.subplot(131), plt.imshow(img1_th1, cmap='gray'), plt.title('thresh=100'), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.imshow(img1_th2, cmap='gray'), plt.title('thresh=200'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(img1_th3, cmap='gray'), plt.title('thresh=300'), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20210501231313p:plain:w600

値100で非エッジとエッジの区別ができそうです。
100だとエッジ周辺も拾っているので、200ぐらいがエッジ位置になるかと思います。
300はやり過ぎですね。

Canny法実施

前の結果から、2つの閾値は100と200でやってみます。
結局チュートリアルと同じ値になりますが。

img1_canny = cv2.Canny(img1, 100, 200)
plt.imshow(img1_canny, cmap='gray'), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20210501232508p:plain:w400

シールの外枠、点数文字の境界がとれた感じです。
台紙の細かい文字もエッジとしてとれています。

試しに閾値を変えてみます。

img1_canny2 = cv2.Canny(img1, 200, 300)

f:id:nokixa:20210501233023p:plain:w400

あまり変わらない…
それほど閾値にこだわらなくても大丈夫か?


ここまで

今回はここまでにします。
まだまだチュートリアルに沿って進めます。
次は輪郭の検出をやります。