MotoJapan's Tech-Memo

技術めも

【Javascript】カメラ(インカメラ/アウトカメラ)を切り替える方法 【お家IT#11】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp


目次

前回からWebカメラ周り(Video要素やwebRTC, UserMedia)の操作を進めていたが、カメラリソース周りを今回進める。
やりたいことはWebアプリでのスマホカメラ起動なのでフロントカメラ、バックカメラの切り替えを行いたい。

参考資料

この為にいろいろ調べたけど、パッとした記事はあまりなく結局下記の資料に行き着いた。
ここを読めば大体使い方のイメージはつく。

Demo
webrtc.github.io

Github
github.com

というこれ本家。

MediaDeviceInfoの解説とUserMediaの使い方手順

上を読めばカメラだけでなくマイク、スピーカーのUserMedia周りの実装が分かるし、私が纏めるまでもないが、
重要なのは、”navigator.mediaDevices.enumerateDevices()”

この関数の戻り値は、フロントエンド側でアクセス可能なI/Oメディアデバイスの情報(MediaDeviceInfoオブジェクトの配列)である。(Promise Object)
MediaDeviceInfoには下記の情報が含まれている。

label バイス名(Macなら”FaceTime HD Cam” とか)
deviceId 同一セッション内で一貫したデバイスID (セッションが切れるとrefresh)
groupId 物理デバイス単位でのグループID
kind videoinput / audioinput / audiooutput

手順としては次の3つ
1) navigator.mediaDevices.enumerateDevices()でdeviceIdを取得
2) 1をvideoのdeviceIdと指定し、navigator.mediaDevices.getUserMediaでstreamを取得
3) 2のstreamをwindow.stream / video elementのstreamに代入


以上。

次回は、カメラ起動前後のvideoタグのサイズ変化を検出する。

【Javascript】video/canvasを上下180度反転(回転)させる方法 【お家IT#10】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp

目次 

前回までの投稿でバックエンドとフロントエンドの通信周りは整理した。
ここからはWebカメラの操作を進める。

今回は、video/canvasを上下180度反転(回転)させるたい。
原因は、Windowsタブレットのカメラが上下反転して表示されてしまう。(ハードウェア原因)
スマホなら問題ないのだが、気持ち悪いので補正する。

Videoの回転

HTML5&CSS3の組み合わせで、webkitのtransform rotationを使えば回転可能
回転中心は、Video領域の中心である

//**************** html **********************

<video id="camera" width="720px" autoplay></video>

//**************** css ***********************

<style type="text/css">
.Rotate0{
    -webkit-transform: rotate(0deg);
}
.Rotate180{
    -webkit-transform: rotate(180deg);
}
</style>

//************ javascript ********************

//関数から動的に変更する
<script type="text/javascript">
function rotate_video(degree) {
    var video = document.getElementById('camera');
    //スタイルを適用する関数を定義
    video.className = "Rotate"+degree;
}

//元の画像から180度反転する
rotate_video(180)
//元の画像に戻る
rotate_video(0)
</script>

Canvasの回転

Canvasの回転はテクニックが必要で、
単純にCanvasのcontextをrotateすると回転中心が(x, y) = (0, 0)つまり、左上部になってしまい、Canvasの中心では回転しない
なので、translate(移動1) -> rotate(回転) -> translate(移動2)の手順で意図した回転になる。
移動1は、Canvas中心が(x, y) = (0, 0)になるように、画像半分の縦横長で移動
移動2は、Canvas中心が移動1実行前の位置に戻す為に、移動1と同じだけ反対方向に移動

今回はcanvasを回転させてから、videoタグの画像を張り付けるようにします。

//**************** html **********************

<canvas id="canvas"></canvas>

//**************** css ***********************

//不要

//************ javascript ********************

