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

回答編集履歴

1

追記

2020/03/01 12:35

投稿

8524ba23
8524ba23

スコア38352

answer CHANGED
@@ -1,5 +1,94 @@
1
1
  > アウトラインの修正
2
2
 
3
+ [Skeletonize](https://scikit-image.org/docs/dev/auto_examples/edges/plot_skeleton.html)という手法が使えそうです。
4
+ これにて閉領域の中心を通る線を得られます。
5
+ さらに[Skeleton Network](https://github.com/Image-Py/sknw)にてこれをグラフ化できます。
6
+ グラフの`Edge`から線、あるいはBezier曲線を得ることができます。
7
+ 以上を組み合わせると以下のようなSVGを出力するコードになります。
8
+ ```Python
9
+ import numpy as np
10
+ from numpy import array, linalg, matrix
11
+ from scipy.special import comb
12
+ import cv2
13
+ from skimage.morphology import skeletonize
14
+ import sknw
15
+ import svgwrite
16
+
17
+ #
18
+ # Bézier curve fitting with SciPy
19
+ # https://stackoverflow.com/questions/12643079/b%C3%A9zier-curve-fitting-with-scipy
20
+ #
21
+ Mtk = lambda n, t, k: t**(k)*(1-t)**(n-k)*comb(n,k)
22
+ bezierM = lambda ts: matrix([[Mtk(3,t,k) for k in range(4)] for t in ts])
23
+
24
+ def lsqfit(points, M):
25
+ M_ = linalg.pinv(M)
26
+ return M_ * points
27
+
28
+ def get_bezier(pts):
29
+ points = array(pts)
30
+ ts = array(range(points.shape[0]), dtype='float')/(points.shape[0]-1)
31
+ M = bezierM(ts)
32
+ control_points = lsqfit(points, M)
33
+ return control_points.tolist()
34
+
35
+
36
+ if __name__ == '__main__':
37
+
38
+ fname = 'test'
39
+ src = cv2.imread(fname + '.png', cv2.IMREAD_GRAYSCALE)
40
+ _, src = cv2.threshold(src, 192, 255, cv2.THRESH_BINARY)
41
+
42
+ # Skeleton化
43
+ # https://scikit-image.org/docs/dev/auto_examples/edges/plot_skeleton.html
44
+ ske = skeletonize(~(src != 0))
45
+ ske_gray = (ske * 255).astype(np.uint8)
46
+ ske_rgb = cv2.cvtColor(ske_gray, cv2.COLOR_GRAY2RGB)
47
+ cv2.imwrite(fname + '_ske.png', ske_rgb)
48
+
49
+ # Skeleton Networkを作成
50
+ # https://github.com/Image-Py/sknw
51
+ graph = sknw.build_sknw(ske.astype(np.uint16), multi=True)
52
+
53
+ dwg = svgwrite.Drawing(fname + '.svg', profile='tiny')
54
+
55
+ # Edge
56
+ for (s,e) in graph.edges():
57
+
58
+ pt_s = graph.node[s]['o'].tolist()
59
+ pt_e = graph.node[e]['o'].tolist()
60
+
61
+ for g in graph[s][e].values():
62
+
63
+ # 開始 + 中間点 + 終点
64
+ pts = g['pts'].tolist()
65
+ pts = [pt_s] + pts + [pt_e]
66
+
67
+ # 点群にフィットするBezierのパラメータを取得
68
+ params = get_bezier(pts)
69
+
70
+ # Bezierとして描画
71
+ d = 'M{},{} C{},{} {},{}, {},{}'.format( params[0][0], params[0][1], params[1][0], params[1][1], params[2][0], params[2][1], params[3][0], params[3][1])
72
+ p = dwg.path( d=d, stroke='#000', fill='none', stroke_width=5)
73
+ dwg.add(p)
74
+
75
+ # 線をそのまま描画
76
+ #for i in range(len(pts)-1):
77
+ # dwg.add(dwg.line(pts[i], pts[i+1], stroke='#000', stroke_width=5))
78
+
79
+ dwg.save()
80
+ ```
81
+ 結果は、まあそれなりですね。
82
+
83
+ 元画像
84
+ ![イメージ説明](1ef77af0f205797387a95ee73115510c.png)
85
+ Skelton化した画像
86
+ ![イメージ説明](3b66ff8aaebd52c86dc0ae7272b91094.png)
87
+ SVGをpng化した画像
88
+ ![イメージ説明](e423449246bf0d7a794c45dddb98bf42.png)
89
+ 修正前
90
+ -----
91
+
3
92
  変換結果の画像を見ると、アウトラインのひとつひとつはもともとは2次元ポリゴンで構成されているかと思われます。
4
93
  であれば[Medial axis](https://en.wikipedia.org/wiki/Medial_axis)、限定(直線)的には[Straight skeleton](https://en.wikipedia.org/wiki/Straight_skeleton)が求めたいラインに近いものになるかと思います。
5
94
  `python`でのそのものずばりの実装は見つかりませんでしたが、以下が参考になるかと思います。