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()
ラプラシアンだけわかりにくくなってしまった…
これだけで表示してみます。
plt.imshow(img1_laplacian, cmap='gray'), plt.title('laplacian'), plt.xticks([]), plt.yticks([]) plt.show()
カーネルサイズを大きくしてやってみます。
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()
エッジがはっきりしました。
一応横に並べて表示。
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()
画像の解像度やエッジの幅に合わせてカーネルサイズも調整する必要がある?
あらかじめノイズ除去や平滑化しておく必要もある?
解像度の変更も必要か?
解像度を縦横それぞれ半分にしてラプラシアンフィルタをかけてみると、元の画像でカーネルサイズ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()
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()
いい感じにエッジが見えています。
前までのエッジ画像では正負の値があったためか、ベースがグレーで、エッジのところが黒または白となっていましたが、今回は勾配強度の絶対値を取っているので、ベースが黒でエッジが白となっています。
このエッジ画像のヒストグラムを見てみます。
まずは値の範囲を確認。
>>> 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()
ピークの出ているのがバックグラウンドの黒でしょうか。
値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()
値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()
シールの外枠、点数文字の境界がとれた感じです。
台紙の細かい文字もエッジとしてとれています。
試しに閾値を変えてみます。
img1_canny2 = cv2.Canny(img1, 200, 300)
あまり変わらない…
それほど閾値にこだわらなくても大丈夫か?
ここまで
今回はここまでにします。
まだまだチュートリアルに沿って進めます。
次は輪郭の検出をやります。