ラズパイのカメラモジュールで撮影した画像を表示する

手持ちのラズパイ4にカメラモジュールV2.1を付けたので、試しに簡単なプログラムを作成してみました。その際色々苦労したので、調べ直す必要がないようにメモを残します。

#openCVを使ったプログラム
openCVにはカメラ取り込む機能もあり、また画面表示の機能もあるので非常に便利です。

```python:test.py
import cv2
camera = cv2.VideoCapture(0)
camera.set(cv2.CAP_PROP_FRAME_WIDTH,640)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT,480)
while True:
    _,image = camera.read()
#エフェクトを追加する行
    cv2.imshow('camera',image)
    key = cv2.waitKey(1)
    if key != -1:
        break
camera.release()
cv2.destroyAllWindows()
```

ウィンドウ上でキーを押すと、停止するようになっています。

##プログラムの説明
カメラの初期化は下の部分で行っています。画面サイズは特定の値を入れないと画像が崩れます。また`VideoCapture`の引数はデバイスIDなので環境により変化するかもしれません。

```python:
camera = cv2.VideoCapture(0)
camera.set(cv2.CAP_PROP_FRAME_WIDTH,640)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT,480)
```

連続して画像を撮るのは下の部分で行っています。`camera.read`メソッドによりループが回る度に`frame`に新しい画像データを入れています。

```python:
while True:
    _,image = camera.read()
```

ウィンドウ表示は下の部分で行っています。`waitKey`メソッドがないと表示されません。

```python:
    cv2.imshow('camera',image)
    key = cv2.waitKey(1)
```

開いたウィンドウを閉じる処理は下の部分で行っています。

```python:
camera.release()
cv2.destroyAllWindows()
```


#picameraとpygameを使ったプログラム
openCVだけもよいのですが、同じウィンドウ上にアイコン表示やマウス操作も考えるとpygameを使った方が便利そうです。（ついでに、画像取り込みを`picamera`モジュールにしてみました。）ネットでは`capture`メソッドを使っているサイトが多々ありましたが、今回は`capture_continuous`メソッドを使っています。

```python:test2.py
import picamera
from picamera.array import PiRGBArray
import pygame
import cv2                                                           #エフェクト用
width = 480
height = 640
camera = picamera.PiCamera()
camera.resolution = (height,width)
pygame.init()
screen = pygame.display.set_mode((width,height))
rawCapture = PiRGBArray(camera,size=(height,width))
for frame in camera.capture_continuous(rawCapture,format='rgb',use_video_port=True):
    image = frame.array
#エフェクトを追加する行
    surf = pygame.surfarray.make_surface(image)
    screen.blit(surf,(0,0))
    pygame.display.flip()
    rawCapture.truncate(0)
```

終了する際はCtrl+Cでプログラムを止めてください。

##プログラムの説明
カメラの初期化は下の部分で行っています。`resolution`で取り込む画像の大きさを決めていますが、何故か縦と横の並びが逆になっています。

```python:
camera = picamera.PiCamera()
camera.resolution = (height,width)
```

pygameのウィンドウ表示は下の部分で行っています。

```python:
pygame.init()
screen = pygame.display.set_mode((width,height))
```

連続して画像を撮るのは下の部分で行っています。`capture_continuous`メソッドは`generator`を作成し、ループが回る度に`frame`に新しい画像データを入れています。なお、`use_video_port=True`は直にビデオポートからデータを取り出すことにより高速化を図るものです。また、最後の`truncate`メソッドがないと新しい画像を取り込むことができません。

```python:
rawCapture = PiRGBArray(camera,size=(height,width))
for frame in camera.capture_continuous(rawCapture,format='rgb',use_video_port=True):
    ....
    ....
    rawCapture.truncate(0)
```

pygameの描写に関しては下の部分で行っています。`frame.array`の部分で画像をRGBの3次元配列(480,640,3)にしています。それを`pygame.surfarray.make_surface`メソッドでpygameに使える`surface`形式に変換し、`blit`メソッドで画面に書き込み、`flip`メソッドで画面更新をしています。

```python:
    image = frame.array
    surf = pygame.surfarray.make_surface(image)
    screen.blit(surf,(0,0))
    pygame.display.flip()
```

#エフェクトの追加（openCV、picamera+pygame共通）
（`picamera`モジュールにもエフェクトがありますが）ここではopenCVのエフェクトを入れてみます。プログラムの`#エフェクトを追加する行`を下のように変更すると、エッジ検出の画像に変えることができます。

```python:
    image = cv2.Canny(image,100,200)
```

画像データを受け取った`image`変数はRGBの配列形式のデータになっておりnumpyの配列と同じように演算が可能なので、直接RGBの数値を操作し画像編集をすることもできます。

#さいごに
どの方法でも画像読み込みが遅いのが気になります。もっと高速化できる方法がないかを調べたいと思います。