RaspberryPiで監視カメラ 暗い部屋の撮影編

# はじめに

[前回](https://qiita.com/magiclib/items/6e09eb22b4c8816e24c4)、RaspberryPiで監視カメラを作成したのですが、暗い部屋でベビーモニタとして利用するには工夫が必要という状況になっていました。

暗い部屋で使うために

- USBライトの制御
- カメラの設定変更

について調査してみたので、まとめておきます。


# USBライトの制御

## ハードウェア

Can☆Doで買ったUSBライトを使用します。

![usb.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264524/74ec0fa5-9d4e-65bc-8810-8a776ab5b152.jpeg)


RaspberryPiに差してもlsusbで認識されないので、クラスとして認識するような仕組みはなくUSBコネクタから電力供給しているだけの動作のようです。

たぶん、どのメーカのUSBライトも同じ仕様だと思います。
Linux側からUSBポートの電源供給を制御する方法を探してみます。

## hub-ctrl

ぐぐったら、[hub-ctrl](https://github.com/codazoda/hub-ctrl.c)というソフトを使うことでUSBの制御ができるとのこと。

手順通りにgccでビルドしてインストールできました。

```
gcc -o hub-ctrl hub-ctrl.c -lusb
```

以下のコマンドで点灯、消灯ができました。

```
sudo hub-ctrl -h 0 -P 2 -p 0
sudo hub-ctrl -h 0 -P 2 -p 1
```


## 権限設定等

pythonからhub-ctrlを起動する場合にsudoのパスワード入力が問題になるので回避策を探します。

最初はsudoしなくてもUSB操作ができるグループを追加すればいけると思いましたが、そういうグループは無いんですね。
グループを追加はあきらめてsudoのパスワード要求をしないように設定を変更します。


```
sudo visudo
```

/usr/local/bin/hub-ctrl にコピーしたのでこれにNOPASSWDを指定した行を追加します

```
monitor_user    ALL=NOPASSWD: /usr/local/bin/hub-ctrl
```

これでsudoでのパスワード要求されなくなりますので、pythonからの呼び出しに支障はなくなりました。


## クライアント側

### HTML

点灯制御するトリガーのボタンを用意します。

```html:usb.html

<html>
  <head>
    <script src="./usb.js"></script>
  </head>
  <body onload="on_load();">
    <input type="button" value="usb" onclick="on_button_light();">
    <div>
      <canvas id="canvas_image" width="640" height="480"></canvas>
    </div>
  </body>
</html>


```

### javascript

ボタンのハンドラでUSBライトの点灯制御を要求するコマンドを送信します。

```javascript:usb.js

var image_socket = null;

var usb_socket = null;
var mode_usb = true;


function on_load()
{
  // 画像通信用WebSocket接続
  img_url = "ws://" + location.hostname + ":60002"
  image_socket = new WebSocket(img_url);
  image_socket.binaryType = 'arraybuffer';
  image_socket.onmessage = on_image_message;

  usb_url = "ws://" + location.hostname + ":60004"
  usb_socket = new WebSocket(usb_url);
  usb_socket.binaryType = 'arraybuffer';
}

function on_button_light()
{
  mode_usb = mode_usb ? false : true;

  var command_data = { mode: mode_usb };
  usb_socket.send(JSON.stringify(command_data));
}


function on_image_message(recv_data)
{
  // 受信したデータをbase64文字列に変換
  var recv_image_data = new Uint8Array(recv_data.data);
  var base64_data = ""
  for (var i=0; i < recv_image_data.length; i++) {
    base64_data += String.fromCharCode(recv_image_data[i]);
  }

  // 画像をcanvasに描画
  var canvas_image = document.getElementById('canvas_image');
  var ctx = canvas_image.getContext('2d');
  var image = new Image();
  image.onload = function() {
    ctx.drawImage(image, 0, 0);
  }
  image.src = 'data:image/jpeg;base64,' + window.btoa(base64_data);
}


```
## サーバー側


USBの電源制御するWebSocketのサーバーを用意します。

### USB制御サーバー

クライアント側で送信したON/OFFの要求を受けてhub-ctrlを呼び出します。

```python:UsbServer.py

import asyncio
import websockets
import json
import subprocess


class UsbServer:
    def __init__(self, loop, address, port):
        self.loop = loop
        self.address = address
        self.port = port


    async def _handler(self, websocket, path):
        while True:
            try:
                recv_data = await websocket.recv()
                dic_data = json.loads(recv_data)
            except:
                print('usb recv Error.')
                break

            if dic_data['mode']:
                command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '0']
            else:
                command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '1']

            print(command)
            try:
                process = subprocess.Popen(command, stdout=subprocess.PIPE)
                output = process.stdout.read().decode('utf-8')
                print(output)

            except:
                print('usb proc Error.')

    def run(self):
        self._server = websockets.serve(self._handler, self.address, self.port)
        self.loop.run_until_complete(self._server)
        self.loop.run_forever()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    wss = UsbServer(loop, '0.0.0.0', 60004)
    wss.run()

```

USBの親HUBごと電源をOFFにするのでUSB AudioもOFFになるのは問題ですが、電源制御対応の子USB HUBを挟めば対応できるそうです。（手持ちのHUBでは動作確認できませんでした）


# カメラの設定変更

USBライトの明かりだけはうまく撮影できなかったので、カメラ側の設定もいじることにします。

PiCameraで変更できるプロパティで明るさを制御します。


## 明るく撮影する設定

前回作成したImageサーバーにフレームレート、シャッタースピード、露出補正の設定を追加します。

[公式](https://picamera.readthedocs.io/en/release-1.10/api_camera.html)の説明を読んでいろいろ設定してみましたが、バージョンで動作が違うので正しい設定がよくわかりませんでした。ネットの情報もばらばらで正解がよくわからないです。試行錯誤してうまくいった設定値にしています。


```python:ImageServer.py 

import asyncio
import websockets
import io
from picamera import PiCamera

class ImageServer:

    def __init__(self, loop, address , port):
        self.loop = loop
        self.address = address
        self.port = port
        self.camera = PiCamera()
        self.camera.framerate = 1
        self.camera.exposure_compensation = 10
        self.camera.shutter_speed = 1000 * 8000

    async def _handler(self, websocket, path):
        with io.BytesIO() as stream:
            for _ in self.camera.capture_continuous(stream, format='jpeg', use_video_port=True, resize=(640,480)):
                stream.seek(0)

                try:
                    await websocket.send(stream.read())
                except:
                    print('image send Error.')
                    break

                stream.seek(0)
                stream.truncate()

    def run(self):
        self._server = websockets.serve(self._handler, self.address, self.port)
        self.loop.run_until_complete(self._server)
        self.loop.run_forever()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    ws_is = ImageServer(loop, '0.0.0.0', 60002)
    ws_is.run()


```


## 動作確認

シャッタースピードが遅いのでライトのON/OFFしてから少し待たないと更新されないですが、それなりに撮影できています。

![anime_usb.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264524/17ee2743-7903-9081-d1bd-aa6fceb10fb3.gif)


# おわりに

USBの電源制御とシャッタースピードの制御でなんとか実用レベルになりました。

![omake.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264524/cf5977a2-3c5f-2b72-64d1-74b84f7449e4.jpeg)