LKY 只有原創內容的 Blog

今之能者,謂能轉貼,至於魯蛇,皆能轉貼。不原創,何以別乎?

  1. 1. 入門 for loop 寫法,速度定義為 1x
  2. 2. 用 NumPy 向量化計算,速度 200x

上一篇已經介紹過用「Boolean array indexing」的技巧,快速過濾一維陣列。

這一篇我們要用顏色過濾的需求,來示範快速過濾三維陣列。


需求:把圖片中的膚色去除,只留下其他顏色

入門 for loop 寫法,速度定義為 1x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import NumPy as np
import cv2 as cv
import time

# 需求:把圖片中的膚色去除,只留下其他顏色
# 注意:這份程式碼只是示範如何使用向量化,並不是在教學如何做膚色檢測,不要來跟我槓膚色檢測效果好壞

img = cv.imread("boys.jpg")
lower_skin = [0, 48, 80]
upper_skin = [18, 225, 255]

# 方法1: 使用 for 迴圈 (最慢)
img_forloop = img.copy()
img_hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) # 轉換成 HSV
h, s, v = img_hsv[:, :, 0], img_hsv[:, :, 1], img_hsv[:, :, 2] # 分離出 H, S, V
start_time = time.time()
for i in range(img.shape[0]):
for j in range(img.shape[1]):
if h[i, j] > lower_skin[0] and h[i, j] < upper_skin[0] and \
s[i, j] > lower_skin[1] and s[i, j] < upper_skin[1] and \
v[i, j] > lower_skin[2] and v[i, j] < upper_skin[2]:
img_forloop[i, j] = 0
elapsed_time_for_loop = time.time() - start_time
print("Elapsed time for loop: %s seconds" % (elapsed_time_for_loop))

輸出如下:

1
Elapsed time for loop: 0.38222789764404297 seconds

上面這個程式碼,就是一般初學者最直覺,用 for loop 寫出來的程式碼,但是效能很差,for 迴圈一次只能處理一個數,數據頻繁的進出往來 RAM 與 CPU,拖慢很多時間。


用 NumPy 向量化計算,速度 200x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法2: 使用 NumPy 向量化
# 以上重複部份程式碼省略
img_vectorlize = img.copy()
img_hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) # 轉換成 HSV
h, s, v = img_hsv[:, :, 0], img_hsv[:, :, 1], img_hsv[:, :, 2] # 分離出 H, S, V
start_time = time.time()
skin_mask = np.bitwise_and.reduce([
h > lower_skin[0], h < upper_skin[0],
s > lower_skin[1], s < upper_skin[1],
v > lower_skin[2], v < upper_skin[2]
])
img_vectorlize[skin_mask] = 0
elapsed_time_vectorlize = time.time() - start_time
print("Elapsed time vectorlize: %s seconds" % (elapsed_time_vectorlize))

輸出如下:

1
Elapsed time vectorlize: 0.001753091812133789 seconds

技巧上的大方向,跟上一篇是一樣的。

我們不逐一比較,而是先產生一個篩子,一個與樣本數量同樣長度的篩子,直接拿這個篩子去一次過濾所有樣本,只要一次!

只是這一次需求更加複雜。上只有一個條件,這一次有六個條件取交集,所以用 np.bitwise_and.reduce() 來產生篩子。

我用以下程式碼,給六個條件都產生與樣本數量同樣維度的篩子

1
2
3
4
5
[
h > lower_skin[0], h < upper_skin[0],
s > lower_skin[1], s < upper_skin[1],
v > lower_skin[2], v < upper_skin[2]
]

把這六個篩子放進一個陣列。

為什麼用 np.bitwise_and.reduce()

bitwise_and 就是取交集、reduce 就是把陣列裡面的元素做某種「化N合1」的運算。

所以這六個篩子就會化簡成一個,篩出所有符合條件的樣本。

最後可以用 img_vectorlize[skin_mask] = 0 一次對符合六個條件的樣本,改顏色。

在我完整的程式碼中,還用了 np.array_equal() 來驗證兩種方法的結果是否一樣。


原圖:

原圖

膚色換黑色

膚色換黑色

膚色換綠色

膚色換綠色


這篇文章主要是介紹了 NumPy 的向量化計算,用一個生活化簡單案例,來說明 NumPy 向量化計算的快速。

這篇文章的完整程式碼,可以在這裡找到:https://gist.github.com/mosdeo/5b0193bead09251f13e649f5cf6129da

本文最后更新于 天前,文中所描述的信息可能已发生改变