傳送門
思路
??使用NumPy。NumPy對數(shù)組和矩陣的運算有大幅度的提速戈盈。因此欲诺,使用NumPy設(shè)計算法時瓢喉,應(yīng)該充分利用這一特性狱庇,盡可能用NumPy中的矩陣運算來代替遍歷等耗時的操作惊畏。
RGB轉(zhuǎn)HSV
非矩陣的方法
??根據(jù)RGB和HSV的轉(zhuǎn)換公式可以構(gòu)建出以下數(shù)值計算的代碼恶耽,使用控制語句實現(xiàn)分段函數(shù),使用python內(nèi)置函數(shù)實現(xiàn)數(shù)學(xué)運算颜启。 然而偷俭,以下代碼只對一個像素點進行轉(zhuǎn)換,對于一張1000*1000的圖片农曲,需要循環(huán)調(diào)用100萬次社搅。顯然驻债,這是一種容易理解的算法乳规,但性能并不好。
def rgb2hsv(r, g, b):
r, g, b = r / 255.0, g / 255.0, b / 255.0
mx = max(r, g, b)
mn = min(r, g, b)
df = mx - mn
if mx == mn:
h = 0
elif mx == r:
h = (60 * ((g - b) / df) + 360) % 360
elif mx == g:
h = (60 * ((b - r) / df) + 120) % 360
elif mx == b:
h = (60 * ((r - g) / df) + 240) % 360
if mx == 0:
s = 0
else:
s = df / mx
v = mx
return h, s, v
NumPy的方法
??算術(shù)運算部分比較簡單合呐,可以直接轉(zhuǎn)換為NumPy中的操作暮的。以下代碼對應(yīng)上述代碼的第2-5行。其中np.max和np.min的axis=-1
表示在數(shù)組的最后一個維度中求最值淌实,也就是在每個像素點的[r, g, b]中求最值冻辩,keepdims=True
表示求出最值后保持原來的維度。代碼中的注釋表示數(shù)組的形狀拆祈。
def rgb2hsv_mat(img):
rgbImg = np.array(img, dtype=np.float) / 255.0 # (height, width, 3)
maxVal = np.max(rgbImg, axis=-1, keepdims=True) # (height, width, 1), 若keepdims=False則為(height, width)
minVal = np.min(rgbImg, axis=-1, keepdims=True) # (height, width, 1)
difVal = maxVal - minVal # (height, width, 1)
??實現(xiàn)比較復(fù)雜的是其中的分段函數(shù)恨闪,如何對數(shù)組中的每個像素點同時進行判斷呢?這里需要用到掩碼(mask)的概念放坏。NumPy支持邏輯運算和布爾運算咙咽,如rgbImg == 255
將輸出大小與rgbImg相同的矩陣,這個結(jié)果就是掩碼數(shù)組淤年,其中每個元素的值是rgbImg中對應(yīng)位置的元素與255作==
運算的結(jié)果钧敞。利用掩碼數(shù)組作為因子可以在操作時“過濾”掉不滿足條件的元素。
??該算法根據(jù)每個點的最大值的不同采用不同的H值計算方法麸粮,因此需要對maxVal
數(shù)組作4個布爾運算溉苛,得到4個掩碼數(shù)組,代碼如下弄诲。mask0比較好理解愚战。在mask1中,rgbImg[:, :, :1]
表示對數(shù)組中每個點只取r的值齐遵,且保留原數(shù)組維度寂玲,因此布爾表達式maxVal == rgbImg[:, :, :1]
就是判斷數(shù)組中每個點的最大值是否等于r所得到的掩碼數(shù)組。對于g, b的判斷同理洛搀。
??根據(jù)每個點最大值的不同敢茁,計算各點H的值的公式也有所不同。使用NumPy時留美,將各公式的計算結(jié)果乘上對應(yīng)的掩碼數(shù)組彰檬,不滿足要求的位置都被置為0伸刃,只有滿足要求的位置有計算結(jié)果,然后加到結(jié)果數(shù)組中逢倍。需要特別注意的是捧颅,mx == mn
、mx == r
较雕、mx == g
和mx == b
同時成立時只能取mx == r
時的結(jié)果碉哑,也就是對于同一個位置如果mask0
、mask1
亮蒋、mask2
和mask3
中的取值均為1扣典,需要將mask1
、mask2
和mask3
中該位置上的1置為0慎玖,只考慮最先出現(xiàn)1的掩碼贮尖,這與if xxx else if xxx
的邏輯是相對應(yīng)的。因此對于上面求出的每個mask趁怔,都需要將其和其前面的每一個mask的非~
作與&
操作湿硝,如mask1 &= ~mask0
.
??最后,difVal作為計算公式的除數(shù)需要考慮除0的問題润努。由于difVal
中為0的位置在后續(xù)掩碼中一定為0关斜,因此可將difVal中這些位置的上的數(shù)置為任意非0的數(shù),避免出現(xiàn)除0異常铺浇。后續(xù)S和V的求法比較簡單痢畜,請直接見最終代碼。
最終代碼
def rgb2hsv_mat(img):
rgbImg = np.array(img, dtype=np.float) / 255.0
maxVal = np.max(rgbImg, axis=-1, keepdims=True)
minVal = np.min(rgbImg, axis=-1, keepdims=True)
difVal = maxVal - minVal
h, w, _ = rgbImg.shape
HSV = np.zeros([h, w, 3]) # 初始化HSV圖像的數(shù)組用于存儲結(jié)果
mask0 = np.array(maxVal == minVal, dtype=np.int) # 判斷mx == mn
mask1 = np.array(maxVal == rgbImg[:, :, :1], dtype=np.int) # 判斷mx == r
mask2 = np.array(maxVal == rgbImg[:, :, 1:2], dtype=np.int) # 判斷mx == g
mask3 = np.array(maxVal == rgbImg[:, :, 2:], dtype=np.int) # 判斷mx == b
for i in range(4):
for j in range(i + 1, 4):
masks[i] &= ~masks[j]
difValNonZero = difVal - (difVal == 0)
H = HSV[:, :, :1]
H += mask1 * ((60 * ((rgbImg[:, :, 1:2] - rgbImg[:, :, 2:3]) / difValNonZero) + 360) % 360)
H += mask2 * ((60 * ((rgbImg[:, :, 2:3] - rgbImg[:, :, 0:1]) / difValNonZero) + 120) % 360)
H += mask3 * ((60 * ((rgbImg[:, :, 0:1] - rgbImg[:, :, 1:2]) / difValNonZero) + 240) % 360)
mask4 = np.array(maxVal != 0, dtype=np.int)
S = HSV[:, :, 1:2]
maxValNonZero = maxVal - (maxVal == 0)
S += mask4 * (difVal / maxValNonZero)
S *= np.array(S >= 0, dtype=np.int)
V = HSV[:, :, 2:]
V += maxVal
return HSV
對比實驗
??下圖是兩種方法的計算時間隨圖片變長變化的曲線随抠,藍色是非NumPy的方法的時間曲線裁着,黃色是NumPy的方法的時間曲線。
??下圖是NumPy方法單獨的時間曲線拱她,根據(jù)曲線可以推測其時間復(fù)雜度仍然為二驰,因此NumPy的方法并沒有降低算法的時間復(fù)雜度,只是從編譯層面對Python中的數(shù)學(xué)運算進行了優(yōu)化秉沼。