【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


python labelImg.pyで、アプリが起動します。

labelImg_display
labelImg操作画面

基本的にフォルダ内の画像をどんどんアノテーションしていくと思うので、Open Dirを選択して対象のフォルダを開きます。
アノテーションの座標ファイルの保存先はChange Save Dirで選択します。

Next Image、Prev Imageで画像の切替を行います。アノテーションが保存されてない場合はWarningが出るのでうっかり移動してしまうこともないです。

アノテーションの形式はYoloPascal Voc
です。ここでは、Keras-yolo3に合うフォーマットがないですが、情報量の多いPascalVocを選択します。こちらはxml形式で座標ファイルが作成されます。


さて、実際のアノテーション操作ですが、まずはCreate RectBoxを選択してカーソルを表示します。

labelImg_cursol
アノテーション開始

マウスをクリックしたまま、領域を広げて離すと、表示された領域がアノテーション領域と認識されます。
ラベル付けのポップアップが出るのでラベル名を選択または新しく入力します。デフォルトのラベルが下の方に表示されていますがこちらはdataフォルダ内のpredefined_classes.txtを編集することで設定出来ます。

labelImg_annotation
ラベル付け

この状態で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ファイルを元に画像の情報を取得して学習していきます。
例としては以下のようです。

画像パス/test.jpg 50 100 100 200 0

座標はそれぞれ左から

xmin ymin xmax ymax class_id
です。
class_idは後で紹介するクラスファイルの中身を上から0から始まる連番をしたものです。


3. クラスファイルを作成

学習させたい物体の名前を記載したファイルを作成します。
例として、「リンゴ、梨、ミカン」を学習させたい場合、以下のように改行して書きます。

(例)
リンゴ

ミカン

4. 初期weightsファイルを取得する

学習を始める際に初期の重みファイルを必要とします。以下wgetで取得します。

オリジナルのyoloの形式になっているのでkeras-yolo3用のフォーマットに変換します。

python convert.py -w darknet53.cfg darknet53.weights model_data/darknet53_weights.h5/span>

5. 学習の設定をする

train.pyを開き、以下デフォルト値を設定します。

def _main():
    annotation_path = 'train.txt' #学習リストのパス
    log_dir = 'logs/000/' #作成されるモデルのパス
    classes_path = 'model_data/voc_classes.txt' #クラスファイルのパス

上記で作成したそれぞれのパスに変更してください。

6. 学習を開始する

python train.pyで、学習が開始されます。


7. おわりに
今回は簡単な自前データでの学習の仕方を説明しました。次回あたりに考察とう含めた学習、認識系の内容を書こうかと思います。

【Deep Learning】Keras-yolo3で画像認識

とりあえずKeras-yolo3を動かして画像認識を行うところまでを説明します。

準備として以下ようにgitをクローンします。
カレントディレクトリに新しくフォルダが出来ます。

新しく出来たフォルダに移動してください。

cd keras-yollo3

wgetで本家サイトからモデルファイルを取得します。

kerasのフォーマットに変換します。既にスクリプトは用意されています。

python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5

以下コマンドで実行。
問題なければ画像のパスを入力します。

python yolo_video.py --image


実際に画像認識をした結果がこちらです。

detect_result
認識結果

精度やアノテーション位置としては申し分ないです。
次回は詳しいプログラムの内容等を説明しようかと思います。

【AWS】WAFでIP制限

1. 使用するサービス

(AWS)

  • CloudFront

2. 概要

前提として、API GatewayとCloudFrontを使用したwebサイト等を実装しているとします。また、ドメインでアクセスが可能であるとします。
接続先IPからCloudFrontへのアクセス許可を判断するWAFの簡単な使い方を説明します。

3. WAF

AWSコンソールの検索からWAFと入力すると、画像のようなメニュー画面になります。

WAF_menu
WAFメニュー画面

3-1. IP Sets
先にIP Setsを作成しておきます。ここで許可するIPアドレスのグループを作成し、後々のACLに紐付けます。
サイドバーからIP Setsを選択し、「Create IP Set」を押します。

WAF_IPSets
IP Sets

Nameは任意の物を入れてもらい、リージョンはGlobalで、IP versionはIPv4、下の方のIPアドレスに許可したいIPアドレスをCIDR方式で入れてもらいます。
その後、「Create IP Set」で完成です。

WAF_IPSets_detail
IP Sets作成


3-2. ACL
サイドバーからAWS ACLsを選択し、「Create Web ACL」を選択し、ACLの作成を行います。
各種Nameを任意の物を入れてもらい、後はデフォルトで大丈夫です。「Next」ボタンで次の設定へいきます。

WAF_ACL_detail
WAF 詳細設定


ACLのルールの設定を行います。「Add Rules」ボタンでルールを追加していきます。
ボタンを押すとドロップダウンで「add my own rules and rule groups」が出るのでそれを選択します。

WAF_ACL_rule_add
ACL ルール追加

Rule Typeに「IP Set」を選択し、Nameに任意の物を入れてもらいます。

WAF_ACL_rule_ipset1
IPSet 選択

IP setで先程作成したIP Setを選択してもらい、「IP address to use as the originating address」をSource IPにし、ActionはAllowにすることで
該当のIP Setを許可することになります。
「Add Rule」で作成完了です。

WAF_ACL_rule_ipset2
IP Set 選択

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」ボタンで次へいきます。

WAF_ACL_rule_done
ACLルール設定

最後にルールの確認です。「Next」で次へいきます。

WAF_ACL_rule_confirm
ACL ルール確認

CloudWatchのメトリックスの設定です。Log等の吐き出し先です。最初の方で設定した名前のままでいいでしょう。

WAF_ACL_CloudWatch_Metrics
ACL CloudWatach メトリックス

最後にACLの設定確認です。確認して問題なければ下の「Create web ACL」で作成完了です。

WAF_ACL_done
ACL作成完了


4. CloudFrontの紐付け
ACLが作成されたので、後はCloudFrontへ紐付けを行うことでCloudFrontへのアクセスを制限します。
Web ACLsから先程作成したACLを選択し、メニュー画面へ遷移します。メニューのタブにある「Associated AWS resources」を選択し
右上の「Add AWS resources」ボタンを押します。

WAF_CloudFront_add
CloudFront紐付け

モーダルウィンドウが開くので作成済のCloudFrontを選択し、「Add」で追加します。
すると、メニュー画面で追加されたことがわかります。

5. おわりに
以上で、WAFを用いたIPアドレスによるCloudFrontへのアクセス制限が出来ました。
ACLの「Overview」からアクセスの履歴が見れます。ここで実際にアクセスしてみてどのIPをACLが許可しているか、
どのパスに対するアクセスかなどモニタリング出来ます。作成した後は、こちらを見ながら、様々なIPからアクセスして
ACLが作動しているか確認してください。

【AWS】bitflyer注文キャンセル

【きっかけ】

過去の記事参照
 
buffalokusojima.hatenablog.com


1. 使用するサービス

(AWS)

  • Lambda

(外部サービス)

bitflyer Lightning API


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

(外部サービス)

bitflyer Lightning API

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. おわりに
実装するとこんな感じになります。

show_position
ポジション表示

【きっかけ】

過去の記事参照
 
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);
                    }
                }
            }