OpenCV+pySide2でWebカメラ画像表示

OpenCV単体でも[GUIが作れます](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_gui/py_table_of_contents_gui/py_table_of_contents_gui.html)が、ここではpySide2との連携について。

# 環境
macOS + vscode
macOSとvscodeの組み合わせでWebカメラを使うにはTerminalから起動しないとcv2.VideoCapture(0)で失敗してうまくいかない。権限不足でiSight(MacのWebカメラ)が動作しないので注意。

```zsh:zsh
% sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H114
% uname -m
x86_64
% python --version
Python 3.8.6
% code --version
1.53.0
% code . # vscodeはTerminalから起動しないと権限不足でiSight(MacのWebカメラ)が動作しない
```

# セットアップ
```zsh:zsh
pip install opencv-python-headless # headless (GUIなし)でないとpySide2のQtと被る
pip install PySide2
```

# サンプルコード
pySide2でWebカメラ表示するだけのプログラムをいくつかの方法で書いてみた。

## サンプル1 (シンプル版)
```python:webcam.py
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *
import cv2

def displayFrame():
    ret, frame = cap.read()
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    h, w = frame.shape[:2]
    image = QImage(frame.flatten(), w, h, QImage.Format_RGB888) # cv::Mat -> Qt(numpy.array)
    label.setPixmap(QPixmap.fromImage(image))

app = QApplication()
window = QWidget()

# Webカメラ起動
cap = cv2.VideoCapture(0)

# 画面表示タイミング
timer = QTimer()
timer.timeout.connect(displayFrame)
timer.start(33) # [msec]

# 画面構成
label = QLabel('No Camera Feed')
layout = QVBoxLayout()
layout.addWidget(label)
window.setLayout(layout)
window.setWindowTitle("Camera")
window.show()
app.exec_()
```

## サンプル2 （QGraphicsScene版、マウスでクリックした位置(画像座標)を取得）
画像の座標を得るにはQGraphicsSceneでmousePressEventを取ってscenePosを得る。

```python:webcam_qgraphicsscene.py
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *
import cv2

class myImage(QGraphicsScene):
    mScene = None
    def __init__(self, parent=None):
        super(myImage, self).__init__()

    def mousePressEvent(self, event):
        print(event.type())
        p = event.scenePos() # relative to widget
        print(p)

class webCam(QWidget):
    cap = None
    view = None
    scene = None
    timer = None

    def __init__(self, parent=None):
        super(webCam, self).__init__()
        self.left = 70
        self.top = 70
        self.width = 640
        self.height = 480
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.init()

    def cv_to_pixmap(self, cv_img):
        frame = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
        h, w = frame.shape[:2]
        qimage = QImage(frame.flatten(), w, h, QImage.Format_RGB888) # cv::Mat -> Qt(numpy.array)
        pixmap = QPixmap.fromImage(qimage)
        return pixmap

    def displayFrame(self):
        ret, frame = self.cap.read()
        pixmap = self.cv_to_pixmap(frame)
        self.scene.clear()
        self.scene.addPixmap(pixmap)
        self.view.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio) # 原寸表示したい時はこの行をコメントアウト

    def init(self):
        # Webカメラ起動
        self.cap = cv2.VideoCapture(0)

        # 画面構成
        self.scene = myImage('No camera frame')
        self.view = QGraphicsView()
        self.view.setScene(self.scene)
        self.view.setMouseTracking(True) 
        layout = QVBoxLayout()
        layout.addWidget(self.view)
        scrollArea = QScrollArea() # 原寸表示時のスクロール用
        scrollArea.setLayout(layout)
        super(webCam, self).setLayout(layout)
        super(webCam, self).setWindowTitle("Camera")

        # 画面表示タイミング
        self.timer = QTimer()
        self.timer.timeout.connect(self.displayFrame)
        self.timer.start(33) # [msec]

def main():
    app = QApplication()
    window = webCam()
    window.show()
    app.exec_()

if __name__ == '__main__':
    main()
```