美文网首页知识的搬运者
【Python图像处理】RGB颜色转HSV颜色的快速实现

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

作者: liadrinz | 来源:发表于2020-02-11 16:58 被阅读0次

    传送门

    思路

      使用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

    相关文章

      网友评论

        本文标题:【Python图像处理】RGB颜色转HSV颜色的快速实现

        本文链接:https://www.haomeiwen.com/subject/avxxfhtx.html