法人営業課でOJT中の鎌田です。少年老い易く学成り難し、あっという間に入社して一年が経とうとしています。まだまだ一年生を満喫していたいのですが・・
なるべく手軽に画像処理がしたい
近年ダヴィンチmini makerなど、3万円台で購入することができる安価なプリンタが登場しています。3Dプリンタを使うには、.stl、XYZ形式(.3w)、3mfなどのファイルフォーマットに対応したデータを用意する必要がありますが、Blenderなどの3Dレンダリングソフトを使う必要があり、少し敷居が高いです。なので、例えば画像を取り込んで、そこからstlファイルを生成するような仕組みがあれば、とても簡単に利用ができると考えました。
なるべく安く画像処理がしたい
今回、この仕組みをAWSで実現することを考えました。また、オンデマンドインスタンスを用意することに比べメンテナンスに手間がかからず、安価で済むサーバーレスでできるように、試行錯誤しました。
できたこと、できなかったこと
できたこと
・画像からstlファイルの生成
・生成したstlファイルのブラウザ上でのプレビュー
・S3を用いたウェブサイトの実装
・上記ウェブサイトでCognitoによる認証を追加(オプション)
できなかったこと
・Lambdaを使ったstlファイルの生成
・S3のみでstlファイルの生成
今回、stlファイルの生成にはPythonのライブラリstl_toolsを使用しました。ですがパッケージサイズが200MBを超え、Lambdaのファイルサイズ上限を超えてしまいました。よって、Lambdaは実行できませんでした。
S3のみの場合、BrythonというJS内でPythonを実行可能にする実装を使用します。こちらを使ってみましたが、Python実装のファイルサイズが大きくクライアント側のロード時間に難があり、断念しました。
ということで今回の構成は以下になりました。
S3に画像をアップロードする画面が用意されており、画像がアップされるとQueueが発行されます。
EC2側でPythonスクリプトがQueueに対してポーリングを行っており、SQSからアップロードされたファイル情報を取得してS3よりファイルをダウンロードし、stlファイルを生成してS3にアップロードします。S3の画面をリフレッシュすると、生成されたstlモデルがくるくると表示されるようにしました。
デモ動画
EC2の実装
以下よりEC2の実行スクリプト(Python)の説明を行います。 ファイル名はconvert_stl.pyとしています。
import json import urllib import boto3 from stl_tools import numpy2stl from scipy.ndimage import gaussian_filter from pylab import imread
今回使うライブラリは以上となります。
こちらはpip install コマンドなどであらかじめインストールしておく必要があります。あるいは、こちらからrequirements.txtを落として
pip install -r requirements.txt
としてもokです。
client = boto3.client('sqs') while 1: que_message = client.receive_message( QueueUrl = 'YourQueueUrl' ) if "Messages" in que_message: records = que_message['Messages'][0]['Body'] head = records.rindex('key') print records if 'png' in records: tail = records.rindex('png') key = records[head+6:tail+3].replace('+',' ') elif 'jpg' in records: tail = records.rindex('jpg') key = records[head+6:tail+3].replace('+',' ') else: print "wrong queue" break; s3 = boto3.resource('s3') s3.Object('yourbucketname', key).download_file(key) A = 256 * imread(key) A = A[:, :, 2] + 1.0*A[:,:, 0] # Compose RGBA channels to give depth A = gaussian_filter(A, 1) # smoothing numpy2stl(A, 'examples.stl', scale=0.05, mask_val=5., solid=True) s3.meta.client.upload_file('examples.stl', 'yourbucketname', 'examples.stl') client.delete_message( QueueUrl = 'YourQueueUrl', ReceiptHandle = que_message["Messages"][0]["ReceiptHandle"] ) print "successfully uploaded!" else: print 'None of queue'
上記はclient.receive_message
によってキューを取得し、その中にpng,もしくはjpgという文字列が含まれていればファイル名を取得し、S3からダウンロードを行っています。ダウンロード後にスムーシング処理などを行いstlファイルを生成し、作成したファイルをS3にアップロード、最後にキューのメッセージを削除しています。
S3の実装
下記より実装したindex.htmlを抜粋して説明します。ソースコードはこちらにありますのでご興味があればご覧ください。
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script> <script src="//sdk.amazonaws.com/js/aws-sdk-2.1.34.min.js"></script> <script src="src/three.min.js"></script> <script src="src/stats.js"></script> <script src="src/detector.js"></script>
上記では最初にライブラリを取得しています。S3でウェブサイトをホストする場合、ライブラリを直接置いていても課金額は微々たるものですから重たいものは直置きしています。階層構造は下記のような構造が望ましいと思います。
[user]$ tree -L 3 [stl(master)] . ├── requirements.txt ├── server.py └── www ├── convert_stl.py ├── index.html └── src ├── detector.js ├── stats.js └── three.min.js 4 directories, 12 files
なお、server.pyはローカルでデバックを行うために用意しています。 python server.py
と実行するとwww以下にあるindex.htmlをブラウザで読み込んでくれます。S3と同期する際にはコマンドラインインターフェースのaws s3 syncを右記のように使うと簡単に差分同期できます。 aws s3 sync local/developments/project_name s3://tekito-bucket
<br> <br> <br> <input type="file" name="upload" id="upload-file"> <a href="javascript:void(0)" id="apply-upload">upload</a> <br> <a href="https://s3-ap-northeast-1.amazonaws.com/yourbucket-name/examples.stl"> <button>download</button> </a> <br> <input type="button" value="refresh this page" onclick="location.reload();" />
上記はユーザーインターフェイスを定義しています。アップロード、ダウンロード、ページのリフレッシュボタンを設定しています。
<br> <script> $(function() { var s3_client = function() { AWS.config.region = "ap-northeast-1"; AWS.config.credentials = new AWS.CognitoIdentityCredentials({IdentityPoolId: "yourcognito pool id"}); AWS.config.credentials.get(function(err) { if (!err) { console.log("Cognito Identify Id: " + AWS.config.credentials.identityId); } }); return new AWS.S3({params: {Bucket: "yourbucketname"}}); }; $("#apply-upload").click(function() { var file = $("#upload-file").prop("files")[0]; var timestamp = new Date().getTime(); var filename = timestamp + ".png"; s3_client().putObject({Key: "row-image/" + filename, ContentType: file.type, Body: file, ACL: "public-read"}, function(err, data){ // if failed, alert if(data !== null){ alert("アップロード成功!"); } else { alert("アップロード失敗."); } }); }); }); </script>
上記コードは、Cognitoを用いて認可を行っています。ユーザーはlocalStorageにSTS(一時認証情報)をストックすることで、操作に必要な権限を手に入れることができます。Macなら、ブラウザ上でoption + command + i を押すことでブラウザコンソールが開きます。そこでlocalStorageと入力すると下記のような表示がされ、一時認証情報を取得できていることを確認することができます。Cognitoについてはこちらの公式ドキュメントをご参照ください。
localStorage Storage {aws.cognito.identity-id.ap-northeast-1:brabrabra: "ap-northeast-1:brabrabra", length: 1}
また、今回一部のスクリプトソースを他サイトより取得しているので、S3のマネジメントコンソール、バケットのプロパティよりCORSの設定が必要となります。下記のように設定をすることで許可ができます。
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>Authorization</AllowedHeader> <AllowedHeader>Content-*</AllowedHeader> <AllowedHeader>Host</AllowedHeader> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
後の、stlファイルをブラウザに表示するための実装部分はあまりにも長いため割愛しますが、ご興味があればこちらよりご参照ください。なお、WebGLを用いた描写をしています、ブラウザの種類やバージョンによって動作しないこともありますのでご注意ください。(Google Chrome最新版では動作しません。Firefoxでは動作を確認しています)
作ったstlファイルを印刷する
先日家庭用3DプリンタALUNAR Reprap Prusa i3を買い、組み立てを行いました。後ほど、stlファイルの印刷を行おうと思います。なお組み立て時の様子は
以下のスクリプトをRaspberry Pi3で実行し、定点観測しています。
!/bin/sh while : do DATE=$(date +"%Y-%m-%d_%H%M%S") fswebcam $DATE.jpg aws s3 cp $DATE.jpg s3://yourbucketname rm $DATE.jpg sleep 30 done
組み立て時の動画はこちら。
終わりに
最初の目的であった、手軽に画像からstlファイルを作ることはできました。価格面では、今回の環境では、AWS利用料は$100/月 ほどとなりました。ちょっと高いですね・・・
料金のうちEC2が主たるウェイトを占めています。しかし、今回使ったインスタンスタイプはc4.largeでややスペック過剰であることを踏まえるとt2.mediumに代替することで$50/月くらいになると思います。また、ここに加えてサービス提供を国内に限定した場合、24時間稼働させる必要はないかもしれません。Cloud Automatorなどの運用自動化ツールを使って夜間は停止すれば、$34/月ですみます。(けどやっぱり、サーバーレスの方が安いなあ・・
また、本来なら認可は実装に手間がかかるものですが、Cognitoを利用することでかなり楽ができました。こういったマネージドサービスは積極的に活用することで、本当に力を注ぐべきところに注力できて、良いですね。