Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
10
Help us understand the problem. What are the problem?
@abemaki29

【90分】AzureのAI を活用して野生のシロクマを追跡する

【Azure】AI を活用して野生のシロクマを追跡する

DA-100という資格を取る過程で勉強したラーニングパスでとても面白いものがあったので紹介します。
以下のラーニングパスを実勢した内容をこの記事で紹介していきます。

出典:

 

 

このラーニングパスでのハンズオンの成果物

仮想カメラで撮影した写真をAzure Custom Visionで解析し
写真に写っている動物を分類します。 写真のメタデータから撮影場所の情報を取得し、Power BIで白くま生息地を分析するためのレポートを作成します。

image.png

 
 

必要なAzureサービスの構成と説明

色々なAzureサービスを駆使して、ラーニングパスで成果物を作成していくのですが
各サービスの構成と利用目的については以下のように理解しています。

image.png

 

 

このラーニングパスを実勢するご利益

Azure FunctionやAzure Custom VisionやAzure Storare/SQL Database等の主要なAzureサービスに一通り触れて理解することができる。
Power BIの基本機能から外部データソースとの連携/視覚化の装飾等を学ぶことができると思います。

Azure Functionではフローの自動化を学ぶことができました。
Azure Custom Visionでは写真の分類に必要な学習モデルの作成方法から分類まで、他サービスとの連携方法を広く学ぶことができました。
Azure Storare/SQL Databaseでは基本な機能から、他サービスとの連携方法を学ぶことができました。
Power BIについても基本機能のから外部データソースとの連携/視覚化の装飾等を学ぶことができると思います。

 

実践していく

基本的にラーニングパスに沿ってやったことを覚書としてかいています。このハンズオンに興味がある方は是非、本家のラーニングパスを実勢してみてください。

本家はこちら:

 

アカウントを作成し、仮想カメラをデプロイしていきます。

Azure Portalに接続して、Cloud Shell を使っていきます。

image.png

polar-bear-rgという名前のリソースグループを作成します。

az group create --name polar-bear-rg --location southcentralus

sirokumasanというアカウント名の変数を作成します。

ACCOUNT_NAME=sirokumasan

先ほど作成したリソースグループにsirokumasanというアカウント名のストレージを作成します。

az storage account create --name $ACCOUNT_NAME --resource-group polar-bear-rg --location southcentralus --sku Standard_LRS

ストレージにphotosというコンテナを作成します。

az storage container create --name photos --account-name $ACCOUNT_NAME

 
 
この時点で以下のようになっていれば問題なしです。
image.png

 

アカウントキーを取得して、メモに控えておきます

az storage account keys list --account-name $ACCOUNT_NAME

 

これをメモ帳に控えます。
image.png

VSCodeでnode環境を作成する

ターミナルを開いて、作業用フォルダを作成し、フォルダに移動します。

mkdir C:\sirokumasan
cd C:\sirokumasan

image.png

 

プロジェクトを作成し、先ほどPortalで作成したストレージへアクセスするためのライブラリを取得します。

npm init -y
npm install azure-storage --save

 

プロジェクトにphotosというフォルダを作成します。

image.png

 

作成したフォルダに以下から取得した画像を格納していきます。

画像:
https://github.com/MicrosoftDocs/mslearn-build-ml-model-with-azure-stream-analytics/raw/master/camera-images.zip

画像は30個あるので置いたあとはこのようになると思います!
image.png

 
 

プロジェクトにcameras.json を作成します。

image.png

[
    {
        "deviceId" : "polar_cam_0001",
        "latitude" : 75.401451,
        "longitude" : -95.722518
    },
    {
        "deviceId" : "polar_cam_0002",
        "latitude" : 75.027715,
        "longitude" : -96.041859
    },
    {
        "deviceId" : "polar_cam_0003",
        "latitude" : 74.996653,
        "longitude" : -96.601780
    },
    {
        "deviceId" : "polar_cam_0004",
        "latitude" : 75.247701,
        "longitude" : -96.074436
    },
    {
        "deviceId" : "polar_cam_0005",
        "latitude" : 75.044926,
        "longitude" : -93.651951
    },
    {
        "deviceId" : "polar_cam_0006",
        "latitude" : 75.601571,
        "longitude" : -95.294407
    },
    {
        "deviceId" : "polar_cam_0007",
        "latitude" : 74.763102,
        "longitude" : -95.091160
    },
    {
        "deviceId" : "polar_cam_0008",
        "latitude" : 75.473988,
        "longitude" : -94.069432
    },
    {
        "deviceId" : "polar_cam_0009",
        "latitude" : 75.232307,
        "longitude" : -96.277683
    },
    {
        "deviceId" : "polar_cam_0010",
        "latitude" : 74.658811,
        "longitude" : -93.783787
    }
]

 
 

プロジェクトにrun.jsを作成します。

