勉強しないとな~blog

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

OpenCVやってみる - 37. 処理の調整

春のパン祭り点数計算は前回までの処理でおおよそできるようになりましたが、少し調整していきます。

  • ICP収束条件
  • 初期変換行列での判定

文章を整える気があまりなく…
以下雑な文章ご容赦で。

下準備

Jupyter notebookを改めて作っているので、始めに必要な処理を再度行います。

  • ライブラリインポート
  • 画像データ読み込み
  • 必要な関数の定義(今回は点数文字候補輪郭の検出)
  • 点数文字テンプレートのデータ準備

いらなさそうなデバッグ表示などは削除しておきます。

スクリプトにまとめておいたほうがいいかな…

ライブラリインポート、画像データ読み込み

import cv2
import numpy as np
%matplotlib inline
from matplotlib import pyplot as plt
import math

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

点数文字候補輪郭の検出

def detect_candidate_contours(image, res_th=800):
    h, w, chs = image.shape
    if h > res_th or w > res_th:
        k = float(res_th)/h if w > h else float(res_th)/w
    else:
        k = 1.0
    img = cv2.resize(image, None, fx=k, fy=k, interpolation=cv2.INTER_AREA)
    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()
    th_hue = cv2.inRange(hsv[:,:,0], 135, 190)
    # Retrieve all points on the contours (cv2.CHAIN_APPROX_NONE)
    contours, hierarchy = cv2.findContours(th_hue, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    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) > float(res_th)*float(res_th)/4000]
    return contours1_filtered, img

補助処理

  • 輪郭周辺の小画像作成
    引数として、輪郭のリストと対象輪郭のインデックスを用意していたが、インデックスは単にリストの要素選択に使うだけなので、対象輪郭自体を引数にするよう変更
  • 輪郭の塗りつぶし画像作成
def create_contour_area_image(img, ctr):
    x,y,w,h = cv2.boundingRect(ctr)
    rtn_img = img[y:y+h,x:x+w,:].copy()
    rtn_ctr = ctr.copy()
    origin = np.array([x,y])
    for c in rtn_ctr:
        c[0,:] -= origin
    return rtn_img, rtn_ctr

# ctr: Should be output of create_contour_area_image() (Origin of points is the origin of bounding box)
# img_shape: Optional, tuple of (image_height, image_width), if omitted, calculated from ctr
def create_solid_contour(ctr, img_shape=(int(0),int(0))):
    if img_shape == (int(0),int(0)):
        _,_,w,h = cv2.boundingRect(ctr)
    else:
        h,w = img_shape
    img = np.zeros((h,w), 'uint8')
    img = cv2.drawContours(img, [ctr], -1, 255, -1)
    return img

# ctr: Should be output of create_contour_area_image() (Origin of points is the origin of bounding box)
# img_shape: Optional, tuple of (image_height, image_width), determined from fitted ellipse if omitted
def create_upright_solid_contour(ctr,img_shape=(int(0),int(0))):
    (cx,cy),(w,h),angle = cv2.fitEllipse(ctr)
    if img_shape == (int(0),int(0)):
        # Default: same as fitted ellipse
        img_shape = (math.ceil(w), math.ceil(h))
    ctr_img = create_solid_contour(ctr)
    Mrot = cv2.getRotationMatrix2D((cx,cy), angle, 1)
    Mrot[0,2] -= cx - w/2
    Mrot[1,2] -= cy - h/2
    rotated_ctr_img = cv2.warpAffine(ctr_img, Mrot, dsize=img_shape, flags=cv2.INTER_NEAREST)
    return rotated_ctr_img

輪郭データ取得

各輪郭周辺の切り出し画像、および原点を変更した輪郭データも用意

imgs = [img1, img2, img3, img4, img5, img6, img7]
resized_imgs = []
ctrs_all = []
subctrs_all = []
subimgs_all = []
for img in imgs:
    ctrs, im = detect_candidate_contours(img)
    resized_imgs += [im]
    ctrs_all += [ctrs]
    
    subctrs = []
    subimgs = []
    for ctr in ctrs:
        subimg,subctr = create_contour_area_image(im, ctr)
        subctrs += [subctr]
        subimgs += [subimg]
    subctrs_all += [subctrs]
    subimgs_all += [subimgs]

テンプレートデータ

ctrs1_idx_zero = 26
ctrs1_idx_one = 27
ctrs1_idx_two = 24
ctrs1_idx_three = 33
ctrs1_idx_five = 8
ctrs1_idx_numbers = [ctrs1_idx_zero, ctrs1_idx_one, ctrs1_idx_two, ctrs1_idx_three, ctrs1_idx_five]

