勉強しないとな~blog

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

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

まだ春のパン祭りの続きですが、1点だけ処理の調整を試したいと思います。

-- 追記 --

今までmarkdown記法でブログを書いていると、ときどき画像がうまく表示されない(半角スペース4つのブロックの後に画像が表示されない)問題があって、前にはてなブログさんに修正の希望を出していましたが、どうも前回の記事を書いているときから、直っているようでした。
はてなさんありがとう~

変更内容

今まで見た点数輪郭では、"0"を除いて、その内部には輪郭を含みませんでした。 これを利用して、ICP処理を実施する前に候補輪郭を絞り、処理の高速化ができないか、試してみます。
"0"の輪郭は除外されてしまいますが、点数計算に影響ないので問題なし。

まずは今まで通りのスクリプト読み込みですが、どうも途中から関数を更新したりするのがうまくいかなかったので、全部コピペしておきます。

######################################################
# Importing libraries
######################################################
import cv2
import numpy as np
from matplotlib import pyplot as plt
import math
import copy
import random
import json

######################################################
# Detecting contours
######################################################
def reduce_resolution(img, res_th=800):
    h, w, chs = img.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
    rtn_img = cv2.resize(img, None, fx=k, fy=k, interpolation=cv2.INTER_AREA)
    return rtn_img

def harupan_binarize(img, sat_th=100):
    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] < sat_th, 0, hsv[:,:,0])
    # Thresholding with cv2.inRange()
    binary_img = cv2.inRange(hsv[:,:,0], 135, 190)
    return binary_img

def detect_candidate_contours(image, res_th=800, sat_th=100):
    img = reduce_resolution(image, res_th)
    binimg = harupan_binarize(img, sat_th)
    # Retrieve all points on the contours (cv2.CHAIN_APPROX_NONE)
    contours, hierarchy = cv2.findContours(binimg, 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

# image: Entire image containing multiple contours
# contours: Contours contained in "image" (Retrieved by cv2.findContours(), the origin is same as "image")
def refine_contours(image, contours):
    subctrs = []
    subimgs = []
    binimgs = []
    thresholds = []
    n_ctrs = []
    for ctr in contours:
        img, _ = create_contour_area_image(image, ctr)
        # Thresholding using G value in BGR format
        thresh, binimg = cv2.threshold(img[:,:,1], 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        # Add black region around thresholded image, to detect contours correctly
        binimg = cv2.copyMakeBorder(binimg, 2,2,2,2, cv2.BORDER_CONSTANT, 0)
        ctrs2, _ = cv2.findContours(binimg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
        max_len = 0
        for ctr2 in ctrs2:
            if max_len <= ctr2.shape[0]:
                max_ctr = ctr2
                max_len = ctr2.shape[0]
        subctrs += [max_ctr]
        subimgs += [img]
        binimgs += [binimg]
        thresholds += [thresh]
        n_ctrs += [len(ctrs2)]
    debug_info = (binimgs, thresholds, n_ctrs)
    return subctrs, subimgs, debug_info

######################################################
# Auxiliary functions
######################################################
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)
def create_upright_solid_contour(ctr):
    ctr2 = ctr.copy()
    (cx,cy),(w,h),angle = cv2.minAreaRect(ctr2)
    M = cv2.getRotationMatrix2D((cx,cy), angle, 1)
    for i in range(ctr2.shape[0]):
        ctr2[i,0,:] = ( M @ np.array([ctr2[i,0,0], ctr2[i,0,1], 1]) ).astype('int')
    rect = cv2.boundingRect(ctr2)
    img = np.zeros((rect[3],rect[2]), 'uint8')
    ctr2 -= rect[0:2]
    M[:,2] -= rect[0:2]
    img = cv2.drawContours(img, [ctr2], -1, 255,-1)
    return img, M, ctr2


######################################################
# Dataset classes
######################################################
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])