image.png

'use strict';

// Connect to the storage account
var storage = require('azure-storage');

var blobService = storage.createBlobService(
    process.env.ACCOUNT_NAME,
    process.env.ACCOUNT_KEY
);

// Load image file names and create an array of cameras
var fs = require('fs');

fs.readdir('photos', (err, files) => {
    var cameras = JSON.parse(fs.readFileSync('cameras.json', 'utf8')).map(
        camera => new Camera(
            camera.deviceId,
            camera.latitude,
            camera.longitude,
            blobService,
            files
        )
    );

    // Start the cameras
    cameras.forEach(camera => {
        camera.start();
    });
});

class Camera {
    constructor(id, latitude, longitude, blobService, files) {
        this._id = id;
        this._latitude = latitude;
        this._longitude = longitude;
        this._blobService = blobService;
        this._files = files.slice(0);
        this._interval = 300000;
    }

    start() {
        // Register the first callback
        setTimeout(this.timer, Math.random() * this._interval, this);
        console.log('Started ' + this._id);
    }

    timer(self) {
        // Randomly select a photo
        var index = Math.floor(Math.random() * self._files.length);
        var filename = self._files[index]

        // Define the metadata to be written to the blob
        var metadata = {
            'latitude': self._latitude,
            'longitude': self._longitude,
            'id': self._id
        };

        // Upload the blob
        self._blobService.createBlockBlobFromLocalFile('photos', filename, 'photos/' + filename, { 'metadata': metadata }, (err, result) => {
            if (!err) {
                console.log(self._id + ': Uploaded ' + filename);
            }
            else {
                console.log(self._id + ': Error uploading ' + filename);
            }
        });

        // Register the next callback
        setTimeout(self.timer, Math.random() * self._interval, self);
    }
}

 
 

VSCodeのターミナルでストレージアカウントへの接続情報を設定します。

set ACCOUNT_NAME=sirokumasan
set ACCOUNT_KEY=<事前にメモしたアカウントキー>

image.png

 

VSCodeのターミナルでストレージアカウントへの接続情報を設定します。

set ACCOUNT_NAME=sirokumasan
set ACCOUNT_KEY=<事前にメモしたアカウントキー>

 

run.jsを実行します。

node run.js

成功すると以下のようになります。
image.png

※うまくいかない場合はrun.jsにアクセス情報を直書きしてみると良いかもしれません。
image.png

シロクマを認識するように機械学習モデルをトレーニングしてみる

Custom Visionポータルにアクセスしてサインインします。

 

新しいプロジェクトを作成し、設定していきます。

新しいプロジェクトを作成します

image.png

プロジェクトを設定していきます。

リソース: polar-bear-vision
プロジェクトの種類: "分類"
分類の種類: Multiclass (Single tab per image) (マルチクラス (画像ごとに 1 つのタブ))
ドメイン: General (一般)

image.png

プロジェクトで使用するホッキョクギツネの画像を取得して設定していきます。

画像取得先:
https://github.com/MicrosoftDocs/mslearn-build-ml-model-with-azure-stream-analytics/raw/master/training-images/arctic-fox.zip

 

画像をアップロードしていきます。

image.png

ホッキョクギツネとして『arctic-fox』とタグづけます。

image.png

 
 

プロジェクトで使用する白くまの画像を取得して設定していきます。

画像取得先:
https://github.com/MicrosoftDocs/mslearn-build-ml-model-with-azure-stream-analytics/raw/master/training-images/polar-bear.zip

 

画像をアップロードして白くまとして『polar-bear』とタグづけます。

image.png

 
 

プロジェクトで使用するセイウチの画像を取得して設定していきます。

画像取得先:
https://github.com/MicrosoftDocs/mslearn-build-ml-model-with-azure-stream-analytics/raw/master/training-images/walrus.zip

 

画像をアップロードしてセイウチとして『walrus』とタグづけます。

image.png

モデルをトレーニングしていきます。

image.png

トレーニングタイプはクイックを指定します

image.png

 
 

トレーニングが終わると以下のような画面が表示されます。 完了までに少し時間がかかります。

image.png

クイックテストを行っていきます。

クイックテストの実行

image.png

テストで使用する画像は以下から取得します。
https://github.com/MicrosoftDocs/mslearn-build-ml-model-with-azure-stream-analytics/raw/master/testing-images/testing-images.zip

 
 

クイックテストの実行結果

画像にシロクマが含まれるかどうかのモデルによる予測が十分満足できるレベルに熟達するまで
これらの画像を活用して繰り返しテストを行います。

image.png

  
 

モデルを発行します

Publishを選択します

image.png

 

Prediction resourceを選択してモデルを発行します

image.png

Prediction URLから接続情報を取得します

image.png

 
 

赤枠の部分を控えます

image.png

 
 

Azure Portalでファンクションを作成します。