subimgs1 = []
subctrs1 = []
binimgs1 = []
for i,idx in enumerate(ctrs1_idx_numbers):
    img, ctrs = create_contour_area_image(resized_imgs[0], ctrs_all[0][idx])
    if i == 0:
        binimg = create_upright_solid_contour(ctrs)
    else:
        binimg = create_solid_contour(ctrs)
    subimgs1 += [img.copy()]
    subctrs1 += [ctrs.copy()]
    binimgs1 += [binimg.copy()]
    ctr_img = cv2.drawContours(img, [ctrs], -1, (0,255,0), 2)
    plt.subplot(2,5,1+i), plt.imshow(cv2.cvtColor(ctr_img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
    plt.subplot(2,5,6+i), plt.imshow(binimg,cmap='gray'), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20220319233844p:plain

ctrs3_idx_zero = 7
ctrs3_idx_one = 4
ctrs3_idx_two = 17
ctrs3_idx_five = 6
ctrs3_idx_numbers = [ctrs3_idx_zero, ctrs3_idx_one, ctrs3_idx_two, ctrs3_idx_five]

subimgs3 = []
subctrs3 = []
binimgs3 = []
for i,idx in enumerate(ctrs3_idx_numbers):
    img, ctrs = create_contour_area_image(resized_imgs[2], ctrs_all[2][idx])
    if i == 0:
        binimg = create_upright_solid_contour(ctrs)
    else:
        binimg = create_solid_contour(ctrs)
    subimgs3 += [img.copy()]
    subctrs3 += [ctrs.copy()]
    binimgs3 += [binimg.copy()]
    ctr_img = cv2.drawContours(img, [ctrs], -1, (0,255,0), 2)
    plt.subplot(2,4,1+i), plt.imshow(cv2.cvtColor(ctr_img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
    plt.subplot(2,4,5+i), plt.imshow(binimg,cmap='gray'), plt.xticks([]), plt.yticks([])
plt.show()

subimgs3.insert(3, subimgs1[3])
subctrs3.insert(3, subctrs1[3])
binimgs3.insert(3, binimgs1[3])

f:id:nokixa:20220319233847p:plain

ctrs5_idx_zero = 3
ctrs5_idx_one = 4
ctrs5_idx_two = 2
ctrs5_idx_five = 5
ctrs5_idx_numbers = [ctrs5_idx_zero, ctrs5_idx_one, ctrs5_idx_two, ctrs5_idx_five]

subimgs5 = []
subctrs5 = []
binimgs5 = []
for i,idx in enumerate(ctrs5_idx_numbers):
    img, ctrs = create_contour_area_image(resized_imgs[4], ctrs_all[4][idx])
    if i == 0:
        binimg = create_upright_solid_contour(ctrs)
    else:
        binimg = create_solid_contour(ctrs)
    subimgs5 += [img.copy()]
    subctrs5 += [ctrs.copy()]
    binimgs5 += [binimg.copy()]
    ctr_img = cv2.drawContours(img, [ctrs], -1, (0,255,0), 2)
    plt.subplot(2,4,1+i), plt.imshow(cv2.cvtColor(ctr_img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
    plt.subplot(2,4,5+i), plt.imshow(binimg,cmap='gray'), plt.xticks([]), plt.yticks([])
plt.show()

subimgs5.insert(3, subimgs1[3])
subctrs5.insert(3, subctrs1[3])
binimgs5.insert(3, binimgs1[3])

f:id:nokixa:20220319233850p:plain

テンプレート輪郭点選択

subctrs1_selected_pts_one = [i for i in range(subctrs1[1].shape[0]) if i % 5 == 0]
subctrs1_selected_pts_two = [i for i in range(subctrs1[2].shape[0]) if i % 5 == 0]
subctrs1_selected_pts_three = [i for i in range(subctrs1[3].shape[0]) if i % 5 == 0]
subctrs1_selected_pts_five = [i for i in range(subctrs1[4].shape[0]) if i % 5 == 0]

subctrs1_selected_pts = [subctrs1_selected_pts_one, subctrs1_selected_pts_two, subctrs1_selected_pts_three, subctrs1_selected_pts_five]
for i in range(4):
    img = subimgs1[i+1].copy()
    for p in subctrs1_selected_pts[i]:
        img = cv2.drawMarker(img, subctrs1[i+1][p,0,:], (0,255,0), markerType=cv2.MARKER_CROSS, markerSize=3)
    plt.subplot(1,4,1+i), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20220319233853p:plain

subctrs3_selected_pts_one = [i for i in range(subctrs3[1].shape[0]) if i % 5 == 0]
subctrs3_selected_pts_two = [i for i in range(subctrs3[2].shape[0]) if i % 5 == 0]
subctrs3_selected_pts_three = [i for i in range(subctrs3[3].shape[0]) if i % 5 == 0]
subctrs3_selected_pts_five = [i for i in range(subctrs3[4].shape[0]) if i % 5 == 0]

subctrs3_selected_pts = [subctrs3_selected_pts_one, subctrs3_selected_pts_two, subctrs3_selected_pts_three, subctrs3_selected_pts_five]
for i in range(4):
    if subimgs3[i+1].shape == (1,):
        continue
    img = subimgs3[i+1].copy()
    for p in subctrs3_selected_pts[i]:
        img = cv2.drawMarker(img, subctrs3[i+1][p,0,:], (0,255,0), markerType=cv2.MARKER_CROSS, markerSize=3)
    plt.subplot(1,4,1+i), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20220319233856p:plain

subctrs5_selected_pts_one = [i for i in range(subctrs5[1].shape[0]) if i % 5 == 0]
subctrs5_selected_pts_two = [i for i in range(subctrs5[2].shape[0]) if i % 5 == 0]
subctrs5_selected_pts_three = [i for i in range(subctrs5[3].shape[0]) if i % 5 == 0]
subctrs5_selected_pts_five = [i for i in range(subctrs5[4].shape[0]) if i % 5 == 0]

subctrs5_selected_pts = [subctrs5_selected_pts_one, subctrs5_selected_pts_two, subctrs5_selected_pts_three, subctrs5_selected_pts_five]
for i in range(4):
    if subimgs5[i+1].shape == (1,):
        continue
    img = subimgs5[i+1].copy()
    for p in subctrs5_selected_pts[i]:
        img = cv2.drawMarker(img, subctrs5[i+1][p,0,:], (0,255,0), markerType=cv2.MARKER_CROSS, markerSize=3)
    plt.subplot(1,4,1+i), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
plt.show()

f:id:nokixa:20220319233858p:plain

各画像の正解ラベル

labels1 = [-1,-1,-1,-1,-1
           ,-1,5,0,5,1
           ,5,0,2,1,2
           ,-1,-1,1,1,5
           ,0,2,5,0,2
           ,5,0,1,2,-1
           ,5,1,2,3,1
           ,5,0,-1]

labels2 = [-1,-1,-1,-1,-1
           ,-1,5,0,5,1
           ,5,0,2,1,2
           ,-1,-1,1,1,5
           ,0,-1,2,5,0
           ,2,5,0,1,2
           ,5,0,1,-1,2
           ,-1,-1,-1,3,-1
           ,5,0,-1,1,-1
           ,-1]

labels3 = [-1,-1,-1,-1,1
           ,1,5,0,1,1
           ,5,0,5,0,-1
           ,-1,-1,2,-1,-1
           ,-1,1,1,1,-1
           ,1,-1,-1,1,1
           ,-1,2,-1,1,-1
           ,1,2,-1,1,-1
           ,-1,2,5,-1,0
           ,-1,1,1]

labels4 = [-1,-1,-1,-1,-1
           ,-1,-1,-1,-1,-1
           ,-1,-1,-1,-1,-1
           ,-1,-1,-1,1,1
           ,1,1,1,1,1
           ,-1,5,0,2,5
           ,0,2,1,2,2
           ,-1,-1,-1,1,1
           ,1]

labels5 = [-1,-1,2,0,1
           ,5,-1,1,1,1
           ,1,1,1,1,1
           ,1,-1,5,1,0
           ,5,1,2,0,5
           ,0,2,1,2,2
           ,-1,-1,1,1,1
           ]

labels6 = [-1,0,1,5,2
                ,-1,1,1,1,1
                ,5,1,0,5,0
                ,2,1,5,0,2
                ,2,2,1,-1,-1
                ,1,1,1,1,1
                ,1,1,1]

labels7 = [-1,-1,-1,-1,-1
           ,-1,1,2,2,2
           ,2,1,2,2,2
           ,1,-1,-1,-1,2
           ,1,2,1,1]

点数文字認識処理の整理

あまりいい関数の区切り方ができていなかったので、区切り直す

区切れる処理

以下の処理はICPアルゴリズムで使用するサブ処理、これは適切に区切れていると考える。

# pts: list of 2D points, or ndarray of shape (n,2)
# query: 2D point to find nearest neighbor
def find_nearest_neighbor(pts, query):
    min_distance = float('inf')
    min_idx = 0
    for i, p in enumerate(pts):
        d = np.linalg.norm(query - p)
        if(d < min_distance):
            min_distance = d
            min_idx = i
    return min_idx, min_distance

# src, dst: ndarray, shape is (n,2) (n: number of points)
def estimate_affine_2d(src, dst):
    n = min(src.shape[0], dst.shape[0])
    x = dst[0:n].flatten()
    A = np.zeros((2*n,6))
    for i in range(n):
        A[i*2,0] = src[i,0]
        A[i*2,1] = src[i,1]
        A[i*2,2] = 1
        A[i*2+1,3] = src[i,0]
        A[i*2+1,4] = src[i,1]
        A[i*2+1,5] = 1
    M = np.linalg.inv(A.T @ A) @ A.T @ x
    return M.reshape([2,3])

デバッグ用ICP

ICP処理は、途中経過を可視化できるように考える。
最適化中の変換行列で変換元の点を変換し、変換先の画像に重ねてみる。そのために変換後の点のリストを各反復ごとに残しておく。

# Find optimum affine matrix using ICP algorithm
# src_pts: ndarray, shape is (n_s,2) (n_s: number of points)
# dst_pts: ndarray, shape is (n_d,2) (n_d: number of points, n_d should be larger or equal to n_s)
# initial_matrix: ndarray, shape is (2,3)
def icp(src_pts, dst_pts, max_iter=100, initial_matrix=np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), debug=False):
    default_affine_matrix = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
    if dst_pts.shape[0] < src_pts.shape[0]:
        print("icp: Insufficient destination points")
        return default_affine_matrix, False
    if initial_matrix.shape != (2,3):
        print("icp: Illegal shape of initial_matrix")
        return default_affine_matrix, False
    M = initial_matrix
    # Store indices of the nearest neighbor point of dst_pts to the converted point of src_pts
    nn_idx = []
    converted_pts_list = []
    for i in range(max_iter):
        nn_idx_tmp = []
        dst_pts_list = [p for p in dst_pts]
        idx_list = list(range(0,dst_pts.shape[0]))
        if debug: converted_pts = [];
        for p in src_pts:
            p2 = M @ np.array([p[0], p[1], 1])
            idx, d = find_nearest_neighbor(dst_pts_list, p2)
            nn_idx_tmp += [idx_list[idx]]
            del dst_pts_list[idx]
            del idx_list[idx]
            if debug: converted_pts += [p2];
        if debug: converted_pts_list += [converted_pts];
        if nn_idx != [] and nn_idx == nn_idx_tmp:
            break
        dst_pts2 = np.zeros_like(src_pts)
        for j,idx in enumerate(nn_idx_tmp):
            dst_pts2[j,:] = dst_pts[idx,:]
        M = estimate_affine_2d(src_pts, dst_pts2)
        nn_idx = nn_idx_tmp
        if i == max_iter -1:
            return M, False, converted_pts_list
    return M, True, converted_pts_list

以下の処理は見直し

  • テンプレート、比較対象輪郭について、同じデータ生成が複数回行われるものがある。
    • 初期変換行列推定で、外接矩形(回転考慮)、塗りつぶし画像
    • ICPで、輪郭点の配列
    • 一致度計算で、テンプレートの塗りつぶし画像
    • "0"のテンプレートでは、垂直に回転した塗りつぶし画像のみあればよい。
    • データセットとして一度生成して、各関数に与えるようにする。
  • 初期変換行列推定(get_initial_trainsform()関数)
    上記のデータセットを引数とする。関数名も、"外接矩形による変換行列計算"と変更する。
  • get_optimum_transform()関数
    初期変換行列推定、ICPの関数を呼んでいるが、1か所でしか呼び出されていないので呼び出し元に展開する。
    また、暫定で初期変換行列での下側閾値(これを下回ったらICPを実施せずに諦める)を用意する。
  • 輪郭一致度計算処理(get_contours_similarity()関数)
    同じくデータセットを引数とする。get_optimum_transform()関数でやっていた処理を直接展開する。
  • 輪郭一致度計算("0"用、get_contours_similarity_zero()関数)
    こちらは内容の変更は不要だが、同じデータセットを引数とするように統一する。

データセットには、後で外接矩形の縦横比を見るのに必要になるので、外接矩形情報も含めておく。

まずはデータセットを用意する。クラスで実装する。

テンプレートについて、"0"とそれ以外で処理を変える必要がある。また、"0"では輪郭点データは不要。

class contour_dataset:
    def __init__(self, ctr):
        self.ctr = ctr.copy()
        self.rrect = cv2.minAreaRect(ctr)
        self.box = cv2.boxPoints(self.rrect)
        self.solid = create_solid_contour(ctr)
        self.pts = np.array([p for p in ctr[:,0,:]])

class template_dataset:
    def __init__(self, ctr, num, selected_idx=[0]):
        self.ctr = ctr.copy()
        self.num = num
        self.rrect = cv2.minAreaRect(ctr)
        self.box = cv2.boxPoints(self.rrect)
        if num == 0:
            self.solid = create_upright_solid_contour(ctr)
        else:
            self.solid = create_solid_contour(ctr)
        self.pts = np.array([ctr[idx,0,:] for idx in selected_idx])
# Prepare template data for "0"
templates1 = [template_dataset(subctrs1[0], 0)]
templates3 = [template_dataset(subctrs1[0], 0)]
templates5 = [template_dataset(subctrs1[0], 0)]
# Prepare template data for other numbers
numbers = [1, 2, 3, 5]
for i,num in enumerate(numbers):
    templates1 += [template_dataset(subctrs1[i+1], num, subctrs1_selected_pts[i])]
    templates3 += [template_dataset(subctrs3[i+1], num, subctrs3_selected_pts[i])]
    templates5 += [template_dataset(subctrs5[i+1], num, subctrs5_selected_pts[i])]
ctrs_all_datasets = [[contour_dataset(ctr) for ctr in ctrs] for ctrs in subctrs_all]

以下は修正した初期変換行列推定、輪郭一致度計算の処理。

# src, dst: contour_dataset or template_dataset (holding member variables box, solid)
def get_transform_by_rotated_rectangle(src, dst):
    # Rotated patterns are created when starting index is slided
    dst_box2 = np.vstack([dst.box, dst.box])
    max_similarity = 0.0
    max_converted_img = np.zeros((dst.solid.shape[1], dst.solid.shape[0]), 'uint8')
    for i in range(4):
        M = cv2.getAffineTransform(src.box[0:3], dst_box2[i:i+3])
        converted_img = cv2.warpAffine(src.solid, M, dsize=(dst.solid.shape[1], dst.solid.shape[0]), flags=cv2.INTER_NEAREST)
        similarity = cv2.matchTemplate(converted_img, dst.solid, cv2.TM_CCORR_NORMED)
        if similarity[0,0] > max_similarity:
            M_rtn = M
            max_similarity = similarity[0,0]
            max_converted_img = converted_img
    return M_rtn, max_similarity, converted_img

def get_similarity_with_template(target_data, template_data, sim_th_high=0.92, sim_th_low=0.7):
    M, sim_init, _ = get_transform_by_rotated_rectangle(template_data, target_data)
    if sim_init < sim_th_high and sim_init > sim_th_low:
        print('get_similarity_with_template: Execute ICP')
        M, _, _ = icp(template_data.pts, target_data.pts)
    Minv = cv2.invertAffineTransform(M)
    converted_ctr = np.zeros_like(target_data.ctr)
    for i in range(target_data.ctr.shape[0]):
        converted_ctr[i,0,:] = (Minv[:,0:2] @ target_data.ctr[i,0,:]) + Minv[:,2]
    converted_img = create_solid_contour(converted_ctr, img_shape=template_data.solid.shape)
    val = cv2.matchTemplate(converted_img, template_data.solid, cv2.TM_CCORR_NORMED)
    return val[0,0], converted_img

def get_similarity_with_template_zero(target_data, template_data):
    dsize = (template_data.solid.shape[1], template_data.solid.shape[0])
    converted_img = cv2.resize(target_data.solid, dsize=dsize, interpolation=cv2.INTER_NEAREST)
    val = cv2.matchTemplate(converted_img, template_data.solid, cv2.TM_CCORR_NORMED)
    return val[0,0], converted_img

後は数字判定処理、SVMを使った形で実装。SVMの学習は別途。
上のデータセットを使うことで記述が少し整理される。

def get_similarities(target, templates, debug_number=-1):
    similarities = []
    for i,tmpl in enumerate(templates):
        if tmpl.num == 0:
            sim, img = get_similarity_with_template_zero(target, tmpl)
        else:
            sim, img = get_similarity_with_template(target, tmpl)
        similarities += [sim]
        if debug_number == tmpl.num:
            dbg_img = img.copy()

    if not dbg_img:
        dbg_img = np.zeros((1,1), 'uint8')
    if debug_number != -1:
        return similarities, dbg_img
    else:
        return similarities

# target: Single contour to compare
# templates: List of template_dataset (for numbers 0, 1, 2, 3, 5)
# svm: Trained SVM
# debug_number: Optional, if specified, comparing image for the number is returned
# return: determined number (0,1,2,3,5), -1 if none corresponds
def determine_number(target, templates, svm, debug_number=-1):
    similarities, dbg_img = get_similarities(target, templates, debug_number)
    _, result = svm.predict(np.array(similarities))
    if debug_number != -1:
        return int(result[0]), similarities, dbg_img
    else:
        return int(result[0])

ICP収束条件検討

各画像、各輪郭、各テンプレートについて、ICP処理の経過を一部見てみる。
一致するテンプレートからの変換、一致しないテンプレートからの変換、あとは収束しない変換を見てみたい。

2つ目の画像で収束しないパターンがあったので、これで見てみます。

labels = [-1, 0, 1, 2, 3, 5]
labels_checked = {lab: False for lab in labels}

for i, target in enumerate(ctrs_all_datasets[1]):
    ctr_img = cv2.drawContours(subimgs_all[1][i].copy(), [subctrs_all[1][i]], -1, (0,255,0), 2)
    for tmpl in templates1[1:5]:
        M, sim_init = get_transform_by_rotated_rectangle(tmpl, target)
        M, result, converted_pts  = icp(tmpl.pts, target.pts, initial_matrix=M, debug=True)
        if labels_checked[labels2[i]] and result:
            continue
        print('ICP iterations: ', len(converted_pts))
        subx = min(10, len(converted_pts)) 
        suby = int(len(converted_pts) / 10) + (1 if len(converted_pts) % 10 else 0)
        plt.figure(figsize=(12.8,1.2*suby),dpi=100)
        plt.suptitle('Contour No. %d' %(i) + ', Label %d' %(labels2[i]) + ', Template %d' %(tmpl.num))
        for j,pts in enumerate(converted_pts):
            img = ctr_img.copy()
            for p in pts:
                img = cv2.drawMarker(img, (int(p[0]),int(p[1])), (0,0,255), markerType=cv2.MARKER_CROSS, markerSize=3)
            plt.subplot(suby,subx,int(j/subx)*subx+j%subx+1)
            plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)),plt.xticks([]),plt.yticks([])
        plt.show()
    labels_checked[labels2[i]] = True
    ICP iterations:  12

f:id:nokixa:20220319233901p:plain

    ICP iterations:  7

f:id:nokixa:20220319233903p:plain

    ICP iterations:  8

f:id:nokixa:20220319233906p:plain

    ICP iterations:  15

f:id:nokixa:20220319233908p:plain

    ICP iterations:  100

f:id:nokixa:20220319233911p:plain

    ICP iterations:  6

f:id:nokixa:20220319233914p:plain

    ICP iterations:  6

f:id:nokixa:20220319233916p:plain

    ICP iterations:  6

f:id:nokixa:20220319233919p:plain

    ICP iterations:  16

f:id:nokixa:20220319233921p:plain

    ICP iterations:  12

f:id:nokixa:20220319233924p:plain

    ICP iterations:  12

f:id:nokixa:20220319233927p:plain

    ICP iterations:  7

f:id:nokixa:20220319233930p:plain

    ICP iterations:  8

f:id:nokixa:20220319233932p:plain

    ICP iterations:  2

f:id:nokixa:20220319233935p:plain

    ICP iterations:  8

f:id:nokixa:20220319233937p:plain

    ICP iterations:  7

f:id:nokixa:20220319233940p:plain

    ICP iterations:  11

f:id:nokixa:20220319233943p:plain

    ICP iterations:  7

f:id:nokixa:20220319233945p:plain

    ICP iterations:  3

f:id:nokixa:20220319233948p:plain

    ICP iterations:  10

f:id:nokixa:20220319233950p:plain

    ICP iterations:  7

f:id:nokixa:20220319233953p:plain

    ICP iterations:  12

f:id:nokixa:20220319233956p:plain

    ICP iterations:  7

f:id:nokixa:20220319233958p:plain

    ICP iterations:  4

f:id:nokixa:20220319234001p:plain

    ICP iterations:  7

f:id:nokixa:20220319234003p:plain

    ICP iterations:  100

f:id:nokixa:20220319234006p:plain

    ICP iterations:  100

f:id:nokixa:20220319234009p:plain

    ICP iterations:  100

f:id:nokixa:20220319234012p:plain

なんとなくICPの過程が見られました。
結構徐々に変化するので少しわかりにくい。

収束しないパターンは見ましたが、ほぼ同じ変換を繰り返すだけで、特に得られることはないですね…
全部非数字の輪郭で、サイズも小さい、というぐらいかな。

ICP処理変更

収束条件としてぱっと思い付いたのは、最近傍点距離の和をとってみる、というところ。
収束しなかったときは同じパターンの繰り返しになっていたので、最近傍点距離の和が増加してしまったらICP終了、というのでいいかと。
最近傍点距離の和がどうなるか、見てみます。

# Find optimum affine matrix using ICP algorithm
# src_pts: ndarray, shape is (n_s,2) (n_s: number of points)
# dst_pts: ndarray, shape is (n_d,2) (n_d: number of points, n_d should be larger or equal to n_s)
# initial_matrix: ndarray, shape is (2,3)
def icp(src_pts, dst_pts, max_iter=100, initial_matrix=np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), debug=False):
    default_affine_matrix = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
    if dst_pts.shape[0] < src_pts.shape[0]:
        print("icp: Insufficient destination points")
        return default_affine_matrix, False
    if initial_matrix.shape != (2,3):
        print("icp: Illegal shape of initial_matrix")
        return default_affine_matrix, False
    M = initial_matrix
    # Store indices of the nearest neighbor point of dst_pts to the converted point of src_pts
    nn_idx = []
    converted_pts_list = []
    nn_distances_list = []
    for i in range(max_iter):
        nn_idx_tmp = []
        dst_pts_list = [p for p in dst_pts]
        idx_list = list(range(0,dst_pts.shape[0]))
        if debug: converted_pts = []; nn_distances = 0.0;
        for p in src_pts:
            p2 = M @ np.array([p[0], p[1], 1])
            idx, d = find_nearest_neighbor(dst_pts_list, p2)
            nn_idx_tmp += [idx_list[idx]]
            del dst_pts_list[idx]
            del idx_list[idx]
            if debug: converted_pts += [p2]; nn_distances += d;
        if debug: converted_pts_list += [converted_pts]; nn_distances_list += [nn_distances];
        if nn_idx != [] and nn_idx == nn_idx_tmp:
            break
        dst_pts2 = np.zeros_like(src_pts)
        for j,idx in enumerate(nn_idx_tmp):
            dst_pts2[j,:] = dst_pts[idx,:]
        M = estimate_affine_2d(src_pts, dst_pts2)
        nn_idx = nn_idx_tmp
        if i == max_iter -1:
            return M, False, converted_pts_list, nn_distances_list
    return M, True, converted_pts_list, nn_distances_list
labels = [-1, 0, 1, 2, 3, 5]
labels_checked = {lab: False for lab in labels}

for i, target in enumerate(ctrs_all_datasets[1]):
    ctr_img = cv2.drawContours(subimgs_all[1][i].copy(), [subctrs_all[1][i]], -1, (0,255,0), 2)
    for tmpl in templates1[1:5]:
        M, sim_init = get_transform_by_rotated_rectangle(tmpl, target)
        M, result, converted_pts, nn_distances  = icp(tmpl.pts, target.pts, initial_matrix=M, debug=True)
        if labels_checked[labels2[i]] and result:
            continue
        print('ICP iterations: ', len(converted_pts))
        plt.figure(figsize=(3.2, 2.4),dpi=100)
        plt.suptitle('Contour No. %d' %(i) + ', Label %d' %(labels2[i]) + ', Template %d' %(tmpl.num))
        plt.plot(nn_distances)
        plt.show()
    labels_checked[labels2[i]] = True
    ICP iterations:  12

f:id:nokixa:20220319234016p:plain

    ICP iterations:  7

f:id:nokixa:20220319234018p:plain

    ICP iterations:  8

f:id:nokixa:20220319234021p:plain

    ICP iterations:  15

f:id:nokixa:20220319234023p:plain

    ICP iterations:  100

f:id:nokixa:20220319234026p:plain

    ICP iterations:  6

f:id:nokixa:20220319234028p:plain

    ICP iterations:  6

f:id:nokixa:20220319234030p:plain

    ICP iterations:  6

f:id:nokixa:20220319234033p:plain

    ICP iterations:  16

f:id:nokixa:20220319234035p:plain

    ICP iterations:  12

f:id:nokixa:20220319234038p:plain

    ICP iterations:  12

f:id:nokixa:20220319234040p:plain

    ICP iterations:  7

f:id:nokixa:20220319234043p:plain

    ICP iterations:  8

f:id:nokixa:20220319234045p:plain

    ICP iterations:  2

f:id:nokixa:20220319234048p:plain

    ICP iterations:  8

f:id:nokixa:20220319234051p:plain

    ICP iterations:  7

f:id:nokixa:20220319234053p:plain

    ICP iterations:  11

f:id:nokixa:20220319234056p:plain

    ICP iterations:  7

f:id:nokixa:20220319234059p:plain

    ICP iterations:  3

f:id:nokixa:20220319234102p:plain

    ICP iterations:  10

f:id:nokixa:20220319234105p:plain

    ICP iterations:  7

f:id:nokixa:20220319234107p:plain

    ICP iterations:  12

f:id:nokixa:20220319234110p:plain

    ICP iterations:  7

f:id:nokixa:20220319234112p:plain

    ICP iterations:  4

f:id:nokixa:20220319234114p:plain

    ICP iterations:  7

f:id:nokixa:20220319234117p:plain

    ICP iterations:  100

f:id:nokixa:20220319234119p:plain

    ICP iterations:  100

f:id:nokixa:20220319234122p:plain

    ICP iterations:  100

f:id:nokixa:20220319234124p:plain

概ね最近傍点距離和は反復ごとに下がっていく傾向ですが、増加することもあり。
収束条件として使うにはあんまりかな…

2より大きいサイクルでの発振が起きるパターンもあります。

きちんと収束しないパターンは初期変換行列の段階で弾くことを期待して、ICPでの収束条件の見直しは諦めることにします。
最大反復回数を小さく設定するぐらい。

初期変換行列での判定

外接矩形による変換行列でどこまでのことが分かるか、確認してみたいと思います。
考えたのは、

  • 一致度がどこまで出るか
    この変換だけで判断できればICPは不要に…
  • 外接矩形の縦横比の差で判断できないか?
    縦横比が大きく違えば、そもそも一致するテンプレートではないかなと。

データ確認

実際のデータから、初期変換行列での情報を集めます。

縦横比は、短辺/長辺で計算したいと思います。
cv2.minAreaRect()の"矩形サイズ"の返り値では、どちらが短辺、長辺なのか分からなかったので、比較してから縦横比を計算しています。
また、以下ではテンプレートの縦横比に対する比率を出しています。

templates1_ratios = []
for tmpl in templates1:
    _,(w,h),_ = tmpl.rrect
    templates1_ratios += [w/h] if w < h else [h/w]

templates3_ratios = []
for tmpl in templates1:
    _,(w,h),_ = tmpl.rrect
    templates3_ratios += [w/h] if w < h else [h/w]

templates5_ratios = []
for tmpl in templates1:
    _,(w,h),_ = tmpl.rrect
    templates5_ratios += [w/h] if w < h else [h/w]

print('template1_ratios: ', templates1_ratios)
print('template3_ratios: ', templates3_ratios)
print('template5_ratios: ', templates5_ratios)
templates_sel = [1, 1, 3, 5, 5, 5, 5]

def select_templates(i):
    if i == 1: return templates1
    elif i == 3: return templates3;
    else: return templates5;

initial_similarities_all = []
rect_ratios_all = []
for tsel,ctrs_datasets in zip(templates_sel, ctrs_all_datasets):
    templates = select_templates(tsel)    
    initial_similarities = []
    rect_ratios = []
    for target in ctrs_datasets:
        _,(w,h),_ = target.rrect
        rect_ratios += [w/h] if w < h else [h/w]
        sims = []
        for tmpl in templates:
            _, sim = get_transform_by_rotated_rectangle(tmpl, target)
            sims += [sim]
        initial_similarities += [sims]
    initial_similarities_all += [initial_similarities]
    rect_ratios_all += [rect_ratios]
template1_ratios:  [0.7247706648520233, 0.45857414752846004, 0.7967290110813836, 0.7636363636363637, 0.7428571034673234]
template3_ratios:  [0.7247706648520233, 0.45857414752846004, 0.7967290110813836, 0.7636363636363637, 0.7428571034673234]
template5_ratios:  [0.7247706648520233, 0.45857414752846004, 0.7967290110813836, 0.7636363636363637, 0.7428571034673234]
labs = labels1 + labels2 + labels3 + labels4 + labels5 + labels6 + labels7
label_colors = {-1:'black', 0:'brown', 1:'red', 2:'orange', 3:'khaki', 5:'green'}
numbers = [0, 1, 2, 3, 5]
for i,num in enumerate(numbers):
    ratios = []
    sims = []
    colors = []
    for j in range(7):
        if templates_sel[j] == 1:
            base_ratio = templates1_ratios[i]
        elif templates_sel[j] == 3:
            base_ratio = templates3_ratios[i]
        else:
            base_ratio = templates5_ratios[i]
        for r in rect_ratios_all[j]:
            ratios += [r/base_ratio]
        for s in initial_similarities_all[j]:
            sims += [s[i]]
    for lab in labs:
        colors += [label_colors[lab]]
    plt.title('Number: %d' %(numbers[i]))
    plt.scatter(ratios, sims, c=colors)
    plt.show()

f:id:nokixa:20220319234127p:plain

f:id:nokixa:20220319234129p:plain

f:id:nokixa:20220319234132p:plain

f:id:nokixa:20220319234134p:plain

f:id:nokixa:20220319234137p:plain

グラフマーカー色は以下を参照。

https://matplotlib.org/stable/gallery/color/named_colors.html

結果を見てみると、

  • 縦横比について、非数字の輪郭は幅広く分布、数字の輪郭はある程度固まっている
    • ある程度は縦横比で候補を絞ることはできそう
  • 各輪郭に対応する数字で、当然ながら縦横比がテンプレートのものに近い値となっている
  • この変換でもそれなりの一致度が出ているが、完全に識別できるほどではない

という感じです。

なんとなく閾値

外接矩形の縦横比と、この変換での一致度について、明らかに判断できるだろう、という閾値を決めます。

  • 縦横比: 点数シールを45°の角度から撮るところまで許容する、と考えます。すると、カメラを倒した方向に1/√2倍に縮小されます。ということで、各数字テンプレートと縦横比を比較して、0.7倍以下、1.4倍以上であれば明らかにその数字とは違うと判断できます。上のグラフを見ても、この閾値で問題なさそうです。
  • 一致度: "0"を除いては、一致度0.95以上であればその数字に一致、0.7以下であれば不一致、という判定ができそう。
    "0"はどうしようというところですが、実は点数計算には"0"の判定は不要なので、特に問題なし。

初期変換行列結果でSVM

上の変換で得られた一致度ベクトルで、SVMで識別できるかやってみます。
これで問題なければ、ICPは不要となります…

import copy
import random

svm_inputs = []
for sims in initial_similarities_all:
    svm_inputs += copy.deepcopy(sims)
svm_labels = copy.deepcopy(labs)

# Remove inadequate contour data in img1
del svm_inputs[30]
del svm_labels[30]

def get_random_sample(data_in, labels_in, selected_labels, n_samples):
    data_rtn = []
    labels_rtn = []
    for lab in selected_labels:
        samples = [d for i,d in enumerate(data_in) if labels_in[i]==lab]
        n = min(n_samples, len(samples))
        data_rtn += random.sample(samples, n)
        labels_rtn += [lab] * n
    return data_rtn, labels_rtn

train_data, train_labels = get_random_sample(svm_inputs, svm_labels, [-1,0,1,2,3,5], 10)

svm = cv2.ml.SVM_create()
svm.setKernel(cv2.ml.SVM_LINEAR)
svm.setType(cv2.ml.SVM_C_SVC)
svm.setC(100)
svm.setGamma(1)
svm.train(np.array(train_data, 'float32'), cv2.ml.ROW_SAMPLE, np.array(train_labels));

result = svm.predict(np.array(svm_inputs, 'float32'))

# Dictionary containing classified count for each number
svm_stats = {k:{k2:0 for k2 in [-1, 0, 1, 2, 3, 5]} for k in [-1, 0, 1, 2, 3, 5]}
for res, lab in zip(result[1], svm_labels):
    svm_stats[lab][int(res[0])] += 1
for k,v in svm_stats.items():
    print('label {:>2}'.format(k), ': {', end='')
    for k2,v2 in v.items():
        print('{}: {:>2}, '.format(k2,v2), end='')
    print('}')
label -1 : {-1: 75, 0:  9, 1:  2, 2:  0, 3:  0, 5:  3, }
label  0 : {-1:  0, 0: 27, 1:  0, 2:  0, 3:  0, 5:  0, }
label  1 : {-1:  3, 0:  0, 1: 75, 2:  0, 3:  0, 5:  0, }
label  2 : {-1:  0, 0:  0, 1:  0, 2: 39, 3:  0, 5:  0, }
label  3 : {-1:  0, 0:  0, 1:  0, 2:  0, 3:  2, 5:  0, }
label  5 : {-1:  2, 0:  0, 1:  0, 2:  0, 3:  0, 5: 27, }

まあまあいけてしまってる…
一応完璧ではないので、ICPをやる意義はあるかな…

縦横比をSVM入力に含めてみるとどうだろう。

svm_inputs = []
for i,sims in enumerate(initial_similarities_all):
    for j,sim in enumerate(sims):
        svm_inputs += [copy.copy(sim + [rect_ratios_all[i][j]])]
svm_labels = copy.deepcopy(labs)

# Remove inadequate contour data in img1
del svm_inputs[30]
del svm_labels[30]

train_data, train_labels = get_random_sample(svm_inputs, svm_labels, [-1,0,1,2,3,5], 10)

svm = cv2.ml.SVM_create()
svm.setKernel(cv2.ml.SVM_LINEAR)
svm.setType(cv2.ml.SVM_C_SVC)
svm.setC(100)
svm.setGamma(1)
svm.train(np.array(train_data, 'float32'), cv2.ml.ROW_SAMPLE, np.array(train_labels));

result = svm.predict(np.array(svm_inputs, 'float32'))

# Dictionary containing classified count for each number
svm_stats = {k:{k2:0 for k2 in [-1, 0, 1, 2, 3, 5]} for k in [-1, 0, 1, 2, 3, 5]}
for res, lab in zip(result[1], svm_labels):
    svm_stats[lab][int(res[0])] += 1
for k,v in svm_stats.items():
    print('label {:>2}'.format(k), ': {', end='')
    for k2,v2 in v.items():
        print('{}: {:>2}, '.format(k2,v2), end='')
    print('}')
label -1 : {-1: 69, 0: 11, 1:  4, 2:  0, 3:  0, 5:  5, }
label  0 : {-1:  0, 0: 27, 1:  0, 2:  0, 3:  0, 5:  0, }
label  1 : {-1:  3, 0:  0, 1: 75, 2:  0, 3:  0, 5:  0, }
label  2 : {-1:  0, 0:  0, 1:  0, 2: 39, 3:  0, 5:  0, }
label  3 : {-1:  0, 0:  0, 1:  0, 2:  0, 3:  2, 5:  0, }
label  5 : {-1:  0, 0:  0, 1:  0, 2:  0, 3:  0, 5: 29, }

それほど良くはなっていません。
※Jupyter notebook上では上のコードを何回かやっていて、トレーニングデータはランダムサンプルしているので、ときどき結果が良くなったりすることもありました。

処理修正

上で書いた処理の修正を適用して、改めてSVMでの文字判定をやってみたいと思います。

ついでに、

  • デバッグ用機能は外す
  • ICP反復回数のデフォルトは20ぐらいにする

という変更も入れます。

# Find optimum affine matrix using ICP algorithm
# src_pts: ndarray, shape is (n_s,2) (n_s: number of points)
# dst_pts: ndarray, shape is (n_d,2) (n_d: number of points, n_d should be larger or equal to n_s)
# initial_matrix: ndarray, shape is (2,3)
def icp(src_pts, dst_pts, max_iter=20, initial_matrix=np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])):
    default_affine_matrix = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
    if dst_pts.shape[0] < src_pts.shape[0]:
        print("icp: Insufficient destination points")
        return default_affine_matrix, False
    if initial_matrix.shape != (2,3):
        print("icp: Illegal shape of initial_matrix")
        return default_affine_matrix, False
    M = initial_matrix
    # Store indices of the nearest neighbor point of dst_pts to the converted point of src_pts
    nn_idx = []
    for i in range(max_iter):
        nn_idx_tmp = []
        dst_pts_list = [p for p in dst_pts]
        idx_list = list(range(0,dst_pts.shape[0]))
        for p in src_pts:
            p2 = M @ np.array([p[0], p[1], 1])
            idx, d = find_nearest_neighbor(dst_pts_list, p2)
            nn_idx_tmp += [idx_list[idx]]
            del dst_pts_list[idx]
            del idx_list[idx]
        if nn_idx != [] and nn_idx == nn_idx_tmp:
            break
        dst_pts2 = np.zeros_like(src_pts)
        for j,idx in enumerate(nn_idx_tmp):
            dst_pts2[j,:] = dst_pts[idx,:]
        M = estimate_affine_2d(src_pts, dst_pts2)
        nn_idx = nn_idx_tmp
        if i == max_iter -1:
            return M, False
    return M, True

