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

回答編集履歴

1

解説追記

2018/07/26 14:05

投稿

copepoda
copepoda

スコア324

answer CHANGED
@@ -17,4 +17,95 @@
17
17
 
18
18
  環境はだいぶ違いますが、Windows 10 (Core i5)、4 コア(論理プロセッサの最大)で比較したところ
19
19
  5472x3648 ピクセルの画像は、約 130 秒が 5 秒に、
20
- 1920x1200 ピクセルの画像は、約 9 秒が 2 秒に短縮しました。
20
+ 1920x1200 ピクセルの画像は、約 9 秒が 2 秒に短縮しました。
21
+
22
+ ---
23
+ **[以降追記]**
24
+ まずは、NumPy についてです。
25
+
26
+ [NumPy - Wikipedia](https://ja.wikipedia.org/wiki/NumPy#%E7%9B%AE%E7%9A%84)
27
+ > 目的
28
+ > Pythonは動的型付け言語であるため、プログラムを柔軟に記述できる一方で、純粋にPythonのみを使って数値計算を行うと、ほとんどの場合C言語やJavaなどの静的型付き言語で書いたコードに比べて大幅に計算時間がかかる。そこでNumPyは、Pythonに対して型付きの多次元配列オブジェクト (numpy.ndarray) と、その配列に対する多数の演算関数や操作関数を提供することにより、この問題を解決しようとしている。NumPyの内部はC言語 (およびFortran)によって実装されているため非常に高速に動作する。したがって、目的の処理を、大きな多次元配列(ベクトル・行列など)に対する演算として記述できれば(ベクトル化できれば)、計算時間の大半はPythonではなくC言語によるネイティブコードで実行されるようになり大幅に高速化する。
29
+
30
+ したがって、以下のように配列の 1 要素ごとに Python で演算を行っていたのでは NumPy を活かすことができません。
31
+
32
+ ```Python
33
+ def changeToGray( number: int, width: np.ndarray ):
34
+ for pixel in width:
35
+ gray = int(pixel[0]*0.3) + int(pixel[1]*0.59) + int(pixel[2]*0.11)
36
+ pixel[0] = gray
37
+ pixel[1] = gray
38
+ pixel[2] = gray
39
+ return number, width
40
+ ```
41
+
42
+ `np.tile((width * [0.3, 0.59, 0.11]).astype(np.int).sum(axis=1), (3, 1)).T` は、
43
+ “配列に対する多数の演算関数や操作関数”を使用するように置き換えたものです。
44
+
45
+ NumPy では `+` や `*` などの演算子は、配列同士の演算ができるように定義されています。
46
+ 以下のように、配列 `a` と `b` に対して、`a + b` を行った結果は、同じ位置の要素同士を加算したものになります。
47
+
48
+ ```Python
49
+ >>> a = np.arange(6).reshape(2, 3)
50
+ >>> a
51
+ array([[0, 1, 2],
52
+ [3, 4, 5]])
53
+
54
+ >>> b = np.ones((2, 3))
55
+ >>> b
56
+ array([[1., 1., 1.],
57
+ [1., 1., 1.]])
58
+
59
+ >>> a + b
60
+ array([[1., 2., 3.],
61
+ [4., 5., 6.]])
62
+ ```
63
+
64
+ また、「ブロードキャスト」といって、長さが異なる配列の演算は、不足している要素を、ルールにしたがって自動的に補完してくれます。詳しくは「NumPy ブロードキャスト」などで検索してください。
65
+
66
+ ```Python
67
+ >>> c = np.array([3, 5, 7])
68
+ >>> c
69
+ array([3, 5, 7])
70
+
71
+ >>> a + c
72
+ array([[ 3, 6, 9],
73
+ [ 6, 9, 12]])
74
+
75
+ >>> d = np.array([5])
76
+ >>> d
77
+ array([5])
78
+
79
+ >>> a + d
80
+ array([[ 5, 6, 7],
81
+ [ 8, 9, 10]])
82
+ ```
83
+
84
+ つまり、`width * [0.3, 0.59, 0.11]` は、以下の処理と等価です。
85
+
86
+ ```Python
87
+ for pixel in width:
88
+ gray = pixel[0]*0.3 + pixel[1]*0.59 + pixel[2]*0.11
89
+ ```
90
+
91
+ この結果に対して、`.astype(np.int)` は配列の `int` 変換を、`.sum(axis=1)` は 1 次元の要素同士の合計(つまりR+G+B)を求めています。
92
+ そして、その合計を `np.tile` によって、元の長さに戻しています。
93
+
94
+ ```Python
95
+ >>> e = np.tile(np.arange(5), (3, 1))
96
+ >>> e
97
+ array([[0, 1, 2, 3, 4],
98
+ [0, 1, 2, 3, 4],
99
+ [0, 1, 2, 3, 4]])
100
+ ```
101
+
102
+ ただし、このままだと、0次元目と1次元目が入れ替わってしまっているため、`.T` で転置しています。
103
+
104
+ ```Python
105
+ >>> e.T
106
+ array([[0, 0, 0],
107
+ [1, 1, 1],
108
+ [2, 2, 2],
109
+ [3, 3, 3],
110
+ [4, 4, 4]])
111
+ ```