Dhruv Marwha
Dhruv Marwha

Reputation: 1074

RGB to TSL color space conversion issue in Python

I've written code to convert an RGB image into a TSL (Tint, saturation, lightness) color space using python. I've also implemented the reverse function to check if the image is being re-generated proeprly so I know that my implementation is correct.

In this, I have doubts about my implementation as I am not able to re-generate the input image again.

I am not sure exactly in what part am I making this error. I've followed the wikipedia formula for TSL to RGB

For the RGB to TSL conversion, I've referred to the original paper RGB to TSL

Following the input from @statemachine, I have incorporated his edits.

def rgb_tsl(image_path, gamma_factor):
    # BGR order is read by default
    object_data = cv2.imread(image_path)

    # scaled data (convert 0-255 to 0-1 scale)
    scaled_data = object_data / 255

    # Power Law transformation for gamma correction
    corrected_image_gamma = np.power(scaled_data, gamma_factor)

    # Blue channel
    base_blue_channel = corrected_image_gamma[:, :, 0]
    base_green_channel = corrected_image_gamma[:, :, 1]
    base_red_channel = corrected_image_gamma[:, :, 2]

    # T, S, L value calculations

    common_divisor = (base_red_channel + base_green_channel + base_blue_channel)
    small_r = base_red_channel / common_divisor
    small_g = base_green_channel / common_divisor
    r_hyphen = small_r - (1/3)
    g_hyphen = small_g - (1/3)

    #print("Small g", small_g)
    #print("G hyphen calculation", g_hyphen)

    luma = (0.299 * base_red_channel) + (0.587 * base_green_channel) + (0.114 * base_blue_channel)
    saturation = np.sqrt((9/5)*(np.power(r_hyphen, 2) + np.power(g_hyphen, 2)))

    # Not sure if this is correct

    tint_arr = np.zeros_like(g_hyphen)

    for index, item in np.ndenumerate(g_hyphen):

        if item == 0:
            tint_arr[index] = 0
        else:
            # Fetch corresponding r_hyphen value
            corresponding_r_hyphen = r_hyphen[index]
            if item < 0:
                arctan_value_less_zero = ((np.arctan(corresponding_r_hyphen / item)) / (2 * np.pi)) + (3/4)
                #original_value = tint_arr[index]
                tint_arr[index] = arctan_value_less_zero
                #print(f"Original value and final value is {original_value}, {tint_arr[index]}")
            else:
                arctan_value_greater_zero = ((np.arctan(corresponding_r_hyphen / item)) / (2 * np.pi)) + (1 / 4)
                # original_value = tint_arr[index]
                tint_arr[index] = arctan_value_greater_zero
                # print(f"Original value and final value is {original_value}, {tint_arr[index]}")

    merged_image = cv2.merge([tint_arr, saturation, luma])
    merged_image = (255 * merged_image).astype(np.uint8)

    return merged_image


def tsl_rgb(image):

    tint = image[:, :, 0] / 255.0
    saturation = image[:, :, 1] / 255.0
    luma = image[:, :, 2] / 255.0

    x_val = np.power(np.tan((2 * np.pi) * (tint - (1/4))), 2)
    r_hyphen_tsl = np.sqrt((5 * np.power(saturation, 2)) / 9 * ((1/x_val) + 1))
    g_hyphen_tsl = np.sqrt((5 * np.power(saturation, 2)) / 9 * (x_val + 1))

    r_tsl = r_hyphen_tsl + (1/3)
    g_tsl = g_hyphen_tsl + (1/3)

    k = luma / ((0.185 * r_tsl) + (0.473 * g_tsl) + 0.114)

    final_r = k * r_tsl
    final_g = k * g_tsl
    final_b = k * (1-r_tsl-g_tsl)

    # Multiplying by 255 to get the rescaled values (Since these values are in 0-1 range)
    final_rgb_image = cv2.merge([final_r, final_g, final_b])
    clippedImg = np.clip(final_rgb_image, 0, 1)  # Clip all values to 0-1
    final_rgb_image = (255 * clippedImg).astype(np.uint8)
    return final_rgb_image

# Height is 450 and width is 600

#current_image_path = '/home/xyz/Data_Science/Skin Cancer/ISIC_0034316_dullrazor.jpg'
current_image_path = '/home/xyz/Data_Sciencet/Skin Cancer/ISIC_0034202_dullrazor.jpg'
# Standard Gamma values https://www.viewsonic.com/de/colorpro/articles/detail/accurate-gamma_128
original_img, converted_image_tsl = rgb_tsl(current_image_path, 1.5)
reverse_image = tsl_rgb(converted_image_tsl)
cv2.imshow("Original RGB image", original_img)
cv2.imshow("TSL image", converted_image_tsl)
cv2.imshow("Re-constructed RGB image", reverse_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Here is how the output looks currently: Maybe it can help.

Original RGB Image

Original RGB Image

TSL from RGB

TSL Image from RGB

Re-constructed RGB from TSL

Reconstructed RGB from TSL

Upvotes: 0

Views: 169

Answers (1)

stateMachine
stateMachine

Reputation: 5815

This is a partial answer.

Data Types. Images cannot be any data type; they have to be 8-bit unsigned integers per channel. In your operations you deal with floats, which is alright, but you must convert to uint8 at some point (assuming you are not feeding this images to a Machine Learning algorithm). There are a couple of problems with your conversions.

The first one, you don’t create a final uint8 BGR image in your rgb_tsl conversion. Now, I do not know the range of values that tint_arr, saturation, luma should be in, but assuming they are in the [0-1] range, the fix is quick. Modify the lines below the comment to this:

# Combine the 3 channels
merged_image = cv2.merge([tint_arr, saturation, luma])
merged_image = (255 * merged_image).astype(np.uint8) # Convert to uint8

Assuming the converted image is what you expect, let’s move on to the tsl_rgb function. The first thing you need to do is convert back the uint8 values back in the [0-1] range, again, assuming you are working in that range. It appears you do, so the first lines are modified to:

tint = image[:, :, 0] / 255.0
saturation = image[:, :, 1] / 255.0
luma = image[:, :, 2] / 255.0

There are some strange things happening in this conversion, I don’t why, but your final_r, final_g and final_b arrays contain negative values and you shouldn’t expect those values. You need to check the conversion details here, because this is where the conversion back to BGR starts going wrong. Those arrays should contain values in the [0-1] range.

Anyway, again, you need to cast to uint8 if you are going to get a BGR matrix out of this function. Modify the last lines to this:

# Multiplying by 255 to get the rescaled values (Since these values are in 0-1 range)
final_rgb_image = cv2.merge([final_r, final_g, final_b])
clippedImg = np.clip(final_rgb_image, 0, 1) # Clip all values to 0-1
final_rgb_image = (255 * clippedImg).astype(np.uint8)

However, the result is still wrong, but at least your arrays have now the correct data types:

enter image description here

Upvotes: 0

Related Questions