def get_similarity_with_template(target_data, template_data, sim_th_high=0.95, sim_th_low=0.7):
    _,(w1,h1), _ = target_data.rrect
    _,(w2,h2), _ = template_data.rrect
    r = w1/h1 if w1 < h1 else h1/w1
    r = r * h2/w2 if w2 < h2 else r * w2/h2
    M, sim_init, converted_img = get_transform_by_rotated_rectangle(template_data, target_data)
    if sim_init > sim_th_high or sim_init < sim_th_low or r > 1.4 or r < 0.7:
        return sim_init, converted_img
    M, _ = icp(template_data.pts, target_data.pts, initial_matrix=M)
    Minv = cv2.invertAffineTransform(M)
    converted_ctr = np.zeros_like(target_data.ctr)
    for i in range(target_data.ctr.shape[0]):
        converted_ctr[i,0,:] = (Minv[:,0:2] @ target_data.ctr[i,0,:]) + Minv[:,2]
    converted_img = create_solid_contour(converted_ctr, img_shape=template_data.solid.shape)
    val = cv2.matchTemplate(converted_img, template_data.solid, cv2.TM_CCORR_NORMED)
    return val[0,0], converted_img

def get_similarities(target, templates):
    similarities = []
    converted_imgs = []
    for tmpl in templates:
        if tmpl.num == 0:
            sim,converted_img = get_similarity_with_template_zero(target, tmpl)
        else:
            sim,converted_img = get_similarity_with_template(target, tmpl)
        similarities += [sim]
        converted_imgs += [converted_img]
    return similarities, converted_imgs

