見出し画像

OpenCV4Python05:Qt(PyQt6,pyside6)にOpenCVのデータを表示する

【0】はじめに

今回はOpenCVで読み込んだ画像を「Qt」で作成したウィンドウ上に表示する。公式サイトは以下だが、ざっと概要を見るにはWikipediaのほうがわかりやすい。

pythonのバインディングライブラリには「PyQt」「PySide」がある。大した違いはないので両方でやってみる。(商用利用などの実用レベルではライセンスに気をつけよう)

【1】PyQt6で表示:QImageオブジェクトへ取り込む

OpenCVで読み込んだ「画像オブジェクト(numpy.ndarray)」から「QImageオブジェクト」を作ればよい。なお、いつもの注意点だが、チャネル順をBGR⇒RGBの順に変換する必要がある。

【例】:OpenCVで読み込んだsample3.jpgからQImageオブジェクトを作る

import numpy as np
import cv2 as cv
from PyQt6.QtGui import QImage

... ...

# OpenCVで画像をロードしてBGR⇒RGBにチャネル順を修正
img = cv.imread('sample3.jpg',cv.IMREAD_COLOR)
img = cv.cvtColor(img,cv.COLOR_BGR2RGB)

height, width, _ = img.shape
bytePerLine = img.strides[0] # ⇒ 3*width相当

# QImageオブジェクト作成 PyQt6.0.0時点ではQImageの引数の型が変わっているので注意 #qimg  = QImage(img.tobytes()
#              , width
#              , height
#              , bytePerLine
#              , QImage.Format.Format_RGB888)

# PyQt6.0.3のアプデ時点では従来の指定方法で動く
qimg = QImage(img.data
              , width
              , height
              , bytePerLine
              , QImage.Format.Format_RGB888)

【書式】:PyQt6.QtGui.QImage 
このオブジェクトの中身をみると「__init__()」がたくさん定義されているのでわかりにくいかもしれないが、以下のように引数を指定すればいい。

#【書式】
QImageオブジェクト = QImage(bytes型
              , 横幅
              , 高さ
              , 画像データの1行あたりのbyte数
              , 画像データのフォーマット)

※PyQt6.0.0時点での画像データの指定の仕方について(追記あり)
PyQt5と同じように「img.data」で画像データを指定した場合、PyQt6.0.0では「QImage.__init()__」の定義が変わったせいなのか、以下の書き方だと「キャストがうんたらかんたら~」とエラーになった。そこで、明示的にbytes型にして渡して対応した。

【PyQt5だと動作していた従来の書き方】

qimg = QImage(img.data
              , width
              , height
              , bytePerLine
              , QImage.Format.Format_RGB888)

なお、後述するPySide6側ではこの書き方でも通用する。

【追記 2021.3.30】
PyQt6.0.3時点では従来の書き方で動作するようになっている。

【2】PyQt6で表示:QImage⇒表示用オブジェクトQPixmapにする

画像データを「QImageオブジェクト」に取り込んだら、「QtのGUI上に表示できるようにするための専用のオブジェクト:QPixmapオブジェクト」に変換する。

【例】:QPixmapオブジェクトにする

from PyQt6.QtGui import QImage, QPixmap

... ...

# QImageオブジェクト作成 PyQt6.0.0時点ではQImageの引数の型が変わっているので注意
# qimg = QImage(img.tobytes(), width, height, bytePerLine, QImage.Format.Format_RGB888)
# アプデ後のPyQt6.0.3ではこれまでの書き方で問題なし
qimg = QImage(img.data, width, height, bytePerLine, QImage.Format.Format_RGB888)       
       
# 表示用にQPixmapオブジェクトにする
pixmap = QPixmap(QPixmap.fromImage(qimg))

【書式】

QPixmap = QPixmap.fromImage( QImageオブジェクト [, flags])

あとはこの「QPixmapオブジェクト」を「QLabel」などにのせて、表示すればよい。

【全体コード】PyQt6版

import numpy as np
import cv2 as cv

from PyQt6.QtWidgets import QApplication, QLabel, QWidget
from PyQt6.QtGui import QImage, QPixmap


