Reputation: 1074
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
TSL from RGB
Re-constructed RGB from TSL
Upvotes: 0
Views: 169
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 float
s, 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:
Upvotes: 0