//関数から動的に変更する
<script type="text/javascript">
function rotate_canvas(degree) {
    var canvas = document.getElementById('canvas');
    //canvasの描画モードを2sに
    var ctx = canvas.getContext('2d');
 
   //Canvasに設定するサイズを決める
    //(例えば、videoの縦幅横幅を取得)
    var video = document.getElementById('camera');
    var w = video.offsetWidth;
    var h = video.offsetHeight;

    //同じサイズをcanvasに指定
    canvas.setAttribute("width", w);
    canvas.setAttribute("height", h);

    //一旦canvasをリセットしたければ
    //ctx.clearRect(0, 0, canvas.width, canvas.height);

    //[移動1] Canvas中心が(x, y) = (0, 0)になるように、画像半分の縦横長で移動
    ctx.translate(w/2, h/2);
    //[回転]
    ctx.rotate(degree * Math.PI / 180);
    //[移動2] Canvas中心が移動1実行前の位置に戻す為に、移動1と同じだけ反対方向に移動
    ctx.translate( -1 * w/2, -1 * h/2 );    

    //videoの画像をcanvasにコピー
    ctx.drawImage(video, 0, 0, w, h);
}

//元の画像から180度反転する
rotate_canvas(180)
//元の画像に戻る
rotate_canvas(0)
</script>

以上。
次回はカメラ切り替えを予定。

【flask to Javascript】jsonデータを送信/受信する方法(jsonify) 【お家IT#9】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp

目次 

前回までで、javascript->flaskへbase64を送信した。
これを画像処理して、その結果を返信するときjsonだと汎用性も高い。
なのでjsonデータの返信方法をメモ。

送信側実装 (python:flask)

flaskのjsonifyを使うと簡単。

from flask import Flask, request, abort, make_response, current_app, jsonify

...
@app.route('/hoge_image_processing', methods=['POST'])
@crossdomain(origin='*')
def image_processing():

    ...

    res = {
        'ip_type' : 'OCR',           # 画像処理タイプ
        'result'  : 'hogehoge',      # 画像結果
        'prefix'  : '201701011200'   # 時刻プレフィクス
    }    
    
    return jsonify(ResultSet=res)
...

受信側実装 (javascript)

いままでの続きに追記です。
重複部分はある程度省略。

#ajax用モジュール読み込み
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
    ...

    function send_img(){
        ...

        #ajax送信
        $.ajax({
            //画像処理サーバーに返す場合
            url: 'https://192.168.0.100:12345/hoge_image_processing',   
            type: 'POST',
            data: fData ,
            contentType: false,
            processData: false,
            success: function(data, dataType) {
                //非同期で通信成功時に読み出される [200 OK 時]
                //console.log('Success', data);
                if (data.ResultSet.ip_type == 'OCR') {
                    var res    = data.ResultSet.result; // 'hogehoge'
                    var prefix = data.ResultSet.prefix; // '201701011200'

                }

            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                //非同期で通信失敗時に読み出される
                console.log('Error : ' + errorThrown);
            }
        });
    }
    ...
</script>

以上。

ここまでの4回の投稿でバックエンドとフロントエンドの通信周りは整理されたので、次回からはWebカメラの操作(反転、カメラ切り替え、サイズ変化検出)を進める。

【flask】Replyヘッダーに「Access-Control-Allow-Origin」を追加する【お家IT#8】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp

目次 

前回は、base64でバックエンドに画像を送信したが、次のエラーが表示れている。

XMLHttpRequest cannot load https://192.168.0.100:12345/hoge_image_processing. 
No ´Access―Control―Allow―Origin’ header is present on the requested resource. 
Origin 'https://192.168.0.100:12345' is therefore not allowed access.

結局は、バックエンドが返信してくるパケットのreplay headerに「Access―Control―Allow―Origin」がないというエラー。

対策

Decoratorを使うと簡単にheaderに´Access―Control―Allow―Origin’と設定できる。
参考資料のコードを参考にすると動く。
(私の環境ではうまく動かない関数があったりしたので、片っ端から修正)

@crossdomainの使い方は下記の通り。

...
@app.route('/hoge_image_processing', methods=['POST'])
@crossdomain(origin='*')
def image_processing():
    ...

これで簡単にヘッダーに必要情報が付加でき、エラーも解消された。
次回は、画像処理結果を通知するために使用する、flask->javascriptへのjsonデータ返信について纏める。

