勉強しないとな~blog

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

OpenCVやってみる - 34. "0"の文字比較

連続の投稿になりますが、続きです。
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]]

f:id:nokixa:20220124025629p:plain

No. 1 : [[0.9259069]]

f:id:nokixa:20220124025631p:plain

No. 2 : [[0.9137973]]

f:id:nokixa:20220124025633p:plain

No. 3 : [[0.90786487]]

f:id:nokixa:20220124025635p:plain

No. 4 : [[0.9035319]]

f:id:nokixa:20220124025637p:plain

No. 5 : [[0.95029587]]

f:id:nokixa:20220124025548p:plain

No. 6 : [[0.8379881]]

f:id:nokixa:20220124025550p:plain

No. 7 : [[0.9888445]]

f:id:nokixa:20220124025552p:plain

No. 8 : [[0.8417428]]

f:id:nokixa:20220124025554p:plain

No. 9 : [[0.8790744]]

f:id:nokixa:20220124025557p:plain

No. 10 : [[0.8300663]]

f:id:nokixa:20220124025559p:plain

No. 11 : [[0.99219877]]

f:id:nokixa:20220124025602p:plain

No. 12 : [[0.78739446]]

f:id:nokixa:20220124025604p:plain

No. 13 : [[0.879098]]

f:id:nokixa:20220124025606p:plain

No. 14 : [[0.7555138]]

f:id:nokixa:20220124025608p:plain

No. 15 : [[0.8576322]]

f:id:nokixa:20220124025611p:plain

No. 16 : [[0.8737255]]

f:id:nokixa:20220124025613p:plain

No. 17 : [[0.86299753]]

f:id:nokixa:20220124025615p:plain

No. 18 : [[0.8445317]]

f:id:nokixa:20220124025618p:plain

No. 19 : [[0.8447284]]

f:id:nokixa:20220124025620p:plain

だいたい思った通りにできました。
"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()

f:id:nokixa:20220124025623p:plain

このテンプレート画像に対して、各輪郭で比較を行ってみます。

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()

f:id:nokixa:20220124025625p:plain

これもいい結果になっています。
"0"の文字では0.96程度になっていて、それ以外では0.9を超えているものはありません。
閾値を0.92とかぐらいに設定すれば判定できそうです。 "0"の検出方法はこれでいいかな。

以上

今回の内容はここまでにします。
今回はまだ2019年の画像1枚だけでやっただけなので、次回他の画像でも評価をしていきたいと思います。

参考

参考にしたサイトを載せておきます。