Python KivyでWebカメラの映像を表示・撮影する【GUI】

#はじめに
KivyはPythonでGUIアプリケーションが開発可能なライブラリです。マルチプラットフォームに対応していて商用利用も可能だそうです。
今回はKivyを使って、「Webカメラの映像を表示・撮影するGUIアプリケーション」を開発してみようと思います！
Kivyで画像を簡単にボタン化する実装も行っています！

**※Kivyなどの詳細な使用方法は省略**
<img width="842" alt="スクリーンショット 2020-07-09 2.56.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381461/2f56600a-bf0b-8378-8b3c-8ffb748ea4fe.png">


#環境
* macOS Catalina 10.15.5
* Python 3.6.7
* Kivy 1.11.1

#pip install
```:OpenCV
$ pip install opencv-python
```
```:Kivy
$ pip install cython pygame pillow
$ pip install Kivy
```

#実践
##Kivyの画面作成 (Hello World!)
```python:main.py
from kivy.app import App

class MainScreen(Widget):
    pass

class MyCameraApp(App):
    def build(self):
        return MainScreen()

if __name__ == '__main__':
    MyCameraApp().run()
```
```python:mycamera.kv
<MainScreen>:
    Label:
        text: 'Hello World!'
        center: root.width * 0.5 , root.height * 0.5
```


Hello World!の表示にLabelクラスを用いました。Kivyではkvファイルを用いることでHTMLのような雰囲気で要素を設置することが可能です。今回は、黒い画面の真ん中にHello World!が表示されたら成功です。

この先では、取得した画像を表示するCameraPreviewクラスと撮影ボタンのImageButtonクラスを作成し、Labelと同様にそれらをkvファイルで設置していきます。

##Webカメラの映像を表示
OpenCVを使ってWebカメラの映像をフレームとして取得します。それらを実現するクラスとしてCameraPreviewクラスを新たに作成します。


###プログラムと説明
```python:main.pyに追記
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
from kivy.clock import Clock
import cv2

class CameraPreview(Image):
    def __init__(self, **kwargs):
        super(CameraPreview, self).__init__(**kwargs)
        # 0番目のカメラに接続
        self.capture = cv2.VideoCapture(0)
        # 描画のインターバルを設定
        Clock.schedule_interval(self.update, 1.0 / 30)

    # インターバルで実行する描画メソッド
    def update(self, dt):
        # フレームを読み込み
        ret, self.frame = self.capture.read()
        # Kivy Textureに変換
        buf = cv2.flip(self.frame, 0).tostring()
        texture = Texture.create(size=(self.frame.shape[1], self.frame.shape[0]), colorfmt='bgr') 
        texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
        # インスタンスのtextureを変更
        self.texture = texture
```

Imageクラスを継承したCameraPreviewというサブクラスを作成しました。このクラスはopencv-pythonの関数を使って取得したWebカメラの映像を取得し更新していきます。
KivyのImageクラスで表示できるように、取得したフレームはTexture形式に変換します。

次に、kvファイルを編集して画面に反映させていきます。

```python:mycamera.kv
<MainScreen>:
    CameraPreview:
        id: camera_preview
        size: root.size
```

**【注意】**
macOSでは、VScodeなどサードパーティ製エディター内のターミナルでプログラムを実行し、本プログラムのようにopencv-pythonを用いてwebカメラの映像を取得しようとするとエラーが出ます！



##撮影ボタンの実装
通常のボタン(Buttonクラス)を使用してもいいのですが、面白みがないので撮影アイコンのような画像を用意し、それをクリックしたときに撮影できるプログラムを作ってみます。ボタンに使用する画像は適当にフリー素材を拾ってきました！作成するクラスの名前はImageButtonクラスです。

###プログラムと説明

```python:main.pyに追記
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty

# 撮影ボタン
class ImageButton(ButtonBehavior, Image):
    preview = ObjectProperty(None)

    # ボタンを押したときに実行
    def on_press(self):
        cv2.namedWindow("CV2 Image")
        cv2.imshow("CV2 Image", self.preview.frame)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
```

このように、ImageクラスだけでなくButtonBehaviorクラスも継承させることで、ボタン押下時に実行されるon_press()を扱えるようになります。on_press()をオーバーライドして自分の行いたい処理に書き換えます。

**【重要】**
CameraPreviewクラスに参照できるようにObjectPropertyを設定します。ObjectPropertyを設定した変数(ここでいうpreview)に何を参照させるのか？を指定するには以下のようにkvファイルで設定します。

```python:mycamera.kv
<MainScreen>:
    CameraPreview:
        id: camera_preview
        size: root.size
        
    ImageButton:
        preview: camera_preview
        source: 'icons/capture.png'
        size: 100, 100
        center: root.width * 0.5, root.height * 0.2
```

PythonファイルでObjectPropertyを設定した変数をkvファイルでも同様に宣言し、値を参照したいもののidに設定します。(preview: camera_previewの部分)
こうすることで、Kivyではクラス間で値の参照を行うことができます。


##完成したプログラム
以上でプログラムは完成です！ページの最初にも登場してもらったコアラさんにもう一度登場してもらいましょう。今回は撮影ボタンをクリックするとOpenCVのimshow()で画像が表示されるというものでしたが、保存する処理に書き換えることももちろん可能です。
<img width="842" alt="スクリーンショット 2020-07-09 2.56.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381461/f97aa406-7042-7256-6c10-90a5d99a5bb7.png">




##補足
撮影ボタンを押したときに、You might be loading two sets of Qt binaries into the same process.のメッセージが出る場合はこれで解決すると思います。

```
$ pip install opencv-python-headless
```


#参考サイト
* [Kivy Pong Game Tutorial](https://kivy.org/doc/stable/tutorials/pong.html)
    * 公式チュートリアル
* [たけし備忘録 - Kv言語の基本](https://takeshid.hatenadiary.jp/entry/2016/06/19/151827)
    * Kivyにおけるクラスの扱い方がわかりやすくまとめられている
* [Kivy— Image as Button](https://stackoverflow.com/questions/33489143/kivy-image-as-button)
    * 画像のボタン化