pythonopencvimage-processingrgbhsv

Increasing the speed of the color space conversion algorithm from HSV to RGB


I work on image pixels. I want to colorize my image in a specific formula and convert and save the image into RGB space after working with HSV. Opencv has functions to convert color spaces, But the color of the image changes my image. and I use the function to convert the pixel pixel, but my loop has a lot of time to run. I wrote a piece of code in Python that contains a loop that takes a lot of time to execute. What operational solutions do you have to reduce execution time? I used the thread, but it is not implemented correctly and the time does not decrease. Also, I used the np.apply_along_axis, but, this function increased the run time!

for i in range(row):
    for j in range(col):
        final[i][j][0], final[i][j][1], final[i][j][2] = colorir.HSV(final[i][j][0], final[i][j][1], final[i][j][2], max_sva=255).rgb()

When I convert the color with this code, my image is displayed in the correct color that I want, but when I use the following function, the image coloring is completely wrong:

final = cv2.cvtColor(final,cv2.COLOR_HSV2RGB)

or

final = matplotlib.colors.hsv_to_rgb(final)

Another question that occurred to me is that Is there a way to save an image that is in color space HSV without converting it to color space RGB? Without coloring it wrong? So that I don't have to use this snippet of runtime code above to convert?

Edit:

My image size is variable: for example 600x600 The execution time of these loops is about 15 seconds, which should be approximately less than 1 second. I color the gray level image.

Below is my executable code:

import numpy as np
from numpy import newaxis
import cv2
from colorir import HSV, sRGB

zero1 = np.zeros((row, col), dtype=int)
new1 = np.dstack((zero1, zero1, num12))

new1 = np.where(num12 > 250, 0, num12)
newww = np.where(new1 < 0, 0, new1)

minus = np.subtract(num1, num2)
minus_2 = minus

minus = np.where(minus <=0, 33, minus)
minus = np.where(num2 >= np.multiply(num1 , 1.1), 33, minus)
minus = np.where(np.logical_and(num2 <= np.multiply(num1 , 1.1),num2 >= np.multiply(num1 , 1)), 107, minus)
minus = np.where(num2 < np.multiply(num1 , 1), 209, minus)
a_255 = np.full([row, col], 255, dtype=int)
final = np.dstack((minus, newww, a_255))
for i in range(row):
    for j in range(col):
        final[i][j][0], final[i][j][1], final[i][j][2] = HSV(final[i][j][0], final[i][j][1], final[i][j][2], max_sva=255).rgb()

My final image should only contain the green, blue, and orange colors I specified, but the image colored by functions 1 is pink and yellow, and unrelated colors.

A small example:

final = [[[105, 213, 235], [105, 213, 235], [105, 213, 235], [105, 213, 235], [105, 213, 235], [105, 213, 235]]]
final = np.asarray(final)
final = cv2.cvtColor(final,cv2.COLOR_HSV2BGR)
cv2.imshow("image", final)
cv2.waitKey(0)

When I run above sample code, with cv2.cvtColor(final,cv2.COLOR_HSV2BGR), I encounter:

error (Unsupported depth of input image: 'VDepth::contains(depth)' where 'depth' is 4 (CV_32S))

I have to use np.float32, but np.float32 color will spoil the final image.


Solution

  • There are two issues here:

    1. OpenCV uses a range of 0-180 for the hue channel (because 360 doesn't fit in an 8-bit integer).
    2. OpenCV defaults to BGR, but if you want RGB, you need to use cv2.COLOR_HSV2RGB, not cv2.COLOR_HSV2BGR.

    Here is my version of your example at the end:

    import numpy as np
    import cv2
    from colorir import HSV, sRGB
    
    hsv = [[[105, 213, 235], [105, 213, 235], [105, 213, 235]]]
    hsv = np.asarray(hsv)
    
    rgb_colorir = np.zeros_like(hsv)
    for i in range(hsv.shape[0]):
        for j in range(hsv.shape[1]):
            rgb_colorir[i][j][0], rgb_colorir[i][j][1], rgb_colorir[i][j][2] = HSV(hsv[i][j][0], hsv[i][j][1], hsv[i][j][2], max_sva=255).rgb()
    
    hsv[:,:,0] //= 2  # OpenCV's different definition
    hsv = hsv.astype(np.uint8)  # OpenCV requires uint8
    rgb_opencv = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
    

    Examining the converted images:

    >>> rgb_colorir
    array([[[ 88, 235,  39],
            [ 88, 235,  39],
            [ 88, 235,  39]]])
    >>> rgb_opencv
    array([[[ 91, 235,  39],
            [ 91, 235,  39],
            [ 91, 235,  39]]], dtype=uint8)
    

    So we can see that there's a small difference in the red channel, which is likely caused by rounding errors. For the OpenCV case we had to round the hue to the nearest even number of degrees (because the integer divide by 2). It should be fairly difficult to see this difference.


    If you want to use the more precise conversion, note that colorir.HSV is just calling colorsys.hsv_to_rgb, and doing a whole lot of extra work like building an object. But it doesn't give you anything extra except a normalization. Using colorsys.hsv_to_rgb directly should be faster. Also, colorsys is part of the standard library, using it directly should be preferred.

    Use np.apply_along_axis() to iterate over your image. This should be relatively fast, but it will not be as fast as the OpenCV solution.

    import colorsys
    
    rgb_colorsys = np.apply_along_axis(
       lambda hsv: colorsys.hsv_to_rgb(hsv[0] / 360, hsv[1] / 255, hsv[2] / 255),
       2,
       hsv,
    ) * 255
    

    The result:

    >>> rgb_colorsys
    array([[[ 87.77941176, 235.        ,  38.70588235],
            [ 87.77941176, 235.        ,  38.70588235],
            [ 87.77941176, 235.        ,  38.70588235]]])
    

    Of course there are other libraries you could try, such as scikit-image:

    import skimage.color
    
    rgb_skimage = skimage.color.hsv2rgb(hsv / [360, 255, 255]) * 255
    

    Scikit-image also has a different definition for how it stores the HSV values. It also works in floating-point format, to avoid rounding errors. Result:

    >>> rgb_skimage
    array([[[ 87.77941176, 235.        ,  38.70588235],
            [ 87.77941176, 235.        ,  38.70588235],
            [ 87.77941176, 235.        ,  38.70588235]]])
    

    With DIPlib (disclosure: I'm an author):

    import diplib as dip
    
    hsv_dip = dip.Convert(dip.Image(hsv, tensor_axis=2), "SFLOAT")
    hsv_dip.SetColorSpace("HSV")
    rgb_dip = dip.ColorSpaceManager.Convert(hsv_dip / [1, 255, 1], "RGB")
    

    Result:

    >>> np.asarray(rgb_dip)
    array([[[ 87.77941176, 235.        ,  38.70588235],
            [ 87.77941176, 235.        ,  38.70588235],
            [ 87.77941176, 235.        ,  38.70588235]]])
    

    Notice how OpenCV is the only implementation that has significant rounding errors, the others produce identical values.

    For the floating-point results, np.round(...).astype(np.uint8) will get you an RGB image that you can display normally in pyplot:

    >>> np.round(rgb_dip).astype(np.uint8)
    array([[[ 88, 235,  39],
            [ 88, 235,  39],
            [ 88, 235,  39]]], dtype=uint8)