リソースの作成から関数アプリを選択します

image.png

関数アプリを作成します

image.png

関数を作成します

image.png

BlobTriggerのindex.jsを編集していきます。 
ストレージコネクションストリングはストレージアカウントの接続文字列を設定します。

※Pathの部分は以下のようにphotos/{name}として監視するコンテナ名を指定する必要があります

image.png

image.png

module.exports = function (context, myBlob) {
    var predictionUrl = process.env.PREDICTION_URL;
    var predictionKey = process.env.PREDICTION_KEY;
    var storageConnectionString = process.env.<CONNECTION_STRING_NAME>;

    var storage = require('azure-storage');
    var blobService = storage.createBlobService(storageConnectionString);
    var blobName = context.bindingData.name;
    var blobUri = context.bindingData.uri;

    // Read the blob's metadata
    blobService.getBlobMetadata('photos', blobName, (err, result, response) => {
        if (!err) {
            var latitude = result.metadata.latitude;
            var longitude = result.metadata.longitude;
            var id = result.metadata.id;

            // Generate a shared access signature for the Custom Vision service
            var now = new Date();
            var expiry = new Date(now).setMinutes(now.getMinutes() + 3);

            var policy = {
                AccessPolicy: {
                    Permissions: storage.BlobUtilities.SharedAccessPermissions.READ,
                    Start: now,
                    Expiry: expiry
                },
            };

            var sas = blobService.generateSharedAccessSignature('photos', blobName, policy);

            // Pass the blob URL to the Custom Vision service
            var request = require('request');

            var options = {
                url: predictionUrl,
                method: 'POST',
                headers: {
                    'Prediction-Key': predictionKey
                },
                body: {
                    'Url': blobUri + '?' + sas
                },
                json: true
            };

            request(options, (err, result, body) => {
                if (!err) {
                    var probability =  body.predictions.find(p => p.tagName.toLowerCase() === 'polar-bear').probability;
                    var isPolarBear = probability > 0.8; // 80% threshold
                     if (isPolarBear) {
                        context.log('POLAR BEAR detected by ' + id + ' at ' + latitude + ', ' + longitude);
                    }
                    else {
                        context.log('Other wildlife detected by ' + id + ' at ' + latitude + ', ' + longitude);
                    }

                    context.done();
                }
                else {
                    context.log(err);
                    context.done();
                }
            });
        }
        else {
            context.log(err);
            context.done();
        }
    });
};

 
 

関数アプリのコンソールで以下のコマンドを実行します。

npm install request
npm install azure-storage

警告は無視していいらしい。

image.png

 

関数アプリのに前工程で取得したPrediction-URLとPrediction-Key を設定していきます。

image.png

 
 

設定後にブロブに新たに画像を追加すると、トリガーが作動して画像を自動で認識して分類することを確認することができます。

image.png

画像は以下の画像をアップロードしました。
https://docs.microsoft.com/ja-jp/learn/modules/build-ml-model-with-azure-stream-analytics/media/image-12.jpg

 
 

ログを確認し、ログ上でカスタムビジョンと連携し、画像が正しく認識され分類されたことを確認します。

※何回か試したので、ファイル名はimage-16.jpgになってます。
(インクリメントしながら確認しました)

image.png

 
 

VScodeでもう一度run.jsを実行して、ストレージに写真を送信してみます。

image.png

 
 
 

カスタムビジョンが実行され、VScodeから送信した画像から白くまを検出していることがわかります!

超おもしろい!!
image.png

 

Azure SQL Databaseをデプロイする

Azure Portalからクラウドシェルを開きます

image.png

作成するデータベース情報を変数セットしておきます

SERVER_NAME="<server-name>"
ADMIN_USERNAME="<admin-username>"
ADMIN_PASSWORD="<admin-password>"
DATABASE_NAME="<database-name>"

※パスワードはパスワードバリデーションチェックにひっかからないようにやや複雑目なパスワードを設定することを推奨します。

 

データベースサーバーを作成します

az sql server create --name $SERVER_NAME --resource-group polar-bear-rg --location westus --admin-user $ADMIN_USERNAME --admin-password $ADMIN_PASSWORD

※デフォルトのロケーションはnorthcentralusになっていますが対象ロケーションだと作成エラーになるのでロケーションはwestusに変更しています。
 
 

データベースを作成します

az sql db create --resource-group polar-bear-rg --server $SERVER_NAME --name $DATABASE_NAME --service-objective S0

 
 

リソースグループにSQLデータベースができていることを確認します

image.png

 
 
 

SQLサーバーのファイアウォールの設定を変更し、あとでPower BIデスクトップから接続できるようにしておきます

image.png

 
 

SQLデータベースのクエリエディタでSQLを実行して、テーブルを作成します

image.png

SQL