# target: Single contour to compare
# templates: List of template_dataset (for numbers 0, 1, 2, 3, 5)
# svm: Trained SVM
# return: determined number (0,1,2,3,5), -1 if none corresponds
def determine_number(target, templates, svm):
    similarities,_ = get_similarities(target, templates)
    _, result = svm.predict(np.array(similarities))
    return int(result[0])
templates_sel = [1, 1, 3, 5, 5, 5, 5]
similarities_all = []
converted_imgs_all = []
templates_sel_all = []
for i,(tsel,ctrs_datasets) in enumerate(zip(templates_sel, ctrs_all_datasets)):
    print('Dataset No. ', i)
    print('  Contour No. ', end='')
    templates = select_templates(tsel)
    for j,target in enumerate(ctrs_datasets):
        print(j, ' ', end='')
        sims, imgs = get_similarities(target, templates)
        similarities_all += [sims]
        converted_imgs_all += [imgs]
    print('')
    templates_sel_all += [tsel]
Dataset No.  0
  Contour No. 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  
Dataset No.  1
  Contour No. 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  
Dataset No.  2
  Contour No. 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  
Dataset No.  3
  Contour No. 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  
Dataset No.  4
  Contour No. 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  
Dataset No.  5
  Contour No. 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  
Dataset No.  6
  Contour No. 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  
