連続の投稿になりますが、続きです。
2月1日から春のパン祭りスタートらしいので、ペースアップ中です…
今回は前回の記事と同じJupyter notebookでやっているので、画像読み込みなどの下準備は省きます。
楕円近似による"0"の判定
"0"の文字の判定についてです。
前回考えた手法を再掲します。
- 楕円で近似、近似した楕円とテンプレートマッチングを実施、一致度が閾値以上であれば"0"の文字であると判定
ひとまずテンプレート選択に使用したのと同じ画像を使って試してみます。
手順は、
- 各輪郭について、楕円近似を行う
- 各輪郭周辺の小画像を用意する
- 輪郭の塗りつぶし画像、近似楕円の塗りつぶし画像を用意する
- これらにテンプレートマッチングを適用、一致度を確認する
という形です。
def check_degree_of_ellipse(ctr): # Fit ellipse ellipse = cv2.fitEllipse(ctr) # The area to compare: straight bounding rectangle of the ellipse bound = cv2.boundingRect(ctr) # Create solid contour image ## Prepare image data array solid_contour = np.zeros((bound[3],bound[2]), 'uint8') ## Move origin of contour points to the corner of the bounding rectangle ctr = ctr - bound[0:2] solid_contour = cv2.drawContours(solid_contour, [ctr], -1, 255,-1) # Create solid ellipse image ## Move position of the ellipse to the corner of the bounding rectangle ellipse2 = ((ellipse[0][0] - bound[0], ellipse[0][1] - bound[1]), ellipse[1], ellipse[2]) solid_ellipse = np.zeros((bound[3],bound[2]), 'uint8') solid_ellipse = cv2.ellipse(solid_ellipse, ellipse2, 255, -1) degree = cv2.matchTemplate(solid_contour.copy(), solid_ellipse, cv2.TM_CCORR_NORMED) return degree, solid_contour, solid_ellipse
for i, ctr in enumerate(ctrs1[0:20]): deg, solid_contour, solid_ellipse = check_degree_of_ellipse(ctr) print("No. ", i, ": ", deg) plt.figure(figsize=(3.2,2.4), dpi=100) plt.subplot(121), plt.imshow(solid_contour, cmap='gray'), plt.title('Original'), plt.xticks([]), plt.yticks([]) plt.subplot(122), plt.imshow(solid_ellipse, cmap='gray'), plt.title('Fitted ellipse'), plt.xticks([]), plt.yticks([]) plt.show()
No. 0 : [[0.96151537]]
No. 1 : [[0.9259069]]
No. 2 : [[0.9137973]]
No. 3 : [[0.90786487]]
No. 4 : [[0.9035319]]
No. 5 : [[0.95029587]]
No. 6 : [[0.8379881]]
No. 7 : [[0.9888445]]
No. 8 : [[0.8417428]]
No. 9 : [[0.8790744]]
No. 10 : [[0.8300663]]
No. 11 : [[0.99219877]]
No. 12 : [[0.78739446]]
No. 13 : [[0.879098]]
No. 14 : [[0.7555138]]
No. 15 : [[0.8576322]]
No. 16 : [[0.8737255]]
No. 17 : [[0.86299753]]
No. 18 : [[0.8445317]]
No. 19 : [[0.8447284]]
だいたい思った通りにできました。
"0"の文字を楕円近似した結果を見ると、かなり一致度が高くなっています。
少し改善
年によって文字のフォントが違う、ということがあったので、この結果も年によって安定しないかも。
ということで考えたのは、
- 楕円近似はするが、その後、楕円のパラメータを元に"0"のテンプレートと同じような見え方になるよう補正(縦横サイズ、角度)し、"0"のテンプレートと比較する
というやり方です。
まずは"0"のテンプレートで、楕円のフィッティングをしてから垂直になるように回転させます。
回転させる際、画像サイズを少し大きめに確保しておく必要があります。
ellipse_2019_zero = cv2.fitEllipse(ctrs1_numbers[0]) print(ellipse_2019_zero)
((645.8063354492188, 285.6666564941406), (38.72261047363281, 54.549617767333984), 167.13916015625)
bound_2019_zero = cv2.boundingRect(ctrs1_numbers[0]) ellipse_2019_zero_w = math.ceil(ellipse_2019_zero[1][0]) ellipse_2019_zero_h = math.ceil(ellipse_2019_zero[1][1]) origin_2019_zero_x = bound_2019_zero[0] - (int((ellipse_2019_zero_w - bound_2019_zero[2])/2.0)) origin_2019_zero_y = bound_2019_zero[1] - (int((ellipse_2019_zero_h - bound_2019_zero[3])/2.0)) print(bound_2019_zero) print(origin_2019_zero_x, origin_2019_zero_y)
(626, 259, 41, 54)
627 259
subimg_2019_zero = np.zeros((bound_2019_zero[3], bound_2019_zero[2]), 'uint8') ctr = ctrs1_numbers[0] - bound_2019_zero[0:2] subimg_2019_zero = cv2.drawContours(subimg_2019_zero, [ctr], -1, 255,-1) Mrot = cv2.getRotationMatrix2D((bound_2019_zero[2]/2.0, bound_2019_zero[3]/2.0), ellipse_2019_zero[2], 1) Mrot[0,2] += (int((ellipse_2019_zero_w - bound_2019_zero[2])/2.0)) Mrot[1,2] += (int((ellipse_2019_zero_h - bound_2019_zero[3])/2.0)) subimg_2019_zero = cv2.warpAffine(subimg_2019_zero, Mrot, dsize=(ellipse_2019_zero_w, ellipse_2019_zero_h), flags=cv2.INTER_NEAREST) plt.imshow(subimg_2019_zero, cmap='gray'), plt.title('Template(zero)'), plt.xticks([]), plt.yticks([]) plt.show()
このテンプレート画像に対して、各輪郭で比較を行ってみます。
def compare_to_template_zero(ctr): ellipse = cv2.fitEllipse(ctr) bound = cv2.boundingRect(ctr) ellipse_w = math.ceil(ellipse[1][0]) ellipse_h = math.ceil(ellipse[1][1]) origin_x = bound[0] - (int((ellipse_w - bound[2])/2.0)) origin_y = bound[1] - (int((ellipse_h - bound[3])/2.0)) subimg = np.zeros((bound[3], bound[2]), 'uint8') ctr = ctr - bound[0:2] subimg = cv2.drawContours(subimg, [ctr], -1, 255,-1) Mrot = cv2.getRotationMatrix2D((bound[2]/2.0, bound[3]/2.0), ellipse[2], 1) Mrot[0,2] += (int((ellipse_w - bound[2])/2.0)) Mrot[1,2] += (int((ellipse_h - bound[3])/2.0)) subimg = cv2.warpAffine(subimg, Mrot, dsize=(ellipse_w, ellipse_h), flags=cv2.INTER_NEAREST) subimg = cv2.resize(subimg, dsize=(ellipse_2019_zero_w, ellipse_2019_zero_h), interpolation=cv2.INTER_NEAREST) degree = cv2.matchTemplate(subimg.copy(), subimg_2019_zero, cv2.TM_CCORR_NORMED) return degree, subimg
plt.figure(figsize=(20,15), dpi=100) for i, ctr in enumerate(ctrs1[0:20]): deg, subimg = compare_to_template_zero(ctr) title = 'No. %d : %lf' %(i,deg[0,0]) plt.subplot(4,5,i+1), plt.imshow(subimg, cmap='gray'), plt.title(title), plt.xticks([]), plt.yticks([]) plt.show()
これもいい結果になっています。
"0"の文字では0.96程度になっていて、それ以外では0.9を超えているものはありません。
閾値を0.92とかぐらいに設定すれば判定できそうです。
"0"の検出方法はこれでいいかな。
以上
今回の内容はここまでにします。
今回はまだ2019年の画像1枚だけでやっただけなので、次回他の画像でも評価をしていきたいと思います。
参考
参考にしたサイトを載せておきます。
- Jupyterでの数式書き方
https://qiita.com/namoshika/items/63db972bfd1030f8264a - Pythonでの文字列操作
https://qiita.com/tomotaka_ito/items/594ee1396cf982ba9887
グラフのタイトルに数値を入れるときに使いました。 - texでargmin、argmax
http://blog.livedoor.jp/itukano/archives/51835904.html - ndarrayで逆行列 https://deepage.net/features/numpy-inv.html