【Javascript to flask】base64で画像を送信/受信する方法 【お家IT#7】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp

目次 

前々回、ChromeWebカメラを起動/撮影した後、Canvasに書き出すところまで進めた。
前回は、Ajaxを利用した単純なデータ通信をまとめた。
この流れでCanvasの画像をbase64化してサーバーに送信したい。

やり方がわかると実はすごく単純。

base64とは

データを["a-z", "A-Z", "0-9", "+", "/"]の64文字で表現するエンコード方式
(データは特に画像などに限らない。)

送信側実装(Javascript)

#ajax用モジュール読み込み
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
    ...

    function send_img(){
        //canvas elementを取得
        var canvas = document.getElementById('canvas');
        //base64データを取得(エンコード)
        var base64 = canvas.toDataURL('image/png');

        var fData = new FormData();
        fData.append('img', base64);

        #ajax送信
        $.ajax({
            //画像処理サーバーに返す場合
            url: 'https://192.168.0.100:12345/hoge_image_processing',   
            type: 'POST',
            data: fData ,
            contentType: false,
            processData: false,
            success: function(data, dataType) {
                //非同期で通信成功時に読み出される [200 OK 時]
                console.log('Success', data);
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                //非同期で通信失敗時に読み出される
                console.log('Error : ' + errorThrown);
            }
        });
    }
    ...
</script>

受信側実装(python : flask)

import base64
from PIL import Image
from io import BytesIO

...
#前回に追記
@app.route('/hoge_image_processing', methods=['POST'])
def set_data():
    enc_data  = request.form['img']
    #dec_data = base64.b64decode( enc_data )              # これではエラー  下記対応↓
    dec_data = base64.b64decode( enc_data.split(',')[1] ) # 環境依存の様(","で区切って本体をdecode)
    dec_img  = Image.open(BytesIO(dec_data))
...

enc_dataを詳しく見てみると次の通り。

data:image/png;base64,kpvdfkgspkhgsKOJIJgmfkdvmkkPOKPO
DSmkpogj/mKRFPVKMPOGKP+OSKPVKS++LKVOPKIGRPOIJpkvmplldk
iVBaasfdojOIAJHSUHFvDFNIOVEOIJobjtredpjORw0KGgoAAAANSU
hEUgAAAoAAAAHgCAYAAAA10dzkAAAgAElEQVR4Xky9W4+la5adNb/z
WisiI3Mfq6vbXY1E26ItVcsWssG ...

“data:image/png;base64,”以降がbase64エンコードされた画像情報(エンコードデータ)。
これをpythonで扱えるようにするためには、次の手順が必要。

  • 1. エンコードデータbase64.b64decodeでデコードして、byte列にする。
  • 2. byte列を、BytesIOでバイナリストリームに変換し、PILでpythonで扱える画像形式にする。

以上。

ここまで来てフロントエンドからは 「No 'Access-Control-Allow-Origin' header is present on the requested resource. 」というエラーがでているので、次回はこれを修正する。

あと、Javascript->flaskへの通信が多いが、画像処理結果をWebアプリに送り返したいこともある。
次々回は、flask->Javascriptjsonデータ通信を纏めたい。

【Javascript to flask】FormDataを送信/受信する方法 【お家IT#6】

本件の実装の一部 
motojapan.hateblo.jp

前回の続き 
motojapan.hateblo.jp

目次 


前回まででWebカメラで撮影した画像をCanvas領域にコピーするところまで終えた。
が、根本的にJavascript - python(flask)環境でのフロントエンド-バックエンド間通信について知識がないので手軽にサーバーに対してデータ送信する方法をメモ。


今回は簡単な文字列"hogehoge"と"fugafuga"を送受信する例。

送信側実装(Javascript)


「FormDataというデータコンテナ」と「Ajaxによる非同期通信」を利用して送信する方法が一番簡単に試せる。
JavascriptのFormDataは、append(,)することで追加し続けられる。

