ESP-WROOM-02の記事が続いたので、今日はまたEdisonに戻って、ESPには到底できないようなことを実行してみました。今回はWebでEdisonを操作したり、ボール認識させた動画を配信させたりしてみます。
左は白い円を認識している。右はOMURON非接触赤外線センサーで16か所の温度を測っている。切り替えはボタン操作)
実際に動かしている動画です。画像にモアレができてますけど、動作は結構早い
Edisonからの動画配信は、過去の記事により、SimpleCVで実現できています。
takesan.hatenablog.com
OpenCVが簡単に実行できたり、図形を合成してMjpeg配信できたりしてかなり面白いのですが、これだけではEdisonからの一方通行に過ぎません。今回は、クライアント側で動画を表示させながらEdisonを操作することに挑戦してみます。
まず材料
Edison,USB外部機器が使えるEaglet、i2cセンサー(今回は制御が簡単なOMURON非接触赤外線センサー。電源を5Vから取れば一応動きます)、安いWebCamera、LED(フローラ用のだと抵抗付きなので実験用に最適)今回の材料はカメラ以外全部スイッチサイエンスで手に入れてます。接続は赤外線センサーが4本(SDA,SCL,GND,5V)。LEDは当然2本(+側はラピロボードのIO12(mraaでは20番に指定),GND)。カメラはUSBに差し込むだけです。
左:Rapiro用Edisonキャリアボード 右:OMURON非接触赤外線センサー
floral用抵抗付きLED
今回はPython主体なので実験用に最適な、Rapiro用Edisonキャリアボードを使いました。理由は、「適度な大きさ、レベルシフターが必要無い(信号は全て3.3V出力)、電源出力が3,3Vと5V、カメラが使える、デジタル・アナログピンが使える。」です。そしてSDカードも外部記憶ディスクとして使えるのもGood!!。とりあえずピンヘッダを半田付けしてから。
Edisonは他のEagletボードに載せ替えOKなので、小さくまとめたいのならカメラが使える、Intel Edison Block - Base+αとかHenryとかが最適です。Edison本体の差し替えは、コネクタの保証範囲である30回以下になっていますが、慎重に抜き差しすれば(私のは30回の保証範囲を軽く超えている)なんとか持つようです。ただし、くれぐれもroot以下のバックアップを怠りなく。そんな場合でもこのボードは、面倒な設定が不要で、/media/sdcardにコピーするだけ。ただし、i2c6が使えないのでArduinoIDEからだとi2cが操作できませんが、デジタルが6本(内PMW2本)****I2S2_FS-->10、I2S2_CLK-->13、I2S2_TXD-->11、I2S2_RXD-->12、IO12-->3 PWM、IO13-->5PWM****使用可能、さらにシリアルが使えます。Arduinoに慣れている方は、私の1月8日の記事とは違う発想で、プロセス間通信を使って時間が掛かる処理をArduino側で実行させるといった様な面白そうなことができそうです。このボードはEDISON FOR PYTHON ボードといった方が良いかも。Pythonからはmraaで全ての端子が使えますが、ピン番号は回路図を見れば分かります。ちょと分かりにくいのですが、ラピロボードの回路図からEdisonのGPIOピン名を調べて、Intel(R) breakout boardの対応表からmraa番号を探します。(例:IO12->GP12->20。mraaの対応表中Pinmode1によりmraa20番はPWMであることが分かります)
https://s3-ap-northeast-1.amazonaws.com/switch-science.public/schematic/Edison_Carrier_Board_for_Rapiro/Edison_Carrier_Board_for_Rapiro%282%29.pdf
mraa: Intel Edison
まずしなければいけないこと
目標は、ボタンとスライダ操作それに動画の表示。Python上で実現するために、いろいろ調べて、簡単そうな「flask 」を使うことにしました。とにかくflaskを使ってSimpleCVのMjpeg動画を送れれば完成したようなもんです。
じゃーHtmlの知識もないのにどうしたらいいの? 例によってサンプルを徹底的に探しました。その中でも一番簡単に実現できそうなそうなサンプルがこれ。
python - How to send a pygame image over websockets? - Stack Overflow
この場合はpythonのOpencv=cv2を使って、SimpleCVの画像を送ってるようなのですけど、cv2(PythonのOpencv)独自のCamera関数は使えないみたい。SimpleCVのCamera関数は簡単に認識するのに不思議。どうやらSimpleCVのCamera関数は、Pygameを使ってる様なので、原因を探るのはまた今度にします(多分Pygameインストールの時のvideo.hあたり)。
まーそんなわけで、2日かかってSimpleCVのMjpeg動画がflaskで表示できるようになりました。カメラ表示部分の変換ソースは、bottol他のライブラリでも使えそうです。SimpleCV標準コマンドで画像配信するより、スピードが多少早いような気がします。変換部分が出来上がってみるとまー簡単だこと!!。Html部分を省いて、たった5行で出来ちゃいました。とても2日掛かりの労作とは思えませんよね。Html部分も私の能力に合わせて最小限にしました(詳しい方は笑ってください)。
あっそうだ。この「Eaglet」ラピロの頭だったんだ。
このボードをラピロの頭に入れると、画像の分析、顔認識、さらには画像の加工までできて動画配信可能。おまけに配信先からはラピロを自由に動せる。アイディアがどんどん膨らみます。pi3が発売されたそうですがpi3にすればもっと早くなる。でも電源が!!てな時、Edisonは未だに強〜い味方です。
ただ、今回のスライダー操作ではにタイムラグがあるので、さらなる改良が必要。
SimpleCvについて
この頃Edison向きにそこそこのスピードで画像処理するコツがつかめてきました。画像を分析する(例えば赤や青や白のボールを判断、顔認識するなど)には、よほど細かいものでない限り180X120ドット程度で十分なんです。でもそのままではWeb画面で表示すると画像が甘いので、実用性とEdisonの処理能力を考慮した限界である倍の解像度、すなわち320X240が許容範囲。これを内部的に半分にして処理すると、ロボットなどに使える実用スピードになります。最初のサンプル動画を見てください。これPythonインタプリタですよ。今回この部分もソースに書きましたので是非参考にしてみてください。
ソースの実行について
まずflaskのインストール。その前にyoctoをクリーンインストールしてSimpleCVをインストールしておくこと。それから
pip install flask
今回追加するライブラリのインストールは、これだけです。
そしてflaskの要求する実行用のディレクトリを作ります
◯◯◯◯◯/test.py
◯◯◯◯◯/templates/index.html
◯◯◯◯◯は好みのディレクトリとする。
実行方法は、python main.py >/dev/null 2>&1 でプログラム起動。Mac又はiPhoneのサファリから ◯◯◯◯◯.local:5000 で画面が表示されます。
(>/dev/null 2>&1 の分は、普通にPythonを実行させると、main.pyの実効後SimpleCVからのlogが際限なく画面に表示されるので、余計な表示をストップさせるため)
test.py index.htmlは、以下のソース。
知識のある方はjavascliptを使うなりどうぞご自由に。
以下test.py
#!/usr/bin/env python import gc from flask import Flask, render_template, Response,request, redirect from SimpleCV import * from cStringIO import StringIO import mraa import time gc.disable() #************ UI ************** atai="0.5" show="Show" on="ON" #************PWM-LED************** x = mraa.Pwm(20) x.period_us(700) x.enable(True) #************ i2c ************** D6T_addr = 0x0A D6T_cmd = 0x4C y = mraa.I2c( 1 ) #***********Main****************** cam=Camera(0,{ "width":320, "height":240 }) app = Flask(__name__) @app.route('/') def index(): return render_template('index.html',pwm="value="+atai,show=show,on=on) print atai def gen(): global show,on tdata=[] while True: if show == "Hide": #*******Ball find******** image=cam.getImage() image.drawText("White Ball", 5, 5,(255,255,255),fontsize=30) img1 = image.scale(0.5) dist=img1.colorDistance((0,0,0)) segmented=dist.stretch(225,225) blobs=segmented.findBlobs() if blobs: circles=blobs.filter([b.isCircle(0.3) for b in blobs]) if circles: image.drawCircle((circles[-1].x*2,circles[-1].y*2),circles[-1].radius()*2,(255,0,0),2) elif on == "OFF": #********OMURON********** image=cam.getImage() y.address(D6T_addr) y.writeByte(0x4C) z=y.read(35) for var in xrange(0, 16): tdata.append((z[(var*2+2)]+(z[(var*2+3)]<<8))/float(10)) for j in xrange(0,4): for i in range(0,4): image.drawText(str(tdata[i+j*4]),20+i*80,15+j*60,(255,94,25),fontsize=30) tdata=[] else: image = cam.getImage() fp = StringIO() #********Mjpeg********** image.save(fp, 'JPEG') frame=fp.getvalue() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') @app.route('/vid') def video_feed(): return Response(gen(),mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/contact', methods=['POST']) def contact(): global atai,on,show,x if request.method == 'POST': if request.form['submit'] == 'Show': show="Hide" on="ON" elif request.form['submit'] == 'Hide': show="Show" elif request.form['submit'] == 'OFF': on="ON" elif request.form['submit'] == 'ON': on="OFF" show="Show" elif request.form['range'] != '300': atai=request.form['range'] xx=float(atai) x.write(xx) return redirect('/') if __name__ == '__main__': app.run(host='0.0.0.0')
以下 index.html
<html> <font face="Chalkduster"> <head> <title>Edison Test!!</title> </head> <body onLoad="test()" bgcolor="#E7E8E2"> <h1> Edison Test!!</h1> <img id="bg" src="{{ url_for('video_feed') }}" width="320" height="240" /> <form action="/contact" method="post"> <p> BALL--> <input style="color:#000000;background-color:#CCEEFF ;font-size:15;width:140px;height:30px" type="submit" name="submit" value={{show}}></p> <p> TEMP --> <input style="color:#000000;background-color:#00ccff;font-size:15;width:140px;height:30px" type="submit" name="submit" value={{on}}></p> <p> PWM---> <input type="range" name="range" min="0" max="1" step="0.01" {{pwm}}> <input style="color:#C000000 ;background-color:#FFEEFF ;font-size:15;width:70px;height:30px" type="submit" name="submit" value="Send"></p> </form> </body> </font> </html>
では、また。