Display numpy array cv2 image in wxpython correctly

Viewed 852

I am trying to convert a numpy array (cv2 image) to a wxpython Bitmap and display it properly. I have looked into various solutions on SO and elsewhere, but without success. You can see two of my attempts in the code below.

import wx
import cv2
import numpy as np


def create_wx_bitmap(cv2_image):
    # type: (np.ndarray) -> wx.Bitmap

    # My Attempt based on https://stackoverflow.com/questions/32995679/converting-wx-bitmap-to-numpy-using-bitmapbufferformat-rgba-python/32995940#32995940
    height, width = cv2_image.shape[:2]

    array = cv2_image # the OpenCV image
    image = wx.Image(width, height)
    image.SetData(array.tobytes())
    wxBitmap = image.ConvertToBitmap()   

    return wxBitmap

    # My other attempt:
    # height, width = cv2_image.shape[:2]
    # cv2_image_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)
    # return wx.Bitmap.FromBuffer(width, height, cv2_image_rgb)

class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title)

        cv2_image = cv2.imread("test1.png", cv2.IMREAD_ANYDEPTH | cv2.IMREAD_COLOR)  # type: np.ndarray
        print(cv2_image.dtype)

        bitmap = create_wx_bitmap(cv2_image)  # type: wx.Bitmap

        wx.StaticBitmap(self, -1, bitmap, (0, 0), self.GetClientSize())
        self.SetSize(bitmap.GetSize())


if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame(None, "wxPython with OpenCV")
    frame.Show()
    app.MainLoop()

The code above seems to work for images under 16-bit (24 bit depth). However, an image that has a bit depth of 64 results in banding like the below screenshot. (which was a render from Blender 3D exported with 16 bit depth setting): enter image description here

I have also tried converting the array datatype, but it didn't seem to make any difference.

Edit (The Final Solution):

The solution to my problem was to convert the array to np.uint8 after normalizing the data as mentioned in this SO answer. Thanks to @PetrBlahos for mentioning that the data needs to be 8bit RGB in his answer.

def create_wx_bitmap(cv2_image):
    # type: (np.ndarray) -> wx.Bitmap

    height, width = cv2_image.shape[:2]

    info = np.iinfo(cv2_image.dtype) # Get the information of the incoming image type
    data = cv2_image.astype(np.float64) / info.max # normalize the data to 0 - 1
    data = 255 * data # Now scale by 255
    cv2_image = data.astype(np.uint8)

    cv2_image_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)
    return wx.Bitmap.FromBuffer(width, height, cv2_image_rgb)
1 Answers

I use

dc.DrawBitmap(wx.Bitmap.FromBuffer(iw, ih, cv_image), 0, 0)

but cv_image must be a numpy byte array of rgb values. So, whatever you do, you must convert your data to 8bit RGB (or possibly RGBA, the use FromBufferRGBA). I do not quite see how your data is structured. 64bits means you have 4 channels (RGBA) each one is 16b integer?

I think you might use cv2.convertScaleAbs, or perhaps convertTo.

Related