teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

3

d

2020/01/22 12:11

投稿

tiitoi
tiitoi

スコア21960

answer CHANGED
@@ -25,183 +25,11 @@
25
25
  楕円の点を作成するのに OpenCV を使っていますが、本題には関係ないので、その部分は CSV から読み込むように置き換えてください。
26
26
 
27
27
  ```python
28
- import numpy
29
- import matplotlib.pyplot as plt
30
- from matplotlib.patches import Ellipse
31
-
32
-
33
- class LSqEllipse:
34
- def fit(self, data):
35
- """Lest Squares fitting algorithm
36
- Theory taken from (*)
37
- Solving equation Sa=lCa. with a = |a b c d f g> and a1 = |a b c>
38
- a2 = |d f g>
39
- Args
40
- ----
41
- data (list:list:float): list of two lists containing the x and y data of the
42
- ellipse. of the form [[x1, x2, ..., xi],[y1, y2, ..., yi]]
43
- Returns
44
- ------
45
- coef (list): list of the coefficients describing an ellipse
46
- [a,b,c,d,f,g] corresponding to ax**2+2bxy+cy**2+2dx+2fy+g
47
- """
48
- x, y = numpy.asarray(data, dtype=float)
49
-
50
- # Quadratic part of design matrix [eqn. 15] from (*)
51
- D1 = numpy.mat(numpy.vstack([x ** 2, x * y, y ** 2])).T
52
- # Linear part of design matrix [eqn. 16] from (*)
53
- D2 = numpy.mat(numpy.vstack([x, y, numpy.ones(len(x))])).T
54
-
55
- # forming scatter matrix [eqn. 17] from (*)
56
- S1 = D1.T * D1
57
- S2 = D1.T * D2
58
- S3 = D2.T * D2
59
-
60
- # Constraint matrix [eqn. 18]
61
- C1 = numpy.mat("0. 0. 2.; 0. -1. 0.; 2. 0. 0.")
62
-
63
- # Reduced scatter matrix [eqn. 29]
64
- M = C1.I * (S1 - S2 * S3.I * S2.T)
65
-
66
- # M*|a b c >=l|a b c >. Find eigenvalues and eigenvectors from this equation [eqn. 28]
67
- eval, evec = numpy.linalg.eig(M)
68
-
69
- # eigenvector must meet constraint 4ac - b^2 to be valid.
70
- cond = 4 * numpy.multiply(evec[0, :], evec[2, :]) - numpy.power(evec[1, :], 2)
71
- a1 = evec[:, numpy.nonzero(cond.A > 0)[1]]
72
-
73
- # |d f g> = -S3^(-1)*S2^(T)*|a b c> [eqn. 24]
74
- a2 = -S3.I * S2.T * a1
75
-
76
- # eigenvectors |a b c d f g>
77
- self.coef = numpy.vstack([a1, a2])
78
- self._save_parameters()
79
-
80
- def _save_parameters(self):
81
- """finds the important parameters of the fitted ellipse
82
-
83
- Theory taken form http://mathworld.wolfram
84
- Args
85
- -----
86
- coef (list): list of the coefficients describing an ellipse
87
- [a,b,c,d,f,g] corresponding to ax**2+2bxy+cy**2+2dx+2fy+g
88
- Returns
89
- _______
90
- center (List): of the form [x0, y0]
91
- width (float): major axis
92
- height (float): minor axis
93
- phi (float): rotation of major axis form the x-axis in radians
94
- """
95
-
96
- # eigenvectors are the coefficients of an ellipse in general form
97
- # a*x^2 + 2*b*x*y + c*y^2 + 2*d*x + 2*f*y + g = 0 [eqn. 15) from (**) or (***)
98
- a = self.coef[0, 0]
99
- b = self.coef[1, 0] / 2.0
100
- c = self.coef[2, 0]
101
- d = self.coef[3, 0] / 2.0
102
- f = self.coef[4, 0] / 2.0
103
- g = self.coef[5, 0]
104
-
105
- # finding center of ellipse [eqn.19 and 20] from (**)
106
- x0 = (c * d - b * f) / (b ** 2.0 - a * c)
107
- y0 = (a * f - b * d) / (b ** 2.0 - a * c)
108
-
109
- # Find the semi-axes lengths [eqn. 21 and 22] from (**)
110
- numerator = 2 * (a * f * f + c * d * d + g * b * b - 2 * b * d * f - a * c * g)
111
- denominator1 = (b * b - a * c) * (
112
- (c - a) * numpy.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)
113
- )
114
- denominator2 = (b * b - a * c) * (
115
- (a - c) * numpy.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)
116
- )
117
- width = numpy.sqrt(numerator / denominator1)
118
- height = numpy.sqrt(numerator / denominator2)
119
-
120
- # angle of counterclockwise rotation of major-axis of ellipse to x-axis [eqn. 23] from (**)
121
- # or [eqn. 26] from (***).
122
- phi = 0.5 * numpy.arctan((2.0 * b) / (a - c))
123
-
124
- self._center = [x0, y0]
125
- self._width = width
126
- self._height = height
127
- self._phi = phi
128
-
129
- @property
130
- def center(self):
131
- return self._center
132
-
133
- @property
134
- def width(self):
135
- return self._width
136
-
137
- @property
138
- def height(self):
139
- return self._height
140
-
141
- @property
142
- def phi(self):
143
- """angle of counterclockwise rotation of major-axis of ellipse to x-axis
144
- [eqn. 23] from (**)
145
- """
146
- return self._phi
147
-
148
- def parameters(self):
149
- return self.center, self.width, self.height, self.phi
150
-
151
-
152
- def make_test_ellipse(center=[1, 1], width=1, height=0.6, phi=3.14 / 5):
153
- """Generate Elliptical data with noise
154
-
155
- Args
156
- ----
157
- center (list:float): (<x_location>, <y_location>)
158
- width (float): semimajor axis. Horizontal dimension of the ellipse (**)
159
- height (float): semiminor axis. Vertical dimension of the ellipse (**)
160
- phi (float:radians): tilt of the ellipse, the angle the semimajor axis
161
- makes with the x-axis
162
- Returns
163
- -------
164
- data (list:list:float): list of two lists containing the x and y data of the
165
- ellipse. of the form [[x1, x2, ..., xi],[y1, y2, ..., yi]]
166
- """
167
- t = numpy.linspace(0, 2 * numpy.pi, 1000)
168
- x_noise, y_noise = numpy.random.rand(2, len(t))
169
-
170
- ellipse_x = (
171
- center[0]
172
- + width * numpy.cos(t) * numpy.cos(phi)
173
- - height * numpy.sin(t) * numpy.sin(phi)
174
- + x_noise / 2.0
175
- )
176
- ellipse_y = (
177
- center[1]
178
- + width * numpy.cos(t) * numpy.sin(phi)
179
- + height * numpy.sin(t) * numpy.cos(phi)
180
- + y_noise / 2.0
181
- )
182
-
183
- return [ellipse_x, ellipse_y]
184
-
185
28
  import cv2
186
29
  import numpy as np
187
30
  import matplotlib.pyplot as plt
188
31
  from matplotlib.patches import Ellipse
189
32
 
190
- alpha = 5
191
- beta = 3
192
- N = 500
193
- DIM = 2
194
-
195
-
196
- np.random.seed(2)
197
-
198
- # Generate random points on the unit circle by sampling uniform angles
199
- theta = np.random.uniform(0, 2 * np.pi, (N, 1))
200
- eps_noise = 0.2 * np.random.normal(size=[N, 1])
201
- circle = np.hstack([np.cos(theta), np.sin(theta)])
202
-
203
- # Stretch and rotate circle to an ellipse with random linear tranformation
204
- B = np.random.randint(-3, 3, (DIM, DIM))
205
33
  noisy_ellipse = cv2.ellipse2Poly((0, 0), (50, 70), 45, 0, 360, 5)
206
34
 
207
35
  # Extract x coords and y coords of the ellipse as column vectors
@@ -247,4 +75,66 @@
247
75
 
248
76
  ![イメージ説明](6330b456050f23b7c2b6c56d9d8c4824.png)
249
77
 
250
- 緑の点がデータ、赤の線が近似した楕円
78
+ 緑の点がデータ、赤の線が近似した楕円
79
+
80
+ ## 追記
81
+
82
+ 長軸 (major axis) と x 軸の角度、短軸 (minor axis) と x 軸の角度が考えられますが、楕円の式を行列表記して、固有値問題を問き、出てきた固有ベクトル2つが長軸、短軸になります。(小さい固有値の固有ベクトルが長軸、大きい固有値の固有ベクトルが短軸に対応する)
83
+
84
+ [二次形式の意味,微分,標準形など | 高校数学の美しい物語](https://mathtrain.jp/quadraticform)
85
+
86
+ あとは逆正弦関数で x 軸と長軸、または x 軸と短軸の角度を求めればよいです。
87
+
88
+ ```python
89
+ import matplotlib.pyplot as plt
90
+ import numpy as np
91
+
92
+ fig, ax = plt.subplots(figsize=(6, 6))
93
+ ax.grid()
94
+
95
+ a, b, c = 10, 12, 10
96
+
97
+
98
+ def plot_ellipse_by_equation(ax, a, b, c):
99
+ """a x^2 + b xy + c y^2 = 1 で表される楕円を描画する。
100
+ """
101
+ x = np.linspace(-1, 1, 100)
102
+ y = np.linspace(-1, 1, 100)
103
+ X, Y = np.meshgrid(x, y)
104
+ eqn = a * X ** 2 + b * X * Y + c * Y ** 2
105
+ Z = 1
106
+
107
+ # 楕円の式をそのまま描画できる関数は matplotlib にないので、
108
+ # f = a * X ** 2 + b * X * Y + c * Y ** 2 の f = 1 の等高線を描画する形で
109
+ # 楕円を描画する。
110
+ ax.contour(X, Y, eqn, levels=[Z], colors=["r"])
111
+
112
+ # 固有値問題を解く
113
+ Q = np.array([[a, b / 2], [b / 2, c]])
114
+ eig_val, eig_vec = np.linalg.eig(Q)
115
+ print(eig_val, eig_vec)
116
+
117
+ # 長軸、短軸を求める。
118
+ idx1, idx2 = eig_val.argsort()
119
+ major_axis = eig_vec[:, idx1] # 固有値が小さいほうの固有ベクトルが major axis
120
+ minor_axis = eig_vec[:, idx2] # 固有値が大きいほうの固有ベクトルが minor axis
121
+
122
+ ax.annotate(
123
+ s="", xy=major_axis, xytext=(0, 0), arrowprops=dict(facecolor="r"),
124
+ )
125
+ ax.annotate(
126
+ s="", xy=minor_axis, xytext=(0, 0), arrowprops=dict(facecolor="b"),
127
+ )
128
+
129
+ plot_ellipse_by_equation(ax, a, b, c)
130
+
131
+ # 角度を求める
132
+ theta1 = np.rad2deg(np.arctan(major_axis[1] / major_axis[0]))
133
+ theta2 = np.rad2deg(np.arctan(minor_axis[1] / minor_axis[0]))
134
+ print(f"angle between major axis and x axis= {theta1:.2f}")
135
+ print(f"angle between minor axis and x axis= {theta2:.2f}")
136
+ # angle between major axis and x axis= -45.00
137
+ # angle between minor axis and x axis= 45.00
138
+ ```
139
+
140
+ ![イメージ説明](b145bd531d08d205d36f4fde20ad4cfc.png)

2

d

2020/01/22 12:11

投稿

tiitoi
tiitoi

スコア21960

answer CHANGED
@@ -241,4 +241,10 @@
241
241
  ax.grid()
242
242
  ax.scatter(X, Y, label="Data Points", color="g")
243
243
  plot_ellipse_by_equation(ax, x[0], x[1], x[2])
244
- ```
244
+ ```
245
+
246
+ ## 結果
247
+
248
+ ![イメージ説明](6330b456050f23b7c2b6c56d9d8c4824.png)
249
+
250
+ 緑の点がデータ、赤の線が近似した楕円

1

d

2020/01/22 06:27

投稿

tiitoi
tiitoi

スコア21960

answer CHANGED
@@ -22,6 +22,8 @@
22
22
 
23
23
  ## コード全体
24
24
 
25
+ 楕円の点を作成するのに OpenCV を使っていますが、本題には関係ないので、その部分は CSV から読み込むように置き換えてください。
26
+
25
27
  ```python
26
28
  import numpy
27
29
  import matplotlib.pyplot as plt