######################################################
# 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_sq = float('inf')
    min_idx = 0
    for i, p in enumerate(pts):
        d = np.dot(query - p, query - p)
        if(d < min_distance_sq):
            min_distance_sq = d
            min_idx = i
    return min_idx, np.sqrt(min_distance_sq)

# 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])

# 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


######################################################
# Calculating similarity and determining the number
######################################################
def binary_image_similarity(img1, img2):
    if img1.shape != img2.shape:
        print('binary_image_similarity: Different image size')
        return 0.0
    xor_img = cv2.bitwise_xor(img1, img2)
    return 1.0 - np.float(np.count_nonzero(xor_img)) / (img1.shape[0]*img2.shape[1])

# 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 = binary_image_similarity(converted_img, dst.solid)
        if similarity > max_similarity:
            M_rtn = M
            max_similarity = similarity
            max_converted_img = converted_img
    return M_rtn, max_similarity, max_converted_img

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, _ = 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:
        dsize = (template_data.solid.shape[1], template_data.solid.shape[0])
        flags = cv2.INTER_NEAREST|cv2.WARP_INVERSE_MAP
        converted_img = cv2.warpAffine(target_data.solid, M, dsize=dsize, flags=flags)
        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 = binary_image_similarity(converted_img, template_data.solid)
    return val, 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 = binary_image_similarity(converted_img, template_data.solid)
    return val, 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

def calc_harupan(img, templates, svm):
    ctrs, resized_img = detect_candidate_contours(img, sat_th=50)
    print('Number of candidates: ', len(ctrs))
    subctrs, _, _ = refine_contours(resized_img, ctrs)
    subctr_datasets = [contour_dataset(ctr) for ctr in subctrs]
    ########
    #### Simple code
    # similarities = [get_similarities(d, templates)[0] for d in subctr_datasets]
    #### Code printing progress
    similarities = []
    for i,d in enumerate(subctr_datasets):
        print(i, end=' ')
        similarities += [get_similarities(d, templates)[0]]
    print('')
    ########
    _, result = svm.predict(np.array(similarities, 'float32'))
    result = result.astype('int')
    score = 0.0
    texts = {0:'0', 1:'1', 2:'2', 3:'3', 5:'.5'}
    font = cv2.FONT_HERSHEY_SIMPLEX
    for res, ctr in zip(result, ctrs):
        if res[0] == 5:
            score += 0.5
        elif res[0] != -1:
            score += res[0]
        
        # Annotating recognized numbers for confirmation
        if res[0] != -1:
            resized_img = cv2.drawContours(resized_img, [ctr], -1, (0,255,0), 3)
            x,y,_,_ = cv2.boundingRect(ctr)
            resized_img = cv2.putText(resized_img, texts[res[0]], (x,y), font, 1, (230,230,0), 5)
    return score, resized_img

######################################################
# Loading template data and SVM model
######################################################
def load_svm(filename):
    return cv2.ml.SVM_load(filename)

def load_templates(filename):
    with open(filename, mode='r') as f:
        load_data = json.load(f)
        templates_rtn = []
        for d in load_data:
            templates_rtn += [template_dataset(np.array(d['ctr']), d['num'], d['pts'])]
    return templates_rtn

画像読み込みと、%matplotlib inlineのマジックコマンドも書いておきます。
学習済みSVM、テンプレートデータの読み込みも行います。

%matplotlib inline

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')
img8 = cv2.imread('harupan_220330_1.jpg')
img9 = cv2.imread('harupan_220330_2.jpg')

svm = cv2.ml.SVM_load('harupan_data/harupan_svm_220412.dat')
templates2019 = load_templates('harupan_data/templates2019_220412.json')
templates2020 = load_templates('harupan_data/templates2020_220412.json')
templates2021 = load_templates('harupan_data/templates2021_220412.json')

また、変更の効果の確認のため、一旦今まで通りの処理を各画像で実施します。
処理時間は、timeit.timeit()で測定します。

import timeit

