Different colors in cv2 and QPixmap/QImage

238 Views Asked by At

I am trying to build a simple image editor app using Python and PyQt6. At the moment, TIFF images are loaded using cv2.imread(). Then, the user can make several adjustments (contrast, colors, etc.). Finally, the images are saved using cv2.imwrite(). This part works just fine. For the GUI, the image is converted in a QImage and then to a QPixmap, which is then displayed on a QLabel on the GUI.

Problem: The colors in the exported images slightly differ from the image being displayed on the GUI. The differences are subtile. But, obviously, for an image editor app I expect the preview image to look identical to the final output.

The problem is easily visible if I run the following code:

image_data = cv2.imread("Color_Adobe.tif")
cv2.imshow("CV2", image_data)

#convert to QImage:
h,w,_ = image_data.shape
bytesPerLine = 3 * w
image = QImage(image_data, w, h, bytesPerLine, QImage.Format.Format_RGB888).rgbSwapped()
pixmap = QPixmap(image)

#set pixmap to QLabel
label.setPixmap(self.pixmap)

The image being loaded here is a .TIF image of a color chart. The output I get is the following:

CV2 vs Pixmap Output: CV2 vs Pixmap Output

Above is the cv2.imshow() output, below is the QLabel with the Pixmap in my GUI. As you can see, the colors are rendered quite differently. In general, the pixmap output seems slightly more reddish.

Looking at the red square on the bottom left, I get these readings with DigitalColorMeter on my screen (Mac): CV2: 236 56 30 Pixmap: 255 0 0 (RGB)

I assume this has something to do with color spaces which don't match - but I just can't get my head around what exactly is happening.

How can I make sure that the pixmap is displayed in the same way as the cv2.imshow() output? Or is there a different/better way of showing the image in the GUI?

Thank you.

1

There are 1 best solutions below

7
Lukas On

Ok, so I finally figured it out. Turns out that when you use cv2.imshow(), the image will be displayed using the monitors specific ICC profile. However, when displaying the same image (on the same screen) via a Pixmap, standard sRGB color space is used. To solve that, I first convert the image to a PIL Image, load the ICC profiles (sRGB and Monitor specific) and convert the colorSpaces. Then convert to Pixmap and display on my GUI. This way, the Pixmap finally looks identical to the imshow() output.

        image_data = cv2.imread("Color_Adobe.tif")

        #CV2 Output
        cv2.imshow("CV2", image_data)

        image_data = cv2.cvtColor(image_data, cv2.COLOR_RGB2BGR)

        
        PIL_image_sRGB = PIL.Image.fromarray(image_data)

        #Path to sRGB ICC Profile
        src_profile_path = "/Library/ColorSync/Profiles/sRGB.icc"

        # Path to Monitors specific ICC Profile
        dst_profile_path = "/Library/ColorSync/Profiles/Displays/HP U32 4K HDR-11C013E4-4731-AAD9-78F8-9D92871BECD3.icc"

        #Open profiles in PIL
        dst_profile = PIL.ImageCms.getOpenProfile(dst_profile_path)
        src_profile = PIL.ImageCms.getOpenProfile(src_profile_path)

        #convert color spaces to new ICC
        PIL_image_ICC = PIL.ImageCms.profileToProfile(PIL_image_sRGB, src_profile, dst_profile)

        #convert back to numpy array
        image_data = np.array(PIL_image_ICC)


        #convert to QImage
        h,w,_ = image_data.shape
        bytesPerLine = 3 * w
        image = QImage(image_data, w, h, bytesPerLine, QImage.Format.Format_RGB888)
        
        #convert to pixmap
        pixmap = QPixmap(image)    
        label.setPixmap(pixmap)