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で表示
【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で表示