【WSL2】Webカメラからの映像をPyQt6で表示する

はじめに

前回WSL2でPyQt6が使えるようになりました。
WSL2上のUbuntu20.04でPyQt6を使う(ファイル選択ダイアログも使用できました) - パソコン関連もろもろ


以前にはWSL2でWebカメラが使えるようになりました。
【更新記事】WSL2でカメラを使うには(3) - パソコン関連もろもろ


そうすると当然同時に使いたくなります。


ということで、Webカメラからの映像をPyQt6で表示することに挑戦しました。


はっきり言って後述する「参考にさせて頂いたサイト」をほぼそのまま使わせて頂きました。詳細に解説もしてくれているので非常にわかりやすいです。


今回はWSL2で動作確認ができたという記事になります。

Pythonスクリプト

import cv2
from PyQt6.QtCore import QSize, pyqtSignal, pyqtSlot, QThread
from PyQt6.QtWidgets import *
from PyQt6.QtGui import QImage, QPixmap

class VideoThread(QThread):

    change_pixmap_signal = pyqtSignal(QImage)
    playing = True

    def run(self):
        cap = cv2.VideoCapture(0)
        while self.playing:
            ret, frame = cap.read()
            if ret:
                h, w, ch = frame.shape
                bytesPerLine = ch * w
                image = QImage(frame, w, h, bytesPerLine, QImage.Format.Format_BGR888)
                self.change_pixmap_signal.emit(image)
        cap.release()

    def stop(self):
        self.playing = False
        self.wait()

class Window(QWidget):

    video_size = QSize(640, 480)

    def __init__(self):
        super().__init__()    
        self.initUI()
        self.thread = VideoThread()
        self.thread.change_pixmap_signal.connect(self.update_image)
        self.thread.start()

    def initUI(self):
        self.setWindowTitle("OpenCV-Python sample")
        
        self.img_label1 = QLabel()
        self.img_label1.setFixedSize(self.video_size)

        self.main_layout = QVBoxLayout()
        self.main_layout.addWidget(self.img_label1)

        self.setLayout(self.main_layout)

    def closeEvent(self, e):
       self.thread.stop()
       e.accept()
    
    @pyqtSlot(QImage)
    def update_image(self, image):
        self.img_label1.setPixmap(QPixmap.fromImage(image))

if __name__ == "__main__":
    app = QApplication([])
    ex =Window()
    ex.show()
    app.exec()

環境

既定のディストリビューション: Ubuntu-20.04
既定のバージョン: 2
WSL1 は、現在のマシン構成ではサポートされていません。
WSL1 を使用するには、"Linux 用 Windows サブシステム" オプション コンポーネントを有効にしてください。
WSL バージョン: 0.51.3.0
カーネル バージョン: 5.10.93.2
WSLg バージョン: 1.0.30
Windows バージョン: 10.0.22000.556
python 3.8.10

numpy==1.22.3
opencv-python==4.5.5.64
pkg_resources==0.0.0
PyQt6==6.2.3
PyQt6-Qt6==6.2.3
PyQt6-sip==13.2.1

参考にさせて頂いたサイト

note.com
stackoverflow.com

2022年5月12日追記

応用してカメラアプリを作りました。
touch-sp.hatenablog.com

2022年9月29日追記

PySide6用のスクリプトはこちらです。
「pyqtSignal」「pyqtSlot」がそれぞれ「Signal」「Slot」になります。

import cv2
from PySide6.QtCore import QSize, Signal, Slot, QThread
from PySide6.QtWidgets import *
from PySide6.QtGui import QImage, QPixmap

class VideoThread(QThread):

    change_pixmap_signal = Signal(QImage)
    playing = True

    def run(self):
        cap = cv2.VideoCapture(1, cv2.CAP_DSHOW)
        while self.playing:
            ret, frame = cap.read()
            if ret:
                h, w, ch = frame.shape
                bytesPerLine = ch * w
                image = QImage(frame, w, h, bytesPerLine, QImage.Format.Format_BGR888)
                self.change_pixmap_signal.emit(image)
        cap.release()

    def stop(self):
        self.playing = False
        self.wait()

class Window(QWidget):

    video_size = QSize(640, 480)

    def __init__(self):
        super().__init__()    
        self.initUI()
        self.thread = VideoThread()
        self.thread.change_pixmap_signal.connect(self.update_image)
        self.thread.start()

    def initUI(self):
        self.setWindowTitle("OpenCV-Python sample")
        
        self.img_label1 = QLabel()
        self.img_label1.setFixedSize(self.video_size)

        self.main_layout = QVBoxLayout()
        self.main_layout.addWidget(self.img_label1)

        self.setLayout(self.main_layout)

    def closeEvent(self, e):
       self.thread.stop()
       e.accept()
    
    @Slot(QImage)
    def update_image(self, image):
        self.img_label1.setPixmap(QPixmap.fromImage(image))

if __name__ == "__main__":
    app = QApplication([])
    ex =Window()
    ex.show()
    app.exec()