#ajax用モジュール読み込み
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
    ...

    function send_text(){
        var text1 = 'hogehoge'
        var text2 = 'fugafuga'

        #FormData初期化
        var fData = new FormData();

        fData.append('text1', text1);
        fData.append('text2', text2);

        #ajax送信
        $.ajax({
            //配信元のhttpsサーバーに返す場合
            url: '/register',                            
            //別サーバー(今回でいう画像処理サーバー)に返す場合
            //url: 'https://192.168.0.100:12345/register',   
            type: 'POST',
            data: fData ,
            contentType: false,
            processData: false,
            success: function(data, dataType) {
                //非同期で通信成功時に読み出される [200 OK 時]
                console.log('Success', data);
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                //非同期で通信失敗時に読み出される
                console.log('Error : ' + errorThrown);
            }
        });
    }
    ...
</script>

受信側実装(python : flask)

Flaskのrequest.formは、でアクセスする。
前回のスクリプトの差分で必要箇所を記載(非常に簡単)

from flask import Flask, render_template, request

...
#前回に追記
@app.route('/register', methods=['POST'])
def set_data():
    text1 = request.form['text1']
    text2 = request.form['text2']

...

対応関係

下記に対応関係を簡単にまとめる。

javascript python:flask
Formデータ var fData = new FormData(); request.form
Formデータ読み書き [Write] fData.append('text1','hogehoge'); [Read] request.form['text1']


以上。
これでデータの登録などの単純なやり取りができる。

次回はbase64を送受信する話。

【flask】https対応Webサーバーをバックグランド実行する方法 【お家IT#5】

本件の実装の一部
motojapan.hateblo.jp

前回の続き
motojapan.hateblo.jp

目次

標準入力をバックグラウンドで無視すると落ちる

前回 flaskをhttps化 (http over ssl)したが、実行時にはpass phraseの入力が必要。
サーバーなのでSSHで繋ぎ、バックグラウンドで実行してセッションを切断したいことはよくある。
ただし、前回のスクリプトを無理やりバックグラウンド実行すると次のエラーが出る。

$$ sudo python app.py &
$ Enter PEM pass phrase:
    Traceback (most recent call last):
  File "app.py", line 8, in <module>
    context.load_cert_chain('cert.crt', 'server_secret.key')
IOError: [Errno 22] Invalid argument

バックグラウンド実行対策

load_cert_chainの第3引数にパスワードを入れてあげると可能

#app.py
from flask import Flask, render_template, request
import ssl

app = Flask(__name__)
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)

# >>> 対策前
#context.load_cert_chain('cert.crt', 'server_secret.key')
# <<<

# >>> 対策後
with open('./pwd.txt') as f:
    #平文で書くのは怖いのでローカル管理するファイルを呼び出す
    pwd = f.read()
context.load_cert_chain('cert.crt', 'server_secret.key', pwd)
# <<<

@app.route("/")
def index():
    return render_template('index.html')

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=800, ssl_context=context, threaded=True, debug=True)

下記参考資料のAPI referenceにも次の通りの記載。

password 引数に、秘密鍵を復号するためのパスワードを返す関数を与えることができます。その関数は秘密鍵が暗号化されていて、なおかつパスワードが必要な場合にのみ呼び出されます。その関数は引数なしで呼び出され、string, bytes, または bytearray を返さなければなりません。戻り値が string の場合は鍵を復号化するのに使う前に UTF-8エンコードされます。string の代わりに bytes や bytearray を返した場合は password 引数に直接供給されます。秘密鍵が暗号化されていなかったりパスワードを必要としない場合は、指定は無視されます。

password が与えられず、そしてパスワードが必要な場合には、OpenSSL 組み込みのパスワード問い合わせメカニズムが、ユーザに対話的にパスワードを問い合わせます。


Python 標準ライブラリ」「18.2. ssl — ソケットオブジェクトに対する TLS/SSL ラッパー」より
URL:
https://docs.python.jp/3/library/ssl.html

これでめでたく、PC版Android版でもChromeWebカメラが動くので、次回からは撮影した画像をbase64化してjavascriptで送る部分を進める。