Webカメラを使おう

# はじめに
こんにちは、@yomo93 です。
私は画像処理を用いたロボットの制御について研究しています。そこでロボットの制御と**リアルタイムカメラ映像取得**を同時に行うために作成したプログラムをご紹介したいと思います。
今回はPCにUSB端子を用いて接続したカメラを用いることを想定しています。

# カメラ設定
リアルタイム映像を取得するにあたって、カメラ設定を指定する必要があります。それらの設定は`config.json`という設定ファイルを作成してそこに記述するようにしましょう。Webカメラを使うということは撮影現場での調整が必要ということです。現場ではできる限りプログラムはいじらず、`config.json`を編集する方が楽だし早いです。
以下に`config.json`の記述例を記します。このファイルの中身をプログラムから読み込みます。

``` config.json
{
    "cam_num": 0,
    "width": 1440,
    "height": 1440,
    "fps": 15,
    "is_record": true
    "movie_fn": "save_data.mp4"
}
```

<details>
<summary>JSONのキーについて</summary>
1. cam_num: カメラ番号(PCにつながっているどのカメラの映像を取得するかを指定) <br>
2. width: カメラ映像の幅 <br>
3. height: カメラ映像の高さ <br>
4. fps: 映像を取得する際のFPS <br>
5. is_record: 動画を保存するかどうか(trueで保存, falseで非保存) <br>
6. movie_fn: 保存する動画のファイル名
</details>

次に、プログラムから取得します。

``` camera_main.py
import json
import cv2

f = open('config.json', 'r')
config = json.load(f)
cam_num = config['cam_num']  # カメラ番号
width = config['width']  # 画像の幅
height = config['height']  # 画像の高さ
fps = config['fps']  # FPS
is_record = config['is_record']  # 動画を保存するかどうか(bool型)
movie_fn = config['movie_fn']  # 保存する動画の名前

# Webカメラ映像取得設定
cap = cv2.VideoCapture(cam_num)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
cap.set(cv2.CAP_PROP_FPS, fps)

if is_record:
    # 動画保存初期設定
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(movie_fn,
                          fourcc,
                          fps,
                          (width, height))

```

# 撮影
続いて、撮影開始から終了までを以下に示します。といっても以下のコードは調べたらすぐに出てくるような簡単なものを`is_record`によって動画を保存するかどうかを条件分岐させているだけです。qキーを押すまではカメラ映像を取得し表示し続けます。
コメントアウトされている箇所にみなさんの必要な処理を書きましょう。私の場合は360度カメラを使って人検知をしたかったので、極座標変換とYOLOによる人検知を**関数化して**記述しました。

::: note
<font color="red">**while文の中はできる限り簡素に**しましょう！</font>
プログラムの改変が楽になります。
:::

``` camera_main.py
while True:
    ret, frame = cap.read()

    # 撮影失敗
    if ret is False:
        print('Fail...')
        break

    # #########################
    # 
    # ここに画像に対する処理を記述
    # 例: 物体検出、画像変換など
    # 
    # #########################

    # 動画保存用
    if is_record:
        out.write(frame)

    # 表示
    cv2.imshow('Realtime Movie', frame)

    # 終了操作 キーボードの'q'キーを押すと終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        print('Stoping...')
        break

# 終了処理
cap.release()
# 動画保存用
if is_record:
    out.release()
cv2.destroyAllWindows()

```

# ライブラリ化
これまでに記述してきたPythonプログラムをまとめてライブラリのような形にすることで使用しやすくします。以下の`CameraCapture`クラスを読み込めば、Webカメラの映像を取得できます。
`if __name__ == '__main__':`の行以降はテストです。`CameraCapture`クラスを別のプログラムから読み込む際はこの行以降は実行されません。

``` camera_main.py
import json
import cv2

class CameraCapture:
    def __init__(self):
        self.config = read_json('./config.json')
        self.cam_num = self.config['cam_num']  # カメラ番号
        self.width = self.config['width']  # 画像の幅
        self.height = self.config['height']  # 画像の高さ
        self.fps = self.config['fps']  # FPS
        self.is_record = self.config['is_record']  # 動画を保存するかどうか(bool型)
        self.movie_fn = self.config['movie_fn']  # 保存する動画の名前

        # Webカメラ映像取得設定
        self.cap = cv2.VideoCapture(self.cam_num)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
        self.cap.set(cv2.CAP_PROP_FPS, self.fps)

        if is_record:
            # 動画保存初期設定
            self.fourcc = cv2.VideoWriter_fourcc(*'mp4v')
            self.out = cv2.VideoWriter(self.movie_fn,
                                       slef.fourcc,
                                       self.fps,
                                       (self.width, self.height))

    def stop_and_destroy(self):
        self.cap.release()
        # 動画保存
        if self.is_record:
            self.out.release()
        cv2.destroyAllWindows()

    def capture_main(self):
        while True:
            ret, self.frame = self.cap.read()

            # 撮影失敗
            if ret is False:
                print('Fail ...')
                break

            # %% ######################
            # 
            # ここに画像に対する処理を記述(メソッド)
            # 
            # #########################

            # %% リアルタイム映像出力
            # 動画保存用
            if self.is_record:
                self.out.write(self.frame)

            cv2.imshow('Realtime Movie', self.frame)

            # %% 終了操作
            if cv2.waitKey(1) & 0xFF == ord('q'):
                print('Stopping ...')
                break

        self.stop_and_destroy()

    # %% ######################
    # 
    # ここに画像に対する処理をメソッド化して記述
    # 例: 物体検出、画像変換など
    # 
    # #########################


# %% 他の処理でもよく記述するので関数化
def read_json(json_fp: str) -> dict:
    f = open(json_fp, 'r')
    return json.load(f)


# テスト
if __name__ == '__main__':
    cam = CameraCapture()
    cam.capture_main()

```


::: note warn
私は`read_json`関数は別プログラムに(`utils.py`)記述し、それをimportして使っています。別プログラムから`CameraCapture`クラスのみを呼び出す際はエラーとなるので注意してください。
:::


# おまけ: 並列処理化
ロボット制御をする上でカメラ映像取得を並列処理化させなければすごく重たく感じることがありました。カメラはカメラで独り立ちして欲しい時に`multiprocessing`モジュールを使って並列処理にします。


``` multi_cam.py
from multiprocessing import Process
from camera_main import CameraCapture

def main_method():
    """
    ここにメインの処理
    """
    pass

if __name__ == '__main__':
    cam = CameraCapture()
    cam_pr = Process(target=cam.capture_main)

    main_method()

```

# 終わりに
Webカメラの使い方を扱いやすさに沿って説明しました。
ここまで読んでいただきありがとうございます。

この記事内にもさまざまな部分で拙い箇所があると思いますが(命名やリファランスなど)、アドバイスをいただけると幸いです。
また、この記事は深夜に思いつきで書き始めたので実は動作確認をしていません... それに対してもお知らせいただけると幸いです。