svm_inputs = copy.deepcopy(similarities_all)
svm_labels = copy.deepcopy(labs)

# Remove inadequate contour data in img1
del svm_inputs[30]
del svm_labels[30]

train_data, train_labels = get_random_sample(svm_inputs, svm_labels, [-1,0,1,2,3,5], 20)

svm = cv2.ml.SVM_create()
svm.setKernel(cv2.ml.SVM_LINEAR)
svm.setType(cv2.ml.SVM_C_SVC)
svm.setC(100)
svm.setGamma(1)
svm.train(np.array(train_data, 'float32'), cv2.ml.ROW_SAMPLE, np.array(train_labels))

result = svm.predict(np.array(svm_inputs, 'float32'))

# Dictionary containing classified count for each number
svm_stats = {k:{k2:0 for k2 in [-1, 0, 1, 2, 3, 5]} for k in [-1, 0, 1, 2, 3, 5]}
for res, lab in zip(result[1], svm_labels):
    svm_stats[lab][int(res[0])] += 1
for k,v in svm_stats.items():
    print('label {:>2}'.format(k), ': {', end='')
    for k2,v2 in v.items():
        print('{}: {:>2}, '.format(k2,v2), end='')
    print('}')

print('Misclassified data')
for sims,lab,res in zip(svm_inputs, svm_labels, result[1]):
    if lab != res[0]:
        print('{: }'.format(lab), ' -> ', '{: }'.format(res[0]), ' [',end='')
        for s in sims: print('{:.3f}, '.format(s), end='');
        print(']')

print('All data')
for sims,lab,res in zip(svm_inputs, svm_labels, result[1]):
    print('{: }'.format(lab), ' -> ', '{: }'.format(res[0]), ' [',end='')
    for s in sims: print('{:.3f}, '.format(s), end='');
    print(']')
label -1 : {-1: 77, 0:  9, 1:  0, 2:  0, 3:  0, 5:  3, }
label  0 : {-1:  1, 0: 26, 1:  0, 2:  0, 3:  0, 5:  0, }
label  1 : {-1:  0, 0:  0, 1: 78, 2:  0, 3:  0, 5:  0, }
label  2 : {-1:  0, 0:  0, 1:  0, 2: 39, 3:  0, 5:  0, }
label  3 : {-1:  0, 0:  0, 1:  0, 2:  0, 3:  2, 5:  0, }
label  5 : {-1:  0, 0:  0, 1:  0, 2:  0, 3:  0, 5: 29, }
Misclassified data
 0  ->  -1.0  [0.894, 0.863, 0.730, 0.747, 0.795, ]
-1  ->   5.0  [0.864, 0.767, 0.785, 0.835, 0.899, ]
-1  ->   0.0  [0.913, 0.838, 0.728, 0.744, 0.808, ]
-1  ->   5.0  [0.860, 0.752, 0.796, 0.831, 0.875, ]
-1  ->   0.0  [0.957, 0.873, 0.779, 0.764, 0.802, ]
-1  ->   0.0  [0.928, 0.857, 0.719, 0.718, 0.788, ]
-1  ->   0.0  [0.944, 0.820, 0.724, 0.718, 0.800, ]
-1  ->   0.0  [0.913, 0.861, 0.723, 0.745, 0.792, ]
-1  ->   0.0  [0.947, 0.878, 0.760, 0.769, 0.822, ]
-1  ->   0.0  [0.948, 0.886, 0.725, 0.750, 0.804, ]
-1  ->   0.0  [0.952, 0.839, 0.746, 0.771, 0.825, ]
-1  ->   5.0  [0.868, 0.785, 0.767, 0.819, 0.886, ]
-1  ->   0.0  [0.949, 0.846, 0.745, 0.743, 0.787, ]
All data
-1  ->  -1.0  [0.908, 0.885, 0.733, 0.787, 0.790, ]
-1  ->  -1.0  [0.892, 0.832, 0.748, 0.763, 0.826, ]
-1  ->  -1.0  [0.901, 0.817, 0.783, 0.754, 0.823, ]
-1  ->  -1.0  [0.895, 0.823, 0.798, 0.789, 0.828, ]
-1  ->  -1.0  [0.909, 0.830, 0.738, 0.773, 0.813, ]
-1  ->  -1.0  [0.922, 0.841, 0.784, 0.759, 0.813, ]
 5  ->   5.0  [0.797, 0.811, 0.769, 0.855, 0.935, ]
 0  ->  -1.0  [0.894, 0.863, 0.730, 0.747, 0.795, ]
 5  ->   5.0  [0.826, 0.803, 0.745, 0.838, 1.000, ]
 1  ->   1.0  [0.807, 0.970, 0.817, 0.797, 0.807, ]
 5  ->   5.0  [0.809, 0.805, 0.749, 0.827, 0.974, ]
 0  ->   0.0  [0.942, 0.829, 0.735, 0.733, 0.801, ]
 2  ->   2.0  [0.770, 0.804, 0.965, 0.804, 0.804, ]
 1  ->   1.0  [0.834, 0.969, 0.778, 0.778, 0.779, ]
 2  ->   2.0  [0.759, 0.807, 0.966, 0.805, 0.802, ]