CREATE TABLE [dbo].[PolarBears]
(
    [Id] [uniqueidentifier] NOT NULL,
    [CameraId] [nvarchar](16) NULL,
    [Latitude] [real] NULL,
    [Longitude] [real] NULL,
    [Url] [varchar](max) NULL,
    [Timestamp] [datetime] NULL,
    [IsPolarBear] [bit] NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
    WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
)
ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[PolarBears] ADD DEFAULT (newid()) FOR [Id]
GO

ALTER TABLE [dbo].[PolarBears] ADD DEFAULT (getdate()) FOR [Timestamp]
GO

ALTER TABLE [dbo].[PolarBears] ADD DEFAULT ((0)) FOR [IsPolarBear]
GO

 
 

テーブルが作成できていることを確認します。

image.png

 
 

関数を変更します。

 

関数アプリのコンソールでnpm installを実行します
npm install tedious

image.png

tedious パッケージにより、Node.js アプリから SQL Server や Azure SQL Database と接続が可能になります。

 
 

最初のほうに作ったBlob Triggerのコードとテストを開きます。

image.png

 

コードにデータベースへの接続情報を追記します。 ダブルクォーテーション内は自身が設定した接続情報で置き換えます。
var databaseUserName = process.env.DATABASE_USER_NAME;
var databasePassword = process.env.DATABASE_PASSWORD;
var databaseServer = 'DATABASE_SERVER_NAME.database.windows.net';
var databaseName = 'DATABASE_NAME';
関数を一部差し換えます

◆差し換え前

if (isPolarBear) {
    context.log('POLAR BEAR detected by ' + id + ' at ' + latitude + ', ' + longitude);
}
else {
    context.log('Other wildlife detected by ' + id + ' at ' + latitude + ', ' + longitude);
}

context.done();

 
◆差し換え後

以下、本家のコードに足りてない部分があり追加しました。
dbConnection.connect();
上記のコードが抜けているとSQLDatabaseへの接続エラーが出てしまいます。
2021/9/13時点では本家のコードだけではエラーになります。

// Update the database
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;

var config =
{
    authentication:
    {
        type: 'default',
        options:
        {
            userName: databaseUserName,
            password: databasePassword
        }
    },
    server: databaseServer,
    options:
    {
        database: databaseName,
        encrypt: true
    }
};

var dbConnection = new Connection(config);

dbConnection.on('connect', err => {
    if (!err) {
        var query = "INSERT INTO dbo.PolarBears (CameraId, Latitude, Longitude, URL, Timestamp, IsPolarBear) " +
            "VALUES ('" + id + "', " + latitude + ", " + longitude + ", '" + blobUri + "', GETDATE(), " + (isPolarBear ? "1" : "0") + ")";

        var dbRequest = new Request(query, err => {
            // Called when request completes, with or without error
            if (err) {
                context.log(err);
            }

            dbConnection.close();
            context.done();
        });

        dbConnection.execSql(dbRequest);
    }
    // 以下、本家のコードに足りてない部分があり追加しました。
    dbConnection.connect();
    else {
        context.log(err);
        context.done();
    }
});

 
 

VSCodeで再度run.jsを起動します

image.png

 
 

クエリエディタでデータが蓄積できていることを確認します

image.png

 
 
 

Power BI レポートを作製する

 

Power BIデスクトップを起動してSQLデータベースに接続します

image.png

 

image.png

 
 

Power BIデスクトップを起動してSQLデータベースに接続します

image.png

SQLステートメント

SELECT TOP 20 Id, CameraId, Latitude, Longitude, Url, Timestamp, FORMAT(Timestamp,'MM/dd/yyyy h:mm:ss tt') AS TimestampLabel, IsPolarBear FROM dbo.PolarBears ORDER BY Timestamp DESC

 

接続の方法をデータベースに切り替えて、接続していきます。

image.png

 
 

いよいよデータベースの情報を視覚化していきます!!

 

視覚化で白くま判定と経度情報を設定していきます。

image.png

 

各経度の情報を集計しないように設定していきます。

image.png

 
 

少しずつ形になっています

image.png

 

空きスペースにCameraId、IsPolarBear、TimestampLabelの表を作成します

image.png

 

空きスペースにIsPolarBear と Latitudeの円グラフを作成します

image.png

 

視覚化の表示の値をカウントに切り替えます

image.png

 

IsPolarBearのスライサーを作成します

image.png

 
 

装飾していきます

image.png

動作確認していく

SQLデータベースの情報を削除します

DELETE FROM dbo.PolarBears

 
 

VSCodeでデータを再投入します

node run.js

 
 

Power BIのデータを更新します

image.png

データが更新され、白くまの生息地を追うことができるようになります

image.png

非常に面白いラーニングパスでした。
これからAzureを学ばれる方に是非おすすめしたいラーニングパスだと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
10
Help us understand the problem. What are the problem?