Home>



Purpose: The yellow part of the upper image is rotated by Affine transformation to convert it vertically as shown in the lower image.
Rotate from -90 degrees to 90 degrees to output the optimum angle.

There are two patterns that I thought about myself,

  1. Enclose the teeth with a rectangle and add up the pixels other than the teeth in it, and the smallest one is the optimum.
  2. The pixel of the tooth part is counted for each column, and the one with the largest number is optimized.

When I actually saw these two, I felt that they were not optimal.
I would like to know how people perceive that they are in portrait orientation. Please teach us the idea of ​​calculating the optimum angle.

Attachments (3 npy files, ipynb files in tooth_array.zip)

# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt
#Rotate image array with affine transformation
def rotate_affine (src, theta):
    # Get the size of the original image
    h, w = src.shape [0], src.shape [1]
    # Array generation for output image (all elements are 0)
    dst = np.zeros ((h, w))
    Convert to # degree or raradian
    rd = np.radians (theta)
    #Affine transformation
    for y in range (0, h):
        for x in range (0, w):
            xi = int ((x --int (w/2)) * np.cos (rd)-(y --int (h/2)) * np.sin (rd) + int (w/2))
            yi = int ((x --int (w/2)) * np.sin (rd) + (y --int (h/2)) * np.cos (rd) + int (h/2))
            # Substitute pixel values ​​in the output image array if the converted coordinates are not out of range
            if yi<h ―― 1 and xi<w ―― 1 and yi>0 and xi>0:
                dst [y] [x] = src [yi] [xi]
    return dst
def pattern1 (src):
    sum_cnt = 1000000
    best_theta = 0
    for theta in range (-90,90 + 1):
        dst = rotate_affine (src, theta)
        vertical_info = np.count_nonzero (dst == 255, axis = 0)
        side_info = np.count_nonzero (dst == 255, axis = 1)
        min_flag = False
        for i, num in enumerate (vertical_info):
            if num! = 0 and min_flag == False:
                min_flag = True
                min_x = i
            if num == 0 and min_flag == True:
                max_x = i-1
                min_flag = False
        for i, num in enumerate (side_info):
            if num! = 0 and min_flag == False:
                min_flag = True
                min_y = i
            if num == 0 and min_flag == True:
                max_y = i-1
                min_flag = False
        tooth_co_info = [min_y, min_x, max_y, max_x]
        ex_dst = dst [tooth_co_info [0]: tooth_co_info [2] + 1, tooth_co_info [1]: tooth_co_info [3] +1]
        print ("theta, sum: {}, {}" .format (theta, np.count_nonzero (ex_dst == 0, axis = 0) .sum ()))
        if np.count_nonzero (ex_dst == 0, axis = 0) .sum ()<sum_cnt:
            sum_cnt = np.count_nonzero (ex_dst == 0, axis = 0) .sum ()
            best_theta = theta
    print ("best")
    print (best_theta, sum_cnt)

def pattern2 (src):
    max_cnt = 0
    best_theta = 0
    for theta in range (-90,90 + 1):
        dst = rotate_affine (src, theta)
        print ("theta, count: {}, {}" .format (theta, np.count_nonzero (dst == 255, axis = 0) .max ()))
        if np.count_nonzero (dst == 255, axis = 0) .max ()>max_cnt:
            max_cnt = np.count_nonzero (dst == 255, axis = 0) .max ()
            best_theta = theta
    print ("best")
    print ("theta, count: {}, {}". format (theta, max_cnt))
if __name__ =='__main__':
    mask = np.load ('mask.npy')
    bi_mask = np.where (mask == True, 255,0)
    h, w = bi_mask.shape [0], bi_mask.shape [1]
    src = np.zeros ((int ((w ** 2 + h ** 2) ** (1/2)), int ((w ** 2 + h ** 2) ** (1/2)) ))
    dx, dy = int ((w ** 2 + h ** 2) ** (1/2)/2) --int (w/2), int ((w ** 2 + h ** 2) ** (1/2)/2) --int (h/2)
    for y in range (0, h): # translation
        for x in range (0, w):
            src [y + dy] [x + dx] = bi_mask [y] [x]
    theta = 0
    dst = rotate_affine (src, theta)
    plt.imshow (dst)
    plt.show ()
    pattern1 (src) # switching required
# pattern2 (src)
  • Answer # 1

    I tried to make it clean as a whole by making full use of scipy.

    The idea is to first align the center of gravity of the tooth shape on a flat surface, and then shape the tooth to find a balanced rotation position.

    Balance is defined as the minimum moment of inertia with respect to the central axis when the entire tooth is viewed as a rigid body with a constant weight density and standing vertically.

    The first center of gravity, translation, rotation are allscipy.ndimageI can't find only the moment calculation, so I made it myself, and to find the minimum momentscipy.optimizeI used.

    The tooth image was created by capturing the first image of the questioner (so the pixel size is different from the original). It is assumed that the tooth image does not protrude outside the frame due to rotation.

    Below is a diagram of the code and execution results. It came out that a rotation of about -23.36 degrees is optimal. Compared to the questioner's result, it has turned slightly to the right and looks very well balanced.

    * Initially, the left and right rotation moments were used in opposite directions, but it turned out that using the moment of inertia gives a more stable shape. In the case of a rotary moment, even if it is stretched horizontally, it will be stable if it is symmetrical, but if it is a moment of inertia, it will be advantageous to make the width as small as possible. The principle is the same as a figure skating player folding his arms and spinning at high speed.

    import numpy as np
    from scipy import ndimage, optimize
    import matplotlib.pyplot as plt
    import cv2
    #Image loading and binarization
    img = cv2.imread ('tooth.png', 0)
    _, img = cv2.threshold (img, 100, 1, cv2.THRESH_BINARY)
    # Translate and center the center of gravity
    center = ndimage.center_of_mass (img)
    img = ndimage.shift (img, np.array (img.shape)/2-np.array (center))
    # Moment of inertia with respect to the central axis when the figure is erected
    # mass: number of pixels on the vertical axis, radius: pixel distance to the left and right from the central axis
    # Moment of inertia = Σ mass * radius ^ 2
    def inertia (img):
        mass = img.sum (axis = 0)
        radius = np.abs (np.arange (-len (mass) // 2, len (mass) // 2 + 1))
        if len (mass)! = len (radius):
            radius = radius [radius! = 0] --0.5 # Center position correction
        return (mass * radius * radius) .sum ()
    # Rotate and measure moment of inertia = minimization function
    def rotated_inertia (degree, img):
        return inertia (ndimage.rotate (img, degree, reshape = False))
    # Obtain the minimum moment of inertia when rotating -90 to 90 degrees by finding the minimum value of the minimization function.
    res = optimize.minimize_scalar (
        rotated_inertia, bounds = [-90,90], args = (img), method ='Bounded')
    # Find the rotation angle at the minimum value
    print (res.x)
    # Actually rotate at the minimum value
    img = ndimage.rotate (img, res.x, reshape = False)
    plt.imshow (img)
    plt.show ()

    Result