-1  ->  -1.0  [0.787, 0.799, 0.778, 0.748, 0.745, ]
-1  ->   5.0  [0.864, 0.767, 0.785, 0.835, 0.899, ]
 1  ->   1.0  [0.785, 0.972, 0.812, 0.802, 0.786, ]
 1  ->   1.0  [0.713, 0.978, 0.801, 0.774, 0.784, ]
 5  ->   5.0  [0.824, 0.813, 0.750, 0.834, 0.971, ]
 0  ->   0.0  [0.966, 0.813, 0.731, 0.732, 0.800, ]
 2  ->   2.0  [0.776, 0.790, 0.966, 0.804, 0.796, ]
 5  ->   5.0  [0.820, 0.773, 0.765, 0.844, 0.932, ]
 0  ->   0.0  [0.978, 0.811, 0.741, 0.735, 0.784, ]
 2  ->   2.0  [0.764, 0.794, 1.000, 0.805, 0.796, ]
 5  ->   5.0  [0.837, 0.777, 0.749, 0.808, 0.960, ]
 0  ->   0.0  [0.953, 0.816, 0.740, 0.709, 0.808, ]
 1  ->   1.0  [0.832, 1.000, 0.802, 0.776, 0.785, ]
 2  ->   2.0  [0.769, 0.795, 0.976, 0.804, 0.807, ]
-1  ->  -1.0  [0.885, 0.808, 0.770, 0.787, 0.832, ]
 1  ->   1.0  [0.819, 0.951, 0.787, 0.777, 0.799, ]
 2  ->   2.0  [0.716, 0.813, 0.972, 0.799, 0.811, ]
 3  ->   3.0  [0.789, 0.760, 0.804, 1.000, 0.838, ]
 1  ->   1.0  [0.671, 0.983, 0.793, 0.763, 0.785, ]
 5  ->   5.0  [0.834, 0.805, 0.786, 0.833, 0.924, ]
 0  ->   0.0  [0.933, 0.859, 0.746, 0.742, 0.807, ]
-1  ->  -1.0  [0.776, 0.841, 0.681, 0.701, 0.777, ]
-1  ->  -1.0  [0.913, 0.906, 0.793, 0.786, 0.792, ]
-1  ->  -1.0  [0.899, 0.847, 0.771, 0.774, 0.837, ]
-1  ->   0.0  [0.913, 0.838, 0.728, 0.744, 0.808, ]
-1  ->  -1.0  [0.898, 0.825, 0.758, 0.817, 0.782, ]
-1  ->  -1.0  [0.917, 0.824, 0.765, 0.755, 0.818, ]
-1  ->  -1.0  [0.914, 0.834, 0.795, 0.736, 0.793, ]
 5  ->   5.0  [0.788, 0.808, 0.761, 0.840, 0.938, ]
 0  ->   0.0  [0.901, 0.856, 0.728, 0.738, 0.792, ]
 5  ->   5.0  [0.818, 0.816, 0.751, 0.780, 0.972, ]
 1  ->   1.0  [0.806, 0.979, 0.819, 0.807, 0.808, ]
 5  ->   5.0  [0.793, 0.801, 0.746, 0.790, 0.969, ]
 0  ->   0.0  [0.938, 0.840, 0.734, 0.733, 0.803, ]
 2  ->   2.0  [0.774, 0.802, 0.961, 0.805, 0.805, ]
 1  ->   1.0  [0.831, 0.978, 0.793, 0.776, 0.786, ]
 2  ->   2.0  [0.753, 0.810, 0.963, 0.808, 0.805, ]
-1  ->   5.0  [0.860, 0.752, 0.796, 0.831, 0.875, ]
-1  ->  -1.0  [0.800, 0.807, 0.771, 0.752, 0.747, ]
 1  ->   1.0  [0.787, 0.964, 0.807, 0.797, 0.789, ]
 1  ->   1.0  [0.699, 0.980, 0.806, 0.774, 0.791, ]
 5  ->   5.0  [0.816, 0.818, 0.759, 0.831, 0.958, ]
 0  ->   0.0  [0.973, 0.803, 0.741, 0.736, 0.786, ]
-1  ->  -1.0  [0.864, 0.837, 0.766, 0.780, 0.830, ]
 2  ->   2.0  [0.779, 0.794, 0.962, 0.802, 0.799, ]
 5  ->   5.0  [0.827, 0.768, 0.763, 0.853, 0.930, ]
 0  ->   0.0  [0.980, 0.800, 0.744, 0.738, 0.800, ]
 2  ->   2.0  [0.762, 0.798, 0.979, 0.797, 0.801, ]
 5  ->   5.0  [0.841, 0.777, 0.761, 0.770, 0.957, ]
 0  ->   0.0  [0.961, 0.810, 0.739, 0.710, 0.814, ]
 1  ->   1.0  [0.830, 0.979, 0.799, 0.777, 0.780, ]
 2  ->   2.0  [0.780, 0.793, 0.969, 0.808, 0.800, ]
 5  ->   5.0  [0.838, 0.772, 0.785, 0.839, 0.940, ]
 0  ->   0.0  [0.896, 0.839, 0.760, 0.709, 0.817, ]
 1  ->   1.0  [0.813, 0.956, 0.781, 0.774, 0.786, ]
-1  ->  -1.0  [0.842, 0.865, 0.780, 0.768, 0.817, ]
 2  ->   2.0  [0.702, 0.813, 0.974, 0.800, 0.807, ]
-1  ->  -1.0  [0.842, 0.875, 0.757, 0.781, 0.861, ]
-1  ->  -1.0  [0.848, 0.868, 0.777, 0.758, 0.810, ]
-1  ->  -1.0  [0.869, 0.848, 0.766, 0.769, 0.819, ]
 3  ->   3.0  [0.799, 0.767, 0.805, 0.975, 0.843, ]
-1  ->   0.0  [0.957, 0.873, 0.779, 0.764, 0.802, ]
 5  ->   5.0  [0.843, 0.801, 0.778, 0.832, 0.918, ]
 0  ->   0.0  [0.923, 0.846, 0.750, 0.744, 0.799, ]
-1  ->  -1.0  [0.883, 0.865, 0.780, 0.778, 0.829, ]
 1  ->   1.0  [0.663, 0.971, 0.789, 0.758, 0.776, ]
-1  ->  -1.0  [0.847, 0.843, 0.761, 0.764, 0.823, ]
-1  ->  -1.0  [0.879, 0.866, 0.778, 0.793, 0.812, ]
-1  ->  -1.0  [0.905, 0.855, 0.781, 0.782, 0.796, ]
-1  ->  -1.0  [0.781, 0.736, 0.658, 0.695, 0.719, ]
-1  ->  -1.0  [0.914, 0.795, 0.744, 0.772, 0.842, ]
-1  ->  -1.0  [0.906, 0.812, 0.754, 0.757, 0.847, ]
 1  ->   1.0  [0.791, 1.000, 0.804, 0.817, 0.792, ]
 1  ->   1.0  [0.764, 0.979, 0.770, 0.764, 0.787, ]
 5  ->   5.0  [0.833, 0.774, 0.779, 0.777, 1.000, ]
 0  ->   0.0  [0.967, 0.792, 0.733, 0.709, 0.816, ]
 1  ->   1.0  [0.771, 0.978, 0.775, 0.763, 0.775, ]
 1  ->   1.0  [0.763, 0.983, 0.779, 0.767, 0.784, ]
 5  ->   5.0  [0.785, 0.776, 0.779, 0.809, 0.969, ]
 0  ->   0.0  [0.928, 0.815, 0.702, 0.713, 0.805, ]
 5  ->   5.0  [0.833, 0.790, 0.788, 0.756, 0.973, ]
 0  ->   0.0  [0.982, 0.784, 0.707, 0.707, 0.794, ]
-1  ->  -1.0  [0.878, 0.857, 0.784, 0.824, 0.801, ]
-1  ->  -1.0  [0.885, 0.791, 0.726, 0.778, 0.833, ]
-1  ->  -1.0  [0.874, 0.774, 0.760, 0.767, 0.831, ]
 2  ->   2.0  [0.731, 0.780, 1.000, 0.809, 0.801, ]
-1  ->  -1.0  [0.874, 0.813, 0.739, 0.765, 0.821, ]
-1  ->  -1.0  [0.898, 0.801, 0.769, 0.772, 0.833, ]
-1  ->  -1.0  [0.887, 0.844, 0.776, 0.783, 0.836, ]
 1  ->   1.0  [0.817, 0.966, 0.790, 0.776, 0.802, ]
 1  ->   1.0  [0.772, 0.980, 0.777, 0.763, 0.794, ]
 1  ->   1.0  [0.771, 0.976, 0.775, 0.766, 0.783, ]
-1  ->  -1.0  [0.878, 0.812, 0.762, 0.770, 0.812, ]
 1  ->   1.0  [0.792, 0.962, 0.781, 0.767, 0.787, ]
-1  ->  -1.0  [0.883, 0.795, 0.745, 0.803, 0.836, ]
-1  ->  -1.0  [0.889, 0.775, 0.755, 0.765, 0.834, ]
 1  ->   1.0  [0.811, 0.972, 0.779, 0.777, 0.783, ]
 1  ->   1.0  [0.773, 0.967, 0.759, 0.758, 0.779, ]
-1  ->  -1.0  [0.883, 0.786, 0.763, 0.785, 0.843, ]
 2  ->   2.0  [0.736, 0.754, 0.957, 0.802, 0.800, ]
