回答編集履歴

3

d

2020/01/22 12:11

投稿

tiitoi
tiitoi

スコア21956

test CHANGED
@@ -52,7 +52,9 @@
52
52
 
53
53
  ```python
54
54
 
55
+ import cv2
56
+
55
- import numpy
57
+ import numpy as np
56
58
 
57
59
  import matplotlib.pyplot as plt
58
60
 
@@ -60,385 +62,131 @@
60
62
 
61
63
 
62
64
 
63
-
64
-
65
- class LSqEllipse:
66
-
67
- def fit(self, data):
68
-
69
- """Lest Squares fitting algorithm
70
-
71
- Theory taken from (*)
72
-
73
- Solving equation Sa=lCa. with a = |a b c d f g> and a1 = |a b c>
74
-
75
- a2 = |d f g>
76
-
77
- Args
78
-
79
- ----
80
-
81
- data (list:list:float): list of two lists containing the x and y data of the
82
-
83
- ellipse. of the form [[x1, x2, ..., xi],[y1, y2, ..., yi]]
84
-
85
- Returns
86
-
87
- ------
88
-
89
- coef (list): list of the coefficients describing an ellipse
90
-
91
- [a,b,c,d,f,g] corresponding to ax**2+2bxy+cy**2+2dx+2fy+g
92
-
93
- """
94
-
95
- x, y = numpy.asarray(data, dtype=float)
96
-
97
-
98
-
99
- # Quadratic part of design matrix [eqn. 15] from (*)
100
-
101
- D1 = numpy.mat(numpy.vstack([x ** 2, x * y, y ** 2])).T
102
-
103
- # Linear part of design matrix [eqn. 16] from (*)
104
-
105
- D2 = numpy.mat(numpy.vstack([x, y, numpy.ones(len(x))])).T
106
-
107
-
108
-
109
- # forming scatter matrix [eqn. 17] from (*)
110
-
111
- S1 = D1.T * D1
112
-
113
- S2 = D1.T * D2
114
-
115
- S3 = D2.T * D2
116
-
117
-
118
-
119
- # Constraint matrix [eqn. 18]
120
-
121
- C1 = numpy.mat("0. 0. 2.; 0. -1. 0.; 2. 0. 0.")
122
-
123
-
124
-
125
- # Reduced scatter matrix [eqn. 29]
126
-
127
- M = C1.I * (S1 - S2 * S3.I * S2.T)
128
-
129
-
130
-
131
- # M*|a b c >=l|a b c >. Find eigenvalues and eigenvectors from this equation [eqn. 28]
132
-
133
- eval, evec = numpy.linalg.eig(M)
134
-
135
-
136
-
137
- # eigenvector must meet constraint 4ac - b^2 to be valid.
138
-
139
- cond = 4 * numpy.multiply(evec[0, :], evec[2, :]) - numpy.power(evec[1, :], 2)
140
-
141
- a1 = evec[:, numpy.nonzero(cond.A > 0)[1]]
142
-
143
-
144
-
145
- # |d f g> = -S3^(-1)*S2^(T)*|a b c> [eqn. 24]
146
-
147
- a2 = -S3.I * S2.T * a1
148
-
149
-
150
-
151
- # eigenvectors |a b c d f g>
152
-
153
- self.coef = numpy.vstack([a1, a2])
154
-
155
- self._save_parameters()
156
-
157
-
158
-
159
- def _save_parameters(self):
160
-
161
- """finds the important parameters of the fitted ellipse
162
-
163
-
164
-
165
- Theory taken form http://mathworld.wolfram
166
-
167
- Args
168
-
169
- -----
170
-
171
- coef (list): list of the coefficients describing an ellipse
172
-
173
- [a,b,c,d,f,g] corresponding to ax**2+2bxy+cy**2+2dx+2fy+g
174
-
175
- Returns
176
-
177
- _______
178
-
179
- center (List): of the form [x0, y0]
180
-
181
- width (float): major axis
182
-
183
- height (float): minor axis
184
-
185
- phi (float): rotation of major axis form the x-axis in radians
186
-
187
- """
188
-
189
-
190
-
191
- # eigenvectors are the coefficients of an ellipse in general form
192
-
193
- # a*x^2 + 2*b*x*y + c*y^2 + 2*d*x + 2*f*y + g = 0 [eqn. 15) from (**) or (***)
194
-
195
- a = self.coef[0, 0]
196
-
197
- b = self.coef[1, 0] / 2.0
198
-
199
- c = self.coef[2, 0]
200
-
201
- d = self.coef[3, 0] / 2.0
202
-
203
- f = self.coef[4, 0] / 2.0
204
-
205
- g = self.coef[5, 0]
206
-
207
-
208
-
209
- # finding center of ellipse [eqn.19 and 20] from (**)
210
-
211
- x0 = (c * d - b * f) / (b ** 2.0 - a * c)
212
-
213
- y0 = (a * f - b * d) / (b ** 2.0 - a * c)
214
-
215
-
216
-
217
- # Find the semi-axes lengths [eqn. 21 and 22] from (**)
218
-
219
- numerator = 2 * (a * f * f + c * d * d + g * b * b - 2 * b * d * f - a * c * g)
220
-
221
- denominator1 = (b * b - a * c) * (
222
-
223
- (c - a) * numpy.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)
224
-
225
- )
226
-
227
- denominator2 = (b * b - a * c) * (
228
-
229
- (a - c) * numpy.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)
230
-
231
- )
232
-
233
- width = numpy.sqrt(numerator / denominator1)
234
-
235
- height = numpy.sqrt(numerator / denominator2)
236
-
237
-
238
-
239
- # angle of counterclockwise rotation of major-axis of ellipse to x-axis [eqn. 23] from (**)
240
-
241
- # or [eqn. 26] from (***).
242
-
243
- phi = 0.5 * numpy.arctan((2.0 * b) / (a - c))
244
-
245
-
246
-
247
- self._center = [x0, y0]
248
-
249
- self._width = width
250
-
251
- self._height = height
252
-
253
- self._phi = phi
254
-
255
-
256
-
257
- @property
258
-
259
- def center(self):
260
-
261
- return self._center
262
-
263
-
264
-
265
- @property
266
-
267
- def width(self):
268
-
269
- return self._width
270
-
271
-
272
-
273
- @property
274
-
275
- def height(self):
276
-
277
- return self._height
278
-
279
-
280
-
281
- @property
282
-
283
- def phi(self):
284
-
285
- """angle of counterclockwise rotation of major-axis of ellipse to x-axis
286
-
287
- [eqn. 23] from (**)
288
-
289
- """
290
-
291
- return self._phi
292
-
293
-
294
-
295
- def parameters(self):
296
-
297
- return self.center, self.width, self.height, self.phi
298
-
299
-
300
-
301
-
302
-
303
- def make_test_ellipse(center=[1, 1], width=1, height=0.6, phi=3.14 / 5):
304
-
305
- """Generate Elliptical data with noise
306
-
307
-
308
-
309
- Args
310
-
311
- ----
312
-
313
- center (list:float): (<x_location>, <y_location>)
314
-
315
- width (float): semimajor axis. Horizontal dimension of the ellipse (**)
316
-
317
- height (float): semiminor axis. Vertical dimension of the ellipse (**)
318
-
319
- phi (float:radians): tilt of the ellipse, the angle the semimajor axis
320
-
321
- makes with the x-axis
322
-
323
- Returns
324
-
325
- -------
326
-
327
- data (list:list:float): list of two lists containing the x and y data of the
328
-
329
- ellipse. of the form [[x1, x2, ..., xi],[y1, y2, ..., yi]]
65
+ noisy_ellipse = cv2.ellipse2Poly((0, 0), (50, 70), 45, 0, 360, 5)
66
+
67
+
68
+
69
+ # Extract x coords and y coords of the ellipse as column vectors
70
+
71
+ X = noisy_ellipse[:, 0:1]
72
+
73
+ Y = noisy_ellipse[:, 1:]
74
+
75
+
76
+
77
+ # Formulate and solve the least squares problem ||Ax - b ||^2
78
+
79
+ A = np.hstack([X ** 2, X * Y, Y ** 2])
80
+
81
+ b = np.ones_like(X)
82
+
83
+ x = np.linalg.lstsq(A, b)[0].squeeze()
84
+
85
+
86
+
87
+ # Print the equation of the ellipse in standard form
88
+
89
+ print(
90
+
91
+ "The ellipse is given by {0:.3}x^2 + {1:.3}xy+{2:.3}y^2 = 1".format(
92
+
93
+ x[0], x[1], x[2]
94
+
95
+ )
96
+
97
+ )
98
+
99
+
100
+
101
+
102
+
103
+ def plot_ellipse_by_equation(ax, a, b, c):
104
+
105
+ """a x^2 + b xy + c y^2 = 1 で表される楕円を描画する。
330
106
 
331
107
  """
332
108
 
333
- t = numpy.linspace(0, 2 * numpy.pi, 1000)
334
-
335
- x_noise, y_noise = numpy.random.rand(2, len(t))
336
-
337
-
338
-
339
- ellipse_x = (
340
-
341
- center[0]
342
-
343
- + width * numpy.cos(t) * numpy.cos(phi)
344
-
345
- - height * numpy.sin(t) * numpy.sin(phi)
346
-
347
- + x_noise / 2.0
348
-
349
- )
350
-
351
- ellipse_y = (
352
-
353
- center[1]
354
-
355
- + width * numpy.cos(t) * numpy.sin(phi)
356
-
357
- + height * numpy.sin(t) * numpy.cos(phi)
358
-
359
- + y_noise / 2.0
360
-
361
- )
362
-
363
-
364
-
365
- return [ellipse_x, ellipse_y]
366
-
367
-
368
-
369
- import cv2
109
+ x = -np.linspace(-100, 100, 1000)
110
+
111
+ y = np.linspace(-100, 100, 1000)
112
+
113
+ X, Y = np.meshgrid(x, y)
114
+
115
+ eqn = a * X ** 2 + b * X * Y + c * Y ** 2
116
+
117
+ Z = 1
118
+
119
+
120
+
121
+ # 楕円の式をそのまま描画できる関数は matplotlib にないので、
122
+
123
+ # f = a * X ** 2 + b * X * Y + c * Y ** 2 の f = 1 の等高線を描画する形で
124
+
125
+ # 楕円を描画する。
126
+
127
+ ax.contour(X, Y, eqn, levels=[Z], colors=["r"])
128
+
129
+
130
+
131
+
132
+
133
+ # Plot the noisy data
134
+
135
+ fig, ax = plt.subplots()
136
+
137
+ ax.grid()
138
+
139
+ ax.scatter(X, Y, label="Data Points", color="g")
140
+
141
+ plot_ellipse_by_equation(ax, x[0], x[1], x[2])
142
+
143
+ ```
144
+
145
+
146
+
147
+ ## 結果
148
+
149
+
150
+
151
+ ![イメージ説明](6330b456050f23b7c2b6c56d9d8c4824.png)
152
+
153
+
154
+
155
+ 緑の点がデータ、赤の線が近似した楕円
156
+
157
+
158
+
159
+ ## 追記
160
+
161
+
162
+
163
+ 長軸 (major axis) と x 軸の角度、短軸 (minor axis) と x 軸の角度が考えられますが、楕円の式を行列表記して、固有値問題を問き、出てきた固有ベクトル2つが長軸、短軸になります。(小さい固有値の固有ベクトルが長軸、大きい固有値の固有ベクトルが短軸に対応する)
164
+
165
+
166
+
167
+ [二次形式の意味,微分,標準形など | 高校数学の美しい物語](https://mathtrain.jp/quadraticform)
168
+
169
+
170
+
171
+ あとは逆正弦関数で x 軸と長軸、または x 軸と短軸の角度を求めればよいです。
172
+
173
+
174
+
175
+ ```python
176
+
177
+ import matplotlib.pyplot as plt
370
178
 
371
179
  import numpy as np
372
180
 
373
- import matplotlib.pyplot as plt
181
+
374
-
375
- from matplotlib.patches import Ellipse
182
+
376
-
377
-
378
-
379
- alpha = 5
380
-
381
- beta = 3
382
-
383
- N = 500
384
-
385
- DIM = 2
386
-
387
-
388
-
389
-
390
-
391
- np.random.seed(2)
392
-
393
-
394
-
395
- # Generate random points on the unit circle by sampling uniform angles
396
-
397
- theta = np.random.uniform(0, 2 * np.pi, (N, 1))
398
-
399
- eps_noise = 0.2 * np.random.normal(size=[N, 1])
183
+ fig, ax = plt.subplots(figsize=(6, 6))
400
-
401
- circle = np.hstack([np.cos(theta), np.sin(theta)])
184
+
402
-
403
-
404
-
405
- # Stretch and rotate circle to an ellipse with random linear tranformation
406
-
407
- B = np.random.randint(-3, 3, (DIM, DIM))
408
-
409
- noisy_ellipse = cv2.ellipse2Poly((0, 0), (50, 70), 45, 0, 360, 5)
410
-
411
-
412
-
413
- # Extract x coords and y coords of the ellipse as column vectors
414
-
415
- X = noisy_ellipse[:, 0:1]
416
-
417
- Y = noisy_ellipse[:, 1:]
418
-
419
-
420
-
421
- # Formulate and solve the least squares problem ||Ax - b ||^2
422
-
423
- A = np.hstack([X ** 2, X * Y, Y ** 2])
424
-
425
- b = np.ones_like(X)
426
-
427
- x = np.linalg.lstsq(A, b)[0].squeeze()
428
-
429
-
430
-
431
- # Print the equation of the ellipse in standard form
432
-
433
- print(
185
+ ax.grid()
434
-
435
- "The ellipse is given by {0:.3}x^2 + {1:.3}xy+{2:.3}y^2 = 1".format(
186
+
436
-
187
+
188
+
437
- x[0], x[1], x[2]
189
+ a, b, c = 10, 12, 10
438
-
439
- )
440
-
441
- )
442
190
 
443
191
 
444
192
 
@@ -450,9 +198,9 @@
450
198
 
451
199
  """
452
200
 
453
- x = -np.linspace(-100, 100, 1000)
201
+ x = np.linspace(-1, 1, 100)
454
-
202
+
455
- y = np.linspace(-100, 100, 1000)
203
+ y = np.linspace(-1, 1, 100)
456
204
 
457
205
  X, Y = np.meshgrid(x, y)
458
206
 
@@ -472,28 +220,60 @@
472
220
 
473
221
 
474
222
 
475
-
223
+ # 固有値問題を解く
224
+
476
-
225
+ Q = np.array([[a, b / 2], [b / 2, c]])
226
+
227
+ eig_val, eig_vec = np.linalg.eig(Q)
228
+
477
- # Plot the noisy data
229
+ print(eig_val, eig_vec)
230
+
231
+
232
+
478
-
233
+ # 長軸、短軸を求める。
234
+
479
- fig, ax = plt.subplots()
235
+ idx1, idx2 = eig_val.argsort()
236
+
480
-
237
+ major_axis = eig_vec[:, idx1] # 固有値が小さいほうの固有ベクトルが major axis
238
+
239
+ minor_axis = eig_vec[:, idx2] # 固有値が大きいほうの固有ベクトルが minor axis
240
+
241
+
242
+
481
- ax.grid()
243
+ ax.annotate(
482
-
244
+
483
- ax.scatter(X, Y, label="Data Points", color="g")
245
+ s="", xy=major_axis, xytext=(0, 0), arrowprops=dict(facecolor="r"),
246
+
484
-
247
+ )
248
+
249
+ ax.annotate(
250
+
251
+ s="", xy=minor_axis, xytext=(0, 0), arrowprops=dict(facecolor="b"),
252
+
253
+ )
254
+
255
+
256
+
485
- plot_ellipse_by_equation(ax, x[0], x[1], x[2])
257
+ plot_ellipse_by_equation(ax, a, b, c)
258
+
259
+
260
+
261
+ # 角度を求める
262
+
263
+ theta1 = np.rad2deg(np.arctan(major_axis[1] / major_axis[0]))
264
+
265
+ theta2 = np.rad2deg(np.arctan(minor_axis[1] / minor_axis[0]))
266
+
267
+ print(f"angle between major axis and x axis= {theta1:.2f}")
268
+
269
+ print(f"angle between minor axis and x axis= {theta2:.2f}")
270
+
271
+ # angle between major axis and x axis= -45.00
272
+
273
+ # angle between minor axis and x axis= 45.00
486
274
 
487
275
  ```
488
276
 
489
277
 
490
278
 
491
- ## 結果
492
-
493
-
494
-
495
- ![イメージ説明](6330b456050f23b7c2b6c56d9d8c4824.png)
279
+ ![イメージ説明](b145bd531d08d205d36f4fde20ad4cfc.png)
496
-
497
-
498
-
499
- 緑の点がデータ、赤の線が近似した楕円

2

d

2020/01/22 12:11

投稿

tiitoi
tiitoi

スコア21956

test CHANGED
@@ -485,3 +485,15 @@
485
485
  plot_ellipse_by_equation(ax, x[0], x[1], x[2])
486
486
 
487
487
  ```
488
+
489
+
490
+
491
+ ## 結果
492
+
493
+
494
+
495
+ ![イメージ説明](6330b456050f23b7c2b6c56d9d8c4824.png)
496
+
497
+
498
+
499
+ 緑の点がデータ、赤の線が近似した楕円

1

d

2020/01/22 06:27

投稿

tiitoi
tiitoi

スコア21956

test CHANGED
@@ -46,6 +46,10 @@
46
46
 
47
47
 
48
48
 
49
+ 楕円の点を作成するのに OpenCV を使っていますが、本題には関係ないので、その部分は CSV から読み込むように置き換えてください。
50
+
51
+
52
+
49
53
  ```python
50
54
 
51
55
  import numpy