【Python图像处理】RGB颜色转HSV颜色的快速实现

传送门

思路

  使用NumPy。NumPy对数组和矩阵的运算有大幅度的提速。因此,使用NumPy设计算法时,应该充分利用这一特性,尽可能用NumPy中的矩阵运算来代替遍历等耗时的操作

RGB转HSV

非矩阵的方法

  根据RGB和HSV的转换公式可以构建出以下数值计算的代码,使用控制语句实现分段函数,使用python内置函数实现数学运算。 然而,以下代码只对一个像素点进行转换,对于一张1000*1000的图片,需要循环调用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的方法

  算术运算部分比较简单,可以直接转换为NumPy中的操作。以下代码对应上述代码的第2-5行。其中np.max和np.min的axis=-1表示在数组的最后一个维度中求最值,也就是在每个像素点的[r, g, b]中求最值,keepdims=True表示求出最值后保持原来的维度。代码中的注释表示数组的形状。

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)

  实现比较复杂的是其中的分段函数,如何对数组中的每个像素点同时进行判断呢?这里需要用到掩码(mask)的概念。NumPy支持逻辑运算和布尔运算,如rgbImg == 255将输出大小与rgbImg相同的矩阵,这个结果就是掩码数组,其中每个元素的值是rgbImg中对应位置的元素与255作==运算的结果。利用掩码数组作为因子可以在操作时“过滤”掉不满足条件的元素。

  该算法根据每个点的最大值的不同采用不同的H值计算方法,因此需要对maxVal数组作4个布尔运算,得到4个掩码数组,代码如下。mask0比较好理解。在mask1中,rgbImg[:, :, :1]表示对数组中每个点只取r的值,且保留原数组维度,因此布尔表达式maxVal == rgbImg[:, :, :1]就是判断数组中每个点的最大值是否等于r所得到的掩码数组。对于g, b的判断同理。

  根据每个点最大值的不同,计算各点H的值的公式也有所不同。使用NumPy时,将各公式的计算结果乘上对应的掩码数组,不满足要求的位置都被置为0,只有满足要求的位置有计算结果,然后加到结果数组中。需要特别注意的是,mx == mnmx == rmx == gmx == b同时成立时只能取mx == r时的结果,也就是对于同一个位置如果mask0mask1mask2mask3中的取值均为1,需要将mask1mask2mask3中该位置上的1置为0,只考虑最先出现1的掩码,这与if xxx else if xxx的逻辑是相对应的。因此对于上面求出的每个mask,都需要将其和其前面的每一个mask的非~作与&操作,如mask1 &= ~mask0.

  最后,difVal作为计算公式的除数需要考虑除0的问题。由于difVal中为0的位置在后续掩码中一定为0,因此可将difVal中这些位置的上的数置为任意非0的数,避免出现除0异常。后续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图像的数组用于存储结果
    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方法单独的时间曲线,根据曲线可以推测其时间复杂度仍然为O(n^2),因此NumPy的方法并没有降低算法的时间复杂度,只是从编译层面对Python中的数学运算进行了优化。

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357