def test_harupan_timeit(img, templates, svm):
    score, result_img = calc_harupan(img, templates, svm)
    n_loop = 5
    result = timeit.timeit('calc_harupan(img, templates, svm)', globals=globals(), number=n_loop)
    print('Score: ', score)
    print('Average process time: ', result/n_loop)
    plt.figure(figsize=(6.4,4.8), dpi=200)
    plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
    plt.show()
    return result/n_loop
imgs = [img1, img2, img3, img4, img5, img6, img7, img8, img9]
templates_sel = [0,0,1,2,2,2,2,2,2]

ts = []
for img, sel in zip(imgs, templates_sel):
    if sel == 0:
        templates = templates2019
    elif sel == 1:
        templates = templates2020
    else:
        templates = templates2021
    ts += [test_harupan_timeit(img, templates, svm)]
Number of candidates:  36
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 
Number of candidates:  36
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 
Number of candidates:  36
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 
Number of candidates:  36
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 
Number of candidates:  36
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 
Number of candidates:  36
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 
Score:  26.0
Average process time:  8.82181928

f:id:nokixa:20220416010523p:plain

Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Score:  26.5
Average process time:  8.280414280000002

f:id:nokixa:20220416010528p:plain

Number of candidates:  40
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 
Number of candidates:  40
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 
Number of candidates:  40
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 
Number of candidates:  40
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 
Number of candidates:  40
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 
Number of candidates:  40
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 
Score:  25.0
Average process time:  6.180490499999999

f:id:nokixa:20220416010533p:plain

Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Number of candidates:  35
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 
Score:  21.5
Average process time:  5.870072019999998

f:id:nokixa:20220416010538p:plain

Number of candidates:  39
0 1 2 3 4 5 6 7 8 9 icp: Insufficient destination points
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 
Number of candidates:  39
0 1 2 3 4 5 6 7 8 9 icp: Insufficient destination points
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 
Number of candidates:  39
0 1 2 3 4 5 6 7 8 9 icp: Insufficient destination points
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 
Number of candidates:  39
0 1 2 3 4 5 6 7 8 9 icp: Insufficient destination points
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 
Number of candidates:  39
0 1 2 3 4 5 6 7 8 9 icp: Insufficient destination points
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 
Number of candidates:  39
0 1 2 3 4 5 6 7 8 9 icp: Insufficient destination points
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 
Score:  28.0
Average process time:  4.848178239999998

f:id:nokixa:20220416010544p:plain

Number of candidates:  34
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 
Number of candidates:  34
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 
Number of candidates:  34
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 
Number of candidates:  34
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 
Number of candidates:  34
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 
Number of candidates:  34
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 
Score:  28.0
Average process time:  4.4394959000000025

f:id:nokixa:20220416010549p:plain

Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Score:  25.0
Average process time:  4.83998542

f:id:nokixa:20220416010554p:plain

Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Score:  26.0
Average process time:  3.852909180000006

f:id:nokixa:20220416010600p:plain

Number of candidates:  24
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Number of candidates:  24
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Number of candidates:  24
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Number of candidates:  24
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Number of candidates:  24
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Number of candidates:  24
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Score:  26.0
Average process time:  6.385288779999996

f:id:nokixa:20220416010607p:plain

for t in ts:
    print(t)
8.82181928
8.280414280000002
6.180490499999999
5.870072019999998
4.848178239999998
4.4394959000000025
4.83998542
3.852909180000006
6.385288779999996

スクリプト変更

スクリプトのうち、detect_candidate_contours()関数を以下のように変更します。

  • 階層情報込みで輪郭検出 (今まで通り)
  • 2段目の階層にある輪郭を取り出し (今まで通り)
  • それより下の階層に輪郭を含まない輪郭を選択する

変更後、スクリプトの関数を上書きします。以下参照。

