引き続きの春のパン祭りシール点数集計。
前回の結果再確認
前回の結果をよく見ると、点数文字の輪郭をきちんと取れていない部分がありました。
3つ目の画像で、中央上部の'1'、'2'の輪郭が取れていません。
対応を検討します。
様子チェック
まずは問題の画像を見てみます。
import cv2 import numpy as np %matplotlib inline from matplotlib import pyplot as plt img3 = cv2.imread('harupan_200317_1.jpg') img3 = cv2.resize(img3, None, fx=800.0/img3.shape[1], fy=800.0/img3.shape[1], interpolation=cv2.INTER_AREA) img3_hsv = cv2.cvtColor(img3, cv2.COLOR_BGR2HSV) ret, th_hue = cv2.threshold(img3_hsv[:,:,0], 135, 255, cv2.THRESH_BINARY) plt.figure(figsize=(64,48), dpi=100) plt.subplot(131), plt.imshow(cv2.cvtColor(img3, cv2.COLOR_BGR2RGB)), plt.title('Original',fontsize=60), plt.xticks([]), plt.yticks([]) plt.subplot(132), plt.imshow(img3_hsv[:,:,0], cmap='gray'), plt.title('Hue',fontsize=60), plt.xticks([]), plt.yticks([]) plt.subplot(133), plt.imshow(th_hue, cmap='gray'), plt.title('Thresholded',fontsize=60), plt.xticks([]), plt.yticks([]) plt.show()
Hue画像を見てみると、輪郭取得に失敗していたシールではシール領域の一部のはずのエリアで黒くなっているように見えます。おそらく元々のシールの色が赤っぽくて、照明の具合で色相がちょうど一周してしまったのではないかと。
その結果、2値化画像でもシール領域の一部が欠けて、外周輪郭が取れなかったものと考えられます。
Hueのヒストグラムも見てみます。
plt.hist(img3_hsv[:,:,0].ravel(),180, [0,180]) plt.show()
やはり分布が179(最大値)まで達していて、分布の一部が一周して0付近に来ているようです。
対応検討
HueよりSaturation(彩度)のほうが使えそうな気がしてきたがどうだろう。
台紙は白色でSaturationは低く、シールの色は鮮やかなピンクでSaturationが高くなります。
まずはSaturationのヒストグラムを確認します。
plt.hist(img3_hsv[:,:,1].ravel(),256, [0,256]) plt.show()
ひとまず閾値は100ぐらいでよさそうかな。
Saturation画像と、2値化した画像を出してみます。
ret, th_sat = cv2.threshold(img3_hsv[:,:,1], 100, 255, cv2.THRESH_BINARY) plt.figure(figsize=(64,48), dpi=100) plt.subplot(131), plt.imshow(cv2.cvtColor(img3, cv2.COLOR_BGR2RGB)), plt.title('Original',fontsize=60), plt.xticks([]), plt.yticks([]) plt.subplot(132), plt.imshow(img3_hsv[:,:,1], cmap='gray'), plt.title('Sat',fontsize=60), plt.xticks([]), plt.yticks([]) plt.subplot(133), plt.imshow(th_sat, cmap='gray'), plt.title('Thresholded',fontsize=60), plt.xticks([]), plt.yticks([]) plt.show()
これでシール輪郭は取れそうですが、少し心配な点が。
- フローリングも検出されているので、第一輪郭を取ると台紙の外形が取れてしまうのでは?
- そうすると第二輪郭はシール外形となってしまう
やっぱりHueの情報も使っていきたいなと。
Hue境界の問題については、値の範囲の変換でどうかと。
すなわち、[0,50)ぐらいの範囲を[180,230)に持ってくる変換をすれば、紫~赤の範囲が連続するようになります。
img3_hsv[:,:,0] = np.where(img3_hsv[:,:,0] < 50, img3_hsv[:,:,0]+180, img3_hsv[:,:,0]) plt.hist(img3_hsv[:,:,0].ravel(),256, [0,256]) plt.show()
ちょっと思い付き。
Saturationの値が低かったらHueの値を0にしてしまうとどうだろう。
HueとSaturationでの2値化処理をまとめてできるし、Hueの値のふるい分けもしやすくなる。
また、色々調べていて、cv2.inRange()
という2値化に便利な関数があることを知ったので、以下ではこれを使っています。
https://docs.opencv.org/4.x/da/d97/tutorial_threshold_inRange.html
img3_hsv[:,:,0] = np.where(img3_hsv[:,:,1] < 100, 0, img3_hsv[:,:,0]) plt.hist(img3_hsv[:,:,0].ravel(),256, [0,256]) plt.show()
これを元に2値化します。閾値は、今までは135~179にしていましたが、今回は135~190に広げてみます。
th_hue = cv2.inRange(img3_hsv[:,:,0], 135, 190) plt.figure(figsize=(64,48), dpi=100) plt.subplot(131), plt.imshow(cv2.cvtColor(img3, cv2.COLOR_BGR2RGB)), plt.title('Original',fontsize=60), plt.xticks([]), plt.yticks([]) plt.subplot(132), plt.imshow(img3_hsv[:,:,0], cmap='gray'), plt.title('Hue',fontsize=60), plt.xticks([]), plt.yticks([]) plt.subplot(133), plt.imshow(th_hue, cmap='gray'), plt.title('Thresholded',fontsize=60), plt.xticks([]), plt.yticks([]) plt.show()
これで点数文字輪郭の検出を試してみると、
contours, hierarchy = cv2.findContours(th_hue, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) indices0 = [i for i,hier in enumerate(hierarchy[0,:,:]) if hier[3] == -1] indices1 = [i for i,hier in enumerate(hierarchy[0,:,:]) if hier[3] in indices0] contours1 = [contours[i] for i in indices1] contours1_filtered = [ctr for ctr in contours1 if cv2.contourArea(ctr) > 800*800/4000] img_ctrs = cv2.drawContours(img3, contours1_filtered, -1, (0,255,0), 2) plt.figure(figsize=(10,10), dpi=100) plt.imshow(cv2.cvtColor(img_ctrs, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([]) plt.show()
なんとかいけました!
再確認
他の画像でも再確認してみます。
前回作った関数を変更して適用する感じです。
# image: Input image, BGR format def calculate_harupan(image, debug): h, w, chs = image.shape if h > 800 or w > 800: k = 800.0/h if w > h else 800.0/w else: k = 1.0 img = cv2.resize(image, None, fx=k, fy=k, interpolation=cv2.INTER_AREA) if debug: print('Resized to ', img.shape) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Convert hue value (rotation, mask by saturation) hsv[:,:,0] = np.where(hsv[:,:,0] < 50, hsv[:,:,0]+180, hsv[:,:,0]) hsv[:,:,0] = np.where(hsv[:,:,1] < 100, 0, hsv[:,:,0]) # Thresholding with cv2.inRange(), using different threshold th_hue = cv2.inRange(hsv[:,:,0], 135, 190) contours, hierarchy = cv2.findContours(th_hue, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) indices0 = [i for i,hier in enumerate(hierarchy[0,:,:]) if hier[3] == -1] indices1 = [i for i,hier in enumerate(hierarchy[0,:,:]) if hier[3] in indices0] if debug: print('Number of contours: ', len(contours)) print('Number of indices0: ', len(indices0), 'indices1: ', len(indices1)) contours1 = [contours[i] for i in indices1] contours1_filtered = [ctr for ctr in contours1 if cv2.contourArea(ctr) > 800*800/4000] if debug: return contours1_filtered, img else: return contours1_filtered
img1 = cv2.imread('harupan_190428_1.jpg') img2 = cv2.imread('harupan_190428_2.jpg') img3 = cv2.imread('harupan_200317_1.jpg') img4 = cv2.imread('harupan_210227_2.jpg') img5 = cv2.imread('harupan_210402_1.jpg') img6 = cv2.imread('harupan_210402_2.jpg') img7 = cv2.imread('harupan_210414_1.jpg') imgs = [img1, img2, img3, img4, img5, img6, img7] plt.figure(figsize=(20,15), dpi=100) for i,im in enumerate(imgs): ctrs, img_resize = calculate_harupan(im, True) img_ctrs = cv2.drawContours(img_resize, ctrs, -1, (0,255,0), 2) plt.subplot(241+i), plt.imshow(cv2.cvtColor(img_ctrs, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([]) plt.show()
Resized to (1067, 800, 3)
Number of contours: 2514
Number of indices0: 1448 indices1: 875
Resized to (1067, 800, 3)
Number of contours: 2269
Number of indices0: 1265 indices1: 818
Resized to (1067, 800, 3)
Number of contours: 2062
Number of indices0: 1154 indices1: 718
Resized to (1067, 800, 3)
Number of contours: 1204
Number of indices0: 450 indices1: 664
Resized to (1067, 800, 3)
Number of contours: 1613
Number of indices0: 698 indices1: 795
Resized to (1067, 800, 3)
Number of contours: 1242
Number of indices0: 373 indices1: 777
Resized to (1067, 800, 3)
Number of contours: 1258
Number of indices0: 555 indices1: 595
閾値が少し心配でしたが、照明の反射で取得できなかったものを除いて、きちんと点数文字の輪郭が取得できました!
以上
今回はここまでです。
次回は、前回考えていたようにテンプレートマッチングをやっていきます。