hx2A / ColorBlindness

Processing library for simulating color blindness
Other
14 stars 2 forks source link

I tried to reproduce your ixora.io color blindness simulation, but I encountered some problems #3

Closed charpng closed 1 year ago

charpng commented 1 year ago

I'm trying content repetition blindness simulation through the following url https://ixora.io/projects/colorblindness/color-blindness-simulation-research/

According to the content of the website, I

  1. removed the gamma correction and got the LRGB image.
  2. Then I multiplied the LRGB image by matrix T, matrix S and the inverse matrix of matrix T according to the steps in the article.

But the output image I got was not ideal. Compared to the effect image you gave in the article, the image I got was more blue, which confused me very much and I hope you can point out my mistake.

Here are pictures of the results of my simulation of deutranopia color blindness:

R to deutranopia output

hx2A commented 1 year ago

Hello, @charpng !

I believe you must re-apply the gamma correction after doing the matrix calculations. Also, can you try reversing the color channels? OpenCV, for example, will give you color channels BGR instead of RGB. The reverse order will interfere with the proper results.

charpng commented 1 year ago

Thank you for your kind advice! I thought image.open () in PIL library of Python opens the Image in RGB instead of BGR, but I just tried to reverse the color channels. I tried converting the image from RGB to BGR or BGR to RGB after reading it in, and the reverse before outputting the results. Either way, the output image changes from blue to red. Actually there`s another question and I wonder if this counts. My codes are as follows: QQ截图20230603233021 QQ截图20230603233031 In line 37, I converted the X×Y×3 image to X×3×Y using np.transpose(). This is because the image is read in as a X×Y×3 matrix (X, Y are the vertical and horizontal sizes of the image), and I cannot directly left-multiply the image by T matrix as in article. I wanted to know if this had an impact, and if so how I could do with it.

Thank you very much for taking the time to correct my mistakes.

hx2A commented 1 year ago

I don't see the mistake but perhaps I can help more if I can try running the code. Can you try pasting the code in your response instead of pasting images? Refer to the github docs for info on now to do that. The main idea is you can use backticks to make the code look like code instead of regular text.

charpng commented 1 year ago

Yes @hx2A I'm more than happy to upload my code! I uploaded the images because I thought they would be easier to read than text.

    import cv2
    import numpy as np
    from PIL import Image

    def RGB_to_lRGB(img):
        return np.where(img <= 0.04045*255, (img/255)/12.92 , np.power(((img/255)+0.055)/1.055 , 2.4))

    def lRGB_to_RGB(img):
        return np.where(img <= 0.0031308 , 255*12.92*img , 255*(np.power(1.055*img,0.41666)-0.055))

    T = np.array([[0.31399022 , 0.63951294 , 0.04649755],
                [0.15537241 , 0.75789446 , 0.08670142],
                [0.01775239 , 0.10944209 , 0.87256922]])  # Matrix T
    TI = np.array([[5.47221206 , -4.6419601 , 0.16963708],
                [-1.1252419 , 2.29317094 , -0.1678952],
                [0.02980165 , -0.19318073 , 1.16364789]]) # Inverse matrix of T

    def protanopia() :
        return np.array([[0 , 1.05118294 , -0.05116099],
                        [0, 1, 0],
                        [0, 0, 1]])

    def deutranopia():
        return np.array([[1, 0, 0],
                        [0.9513092 , 0 , 0.04866992],
                        [0, 0, 1]])

    def tritanopia() :
        return np.array([[1, 0, 0],
                        [0, 1, 0],
                        [-0.86744736 , 1.86727089 , 0]])

    def transform(img_path, colorblindnesstype:str):

        original_image = np.array(Image.open(img_path))
        original_image = cv2.cvtColor(original_image,cv2.COLOR_BGR2RGB)
        ori_img_lRGB = RGB_to_lRGB(original_image)          ##remove the gamma correction
        image_removed_gamma = np.transpose(ori_img_lRGB,(0,2,1))
        original_image_lms = T @ image_removed_gamma

        if colorblindnesstype == 'protanopia':
            trans_matrix = protanopia()
        elif colorblindnesstype == 'deutranopia':
            trans_matrix = deutranopia()
        elif colorblindnesstype == 'tritanopia':
            trans_matrix = tritanopia()

        img_transformed = trans_matrix @ original_image_lms
        img_transformed = TI @ img_transformed
        output_img = np.transpose(img_transformed,(0,2,1))
        output_img = lRGB_to_RGB(output_img)            #re-apply the gamma correction
        output_img = cv2.cvtColor(output_img,cv2.COLOR_RGB2BGR)
        cv2.imwrite('R to %s output.jpg' %(colorblindnesstype),output_img)
        return

    img_path = 'R.jpg'

    transform(img_path,'protanopia')
    transform(img_path,'deutranopia')
    transform(img_path,'tritanopia')

I am a student from District 8 East, so please forgive me for not being able to keep in touch for the next few hours

I fixed an error in the function lRGB_to_RGB() as follows, but I still didn't get the correct result. From

res = np.where(img <= 0.0031308 , 255*12.92*img , 255*(np.power(1.055*img,0.41666)-0.055))

to

res = np.where(img <= 0.0031308 , 255*12.92*img , 255*(1.055*(np.power(img,0.41666))-0.055))
hx2A commented 1 year ago

@charpng , I found the problem with your code.

The main issue had to do with OpenCV's BGR channels and Pillow's RGB channels. You have to be careful when mixing the two libraries together. To repair your code I removed OpenCV altogether because you don't really need it. Also, I see you read the image file with Pillow but then save it with OpenCV. Saving it with OpenCV was reversing the color channels again. You can save it with Pillow like this:

    Image.fromarray(output_img.astype(np.uint8)).save('/tmp/R_to_%s_output.jpg' % (colorblindnesstype))

In the future, if you need to reverse the color channel order, you can do it by reversing the last axis, like this:

img_array = image_array[:, :, ::-1]

Here is the complete code (note I made some other small changes to suit my programming style)

import numpy as np
from PIL import Image

def RGB_to_lRGB(img):
    return np.where(img <= 0.04045*255, (img/255)/12.92, np.power(((img/255)+0.055)/1.055, 2.4))

def lRGB_to_RGB(img):
    return np.where(img <= 0.0031308, 255*12.92*img, 255*(1.055*(np.power(img, 0.41666))-0.055))

T = np.array([[0.31399022, 0.63951294, 0.04649755],
              [0.15537241, 0.75789446, 0.08670142],
              [0.01775239, 0.10944209, 0.87256922]])  # Matrix T
TI = np.array([[5.47221206, -4.6419601, 0.16963708],
               [-1.1252419, 2.29317094, -0.1678952],
               [0.02980165, -0.19318073, 1.16364789]])  # Inverse matrix of T

def protanopia():
    return np.array([[0, 1.05118294, -0.05116099],
                    [0, 1, 0],
                    [0, 0, 1]])

def deutranopia():
    return np.array([[1, 0, 0],
                    [0.9513092, 0, 0.04866992],
                    [0, 0, 1]])

def tritanopia():
    return np.array([[1, 0, 0],
                    [0, 1, 0],
                    [-0.86744736, 1.86727089, 0]])

def transform(img_path, colorblindnesstype: str):

    original_image = np.array(Image.open(img_path))

    ori_img_lRGB = RGB_to_lRGB(original_image)  # remove the gamma correction
    image_removed_gamma = np.transpose(ori_img_lRGB, (0, 2, 1))
    original_image_lms = T @ image_removed_gamma

    if colorblindnesstype == 'protanopia':
        trans_matrix = protanopia()
    elif colorblindnesstype == 'deutranopia':
        trans_matrix = deutranopia()
    elif colorblindnesstype == 'tritanopia':
        trans_matrix = tritanopia()

    img_transformed = trans_matrix @ original_image_lms
    img_transformed = TI @ img_transformed
    output_img = np.transpose(img_transformed, (0, 2, 1))
    output_img = lRGB_to_RGB(output_img)  # re-apply the gamma correction

    output = Image.fromarray(output_img.astype(np.uint8))
    output.save('/tmp/R_to_%s_output.jpg' % (colorblindnesstype))
    return output

img_path = '/tmp/R.jpg'

transform(img_path, 'protanopia')

I am a student from District 8 East,

What is District 8 East?

Good luck with your studies, and have fun learning about Python!

charpng commented 1 year ago

Thank you very much for your teaching! I only knew that OpenCV opened the image as BGR, but I didn't know that it also stored the image as BGR. I successfully got the correct result after modifying the code according to your instructions.

As for District 8 East, I just want to express that I am a student from China, so there is jet lag over ten hours, but I didn't make it clear when I expressed it.

hx2A commented 1 year ago

You are welcome!

I only knew that OpenCV opened the image as BGR, but I didn't know that it also stored the image as BGR.

I should be a bit more clear here. The PNG image format is exactly the same, regardless of how it is saved. When you open a PNG image with OpenCV, it will by default give you an array with the channel order BGR. If you do the same with Pillow and convert it to an array, the channel order will be RGB. When you save a PNG image with OpenCV, it will assume the channel order of the array you provide it is also BGR. When converting an array to a Pillow image, it will assume the channel order of the array you provide it is RGB.

The channel ordering can be confusing it is better to keep everything RGB within a program. When I use OpenCV to read a webcam, I always switch the channel order right away to RGB with [:, :, ::-1]. That keeps everything consistent and avoids these kinds of headaches.