def detect_candidate_contours(image, res_th=800, sat_th=100):
    img = reduce_resolution(image, res_th)
    binimg = harupan_binarize(img, sat_th)
    # Retrieve all points on the contours (cv2.CHAIN_APPROX_NONE)
    contours, hierarchy = cv2.findContours(binimg, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    # Pick up contours that have no parents
    indices = [i for i,hier in enumerate(hierarchy[0,:,:]) if hier[3] == -1]
    # Pick up contours that reside in above contours
    indices = [i for i,hier in enumerate(hierarchy[0,:,:]) if (hier[3] in indices) and (hier[2] == -1) ]
    contours = [contours[i] for i in indices]
    contours = [ctr for ctr in contours if cv2.contourArea(ctr) > float(res_th)*float(res_th)/4000]
    return contours, img
ts2 = []
for img, sel in zip(imgs, templates_sel):
    if sel == 0:
        templates = templates2019
    elif sel == 1:
        templates = templates2020
    else:
        templates = templates2021
    ts2 += [test_harupan_timeit(img, templates, svm)]
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Score:  26.0
Average process time:  7.390565659999993

f:id:nokixa:20220416010615p:plain

Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Score:  26.5
Average process time:  6.902831980000007

f:id:nokixa:20220416010620p:plain

Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Score:  25.0
Average process time:  3.8497284000000036

f:id:nokixa:20220416010624p:plain

Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Score:  19.5
Average process time:  2.6001762999999984

f:id:nokixa:20220416010630p:plain

Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Score:  28.0
Average process time:  3.600558000000001

f:id:nokixa:20220416010635p:plain

Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Score:  28.0
Average process time:  3.107207219999998

f:id:nokixa:20220416010641p:plain

Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Score:  25.0
Average process time:  3.779431460000012

f:id:nokixa:20220416010646p:plain

Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Score:  26.0
Average process time:  3.1119086799999875

f:id:nokixa:20220416010652p:plain

Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Score:  26.0
Average process time:  5.48430937999999

f:id:nokixa:20220416010700p:plain

for t1,t2 in zip(ts, ts2):
    print('{:.3f} vs '.format(t1), '{:.3f}'.format(t2))
8.822 vs  7.391
8.280 vs  6.903
6.180 vs  3.850
5.870 vs  2.600
4.848 vs  3.601
4.439 vs  3.107
4.840 vs  3.779
3.853 vs  3.112
6.385 vs  5.484

少し処理時間は短くなりましたが、劇的には改善せず。
あと4つ目の画像で1つ点数文字の認識が消えてしまった…

もうちょい処理を検討…

変更2

上の9画像を見ると、点数文字が比較的小さく写っているもののほうが処理時間が短いような。
ICPでのマッチング候補点数が少なくなることによるかと思われます。

ということで、ICPで使う輪郭点を間引きます。
contour_datasetsクラスのコンストラクタ内の処理を変更することになります。

どれくらい間引くかですが、前に選択したテンプレートで輪郭点数を見ると、1つの文字当たり100~200点くらいだったので、同程度あれば大丈夫だろうか。

間引き処理

100点に間引きをすることをターゲットにすると、

  • 例えば353点あるとして、これが一列に等間隔(整数点位置)で並んでいるイメージ
  • これを100点分の長さに圧縮する
  • 圧縮後の各点について、一番近い整数点位置に対応付け
  • 同じ整数点に複数の点が対応付けられるが、そのうち1つを代表で選ぶ

という感じ。
cv2.resize()で画像を縮小するときにcv2.INTER_NEARESTを選ぶのと同じようなことかと思われます。

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)
        n = 100
        if n >= ctr.shape[0]:
            self.pts = np.array([p for p in ctr[:,0,:]])
        else:            
            r = n / ctr.shape[0]
            self.pts = np.zeros((100,2), 'int')
            pts = []
            for i in range(ctr.shape[0]):
                f = math.modf(i*r)[0] 
                if (f <= r/2) or (f > 1.0 - r/2):
                    pts += [ctr[i,0,:]]
            self.pts = np.array(pts)
            print('points : ', ctr.shape[0], ' -> ', len(pts))
def test_harupan(img, templates, svm):
    score, result_img = calc_harupan(img, templates, svm)
    print('Score: ', score)
    plt.figure(figsize=(6.4,4.8), dpi=200)
    plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)), plt.xticks([]), plt.yticks([])
    plt.show()
