【Ubuntu】WebRTCで色々画像処理して遊ぶ【Flask】

#目的
WebRTCを使って、Web上からPCやスマホのカメラから映像取得して、それをWebサーバに送信。サーバ側で画像処理して、ユーザ側には画像を返すなり何かしらパラメータ返すなりする。
例えばスマホで撮った映像から物体検出等をリアルタイムですることができる。

#開発環境
- OS
    - Ubuntu16.04.5(AWSのEC2使用)
- 言語
    - Python3.5.2
        - Flask
        - OpenCV
    - HTML5
    - JavaScript
        - WebRTC
        - JQuery

WebRTCを使用する際はSSL通信化しておく必要があります。flaskのSSL通信化については私が以前書いたものを参考にしてください。
[【Ubuntu16.04.5】PythonのFlaskをHTTPS化](https://qiita.com/taka_katsu411/items/fb1ad876c0017b9fe49d)

# 処理の流れ
## WebRTCによるカメラ映像取得
いつのバージョンからか、WebRTCを使用する際はasyncを使用しなければならないようです。
調べても大体async使ってないソースばっかりで、エラーに悩まされてました。公式のサンプルソースコードをしっかり読みましょう[[1]](https://webrtc.github.io/samples/src/content/getusermedia/gum/)。

映像取得だけなら以下の部分だけで十分です（多分）。（#myvideoはvideoタグ、#startはボタンタグ）
ただし停止ボタンが無いので延々とブラウザにカメラ映像が表示されます。停止するならリロードなり何なりしてください。

```javascript
$(function(){
	const constraints = window.constraints = {
	  audio: false,
	  video: {
		facingMode: "environment"
	  }
	};

	async function init() {
	  try {
	    const stream = await navigator.mediaDevices.getUserMedia(constraints);
	    const video = document.querySelector('#myvideo');
	    const videoTracks = stream.getVideoTracks();
	    window.stream = stream; 
	    video.srcObject = stream;
	    e.target.disabled = true;
	  } catch{
		$('#errorMsg').text('カメラの使用を許可してください');
	  }
	}

	$('#start').click(init);
});
```

`facingMode`では、デフォルトで使用されるカメラを指定しています。`"environment"`でフロントカメラ、`"user"`でインカメラを指します。未指定なら後者になります。

## 映像を処理用URLに送信
ぶっちゃけ[参考文献[2]](https://webrtchacks.com/webrtc-cv-tensorflow/)丸パクリです（リンク先はWebRTCからtensorflowの物体検出してる）。
流れとしては

1. canvasタグに一旦描画する
2. canvasタグから画像バイナリデータ取得する
3. データをAjaxで処理先に送信する

となります。以下のように実装しました。

``` javascript
	var canvas = $('#videocanvas')[0];

	$('#myvideo').on('loadedmetadata', function(){
		// canvasのサイズ合わせ
		var video = $('#myvideo')[0];
		var width = canvas.width = video.videoWidth;
		var height = canvas.height = video.videoHeight;
	
		// 描画先の指定
		var ctx = canvas.getContext("2d");
		
		// 送信データの作成
		var fd = new FormData();
		fd.append('video', null);

		//毎フレーム処理
		setInterval(function(){
			ctx.drawImage(video, 0, 0, width, height);
			canvas.toBlob(function(blob){
			
				fd.set('video', blob);
			
				$.ajax({
					url: "/img",
					type : "POST",
					processData: false,
					contentType: false,
					data : fd,
					dataType: "text",
				})
				.done(function(data){
					console.log(data);
				})
				.fail(function(data){
					console.log(data);
				});
			}, 'image/jpeg');
		},1000);
	});
```

`.on('loadedmetadata'`はvideoタグの読み込みが終わった時に実行する処理ということです。つまりはカメラに正常にアクセスできた場合ということですね。
そこからsetIntervalを使って定期的に送信していきます。

## 処理部

``` python
@api.route("/img", methods=["POST"])
def img():
    img = request.files["video"].read()

    # pillow から opencvに変換
    imgPIL = Image.open(io.BytesIO(img))
    imgCV = np.asarray(imgPIL)

    imgCV = cv2.bitwise_not(imgCV)

    # 好きな処理を入れる

    return "success"
```

まあぶっちゃけここはお好きにしてください。私はOpenCV使うので変換挟んでます。

# 処理後の画像を描画したい
Flaskにはもともとimgタグでストリーミングできる仕組みが存在します[[3]](https://qiita.com/sti320a/items/3cdafb737d2c16fbaa51)。
なのでそれを使えば実現できます。

``` python:app.py(一部)
@api.route('/feed')
def feed():
    return Response(gen(), mimetype='multipart/x-mixed-replace; boundary=frame')

def gen():
    while True:
        with open('./templates/dst/test.jpg', 'rb') as f:
            img = f.read()

        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + img + b'\r\n')
```

``` html:index.html(一部)
	<img id="preview" src="{{ url_for('feed') }}"><br>
```

こうして、あとは処理した画像を`test.jpg`に保存すれば解決。

# まとめ
（ぶっちゃけWebでやる必要ある？）
メリットとしては同じ精度や処理速度をスペックの異なるスマホ等で実現できる・・・ということくらいですかね。ただAWSのデフォルトサーバ程度では大した利点は無いです。
特徴点マッチング使った処理で、大体5アクセスの時点でCPU100%になってレスポンス遅くなりましたね。よっぽどのつよつよサーバじゃないとネイティブアプリで実現する方がいいかと思います。

ちなみにそもそもレスポンスは遅いです。大体1秒くらい。

# 参考文献
[1][getUserMedia(Sample Code)]
(https://webrtc.github.io/samples/src/content/getusermedia/gum/)
[2][Computer Vision on the Web with WebRTC and TensorFlow - webrtcHacks]
(https://webrtchacks.com/webrtc-cv-tensorflow/)
[3][FlaskとOpenCVを使ってWEBカメラで撮影した画像をストリーミングする - Qiita]
(https://qiita.com/sti320a/items/3cdafb737d2c16fbaa51)