MotoJapan's Tech-Memo

技術めも

【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で送る部分を進める。

【flask】Webサーバーをhttps対応する方法 【お家IT#4】

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

前回の続き
motojapan.hateblo.jp

目次

JavascriptからWebカメラを起動しようとしたら下記コードで怒られた。
非Secure通信下でのgetUserMediaはdeprecatedとのこと。

[Deprecation] getUserMedia() no longer works on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.

SSL対応の手順

本来はSSLサーバー証明書を購入する必要があるがオレオレ証明書を作成することも可能。(通常利用は避ける)

秘密鍵を作成

まずサーバー側の鍵を作成

$ openssl genrsa -aes128 2048 > server_secret.key

Generating RSA private key, 2048 bit long modulus
.............+++
...............................................................................+++
e is xxxxx (0xxxxxx)
Enter pass phrase: (パスワード入力)
Verifying - Enter pass phrase:(パスワード再入力)

秘密鍵から公開鍵を作成

クライアント側の公開鍵を作成

$ openssl req -new -key server_secret.key > server_pub.csr
Enter pass phrase for server_secret.key: (パスワード入力)
・・・
↓必要事項入力
Country Name (2 letter code) [AU]:(国名)
State or Province Name (full name) [Some-State]:(都道府県名)
Locality Name (eg, city) []:(市区町村名)
Organization Name (eg, company) [Internet Widgits Pty Ltd]:(企業名)
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

デジタル証明書を作成

デジタル証明書とは

  • 秘密鍵から作った)公開鍵を正真であることを証明するデータ
  • 本来はCAを通して発行される

配布用証明書を作成

$ openssl x509 -in server_pub.csr -days 365000 -req -signkey server_secret.key > cert.crt

flask上でHTTPSサーバー起動

#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')

@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)

実行

$ sudo python app.py
Enter PEM pass phrase: (パスワード入力)
 * Running on https://0.0.0.0:800/ (Press CTRL+C to quit)
 * Restarting with stat
Enter PEM pass phrase: (再パスワード入力)
 * Debugger is active!
 * Debugger PIN: 123-456-789


無事起動(めでたしめでたし)

おまけ

ちなみに回避方法もあるにはあるが、
Chrome on Androidの場合、回避すらできないケースもあるので
折角ならhttps対応すべき。

【Javascript】Webカメラを起動&撮影する 【お家IT#3】

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

Webアプリ上でHTML+JSでWebカメラを動作させたい。
私の場合、それほど特殊なWebカメラの使い方をするわけでもないし、こういった類の記事は山のようにあるので自分が参考にした記事をメモ。

カメラ起動

今回はWebRTC(Web Real Time Communications)でカメラを起動する。
キーワードはUserMedia。

参考にした記事①

シンプルなサンプルコード
html5experts.jp
html5experts.jp

参考にした記事②

HTML5 での映像と音声の取得(細かい説明で一番良い)
https://www.html5rocks.com/ja/tutorials/getusermedia/intro/

カメラ撮影

カメラのプレビューを押す or 撮影トリガー(テキストやボタン)を押し、videoタグの画像をcanvasに保存には下記

<!-- HTML -->
<!-- カメラの準備 -->
<video id="camera" width="720px" autoplay></video>
<!-- 記録用canvas -->
<canvas id="canvas"></canvas>
<!-- 記録用img -->
<img id="img">
<!-- 撮影トリガー 1-->
<a id="rec" href="#">TAKE PICTURE</a>
<!-- 撮影トリガー 2-->
<input type="button" value="REC" onclick="take_picture()">
//Javascript
//video element
var video = document.getElementById('camera');

//video element(プレビュー画面)をクリックして撮影
video.addEventListener("click", function() {
    take_picture()
});

//テキストクリック撮影(撮影トリガー 1)
$("#rec").click(function() {
    take_picture()
});

//撮影関数
function take_picture() {
    //videoのstreamをcanvasに書き出す方法
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    //videoの縦幅横幅を取得
    var w = video.offsetWidth;
    var h = video.offsetHeight;    
    canvas.setAttribute("width", w);
    canvas.setAttribute("height", h);
    ctx.drawImage(video, 0, 0, w, h);

    //canvasを更にimgに書き出す方法
    var img = document.getElementById('img');
    img.src = canvas.toDataURL('image/png');
}

とりあえず参考資料を使うと、explorerでは動いた。

下記エラーでchromeだと見事に動かない。

[Deprecation] getUserMedia() no longer works on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.

chromeセキュアな通信(HTTPS, SSL)じゃないとカメラ起動できないとのこと。
ということで次回は、flaskサーバーをHTTPSします。

【お家IT#2】Webアプリと画像認識で家の食品名と賞味期限を簡単に登録したい(構想編)

下記記事の続きです。
motojapan.hateblo.jp

構想

Google Spreadsheetを探して、食品名と賞味期限を手入力するのは、実は結構めんどくさい。
なので、Webアプリ開発と画像認識の勉強も兼ねて、下のような構成を構想。
写真を撮影すると商品情報が取得でき、自動で登録されるようにしたい。

右側データ管理、通知は過去の記事でGASで実装済み。今回は左側webアプリサーバー、画像認識、登録などの一連のフローを実装予定。
f:id:motojapan:20171026004433p:plain

つまりはこんなイメージ。(みんな幸せ)
f:id:motojapan:20171016003018p:plain

環境

想定環境はRaspberry piをサラから使うので、下記の通り。

実装項目

ざっと思いつくやるべき項目を列挙。(超雑)
青字がフロントエンドとバックエンドの通信周り
赤字が最終的に求めたいデータ

バックエンド

  • Webサーバーを立ち上げる
  • Webサーバーをバックグラウンド実行する
  • HTTPS対応する(必要に応じて)
  • formを受信する(単純なPOSTデータの受信)
  • base64で画像を受信する(解析用画像の受信)
  • jsonを送信する(結果の送信)
  • pythonからgoogle spread sheetを読み書きする

フロントエンド

  • Webカメラを起動する
  • カメラを切り替える(フロント/バック)
  • Webカメラを起動後のvideoタグのレイアウトサイズを調べる
  • formを送信する(単純なPOSTデータの送信)
  • base64で画像を送信する(解析用画像の送信)
  • jsonを受信する(結果の受信)
  • CSSでレイアウト整理する

画像解析

  • 画像からバーコード情報を読み取る(画像⇒数列)
  • バーコード情報から商品名を調べる(数列⇒商品情報)
  • 画像から賞味期限を読み取る(画像⇒文字列)


以降は、この項目の開発メモ、ハマった話を書き残す予定。

興味がある人は次回以降の記事をご覧ください。