【Python圖像處理】RGB顏色轉(zhuǎn)HSV顏色的快速實現(xiàn)

傳送門

思路

??使用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 == mnmx == r较雕、mx == gmx == b同時成立時只能取mx == r時的結(jié)果碉哑,也就是對于同一個位置如果mask0mask1亮蒋、mask2mask3中的取值均為1扣典,需要將mask1mask2mask3中該位置上的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ù)雜度仍然為O(n^2)二驰,因此NumPy的方法并沒有降低算法的時間復(fù)雜度,只是從編譯層面對Python中的數(shù)學(xué)運算進行了優(yōu)化秉沼。

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桶雀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子唬复,更是在濱河造成了極大的恐慌矗积,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敞咧,死亡現(xiàn)場離奇詭異棘捣,居然都是意外死亡,警方通過查閱死者的電腦和手機休建,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門乍恐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來评疗,“玉大人,你說我怎么就攤上這事茵烈“俅遥” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵呜投,是天一觀的道長加匈。 經(jīng)常有香客問我,道長仑荐,這世上最難降的妖魔是什么雕拼? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮释漆,結(jié)果婚禮上悲没,老公的妹妹穿的比我還像新娘。我一直安慰自己男图,他們只是感情好,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布甜橱。 她就那樣靜靜地躺著逊笆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岂傲。 梳的紋絲不亂的頭發(fā)上难裆,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音镊掖,去河邊找鬼乃戈。 笑死,一個胖子當著我的面吹牛亩进,可吹牛的內(nèi)容都是我干的症虑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼归薛,長吁一口氣:“原來是場噩夢啊……” “哼谍憔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起主籍,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤习贫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后千元,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苫昌,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年幸海,在試婚紗的時候發(fā)現(xiàn)自己被綠了祟身。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屋厘。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖月而,靈堂內(nèi)的尸體忽然破棺而出汗洒,到底是詐尸還是另有隱情,我是刑警寧澤父款,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布溢谤,位于F島的核電站,受9級特大地震影響憨攒,放射性物質(zhì)發(fā)生泄漏世杀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一肝集、第九天 我趴在偏房一處隱蔽的房頂上張望瞻坝。 院中可真熱鬧,春花似錦杏瞻、人聲如沸所刀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浮创。三九已至,卻和暖如春砌函,著一層夾襖步出監(jiān)牢的瞬間斩披,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工讹俊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垦沉,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓仍劈,卻偏偏與公主長得像厕倍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耳奕,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348