-1  ->  -1.0  [0.889, 0.792, 0.734, 0.788, 0.801, ]
 1  ->   1.0  [0.775, 0.970, 0.754, 0.757, 0.784, ]
-1  ->  -1.0  [0.887, 0.789, 0.750, 0.792, 0.834, ]
 1  ->   1.0  [0.806, 0.967, 0.784, 0.775, 0.784, ]
 2  ->   2.0  [0.736, 0.788, 0.962, 0.800, 0.791, ]
-1  ->  -1.0  [0.889, 0.793, 0.773, 0.794, 0.839, ]
 1  ->   1.0  [0.789, 0.972, 0.776, 0.762, 0.779, ]
-1  ->  -1.0  [0.895, 0.781, 0.736, 0.806, 0.851, ]
-1  ->  -1.0  [0.774, 0.883, 0.672, 0.712, 0.778, ]
 2  ->   2.0  [0.730, 0.781, 0.979, 0.805, 0.790, ]
 5  ->   5.0  [0.830, 0.767, 0.787, 0.769, 0.952, ]
-1  ->  -1.0  [0.746, 0.777, 0.692, 0.735, 0.803, ]
 0  ->   0.0  [0.982, 0.781, 0.700, 0.709, 0.815, ]
-1  ->  -1.0  [0.889, 0.793, 0.769, 0.806, 0.821, ]
 1  ->   1.0  [0.794, 0.954, 0.773, 0.760, 0.799, ]
 1  ->   1.0  [0.773, 0.961, 0.750, 0.766, 0.784, ]
-1  ->  -1.0  [0.701, 0.873, 0.817, 0.804, 0.840, ]
-1  ->  -1.0  [0.892, 0.852, 0.794, 0.816, 0.833, ]
-1  ->   0.0  [0.928, 0.857, 0.719, 0.718, 0.788, ]
-1  ->  -1.0  [0.879, 0.840, 0.788, 0.771, 0.805, ]
-1  ->  -1.0  [0.895, 0.838, 0.784, 0.784, 0.828, ]
-1  ->  -1.0  [0.909, 0.857, 0.775, 0.783, 0.830, ]
-1  ->   0.0  [0.944, 0.820, 0.724, 0.718, 0.800, ]
-1  ->   0.0  [0.913, 0.861, 0.723, 0.745, 0.792, ]
-1  ->  -1.0  [0.964, 0.873, 0.784, 0.768, 0.769, ]
-1  ->   0.0  [0.947, 0.878, 0.760, 0.769, 0.822, ]
-1  ->   0.0  [0.948, 0.886, 0.725, 0.750, 0.804, ]
-1  ->  -1.0  [0.909, 0.848, 0.784, 0.772, 0.822, ]
-1  ->  -1.0  [0.919, 0.855, 0.775, 0.760, 0.799, ]
-1  ->  -1.0  [0.866, 0.856, 0.766, 0.741, 0.766, ]
-1  ->   0.0  [0.952, 0.839, 0.746, 0.771, 0.825, ]
-1  ->  -1.0  [0.809, 0.802, 0.804, 0.787, 0.798, ]
-1  ->   5.0  [0.868, 0.785, 0.767, 0.819, 0.886, ]
-1  ->  -1.0  [0.824, 0.803, 0.741, 0.756, 0.831, ]
 1  ->   1.0  [0.801, 0.976, 0.812, 0.777, 0.818, ]
 1  ->   1.0  [0.795, 0.972, 0.805, 0.777, 0.813, ]
 1  ->   1.0  [0.766, 0.974, 0.810, 0.777, 0.814, ]
 1  ->   1.0  [0.764, 0.968, 0.834, 0.802, 0.819, ]
 1  ->   1.0  [0.652, 0.976, 0.821, 0.769, 0.812, ]
 1  ->   1.0  [0.800, 0.973, 0.811, 0.775, 0.814, ]
 1  ->   1.0  [0.797, 0.965, 0.800, 0.765, 0.808, ]
-1  ->  -1.0  [0.633, 0.719, 0.736, 0.742, 0.779, ]
 5  ->   5.0  [0.823, 0.771, 0.736, 0.833, 0.943, ]
 0  ->   0.0  [0.972, 0.826, 0.736, 0.709, 0.813, ]
 2  ->   2.0  [0.763, 0.809, 0.958, 0.808, 0.755, ]
 5  ->   5.0  [0.816, 0.825, 0.722, 0.834, 0.958, ]
 0  ->   0.0  [0.946, 0.832, 0.690, 0.713, 0.806, ]
 2  ->   2.0  [0.755, 0.808, 0.950, 0.799, 0.760, ]
 1  ->   1.0  [0.753, 0.972, 0.811, 0.770, 0.812, ]
 2  ->   2.0  [0.755, 0.806, 0.942, 0.800, 0.765, ]
 2  ->   2.0  [0.752, 0.808, 0.954, 0.802, 0.756, ]
-1  ->  -1.0  [0.781, 0.876, 0.754, 0.748, 0.796, ]
-1  ->  -1.0  [0.720, 0.792, 0.689, 0.747, 0.772, ]
-1  ->  -1.0  [0.693, 0.741, 0.682, 0.698, 0.801, ]
 1  ->   1.0  [0.800, 0.971, 0.804, 0.777, 0.817, ]
 1  ->   1.0  [0.837, 0.949, 0.800, 0.779, 0.810, ]
 1  ->   1.0  [0.767, 0.970, 0.805, 0.767, 0.809, ]
-1  ->  -1.0  [0.883, 0.810, 0.772, 0.755, 0.799, ]
-1  ->  -1.0  [0.884, 0.804, 0.762, 0.764, 0.806, ]
 2  ->   2.0  [0.750, 0.819, 1.000, 0.805, 0.759, ]
 0  ->   0.0  [0.878, 0.869, 0.719, 0.703, 0.794, ]
 1  ->   1.0  [0.687, 1.000, 0.825, 0.781, 0.794, ]
 5  ->   5.0  [0.776, 0.830, 0.756, 0.830, 1.000, ]
-1  ->  -1.0  [0.623, 0.889, 0.708, 0.691, 0.745, ]
 1  ->   1.0  [0.780, 0.972, 0.845, 0.810, 0.792, ]
 1  ->   1.0  [0.840, 0.966, 0.821, 0.786, 0.789, ]
 1  ->   1.0  [0.796, 0.965, 0.812, 0.776, 0.787, ]
 1  ->   1.0  [0.804, 0.970, 0.813, 0.813, 0.790, ]
 1  ->   1.0  [0.747, 0.971, 0.818, 0.764, 0.785, ]
 1  ->   1.0  [0.812, 0.970, 0.856, 0.813, 0.801, ]
 1  ->   1.0  [0.658, 0.954, 0.822, 0.773, 0.806, ]
 1  ->   1.0  [0.804, 0.968, 0.823, 0.787, 0.797, ]
 1  ->   1.0  [0.785, 0.966, 0.807, 0.792, 0.783, ]
-1  ->   0.0  [0.949, 0.846, 0.745, 0.743, 0.787, ]
 5  ->   5.0  [0.764, 0.735, 0.721, 0.744, 0.919, ]
 1  ->   1.0  [0.765, 0.962, 0.811, 0.772, 0.800, ]
 0  ->   0.0  [0.909, 0.880, 0.705, 0.707, 0.808, ]
 5  ->   5.0  [0.815, 0.817, 0.723, 0.749, 0.934, ]
 1  ->   1.0  [0.813, 0.978, 0.845, 0.813, 0.786, ]
 2  ->   2.0  [0.752, 0.810, 0.966, 0.815, 0.753, ]
 0  ->   0.0  [0.971, 0.818, 0.726, 0.724, 0.795, ]
 5  ->   5.0  [0.850, 0.816, 0.726, 0.751, 0.944, ]
 0  ->   0.0  [0.970, 0.834, 0.716, 0.711, 0.811, ]
 2  ->   2.0  [0.773, 0.806, 0.955, 0.811, 0.766, ]
 1  ->   1.0  [0.774, 0.941, 0.824, 0.780, 0.795, ]
 2  ->   2.0  [0.778, 0.815, 0.942, 0.801, 0.766, ]
 2  ->   2.0  [0.787, 0.818, 0.951, 0.808, 0.761, ]
-1  ->  -1.0  [0.732, 0.782, 0.684, 0.682, 0.773, ]
-1  ->  -1.0  [0.733, 0.808, 0.792, 0.765, 0.813, ]
 1  ->   1.0  [0.798, 0.942, 0.776, 0.803, 0.794, ]
 1  ->   1.0  [0.851, 0.935, 0.807, 0.788, 0.810, ]
 1  ->   1.0  [0.823, 0.972, 0.805, 0.772, 0.805, ]
-1  ->  -1.0  [0.854, 0.872, 0.775, 0.783, 0.834, ]
 0  ->   0.0  [0.937, 0.873, 0.720, 0.741, 0.784, ]
 1  ->   1.0  [0.645, 0.937, 0.809, 0.783, 0.802, ]
 5  ->   5.0  [0.823, 0.808, 0.732, 0.844, 0.927, ]
 2  ->   2.0  [0.723, 0.827, 0.939, 0.807, 0.777, ]
-1  ->  -1.0  [0.651, 0.726, 0.663, 0.611, 0.638, ]
 1  ->   1.0  [0.704, 0.932, 0.838, 0.796, 0.827, ]
 1  ->   1.0  [0.823, 0.961, 0.855, 0.836, 0.848, ]
 1  ->   1.0  [0.851, 0.950, 0.816, 0.788, 0.798, ]
 1  ->   1.0  [0.850, 0.962, 0.820, 0.796, 0.811, ]
 5  ->   5.0  [0.736, 0.759, 0.762, 0.776, 0.935, ]
 1  ->   1.0  [0.828, 0.943, 0.828, 0.803, 0.809, ]
 0  ->   0.0  [0.873, 0.857, 0.732, 0.698, 0.813, ]
 5  ->   5.0  [0.783, 0.803, 0.759, 0.855, 0.902, ]
 0  ->   0.0  [0.931, 0.863, 0.714, 0.703, 0.812, ]
 2  ->   2.0  [0.735, 0.820, 0.953, 0.817, 0.770, ]
 1  ->   1.0  [0.742, 0.963, 0.843, 0.806, 0.831, ]
 5  ->   5.0  [0.823, 0.773, 0.767, 0.793, 0.914, ]
 0  ->   0.0  [0.965, 0.827, 0.710, 0.723, 0.786, ]
 2  ->   2.0  [0.758, 0.810, 0.946, 0.809, 0.775, ]
 2  ->   2.0  [0.793, 0.817, 0.942, 0.820, 0.763, ]
 2  ->   2.0  [0.798, 0.816, 0.946, 0.808, 0.765, ]
 1  ->   1.0  [0.825, 0.968, 0.826, 0.781, 0.798, ]
