【Deep Learning】アノテーションツール labelImg
前回のDeep Learning記事は以下参照
buffalokusojima.hatenablog.com
今回は少し道を外れて、画像データ作成のツールであるlabelImgの紹介です。
インストールはググると出てきます。一応以下参照
git clone https://github.com/tzutalin/labelImg.git cd labelImg sudo apt-get install pyqt5-dev-tools sudo pip3 install lxml make qt5py3
基本的にフォルダ内の画像をどんどんアノテーションしていくと思うので、Open Dirを選択して対象のフォルダを開きます。
アノテーションの座標ファイルの保存先はChange Save Dirで選択します。
Next Image、Prev Imageで画像の切替を行います。アノテーションが保存されてない場合はWarningが出るのでうっかり移動してしまうこともないです。
アノテーションの形式はYoloとPascal Voc
です。ここでは、Keras-yolo3に合うフォーマットがないですが、情報量の多いPascalVocを選択します。こちらはxml形式で座標ファイルが作成されます。
さて、実際のアノテーション操作ですが、まずはCreate RectBoxを選択してカーソルを表示します。
マウスをクリックしたまま、領域を広げて離すと、表示された領域がアノテーション領域と認識されます。
ラベル付けのポップアップが出るのでラベル名を選択または新しく入力します。デフォルトのラベルが下の方に表示されていますがこちらはdataフォルダ内のpredefined_classes.txtを編集することで設定出来ます。
この状態でSaveしてみます。
作成されたxmlファイルを見てみると以下のようになってます。
<annotation> <folder>image</folder> <filename>sample1.jpg</filename> <path>keras-yolo3/image/sample1.jpg</path> <source> <database>Unknown</database> </source> <size> <width>510</width> <height>340</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>リンゴ</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>57</xmin> <ymin>96</ymin> <xmax>252</xmax> <ymax>311</ymax> </bndbox> </object> </annotation>
ここから必要な情報を取得して、keras-yolo3の形式に変換するところを次回説明しようかと思います。
【Deep Learning】Keras-yolo3で学習
とりあえず画像認識してみるのは前回の記事参照
buffalokusojima.hatenablog.com
今回はkeras-yolo3を使った自前データの学習を説明します。
1. 必要なもの
- 学習データ ・・・ 学習リスト
- クラスファイル ・・・ 学習する物体のリスト
- 初期モデル等のyolo独自のデータファイル
2. 学習リストを作成する
keras-yolo3では学習リストといった画像パス等が記載されたtxtファイルを元に画像の情報を取得して学習していきます。
例としては以下のようです。
座標はそれぞれ左から
class_idは後で紹介するクラスファイルの中身を上から0から始まる連番をしたものです。
3. クラスファイルを作成
学習させたい物体の名前を記載したファイルを作成します。
例として、「リンゴ、梨、ミカン」を学習させたい場合、以下のように改行して書きます。
(例)
リンゴ
梨
ミカン
4. 初期weightsファイルを取得する
学習を始める際に初期の重みファイルを必要とします。以下wgetで取得します。
オリジナルのyoloの形式になっているのでkeras-yolo3用のフォーマットに変換します。
5. 学習の設定をする
def _main(): annotation_path = 'train.txt' #学習リストのパス log_dir = 'logs/000/' #作成されるモデルのパス classes_path = 'model_data/voc_classes.txt' #クラスファイルのパス
上記で作成したそれぞれのパスに変更してください。
6. 学習を開始する
7. おわりに
今回は簡単な自前データでの学習の仕方を説明しました。次回あたりに考察とう含めた学習、認識系の内容を書こうかと思います。
【Deep Learning】Keras-yolo3で画像認識
とりあえずKeras-yolo3を動かして画像認識を行うところまでを説明します。
準備として以下ようにgitをクローンします。
カレントディレクトリに新しくフォルダが出来ます。
新しく出来たフォルダに移動してください。
wgetで本家サイトからモデルファイルを取得します。
kerasのフォーマットに変換します。既にスクリプトは用意されています。
以下コマンドで実行。
問題なければ画像のパスを入力します。
実際に画像認識をした結果がこちらです。
精度やアノテーション位置としては申し分ないです。
次回は詳しいプログラムの内容等を説明しようかと思います。
【AWS】WAFでIP制限
1. 使用するサービス
(AWS)
- AWS WAF
- CloudFront
2. 概要
前提として、API GatewayとCloudFrontを使用したwebサイト等を実装しているとします。また、ドメインでアクセスが可能であるとします。
接続先IPからCloudFrontへのアクセス許可を判断するWAFの簡単な使い方を説明します。
3. WAF
AWSコンソールの検索からWAFと入力すると、画像のようなメニュー画面になります。
3-1. IP Sets
先にIP Setsを作成しておきます。ここで許可するIPアドレスのグループを作成し、後々のACLに紐付けます。
サイドバーからIP Setsを選択し、「Create IP Set」を押します。
Nameは任意の物を入れてもらい、リージョンはGlobalで、IP versionはIPv4、下の方のIPアドレスに許可したいIPアドレスをCIDR方式で入れてもらいます。
その後、「Create IP Set」で完成です。
3-2. ACL
サイドバーからAWS ACLsを選択し、「Create Web ACL」を選択し、ACLの作成を行います。
各種Nameを任意の物を入れてもらい、後はデフォルトで大丈夫です。「Next」ボタンで次の設定へいきます。
ACLのルールの設定を行います。「Add Rules」ボタンでルールを追加していきます。
ボタンを押すとドロップダウンで「add my own rules and rule groups」が出るのでそれを選択します。
Rule Typeに「IP Set」を選択し、Nameに任意の物を入れてもらいます。
IP setで先程作成したIP Setを選択してもらい、「IP address to use as the originating address」をSource IPにし、ActionはAllowにすることで
該当のIP Setを許可することになります。
「Add Rule」で作成完了です。
ACLのRule設定を完了します。
「Default web ACL action for requests that don't match any rules」をBlockにし、Rule外のアクセスをブロックします。
つまり、IP Set以外の通信を弾くようにします。逆に先程の「IP address to use as the originating address」をBlockにし、
こちらをAllowにすることで特定のIPを弾く設定も出来ます。
「Next」ボタンで次へいきます。
最後にルールの確認です。「Next」で次へいきます。
CloudWatchのメトリックスの設定です。Log等の吐き出し先です。最初の方で設定した名前のままでいいでしょう。
最後にACLの設定確認です。確認して問題なければ下の「Create web ACL」で作成完了です。
4. CloudFrontの紐付け
ACLが作成されたので、後はCloudFrontへ紐付けを行うことでCloudFrontへのアクセスを制限します。
Web ACLsから先程作成したACLを選択し、メニュー画面へ遷移します。メニューのタブにある「Associated AWS resources」を選択し
右上の「Add AWS resources」ボタンを押します。
モーダルウィンドウが開くので作成済のCloudFrontを選択し、「Add」で追加します。
すると、メニュー画面で追加されたことがわかります。
5. おわりに
以上で、WAFを用いたIPアドレスによるCloudFrontへのアクセス制限が出来ました。
ACLの「Overview」からアクセスの履歴が見れます。ここで実際にアクセスしてみてどのIPをACLが許可しているか、
どのパスに対するアクセスかなどモニタリング出来ます。作成した後は、こちらを見ながら、様々なIPからアクセスして
ACLが作動しているか確認してください。
【AWS】bitflyer注文キャンセル
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
(AWS)
- Lambda
(外部サービス)
2. 概要
Lambdaを使用してbitflyerの該当の注文をキャンセルする機能をフロントエンドとバックエンド両方説明します。
API Gatewayに紐付けてブラウザ形式でAPIを叩くことが出来ます。詳細は前回記事参照
buffalokusojima.hatenablog.com
3. 実装
①バックエンド
Lambdaに以下実装。ブラウザ側から受け取った注文IDを元にbitflyerにキャンセルリクエストします。
const ssm = new (require('aws-sdk/clients/ssm'))(); const request = require('request'); const crypto = require('crypto'); exports.handler = function(event, context, callback) { // 特殊注文と通常注文でパスが異なる const PARENT_PATH = '/v1/me/cancelparentorder'; const CHILD_PATH = '/v1/me/cancelchildorder'; var body = JSON.parse(event.body); console.log(body) if((!body.child_id && !body.parent_id) || !body.coin_pair){ console.log("body empty"); callback(null, { statusCode: 400, body: JSON.stringify({message: "body empty"}), headers: {"Content-type": "application/json"} }); return; } //受け取ったクエリの中身でパス切替 var path; if(body.child_id){ path = CHILD_PATH; }else if(body.parent_id){ path = PARENT_PATH; } //後はいつも通りリクエスト送信 getParameterFromSystemManager('bitflyer-keys',callback) .then(function(data){ if(body.child_id){ body = JSON.stringify({ product_code: body.coin_pair, child_order_id: body.child_id }); }else if(body.parent_id){ body = JSON.stringify({ product_code: body.coin_pair, parent_order_acceptance_id: body.parent_id }); } const apikey = data.split(",")[0]; const sercretKey = data.split(",")[1]; var timestamp = Date.now().toString(); var method = 'POST'; var text = timestamp + method + path + body; var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex'); var option = { url: 'https://api.bitflyer.jp' + path, method: method, headers: { 'ACCESS-KEY': apikey, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': sign, 'Content-Type': 'application/json' }, body: body } return sendRequest(option, callback); }).then(function(data){ var message; if(data.response.statusCode != 200){ console.error("Error:",data.response); message = data.response; }else message = data.body; console.log(message); callback(null, { statusCode: data.response.statusCode, body: JSON.stringify({message: message}), headers: {"Content-type": "application/json"} }); return; }); function getParameterFromSystemManager(apikey_name, callback) { return new Promise(function (resolve) { var apikey = process.env[apikey_name]; if(!apikey || typeof apikey == undefined){ // Fetches a parameter called REPO_NAME from SSM parameter store. // Requires a policy for SSM:GetParameter on the parameter being read. var params = { Name: apikey_name, /* required */ WithDecryption:true }; ssm.getParameter(params, function(err, apikey) { if (err){ console.error(err.stack); callback(null,{ statusCode: 500, body: JSON.stringify({message: err.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); return; } process.env[apikey_name] = apikey.Parameter.Value; resolve(apikey.Parameter.Value); }); }else resolve(apikey); }); } function sendRequest(option, callback){ return new Promise(function (resolve) { request(option, function(error, response, body){ if(error){ console.error(error); callback(null,{ statusCode: 500, body: JSON.stringify({message: error.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); } var data = {response, body} resolve(data); }); }); } }
②フロントエンド
前回作成した注文リスト内からキャンセル出来るようにします。
過去記事見ると既に実装している部分が見られます。
buffalokusojima.hatenablog.com
makeOrderList関数の中身を一部抜粋します。
function makeOrderList(data){ data = JSON.parse(data); var targetDiv = document.getElementById("openOrderArea"); if(targetDiv.firstElementChild){ targetDiv.removeChild(targetDiv.childNodes[0]); } ・・・省略・・・ var input = document.createElement('input'); input.type="button"; input.value="cancel"; if(d.child_order_id){ input.id = "child_id"+d.child_order_id; }else{ input.id = "parent_id"+d.parent_order_acceptance_id; } //イベントを追加して、ボタンクリック時にキャンセル関数が呼ばれるようにします input.addEventListener('click', function(){ console.log("cancel:", this); cancelOrder(d.product_code, this.id); }) td.appendChild(input); tr.appendChild(td) ・・・省略・・・
cancelOrderの中身
引数の注文受付IDをリクエスト送信するだけです。
function cancelOrder(product_code, id){ var type = id.split("child_id"); var body = {"coin_pair": product_code} if(type.length > 1){ body.child_id = type[1]; }else{ type = id.split("parent_id"); body.parent_id = type[1]; } var data = { "method": "POST", "url": URL + "CancelOrder", "body": body } //キャンセル後にリストを更新 sendRequest(data, getOrders, null); }
4. おわりに
以上で、bitflyerに対しての注文キャンセルが出来ます。
挙動としては、キャンセルボタンを押すと、正常に動作すればリストから該当の注文が消えます。
bitflyerのサイトからも消えていれば成功です。
【AWS】bitflyerポジション表示
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
(AWS)
- Lambda
- IAM
(外部サービス)
2. bitflyerからポジションを取得し表示する機能を簡単なのでフロントエンドとバックエンド両方説明します。
3. 実装
(バックエンド)
AWSのLambdaに実装。API Gatewayの設定とかは過去記事参照。
過去作ったLambdaと大差ないので説明は不要かと思います。ほど注文一覧取得とかと同じです。
buffalokusojima.hatenablog.com
const ssm = new (require('aws-sdk/clients/ssm'))(); const request = require('request'); const crypto = require('crypto'); exports.handler = (event, context, callback) => { getParameterFromSystemManager('bitflyer-keys',callback) .then(function(data){ const apikey = data.split(",")[0]; const sercretKey = data.split(",")[1]; var timestamp = Date.now().toString(); var method = 'GET'; var path = '/v1/me/getpositions?product_code=FX_BTC_JPY'; var text = timestamp + method + path; var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex'); var option = { url: 'https://api.bitflyer.jp' + path, method: method, headers: { 'ACCESS-KEY': apikey, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': sign, 'Content-Type': 'application/json' } } return sendRequest(option, callback); }).then(function(data){ if(data.response.statusCode != 200){ console.error("Error:",data.response); callback(null, { statusCode: data.response.statusCode, body: JSON.stringify({message: data.response}), headers: {"Content-type": "application/json"} }); return; } data = JSON.parse(data.body); if(data.length == 0){ console.log('No data Found'); callback(null,{ statusCode: 200, body: JSON.stringify({message: 'No data Found'}), headers: {"Content-type": "application/json"} }); return; } console.log(data) callback(null, { statusCode: 200, body: JSON.stringify({data: data}), headers: {"Content-type": "application/json"} }); return; }); function getParameterFromSystemManager(apikey_name, callback) { return new Promise(function (resolve) { var apikey = process.env[apikey_name]; if(!apikey || typeof apikey == undefined){ // Fetches a parameter called REPO_NAME from SSM parameter store. // Requires a policy for SSM:GetParameter on the parameter being read. var params = { Name: apikey_name, /* required */ WithDecryption:true }; ssm.getParameter(params, function(err, apikey) { if (err){ console.error(err.stack); callback(null,{ statusCode: 500, body: JSON.stringify({message: err.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); return; } process.env[apikey_name] = apikey.Parameter.Value; resolve(apikey.Parameter.Value); }); }else resolve(apikey); }); } function sendRequest(option, callback){ return new Promise(function (resolve) { request(option, function(error, response, body){ if(error){ console.error(error); callback(null,{ statusCode: 500, body: JSON.stringify({message: error.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); } var data = {response, body} resolve(data); }); }); } };
(フロントエンド)
注文一覧上部に表示します。ポジションがない場合はそのメッセージを表示。
価格は全体の平均をとってます。参考程度です。
サイト読み込み時にgetPositionと言う関数を呼んでます。
function getStatus(){ getOrders(); getPosition(); }
getPositionの中身です。
さらにコールバックでmakePositionListを呼んでいます。
function getPosition(){ var data = { "method": "GET", "url": URL + "GetPositionStatus" } sendRequest(data, makePositionList, null); }
makePositionListの中身です。
基本的にサイドは一定なので最初のデータを参照しています。
価格は全体の平均、枚数は合計です。
function makePositionList(data){ data = JSON.parse(data); var targetDiv = document.getElementById("positionArea"); if(targetDiv.firstElementChild){ targetDiv.removeChild(targetDiv.childNodes[0]); } var div = document.createElement('div'); if(!data.data){ div.style.textAlign = "center"; div.innerText="No Position"; targetDiv.appendChild(div); return; } var average_size=0; var average_price=0; var box = document.createElement('div'); for(d of data.data){ var side_area = document.createElement('span'); side_area.innerText = d.side; box.appendChild(side_area); var price_area = document.createElement('span'); price_area.innerText = d.price; average_price += Number(d.price); box.appendChild(price_area); var size_area = document.createElement('span'); size_area.innerText=d.size; average_size += Number(d.size); box.appendChild(size_area) } average_price = average_price / data.data.length; var side_area = document.createElement('span'); side_area.innerText = data.data[0].side; div.append(side_area) var price_area = document.createElement('span'); price_area.innerText = average_price div.appendChild(price_area); var size_area = document.createElement('span'); size_area.innerText=average_size; div.appendChild(size_area) targetDiv.append(div); targetDiv.appendChild(box); }
4. おわりに
実装するとこんな感じになります。
■
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
HTML, Javascript, CSS
2. 概要
前回、バックエンドで注文詳細を取得したところまで説明しました。今回はフロントエンド側で注文詳細を表示する部分を説明します。
以前、フロントエンドで注文一覧を表示する機能を実装したので、それぞれの注文をクリックするとドロップダウン式で詳細が見れるようにします。
3. 実装
前回実装した関数makeOrderListの内部で呼び出しているopenOrderDetailについて説明します。前回の実装部分は以下のようになります。
function makeOrderList(data){ data = JSON.parse(data); var targetDiv = document.getElementById("openOrderArea"); if(targetDiv.firstElementChild){ targetDiv.removeChild(targetDiv.childNodes[0]); } if(!data.data){ var div = document.createElement('div'); div.innerText="No Open Order"; targetDiv.appendChild(div); return; } ...省略 td.innerText=format_str; td.onclick=openOrderDetail; <=ここの部分 if(d.child_order_id){ td.id = "child_id"+d.child_order_id; }else{ td.id = "parent_id"+d.parent_order_acceptance_id; } tr.appendChild(td); ...省略
openOrderDetailの内容は以下になります。
function openOrderDetail(){ console.log(this); var table = document.getElementById('orderListTable'); var index=0; var id = this.id.split("_id")[1] // 注文リストに注文詳細を加える形で実現する。もう一度リストをクリックすると詳細が閉じる仕組み。 for(var element of table.rows){ index++; if(element.id == this.id){ if(table.rows[index] && table.rows[index].className == 'orderDetailRow'){ table.deleteRow(index); return; } var row = table.insertRow(index); row.className='orderDetailRow'; var cell = row.insertCell(0); cell.colSpan="4"; if((data = child_order_map[id])){ makeCellContent(JSON.stringify({data: data}),cell) }else{ getOrderDetail(cell, id); } } }