OpenCVのmomentsを使って丸っぽさと、伸びている方向の角度を求めるコードを書いてみた。
関数moments_round_angle
のX
とY
は以下の論文の定義通りである。これは、二次のモーメントの対称性を表す指標である。(ちなみに一次のモーメントからは重心などが求まる)。丸っぽさは真円度ではなく、任意の直交する軸での対称性を表している。「丸っぽさ」が厳密には正しくない理由は下の方で述べている。
import cv2 import matplotlib.pyplot as plt import numpy as np import math height = 100 width = 120 # 楕円の長軸と短軸の設定 minors = [20, 20] majors = [20, 20] angles = [0, 0] thickness = [5, -1] # 0度から180度までの楕円を描く for angle in range(0,181,10): minors.append(20) majors.append(50) angles.append(angle) thickness.append(-1) # 画像のモーメントから丸っぽさと、伸びている方の角度を求める def moments_round_angle(mom): X=mom["mu20"] + mom["mu02"] Y=((mom["mu20"] - mom["mu02"])**2 + 4.0 * mom["mu11"] **2)**0.5 roundness = (1.0 - Y / X)**0.5 if mom["mu20"] - mom["mu02"] == 0: angle = 0 else: if mom["mu20"] - mom["mu02"]>0: angle = (math.atan(2.0*mom["mu11"]/(mom["mu20"] - mom["mu02"])) / 2.0)/math.pi*180 else: angle = (math.atan(2.0*mom["mu11"]/(mom["mu20"] - mom["mu02"])) / 2.0)/math.pi*180+90 if angle<0: angle += 180 return roundness, angle for mi, ma, an, th in zip(minors, majors, angles, thickness): img1 = np.zeros((height, width, 1)) # OpenCVの角度の定義に変換するため -90 img1 = cv2.ellipse(img1, ((width/2, height/2), (mi, ma), an-90), 255, thickness=th) plt.imshow(img1) plt.xlim(0, width) plt.ylim(0, height) plt.show() print("楕円の短軸長:",mi) print("楕円の長軸長:",ma) print("短軸長/長軸長:",mi/ma) print("楕円の角度:",an,"[deg]") print("楕円の厚み:",th) # 画像のモーメントを計算 m1=cv2.moments(img1) print("丸っぽさ: {:.3f}".format(moments_round_angle(m1)[0])) print("角度: {:.1f} [deg]".format(moments_round_angle(m1)[1]))
描いた楕円の角度が40度で、計算された角度が40.1度なのでほぼあっていると言えるだろう。
次に、実用的な話にするため、アルファベットの小文字を書いて計算してみる。
for i in range(97, 123): img1 = np.zeros((height, width, 1)) # 画像に文字を書く cv2.putText(img1, chr(i), (width//2, height//2), cv2.FONT_HERSHEY_PLAIN, 4, 255, 5, cv2.LINE_AA) # 画像の軸はYが逆転しているのでフリップする img1 = cv2.flip(img1, 0) plt.imshow(img1) plt.xlim(0, width) plt.ylim(0, height) plt.show() print("文字:",chr(i)) # 画像のモーメントを計算 m1=cv2.moments(img1) print("丸っぽさ: {:.3f}".format(moments_round_angle(m1)[0])) print("角度: {:.1f} [deg]".format(moments_round_angle(m1)[1]))
yの字は少し右上に伸びているので、角度として70度というのは正しそうである。
全ての小文字の丸っぽさと角度は以下の表の通り。o
の丸っぽさが1
なのはそのとおりだが、x
も丸っぽさが1
となっている。これは、今回計算した丸っぽさ
は任意の直行する2軸における対称性を表す指標だからである。o
とx
はどちらも任意の直行するX軸とY軸に対して対称なので1
となる。o
とx
を区別するにはさらに高次のモーメントを使う必要がある。
最も丸っぽさが少ないのがl
で0.235
、次にi
、j
と続く。これも直感と一致している。少し飛んでt
である。実際にプログラムを実行して画像を見ながら、丸っぽさと角度を比べてみると良いだろう。
文字 | 丸っぽさ | 角度 |
---|---|---|
a | 0.885 | 78.9 |
b | 0.851 | 117.8 |
c | 0.931 | 90.0 |
d | 0.851 | 62.2 |
e | 0.971 | 87.3 |
f | 0.547 | 78.0 |
g | 0.815 | 86.1 |
h | 0.825 | 137.1 |
i | 0.263 | 90.0 |
j | 0.283 | 83.8 |
k | 0.803 | 110.3 |
l | 0.235 | 90.0 |
m | 0.688 | 176.0 |
n | 0.862 | 166.5 |
o | 1.000 | 90.0 |
p | 0.851 | 62.2 |
q | 0.851 | 117.8 |
r | 0.608 | 61.7 |
s | 0.871 | 97.2 |
t | 0.510 | 102.6 |
u | 0.862 | 166.5 |
v | 0.990 | 180.0 |
w | 0.846 | 0.0 |
x | 1.000 | 0.0 |
y | 0.706 | 72.3 |
z | 0.814 | 82.8 |