-1  ->  -1.0  [0.714, 0.809, 0.704, 0.692, 0.795, ]
-1  ->  -1.0  [0.734, 0.806, 0.794, 0.761, 0.815, ]
 1  ->   1.0  [0.845, 0.913, 0.791, 0.775, 0.819, ]
 1  ->   1.0  [0.863, 0.933, 0.817, 0.801, 0.833, ]
 1  ->   1.0  [0.845, 0.931, 0.803, 0.776, 0.834, ]
 1  ->   1.0  [0.824, 0.937, 0.832, 0.799, 0.845, ]
 1  ->   1.0  [0.703, 0.942, 0.807, 0.763, 0.795, ]
 1  ->   1.0  [0.716, 0.951, 0.849, 0.804, 0.839, ]
 1  ->   1.0  [0.859, 0.941, 0.807, 0.784, 0.798, ]
 1  ->   1.0  [0.839, 0.952, 0.821, 0.797, 0.802, ]
-1  ->  -1.0  [0.907, 0.791, 0.804, 0.793, 0.787, ]
-1  ->  -1.0  [0.886, 0.843, 0.795, 0.798, 0.828, ]
-1  ->  -1.0  [0.951, 0.875, 0.764, 0.795, 0.797, ]
-1  ->  -1.0  [0.886, 0.885, 0.803, 0.793, 0.838, ]
-1  ->  -1.0  [0.870, 0.840, 0.767, 0.745, 0.788, ]
-1  ->  -1.0  [0.920, 0.837, 0.780, 0.767, 0.809, ]
 1  ->   1.0  [0.844, 0.975, 0.826, 0.801, 0.787, ]
 2  ->   2.0  [0.764, 0.810, 0.941, 0.808, 0.770, ]
 2  ->   2.0  [0.765, 0.809, 0.953, 0.809, 0.774, ]
 2  ->   2.0  [0.761, 0.808, 0.942, 0.807, 0.760, ]
 2  ->   2.0  [0.764, 0.817, 0.967, 0.811, 0.772, ]
 1  ->   1.0  [0.814, 0.954, 0.784, 0.757, 0.799, ]
 2  ->   2.0  [0.774, 0.813, 0.942, 0.817, 0.774, ]
 2  ->   2.0  [0.725, 0.830, 0.957, 0.807, 0.766, ]
 2  ->   2.0  [0.737, 0.836, 0.946, 0.815, 0.779, ]
 1  ->   1.0  [0.798, 0.964, 0.836, 0.800, 0.827, ]
-1  ->  -1.0  [0.699, 0.817, 0.759, 0.678, 0.692, ]
-1  ->  -1.0  [0.673, 0.725, 0.672, 0.641, 0.650, ]
-1  ->  -1.0  [0.679, 0.782, 0.787, 0.674, 0.767, ]
 2  ->   2.0  [0.762, 0.831, 0.949, 0.812, 0.771, ]
 1  ->   1.0  [0.830, 0.947, 0.808, 0.775, 0.806, ]
 2  ->   2.0  [0.763, 0.824, 0.947, 0.811, 0.722, ]
 1  ->   1.0  [0.815, 0.965, 0.842, 0.807, 0.815, ]
 1  ->   1.0  [0.828, 0.952, 0.790, 0.758, 0.805, ]

やっぱりまだ誤分類があります。
見てみると、誤分類しているのは全て非数字の輪郭になっている。
"0"に間違えられるだけなら大丈夫(点数計算に影響しないので)ですが、それ以外だと困る。

どんな画像かも一応確認。

subimgs = []
subctrs = []
for imgs in subimgs_all:
    subimgs += imgs
for ctrs in subctrs_all:
    subctrs += ctrs
del subimgs[30]
del subctrs[30]

for sims,lab,res,img,ctr in zip(svm_inputs, svm_labels, result[1], subimgs, subctrs):
    if lab != res[0]:
        print('{: }'.format(lab), ' -> ', '{: }'.format(res[0]), ' [',end='')
        for s in sims: print('{:.3f}, '.format(s), end='');
        print(']')
        img = cv2.drawContours(img, [ctr], -1, (0,255,0), 1)
        plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)),plt.xticks([]),plt.yticks([])
        plt.show()
    -1  ->   0.0  [0.908, 0.885, 0.733, 0.787, 0.790, ]

f:id:nokixa:20220319234139p:plain

    -1  ->   5.0  [0.864, 0.767, 0.785, 0.835, 0.899, ]

f:id:nokixa:20220319234141p:plain

    -1  ->   1.0  [0.913, 0.906, 0.793, 0.786, 0.792, ]

f:id:nokixa:20220319234143p:plain

    -1  ->   0.0  [0.913, 0.838, 0.728, 0.744, 0.808, ]

f:id:nokixa:20220319234146p:plain

    -1  ->   0.0  [0.914, 0.834, 0.795, 0.736, 0.793, ]

f:id:nokixa:20220319234149p:plain

    -1  ->   5.0  [0.860, 0.752, 0.796, 0.831, 0.875, ]

f:id:nokixa:20220319234152p:plain

    -1  ->   0.0  [0.957, 0.873, 0.779, 0.764, 0.802, ]

f:id:nokixa:20220319234154p:plain

    -1  ->   0.0  [0.781, 0.736, 0.658, 0.695, 0.719, ]

f:id:nokixa:20220319234156p:plain

    -1  ->   0.0  [0.928, 0.857, 0.719, 0.718, 0.788, ]

f:id:nokixa:20220319234159p:plain

    -1  ->   0.0  [0.944, 0.820, 0.724, 0.718, 0.800, ]

f:id:nokixa:20220319234202p:plain

    -1  ->   0.0  [0.913, 0.861, 0.723, 0.745, 0.792, ]

f:id:nokixa:20220319234204p:plain

    -1  ->   0.0  [0.964, 0.873, 0.784, 0.768, 0.769, ]

f:id:nokixa:20220319234207p:plain

    -1  ->   0.0  [0.947, 0.878, 0.760, 0.769, 0.822, ]

f:id:nokixa:20220319234209p:plain

    -1  ->   0.0  [0.948, 0.886, 0.725, 0.750, 0.804, ]

f:id:nokixa:20220319234211p:plain

    -1  ->   0.0  [0.919, 0.855, 0.775, 0.760, 0.799, ]

f:id:nokixa:20220319234215p:plain

    -1  ->   0.0  [0.952, 0.839, 0.746, 0.771, 0.825, ]

f:id:nokixa:20220319234217p:plain

    -1  ->   5.0  [0.868, 0.785, 0.767, 0.819, 0.886, ]

f:id:nokixa:20220319234220p:plain

    -1  ->   0.0  [0.949, 0.846, 0.745, 0.743, 0.787, ]

f:id:nokixa:20220319234223p:plain

    -1  ->   0.0  [0.951, 0.875, 0.764, 0.795, 0.797, ]

f:id:nokixa:20220319234225p:plain

"5"の文字はしょうがない感じが…
点数数字ではなくて交換期限の日時の表記の部分ですが、確かに"5"ではあるので。

もう一度縦横比を見てみたときのグラフを見ると、"1"と"5"については、実際に"1"と"5"のデータだけれども一致度が他の輪郭に埋もれているものがある。
この画像も見てみる。

initial_similarities = []
for sims in initial_similarities_all:
    initial_similarities += sims

subimgs = []
subctrs = []
for imgs in subimgs_all:
    subimgs += copy.deepcopy(imgs)
for ctrs in subctrs_all:
    subctrs += copy.deepcopy(ctrs)

data_1 = []
data_5 = []
for sims,lab,img,ctr,convimg  in zip(initial_similarities, labs, subimgs, subctrs, converted_imgs_all):
    if lab == 1:
        data_1 += [[sims[1], img, ctr, convimg[1]]]
    elif lab == 5:
        data_5 += [[sims[4], img, ctr, convimg[4]]]

data_1 = sorted(data_1, key = lambda x:x[0])
data_5 = sorted(data_5, key = lambda x:x[0])

print('------------------------------')
for d in data_1:
    if d[0] > 0.9: break;
    else:
        img = cv2.drawContours(d[1], [d[2]], -1, (0,255,0), 1)
        print(d[0])
        plt.subplot(1,2,1), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
        plt.subplot(1,2,2), plt.imshow(d[3],cmap='gray'), plt.xticks([]), plt.yticks([])
        plt.show()
print('------------------------------')
for d in data_5:
    if d[0] > 0.9: break;
    else:
        img = cv2.drawContours(d[1], [d[2]], -1, (0,255,0), 1)
        print(d[0])
        plt.subplot(1,2,1), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
        plt.subplot(1,2,2), plt.imshow(d[3],cmap='gray'), plt.xticks([]), plt.yticks([])
        plt.show()
    ------------------------------
    0.81210434

f:id:nokixa:20220319234227p:plain

    0.8434281

f:id:nokixa:20220319234230p:plain

    0.8781869

f:id:nokixa:20220319234232p:plain

    0.88676393

f:id:nokixa:20220319234235p:plain

    0.8914103

f:id:nokixa:20220319234237p:plain

    ------------------------------
    0.80079216

f:id:nokixa:20220319234239p:plain

    0.8299814

f:id:nokixa:20220319233829p:plain

    0.84041774

f:id:nokixa:20220319233831p:plain

    0.8437113

f:id:nokixa:20220319233834p:plain

    0.8467006

f:id:nokixa:20220319233836p:plain

    0.8560233

f:id:nokixa:20220319233839p:plain

    0.8829069

f:id:nokixa:20220319233842p:plain

この辺の輪郭の対応を考えたほうがいいか…

ギザギザ感がちょっと気になる…テンプレートをまっすぐに直しておくと違うかな?

一旦ここまで

ごちゃごちゃしてきたので、一旦ここで区切って、次回また新しくワークスペースを用意し直します。