class MainWindow(QWidget):

   def __init__(self):
       super().__init__()

       self.setup()

   
   # 各種セットアップ
   def setup(self):


       # OpenCVで画像をロードしてBGR⇒RGBにチャネル順を修正
       img = cv.imread('sample3.jpg',cv.IMREAD_COLOR)
       img = cv.cvtColor(img,cv.COLOR_BGR2RGB)

       height, width, _ = img.shape
       bytePerLine = img.strides[0] # ⇒ 3*width相当

       # QImageオブジェクト作成 PyQt6.0.0時点ではQImageの引数の型が変わっているので注意
       # qimg = QImage(img.tobytes(), width, height, bytePerLine, QImage.Format.Format_RGB888)
       qimg = QImage(img.data, width, height, bytePerLine, QImage.Format.Format_RGB888)       
       # QPixmapの作成
       pixmap = QPixmap(QPixmap.fromImage(qimg))
       imgLabel = QLabel(self)
       imgLabel.setPixmap(pixmap)
       

       # 表示するwindowの調整
       self.resize(imgLabel.pixmap().size())
       self.setWindowTitle("load from OpenCV")
       #self.setGeometry(0, 0, pixmap.width(), pixmap.height()) # ディスプレイ上の表示位置と描画範囲
       self.show()


if __name__ == '__main__':
   app = QApplication([])
   windows = MainWindow()
   app.exec()

※app.exec()についての補足:
PyQt5以降では「sys.exit(app.exec_())」みたいな書き方はなくてもいい。というのも「QtとPythonがプロセスを停止する順序は修正済みのため」である。また、かつては「exec_()」というものだったが、python3でexecが予約済みキーワードではなくなったので「app.exec()」に変わった。

出力結果:OpenCVで読み込んだデータをPyQt6で表示

画像1

【3】PySide6で表示:QImageオブジェクトへ取り込む

PyQt6とほぼ同じ。

【例】:OpenCVで読み込んだsample3.jpgからQImageオブジェクトを作る

import numpy as np
import cv2 as cv
from PySide6.QtGui import QImage
... ...

# OpenCVで画像をロードしてBGR⇒RGBにチャネル順を修正
img = cv.imread('sample3.jpg',cv.IMREAD_COLOR)
img = cv.cvtColor(img,cv.COLOR_BGR2RGB)

height, width, _ = img.shape
bytePerLine = img.strides[0] # ⇒ 3*width相当

# QImageオブジェクト作成 PyQt6ではQImageの指定が変わっているので注意
qimg = QImage(img.data, width, height, bytePerLine, QImage.Format.Format_RGB888)

「PyQt6 (ver6.0.0時点)」の時は「numpy.ndarray.tobytes()」で画像データを指定していたが、「PySide6」ではこれまで通り「numpy.ndarray.data」を指定して動作してくれる。

【4】PySide6で表示:QImage⇒表示用オブジェクトQPixmapにする

これはPyQt6と同じ。
【例】:QPixmapオブジェクトにする

from PySide6.QtGui import QImage, QPixmap
 
# QImageオブジェクト作成
qimg = QImage(img.data, width, height, bytePerLine, QImage.Format.Format_RGB888)

#表示用にQPixmapオブジェクトにする
pixmap = QPixmap(QPixmap.fromImage(qimg))

こちらも同様に「QPixmapオブジェクト」を「QLabel」などにのせて、表示すればよい。

【全体コード】PySide6版

import sys
import numpy as np
import cv2 as cv


from PySide6.QtWidgets import QApplication, QLabel, QWidget
from PySide6.QtGui import QImage, QPixmap


class MainWindow(QWidget):

   def __init__(self):
       super().__init__()

       self.setup()

   
   #各種セットアップ
   def setup(self):


       #OpenCVで画像をロードしてBGR⇒RGBにチャネル順を修正
       img = cv.imread('sample3.jpg',cv.IMREAD_COLOR)
       img = cv.cvtColor(img,cv.COLOR_BGR2RGB)

       height, width, _ = img.shape
       bytePerLine = img.strides[0] # ⇒ 3*width相当

       # QImageオブジェクト作成
       qimg = QImage(img.data, width, height, bytePerLine, QImage.Format.Format_RGB888)

       #表示用にQPixmapオブジェクトにする
       pixmap = QPixmap(QPixmap.fromImage(qimg))

       imgLabel = QLabel(self)
       imgLabel.setPixmap(pixmap)
       


       self.resize(imgLabel.pixmap().size())
       self.setWindowTitle("load from OpenCV with PySide6")
       #self.setGeometry(0, 0, pixmap.width(), pixmap.height()) #ディスプレイ上の表示位置と描画範囲
       self.show()


if __name__ == '__main__':
   app = QApplication([])
   windows = MainWindow()
   sys.exit(app.exec_())

※「PySide6」ではこれまで通り「sys.exit(app.exec_()」を使う。

出力結果:OpenCVで読み込んだデータをPySide6で表示

画像2


もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。