for img, sel in zip(imgs, templates_sel):
    if sel == 0:
        templates = templates2019
    elif sel == 1:
        templates = templates2020
    else:
        templates = templates2021
    test_harupan(img, templates, svm)
Number of candidates:  22
points :  110  ->  107
points :  128  ->  100
points :  123  ->  101
points :  125  ->  101
points :  201  ->  100
points :  132  ->  100
points :  199  ->  100
points :  135  ->  101
points :  123  ->  101
points :  123  ->  101
points :  201  ->  100
points :  126  ->  100
points :  210  ->  97
points :  133  ->  99
points :  137  ->  101
points :  199  ->  100
points :  126  ->  100
points :  142  ->  100
points :  212  ->  100
points :  223  ->  100
points :  134  ->  100
points :  139  ->  101
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Score:  26.0

f:id:nokixa:20220416010708p:plain

Number of candidates:  23
points :  112  ->  100
points :  130  ->  97
points :  125  ->  101
points :  128  ->  100
points :  205  ->  99
points :  134  ->  100
points :  204  ->  100
points :  139  ->  101
points :  127  ->  100
points :  127  ->  100
points :  207  ->  100
points :  131  ->  100
points :  220  ->  100
points :  140  ->  100
points :  142  ->  100
points :  207  ->  100
points :  130  ->  97
points :  153  ->  101
points :  235  ->  101
points :  226  ->  99
points :  143  ->  100
points :  150  ->  100
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Score:  26.5

f:id:nokixa:20220416010713p:plain

Number of candidates:  23
points :  146  ->  100
points :  144  ->  100
points :  169  ->  100
points :  143  ->  100
points :  143  ->  100
points :  160  ->  100
points :  169  ->  100
points :  208  ->  100
points :  145  ->  99
points :  145  ->  99
points :  144  ->  100
points :  145  ->  99
points :  148  ->  100
points :  144  ->  100
points :  145  ->  99
points :  203  ->  101
points :  144  ->  100
points :  199  ->  100
points :  143  ->  100
points :  200  ->  100
points :  162  ->  100
points :  149  ->  100
points :  150  ->  100
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Score:  25.0

f:id:nokixa:20220416010718p:plain

Number of candidates:  25
points :  147  ->  99
points :  180  ->  100
points :  145  ->  99
points :  125  ->  101
points :  126  ->  100
points :  190  ->  96
points :  122  ->  98
points :  119  ->  101
points :  119  ->  101
points :  103  ->  101
points :  128  ->  100
points :  190  ->  96
points :  300  ->  100
points :  193  ->  100
points :  129  ->  100
points :  128  ->  100
points :  132  ->  100
points :  134  ->  100
points :  140  ->  100
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 
Score:  19.5

f:id:nokixa:20220416010723p:plain

Number of candidates:  31
points :  119  ->  101
points :  157  ->  100
points :  122  ->  98
points :  103  ->  101
points :  167  ->  100
points :  118  ->  102
points :  152  ->  100
points :  112  ->  100
points :  143  ->  100
points :  133  ->  99
points :  138  ->  100
points :  236  ->  100
points :  162  ->  100
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Score:  28.0

f:id:nokixa:20220416010728p:plain

Number of candidates:  28
points :  101  ->  101
points :  124  ->  100
points :  115  ->  96
points :  111  ->  100
points :  107  ->  101
points :  108  ->  100
points :  193  ->  100
points :  123  ->  101
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 
Score:  28.0

f:id:nokixa:20220416010733p:plain

Number of candidates:  18
points :  145  ->  99
points :  138  ->  100
points :  138  ->  100
points :  139  ->  101
points :  138  ->  100
points :  138  ->  100
points :  136  ->  100
points :  219  ->  100
points :  207  ->  100
points :  140  ->  100
points :  131  ->  100
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Score:  25.0

f:id:nokixa:20220416010738p:plain

