-1

I'm trying to display an OpenCV image (NumPy array) using PyQt6. Previous Qt versions (PyQt4 and PyQt5) convert the NumPy array to a QPixmap then display it using a QLabel but this doesn't seem to work in PyQt6.

Input image

When I try to display it, I get a distorted image

Code

from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QHBoxLayout, QGridLayout
from PyQt6.QtGui import QImage, QPixmap 
import sys
import cv2

class DisplayImageWidget(QWidget):
    def __init__(self):
        super(DisplayImageWidget, self).__init__()
        self.image = cv2.imread('2.png')
        self.convert = QImage(self.image, 400, 400, QImage.Format.Format_BGR888)
        self.frame = QLabel()
        self.frame.setPixmap(QPixmap.fromImage(self.convert))
        
        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.frame)

if __name__ == '__main__':
    app = QApplication([])
    
    main_window = QMainWindow()
    main_window.setWindowTitle('Video Frame Acquisition')
    main_window.setFixedSize(500, 500)
    
    central_widget = QWidget()
    main_layout = QGridLayout()
    central_widget.setLayout(main_layout)
    main_window.setCentralWidget(central_widget)
    
    display_image_widget = DisplayImageWidget()
    main_layout.addWidget(display_image_widget, 0, 0)
   
    main_window.show()
    sys.exit(app.exec())

I experimented with all types of different QtGui.QImage.Format types but I'm pretty sure the Format_BGR888 should be correct. The image is showing but I can't get it to format correctly.

nathancy
  • 42,661
  • 14
  • 115
  • 137

1 Answers1

2

Okay I found the solution. It turns out you have to pass the image's width and height instead of an arbitrary dimension.

self.convert = QImage(self.image, self.image.shape[1], self.image.shape[0], QImage.Format.Format_BGR888)

That fixed the random pixels but it was still slanted for some reason.

After some digging, it turns out there's a not well documented bytesPerLine (stride) parameter in QImage. The fix was adding strides[0]. The parameter specifies the number of bytes required by the image pixels in a given row. But I'm still not sure what it does exactly since with other images, the picture displays properly without adding the additional parameter.

self.convert = QImage(self.image, self.image.shape[1], self.image.shape[0], self.image.strides[0], QImage.Format.Format_BGR888)

Full working code

from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow, QLabel, QHBoxLayout, QGridLayout
from PyQt6.QtGui import QImage, QPixmap 
import sys
import cv2

class DisplayImageWidget(QWidget):
    def __init__(self):
        super(DisplayImageWidget, self).__init__()
        self.image = cv2.imread('2.png')
        self.convert = QImage(self.image, self.image.shape[1], self.image.shape[0], self.image.strides[0], QImage.Format.Format_BGR888)
        self.frame = QLabel()
        self.frame.setPixmap(QPixmap.fromImage(self.convert))
        
        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.frame)

if __name__ == '__main__':
    app = QApplication([])
    
    main_window = QMainWindow()
    main_window.setWindowTitle('Video Frame Acquisition')
    main_window.setFixedSize(500, 500)
    
    central_widget = QWidget()
    main_layout = QGridLayout()
    central_widget.setLayout(main_layout)
    main_window.setCentralWidget(central_widget)
    
    display_image_widget = DisplayImageWidget()
    main_layout.addWidget(display_image_widget, 0, 0)
   
    main_window.show()
    sys.exit(app.exec())
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • 1
    "instead of an arbitrary dimension." this feels like someone else had this question because I doubt you would have thought that would work. – Christoph Rackwitz Jun 08 '22 at 12:29