Number of candidates:  20
points :  107  ->  101
points :  111  ->  100
points :  111  ->  100
points :  101  ->  101
points :  102  ->  100
points :  113  ->  99
points :  104  ->  100
points :  107  ->  101
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Score:  26.0

f:id:nokixa:20220416010744p:plain

Number of candidates:  20
points :  149  ->  100
points :  145  ->  99
points :  120  ->  100
points :  184  ->  100
points :  136  ->  100
points :  142  ->  100
points :  167  ->  100
points :  163  ->  100
points :  174  ->  100
points :  167  ->  100
points :  113  ->  99
points :  103  ->  101
points :  130  ->  97
points :  115  ->  96
points :  160  ->  100
points :  157  ->  100
points :  123  ->  101
points :  142  ->  100
points :  158  ->  101
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Score:  26.0

f:id:nokixa:20220416010751p:plain

点数文字の認識は、特に悪くなってはいなさそうです。これでいけそう。
間引き後の輪郭点数は、ときどき100点から少し離れています。小数の丸めと比較のあたりの問題化とは思いますが、まあこれで進めます。

デバッグ表示を消して、改めて処理時間測定します。

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)
        n = 100
        if n >= ctr.shape[0]:
            self.pts = np.array([p for p in ctr[:,0,:]])
        else:            
            r = n / ctr.shape[0]
            self.pts = np.zeros((100,2), 'int')
            pts = []
            for i in range(ctr.shape[0]):
                f = math.modf(i*r)[0] 
                if (f <= r/2) or (f > 1.0 - r/2):
                    pts += [ctr[i,0,:]]
            self.pts = np.array(pts)
ts3 = []
for img, sel in zip(imgs, templates_sel):
    if sel == 0:
        templates = templates2019
    elif sel == 1:
        templates = templates2020
    else:
        templates = templates2021
    ts3 += [test_harupan_timeit(img, templates, svm)]
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Number of candidates:  22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 
Score:  26.0
Average process time:  3.411894039999993

f:id:nokixa:20220416010759p:plain

Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Score:  26.5
Average process time:  3.6053899799999956

f:id:nokixa:20220416010804p:plain

Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Number of candidates:  23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 
Score:  25.0
Average process time:  1.5578179599999884

f:id:nokixa:20220416010808p:plain

Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Number of candidates:  25
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 
Score:  19.5
Average process time:  1.460823019999998

f:id:nokixa:20220416010813p:plain

Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Number of candidates:  31
0 1 2 3 4 5 6 icp: Insufficient destination points
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
Score:  28.0
Average process time:  2.703369799999996

f:id:nokixa:20220416010818p:plain

Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Number of candidates:  28
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 
Score:  28.0
Average process time:  2.9763017799999942

f:id:nokixa:20220416010824p:plain

Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Number of candidates:  18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 
Score:  25.0
Average process time:  1.883919160000005

f:id:nokixa:20220416010830p:plain

Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Score:  26.0
Average process time:  2.917304880000006

f:id:nokixa:20220416010836p:plain

Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Number of candidates:  20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
Score:  26.0
Average process time:  2.4312215799999874

f:id:nokixa:20220416010843p:plain

for t1,t2,t3 in zip(ts, ts2, ts3):
    print('{:.3f} vs '.format(t1), '{:.3f} vs'.format(t2), '{:.3f}'.format(t3))
8.822 vs  7.391 vs 3.412
8.280 vs  6.903 vs 3.605
6.180 vs  3.850 vs 1.558
5.870 vs  2.600 vs 1.461
4.848 vs  3.601 vs 2.703
4.439 vs  3.107 vs 2.976
4.840 vs  3.779 vs 1.884
3.853 vs  3.112 vs 2.917
6.385 vs  5.484 vs 2.431

概ね速くなりました。
元々輪郭点数が少なかったものはそれほど変わらず。

人間の体感的には少し待ちが出そうですが、一応リアルタイム処理できるかな?

以上

今度こそ調整は終了にしようかと。
気が変わらなければ、次回はPCのカメラ画像からのリアルタイム処理の作成になります。