ハードウェア技術者のスキルアップ日誌

某家電メーカーの技術者がスキルアップのために勉強したことを記録するブログです

JDLA G検定 2020#3 の総括

本業の方がバタバタしており、なかなかブログの更新ができておりませんでした。「ラズパイで見守りカメラを作る」がまだ完成していませんが、先日G検定を受験したので、当日までの勉強方法、試験内容、感想等をまとめておきたいと思います。

ちなみに結果の方は無事に合格できました。よかった^^

f:id:masashi_k:20201128204625j:plain

受験するに至った経緯

G検定を受けようと思ったのが2020年8月ごろ。これまでは資格だけ取っても実務ができないと意味ないと思い、別に取る必要ないと思っていたのですが、やっぱり気が変わって受けてみることに。2020年第二回の試験が7月に終わったばかりだったので、11月の第三回をターゲットにした。ちなみに、第二回はコロナによる緊急事態宣言が発令されたことを受けて、受験料が半額になっていた。どうせ受けるんなら、もっと早く思い立っておけば。。。

 

当時のスキル

機械学習ディープラーニングの基礎的なところは講座や書籍で一通り勉強
・画像関連タスクの実務経験 1~2年
といったところ。過去問をサラッとやっておけば簡単に受かるだろうと思っていました。

 

勉強方法

●8月中旬(試験まであと三ヶ月)

まずはJDLAのホームページを見て参考図書を確認。公式テキストを買おうか迷ったのですが、Amazonのレビューを見るとこの範囲からあまり出ないというものが多かったので、結局問題集(通称、黒本)のみ購入しました。

 ●8月後半(試験まであと2ヶ月半)

問題集を解き始める。Deep Learningの分野は解けるが、Deep Learningの歴史、機械学習、法律の問題がズタボロ。これはまずいと焦る。

●9月前半(試験まであと2ヶ月)

問題集を1周したタイミングでネットの情報を探す。こちらの動画を見つけ、解説を聞きながら問題集の2周目を行う。以前勉強していたことを復習でき、忘れていたことも思い出すことができた。2周目が完了した時点で巻末の模擬試験を受ける。あっさり合格点が取れた。


JDLA G検定模試をひたすら解説する!

●9月後半(試験まであと1ヶ月半)

ネットでG検定の情報を集めていたときに、前回2020年第二回の感想を書いたブログを見つける。「白本、黒本の範囲からの内容は3-4割」、「法律やDeep Learningに関する最新動向からの問題が多い」という記述を見てまた焦る。

そんな中、こちらのサイトを見つける。先人がG検定の勉強のためにまとめたサイトのリンク集です。これを隙間時間に読むことにする。

qiita.com

●10月(試験まであと1ヶ月)

仕事で新しいプロジェクトに加入することになり、残業続きで全く勉強する時間が取れなくなる。10月に最終仕上げをするつもりがG検定の勉強に費やした時間はほぼゼロ。ただ、試験申し込みだけは忘れずに。

●11月 試験当日まで

上記まとめサイトの中でも問題集で苦手だった分野(自然言語処理、法律関係、ドローン)とネットの情報でよく出ると言われていたLSTM, 強化学習を重点的に確認。
試験前々日と前日に以下の2種類の模擬試験を受ける。いずれも試験時間120分のうち約30分を余して正答率8割を達成。大丈夫だろうと安心する。

Study-AI
https://study-ai.com/generalist/

 

DIVE INTO EXAM
https://exam.diveintocode.jp/exam

 

試験当日(試験の内容)

G検定はオンラインで受験するため、試験中にググることが可能です。すぐに調べられるように上記まとめサイトの主要記事をあらかじめ開いたうえで試験に臨みました。

問題集で勉強した基礎的なことや得意な分野の問題は即答できたのですが、最新の時事問題や強化学習など苦手な分野の問題は調べないと回答できないレベルばかり。まとめサイトでも引っかからない難しい問題が多く、試験中ひたすら検索していた印象でした。G検定はググり力検定と言われているのも納得です。

今回問題数が多かったと思われる分野は、
・法律関係(個人情報保護法不正競争防止法
・XAI(説明可能AI)
強化学習
自然言語処理
です。調べた回数が多かったため、印象深いのかも。

 試験途中、わからなかった問題は回答を入れずに後回しにしていたのですが、見直す時間がほとんどなくなり、最後半ばあてずっぽうで入力しました。後で見直したい場合でもとりあえず何か入力しておくとよかったと反省です。

感想

無事に受かってほっとしましたが、即答できない問題が多く、検索が使えない環境であればまず間違いなく落ちていました。Deep Learningに関して幅広く知識を持っているとは言えないレベルだと思います。ただ、実務にそこまで広範囲の知識が必要かと言われると疑問ですね。自分の業務に関連する分野に関して、最新情報を収集する程度で十分かと思います。ひとまず肩書が取れたことでOKとします。

対策としては、基礎的な分野の取りこぼしをしないようにきっちり教科書、問題集で勉強しておくこと。そしてDeep Learningに関する最新動向をできるだけ広く抑えておくことかと思います。わからない問題は調べればある程度はカバーできるので、いかに調べる時間を残せるかがポイントですね。

Deep Learningはホットな分野であるが故、プレイヤーが多く、最新の情報がすぐ手に入ります。こういった情報を生かさない手はありませんね。先人の知恵は有効に活用しましょう。

今回、G検定の対策で改めてDeep Learningに関する知識の復習や、知らなかった情報も改めて勉強できたことはよかったと思います。業務でAIモデルを開発する際にも生かせそうです。

この記事ではJDLA G検定 2020#3を受験するまでの経緯や試験の内容、感想をまとめました。今後、G検定を受験される方のご参考になれば幸いです。

ラズパイにWebサーバーを立てる

見守りカメラ作成に向けた第二回目はWebサーバーです。

masaeng.hatenablog.com

カメラの映像や過去の温度データを参照するため、ラズパイにWebサーバーを立てて、スマホやPCからこのサーバーにアクセスするようにします。

今回はWebサーバーを立てるところまでをやってみましたので、手順を書いていきます。

 

①Apatch2のインストール

まず、WebサーバーソフトウェアのApache HTTP Serverを利用するために「apache2」パッケージをインストールします。

$ sudo apt install apache2

 インストールするとApacheWebサービスが自動的に開始されます。ラズパイと同じネットワークにつながったPCやスマホのブラウザで「http://ラズパイのIPアドレス」と入力すると以下のようにApacheのデフォルトページが表示されます。

f:id:masashi_k:20200923222725p:plain

②htmlファイルの作成

次に、デフォルトページの代わりに表示するオリジナルのページを作ってみます。

apacheの設定ファイルは"/etc/apache2/"以下にあり、ディレクトリ構成は以下の通りです。

/etc/apache2/
|-- apache2.conf      # 最初に読み込まれる設定ファイル
|       |--  ports.conf # 使用するポートの設定ファイル
|-- mods-enabled
|       |-- *.load    # モジュールをロードするための設定ファイル
|       |-- *.conf    # モジュールの基本的な設定ファイル
|-- conf-enabled
|       |-- *.conf    # Webサイトに共通する設定ファイル
|-- sites-enabled
|       |-- *.conf    # Webサイトごとの設定ファイル

静的コンテンツの保存場所は"/etc/apache2/sites-enabled/000-default.conf"の「DocumentRoot」で指定されており、デフォルトでは"/var/www/html"となっています。この中にhtmlファイルを置きます。

テスト用に作成したhtml(ファイル名:hello.html)はこちらです。単にHello HTML World!と表示するだけのものとなっています。

<html>
<head>
<title>HTML Sample</title>
</head>
<body>
<p>Hello HTML World!</p>
</body>
</html>

 

これを"/var/www/html/"に格納し、PC、スマホから「http://ラズパイのIPアドレス/hello.html」にアクセスすると以下の画面が表示されます。

f:id:masashi_k:20200923224438p:plain

うまく表示できました!!

 

CGIを使用して動的コンテンツを表示

次は動的コンテンツの表示をやってみます。CGIを使用できるようにするために以下のコマンドを実行します。

$ sudo ln -s ../mods-available/cgi.load /etc/apache2/mods-enabled/
$ sudo systemctl reload apache2

"/etc/apatch2/mods-enabled/"にcgi.loadのシンボリックリンクを作成し、設定をリロードしています。

CGIの実行ファイルは"/usr/lib/cgi-bin"に置きます。

テスト用に現在の時刻を表示する実行ファイル(hello)を作成し、上記フォルダに保存します。実行ファイルの中身は以下の通りです。

#!/bin/sh
echo "Content-Type; text/plain"       # リソースの種類を示す「Content-Type」ヘッダーの出力
echo ""                               # 区切り用のスペース
echo "Hello CGI World! ($(data +%T))" # 時刻の出力

 

実行権限を与えるため、以下のコマンドを実行します。

$ sudo chmod +x /usr/lib/cgi-bin/hello

 

以上の準備を行った後、PC、スマホのブラウザから「http://ラズパイのIPアドレス/cgi-bin/実行ファイル名」にアクセスすると、以下のように表示されます。

f:id:masashi_k:20200925222744p:plain

こちらもうまくいきました!

 

まとめ

ラズパイにWebサーバーを立てて自作のWebページを表示させることができました。これで見守りカメラ完成に向けてまた一歩前進です。次はこのWebページにカメラの映像を表示してみたいと思います。簡単にできるかわかりませんが、いろいろ調べてみて、結果を記事にしてみます。

【Keras】Triplet lossを使って学習する

以前、顔認識を行うAIモデルである、Facenetを動かしたときにTriplet lossについて少し触れました。

masaeng.hatenablog.com

Facenetは顔の類似度を特徴ベクトルの距離で表すことで、大量の顔画像を使って学習することなく、数枚の顔画像だけで顔照合ができるというものでした。これを可能にしているのがTriplet lossという損失関数です。

顔だけではなく、物体の照合もTriplet lossを使ったら同様にできるのではないかと思い、Triplet lossを使った学習のやり方について調べてみました。試行錯誤した中で、ハマってしまったところもあるので備忘録として残しておきます。

 

Tensorflow Addonを使う

Githubの実装やQiitaの記事などいろいろ見てみたのですが、一番簡単に使えそうだったのがTensorflowで用意されている関数です。

TensorFlow Addons Losses: TripletSemiHardLoss

tfa.losses.TripletSemiHardLoss()

 これをloss関数として渡せばOKです。

 

上記URLのサイトにはデータセットにmnistを使った学習例があります。これを元にモデルをmobilenetに変更し、データセットにcifar100を使って学習をしてみます。

最初に試したコード
import io
import numpy as np
import tensorflow as tf
import tensorflow_addons as tfa
import tensorflow_datasets as tfds

train_dataset, test_dataset = tfds.load(name="cifar100", split=['train', 'test'], as_supervised=True)

# Build your input pipelines
train_dataset = train_dataset.shuffle(1024).batch(32)
test_dataset = test_dataset.batch(32)

# model create
inputs = tf.keras.Input(shape=(None, None, 3))
x = tf.keras.layers.Lambda(lambda img: tf.image.resize(img, (224, 224)))(inputs)
x = tf.keras.layers.Lambda(tf.keras.applications.mobilenet.preprocess_input)(x)

model = tf.keras.applications.MobileNet(input_shape=(224,224,3), alpha=1.0,
                                         depth_multiplier=1, dropout=1e-3,
                                         include_top=False, weights=None,
                                         input_tensor=x, pooling="avg")

# Compile the model
model.compile(
    optimizer=tf.keras.optimizers.Adam(0.001), 
loss=tfa.losses.TripletSemiHardLoss(distance_metric="L2", margin=0.1))
# Train the network
history = model.fit(train_dataset, epochs=10)

tensorflowのサイトにある事例はmodelの最後にL2正規化の処理があります。今回作成したmobilenetはL2正規化を含んでいないため、TripletSemiHardLossの引数でL2正規化を指定しています。また、Triplet lossのマージンを持たせたいときは引数marginに値を渡します。

このコードを実行すると、学習の途中で以下のようにlossがnanになってしまいます。

Epoch 1/10
  2/Unknown - 1s 302ms/step - loss: 1.0000
  876/Unknown - 258s 295ms/step - loss: nan

 

原因が分からず、苦戦したのですが、Stack Overflowに原因と解決策が書かれていました。
Nan loss in keras with triplet loss - Stack Overflow

本来triplet lossを使う際には、Anchor, Positive(Anchorと同じクラス), Negative(Anchorと違うクラス)となるデータを3つペアで入力させる必要があります。Tensorflowの関数を使うとこれを意識する必要がなく、train_dataset からうまくAnchor, Positive, Negativeに振り分けて入力してくれるようです。ですが、データセットのクラス数に対してバッチサイズが小さいと、ミニバッチの中にPositiveが存在しないケースが発生することが在ります。この時にtriplet lossがnanになるようです。

改善版

cifar100のクラス数が100なので、バッチ数をクラス数より多い128にします。

# Build your input pipelines
train_dataset = train_dataset.shuffle(1024).batch(128) #32->128
test_dataset = test_dataset.batch(
128) #32->128

これにより、うまく学習を行うことができました。

Epoch 1/10
 391/391 [==============================] - 273s 699ms/step - loss: 0.1384
Epoch 2/10
391/391 [==============================] - 274s 702ms/step - loss: 0.0848
Epoch 3/10
391/391 [==============================] - 274s 702ms/step - loss: 0.0908
Epoch 4/10
391/391 [==============================] - 274s 701ms/step - loss: 0.0545
Epoch 5/10
391/391 [==============================] - 274s 700ms/step - loss: 0.0378
Epoch 6/10
391/391 [==============================] - 272s 696ms/step - loss: 0.0264
Epoch 7/10
391/391 [==============================] - 274s 700ms/step - loss: 0.0249
Epoch 8/10
391/391 [==============================] - 274s 702ms/step - loss: 0.0281
Epoch 9/10
391/391 [==============================] - 275s 703ms/step - loss: 0.0449
Epoch 10/10
391/391 [==============================] - 273s 699ms/step - loss: 0.0548 

 

まとめ

Triplet lossを使った学習方法についてまとめてきました。高位APIを使うと簡単に実装ができる一方で、問題が起きたときに何が起きているかわかりにくいというデメリットがあります。できるだけ内部処理も理解して、低位APIでも書けるようになっていることが重要かなと思います。

また、今回の学習結果を使って、距離による分類がうまくいくかも機会があれば試してみたいと思います。

ラズパイからLINE通知

今回から見守りカメラに必要な機能を作っていきたいと思います。

masaeng.hatenablog.com

 

まず初めはラズパイからLINE通知をする方法です。
温度センサーをラズパイにつないで、測定した温度を定期的にLINEに通知するプログラムを作っていきます。

 

LINE notifyに登録

LINEから自分のアカウントにメッセージを送信するにはLINE notifyというサービスを利用します。早速登録してみましょう。
f:id:masashi_k:20200831230219p:plain

①右上の"ログイン"ボタンからログイン
②ログイン後にマイページを開く

f:id:masashi_k:20200831231745p:plain


➂アクセストークンの発行から"トークンを発行する"をクリック

f:id:masashi_k:20200831231854p:plain

トークン名を入力し、通知を送信するトークルームを選択後、"発行する"をクリック
トークルームは「1:1でLINE Notifyから通知を受け取る」でOKです。
f:id:masashi_k:20200831232011p:plain

トークンが発行されるため、控えておく

f:id:masashi_k:20200831232653p:plain

⑥自分のスマホに"LINE notify"を招待しておく

これで登録は完了です。

 

ラズパイから通知コマンドを送信

発行されたトークンを使って自分のアカウントにラズパイからメッセージを送信します。次のコマンドで実現できます。

$ curl https://notify-api.line.me/api/notify \
     -X POST \
     -H "Authorization: Bearer xxxxxxx" \     # "xxxxxxx"にトークンを入力
     -F "message=Hello world!"                 # Hello world!の部分が送信されるメッセージ

送信結果

f:id:masashi_k:20200831234227p:plain
一番下にHello world!というメッセージが届いています。

 

次に画像を送ってみます。カメラで撮影したスクショを送信する想定です。
コマンドは以下の通り。

$ curl https://notify-api.line.me/api/notify \
     -X POST \
     -H "Authorization: Bearer xxxxxxx" \    # xxxxxxxにトークンを入力
     -F "message=test" \                     # testの部分が送信されるメッセージ
     -F "imageFile=@PATH_TO_PICTURE"         #PATH_TO_PICTUREにラズパイ上の画像のパスを入力

画像だけを送ることは出来ないようで、messageの部分には何か入れる必要があります。

送信結果

f:id:masashi_k:20200831234902p:plain

送信した"test"というメッセージとラズパイのアイコンが届いていることが確認できます。

 

温度センサーの測定データをLINEで通知

最後に温度センサーの測定データを通知してみます。これまではcurlコマンドを使ってきましたが、プログラムを作る上ではPython上で命令を出せた方がいいので、pythonで通知をさせる方法を調べてみました。

 

温度センサーとラズパイの接続

今回使用した温度センサーはBosch製のBMP280です。
  f:id:masashi_k:20200901234238p:plain

 メーカーサイトはこちら。データシートもここから参照できます。 
 購入サイトはこちら

インターフェースはI2CとSPIのいずれかを選択できます。ピン数が少なくて済むので、今回はI2Cを使用します。回路図は以下の通りです。

f:id:masashi_k:20200901235822p:plain

 

CSBpinのレベルでI2C, SPIを切り替えます。I2Cを使う場合はHigh, SPIの場合はLowにしてください。SDOはH/LでI2Cのスレーブアドレスを変更できます。モジュール内部でpull-downされているので、変更の必要がなければオープンでOKです。

 

PythonスクリプトでLINE通知

Pythoncurlコマンドを使いたい場合、requestというモジュールを使用します。Web APIを扱うときなどに使用するようですが、勉強不足で詳細まで理解できませんでした。

ですが、こちらのサイトを使うとcurlコマンドを自動的にpythonのコードに変換してくれます。勉強をさぼって、ありがたく使わせていただきます。
Convert cURL command syntax to Python requests, Ansible URI, browser fetch, MATLAB, Node.js, R, PHP, Strest, Go, Dart, JSON, Elixir, and Rust code

f:id:masashi_k:20200902000748p:plain

このサイトでcurlコマンドを変換すると"Hello world!"のメッセージはPython requestsでは以下のようになります。

import requests

headers = {
    'Authorization': 'Bearer xxxxxxx', # xxxxxxxにトークンを入力
}
files = {
    'message': (None, 'Hello world!'),
}
response = requests.post('https://notify-api.line.me/api/notify', headers=headers, files=files)

 

温度センサーの測定データを送信

下準備ができたところで、いよいよ温度をスマホのLINEアプリに送ります。
I2Cを使用するために初期化した後、1分間隔で温度を測定し、結果を送信するプログラムです。温度算出の部分はBMP280のレジスタ値から温度を求める計算をしています。BMP280のデータシートに記載されているサンプルを実装しているので、詳細はデータシートをご参照ください。 

import wiringpi
import time
import requests

#I2Cの初期化
slv_addr = 0x76wiringpi.wiringPiSetup()
i2c = wiringpi.I2C() #get I2C
Fd = i2c.setup(slv_addr) #setup I2C device
#温度測定開始
i2c.writeReg8(Fd, 0xF4 , 0x23)

try:
    while True:        #レジスタ値を読んで温度を算出(詳細はBMP280のデータシート参照)
        msb = i2c.readReg8(Fd, 0xFA) 
        lsb = i2c.readReg8(Fd, 0xFB)
        xlsb = i2c.readReg8(Fd, 0xFC)
        temp = (msb << 8)| lsb
        temp = temp << 4

        dig_t1 = i2c.readReg16(Fd,0x88)
        dig_t2 = i2c.readReg16(Fd,0x8A)
        if(dig_t2 & 0x8000):    
            dig_t2 = ((~dig_t2 & 0xFFFF) + 1)*-1
        dig_t3 = i2c.readReg16(Fd,0x8C) 
        if(dig_t3 & 0x8000):
            dig_t3 = ((~dig_t3 & 0xFFFF) + 1)*-1

        var1 = ((float)(temp)/16384.0 - (float)(dig_t1)/1024.0)*dig_t2
        var2 = (((float)(temp)/131072.0 - (float)(dig_t1)/8192.0) ** 2 )*dig_t3

        T = (var1 + var2) / 5120.0
        #print('temperture = {:.2f} [deg]'.format(T))
        #測定結果をLINEに通知                headers = { 'Authorization': 'Bearer xxxxxxx', }   #xxxxxxxにトークンを入力        files = { 'message': (None, 'Temp. = {:.2f}[deg]'.format(T)),}
        response = requests.post('https://notify-api.line.me/api/notify', headers=headers, files=files)
               #1分間隔 
        time.sleep(60)
    
except KeyboardInterrupt:
    print('!FINISH!')

 

このスクリプトを動かすと、以下のようになります。おお~、うまくいっていますね!

f:id:masashi_k:20200902234102p:plain

 

 

終わりに

ラズパイからLINEにメッセージや写真を投稿する方法について、実際に動かしてみた結果を書いてみました。これで見守りカメラの機能が1つできました。次はラズパイにWebサーバーを立てる方法を勉強します。

 

参考サイト

https://qiita.com/iitenkida7/items/576a8226ba6584864d95

https://www.itmedia.co.jp/news/articles/1912/01/news009_3.html

https://qiita.com/tottu22/items/9112d30588f0339faf9b

ラズパイで見守りカメラを作ってみる

前回、電子工作の成果物として、踏切を作ってみました。

masaeng.hatenablog.com

 

次回作品はどんなものにしようか、あれこれ算段してみた結果、次はラズパイを使って見守りカメラを作ってみようと思います。イメージはこんな感じです。
楽天市場】見守りカメラ ベビーカメラ ベビーモニター 出産祝い ペットカメラ ネットワークカメラ 防犯カメラ 監視カメラ 屋内用 SDカード録画  PTZ パンチルト 【首ふり見守りカメラ CK-IP350】:HDCトータルプロショップ

具体的な機能は
スマホからカメラの映像が見れる
スマホからカメラのパンを操作できる
●温度センサーがついていて、過去から現在までの部屋の温度が分かる
●温度が異常になったら、LINEで通知が来る

これらの機能を1個ずつ実現して、記事にしていきたいと思います。
なかなか時間が取れず、完成はだいぶ先になるかもしれませんが、地道にやっていきます。

まずは、ラズパイからLINE通知を行う方法です。

DETR(End-to-End Object Detection with Transformers)を動かしてみた

先日、AIに関するニュース記事を見ていて、物体検出の新しい手法が発表されたと、大々的に報じられていたものがあったので、非常に気になっていました。
DETR(End-to-End Object Detection with Transformers)というタイトルで、NMSなどの人手による設計をなくし、End-to-Endで物体検出を実現できる手法とのことです。

ai-scholar.tech

今回は久しぶりにこちらのモデルを動かしてみたいと思います。

論文 : https://arxiv.org/pdf/2005.12872.pdf
GitHub : https://github.com/facebookresearch/detr

 

環境

モデルを動かした環境は以下の通りです。

・OS Ubuntu16.04
GPU GeForce 1080Ti
・CUDA 10.2
・CuDNN 7.6.5
・Pytorch 1.6.0
・torchvision 0.7.0
※AnaconaでPython 3.6の仮想環境を構築

 

実装

こちらにColab上で動作するサンプルコードが公開されています。
https://colab.research.google.com/github/facebookresearch/detr/blob/colab/notebooks/detr_attention.ipynb#scrollTo=_GQzINI-FBWp

これだけで動作させることは出来ますが、カメラの映像や動画に対して推論を行うようにコードを修正していきます。

 

①ライブラリのインストール

必要なライブラリをインストールします。バウンディングボックス描画とフレームレート計算用にcv2とtimeライブラリを作者のコードから追加でインストールします。

今回使用する学習済みモデルはCOCOデータセットで学習したものなので、検出結果を表記する際にクラスを表示できるようにCOCOのクラス名がリストで定義されています。

import math

from PIL import Image
import requests
import matplotlib.pyplot as plt
#%config InlineBackend.figure_format = 'retina'

import ipywidgets as widgets
from IPython.display import display, clear_output

import torch
from torch import nn
from torchvision.models import resnet50
import torchvision.transforms as T
torch.set_grad_enabled(False)

import cv2   #追加
import time  #追加

# COCO classes CLASSES = [ 'N/A', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack', 'umbrella', 'N/A', 'N/A', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'N/A', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table', 'N/A', 'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush' ]

 

②使用する関数の定義

サンプルコードでも使用されている関数をそのまま流用していますが、バウンディングボックスを画像に描画する関数(put_rect)のみ新たに作成しています。

# standard PyTorch mean-std input image normalization
transform = T.Compose([
    T.Resize(800),
    T.ToTensor(),
    T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

# for output bounding box post-processing
def box_cxcywh_to_xyxy(x):
    x_c, y_c, w, h = x.unbind(1)
    b = [(x_c - 0.5 * w), (y_c - 0.5 * h),
         (x_c + 0.5 * w), (y_c + 0.5 * h)]
    return torch.stack(b, dim=1)

def rescale_bboxes(out_bbox, size):
    img_w, img_h = size
    b = box_cxcywh_to_xyxy(out_bbox)
    b = b * torch.tensor([img_w, img_h, img_w, img_h], dtype=torch.float32)
    return b

#以下関数を修正
def put_rect(cv2_img, prob, boxes): colors = COLORS * 100 output_image = cv2_img for p, (xmin, ymin, xmax, ymax), c in zip(prob, boxes.tolist(), colors): xmin = (int)(xmin) ymin = (int)(ymin) xmax = (int)(xmax) ymax = (int)(ymax) c[0],c[2]=c[2],c[0] c = tuple([(int)(n*255) for n in c]) output_image = cv2.rectangle(output_image,(xmin,ymin),(xmax,ymax),(0,0,255), 4) cl = p.argmax() text = f'{CLASSES[cl]}: {p[cl]:0.2f}' output_image = cv2.rectangle(output_image,(xmin,ymin-20),(xmin+len(text)*10,ymin),(0,255,255),-1) output_image = cv2.putText(output_image,text,(xmin,ymin-5),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),2) return output_image

 

➂モデルのロードと動画の読み込み

学習済みモデルはpytorchのライブラリ経由でダウンロードできます。今回はresnet50ベースのものを使いますが、他にもいくつか学習済みモデルが公開されているので、もし興味があればGitHubの方もご参照ください。
https://github.com/facebookresearch/detr

 

動画を読み込むのにOpenCVの関数cv2.VideoCapture()を使用します。カメラ入力か、動画入力かで引数を変えます。

#model load
model = torch.hub.load('facebookresearch/detr', 'detr_resnet50', pretrained=True)

model.eval()
#model = model.cuda()   #GPUを使用する場合はこちら

video_capture = cv2.VideoCapture(0)               #USBカメラ入力
#video_capture = cv2.VideoCapture("video_path")   #動画読み込み

# 幅と高さを取得
width = int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)) size = (width, height) #総フレーム数とフレームレートを取得 frame_count = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))  #動画読み込みの場合 frame_rate = int(video_capture.get(cv2.CAP_PROP_FPS)) fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') out = cv2.VideoWriter('./result.mp4', fmt, frame_rate, size)

seconds = 0.0
fps = 0.0

 

➃メインのwhileループ/forループ

こちらのwhile/forループで動画のフレームを取得し、推論を実行。その結果を入力画像にバウンディングボックスとして貼り付け、表示するということを繰り返します。合わせて、処理速度を計算し、printで表示します。

while True:                    # USBカメラ入力の場合
#for i in range(frame_count): # 動画読み込みの場合
start = time.time() _, frame = video_capture.read() frame_cvt = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) im =Image.fromarray(frame_cvt)
# mean-std normalize the input image (batch-size: 1) #img = transform(im).unsqueeze(0).cuda()  #GPUを使用する場合 img = transform(im).unsqueeze(0)       #CPUしかなければこちら # propagate through the model with torch.no_grad(): outputs = model(img) # keep only predictions with 0.7+ confidence probas = outputs['pred_logits'].softmax(-1)[0, :, :-1] keep = probas.max(-1).values > 0.9 # convert boxes from [0; 1] to image scales #bboxes_scaled = rescale_bboxes(outputs['pred_boxes'][0, keep].cpu(), im.size) #GPU bboxes_scaled = rescale_bboxes(outputs['pred_boxes'][0, keep], im.size) #CPU #display output_image = put_rect(frame, probas[keep], bboxes_scaled) # End time end = time.time() # Time elapsed seconds = (end - start) print("time:{:.3f} msec".format(seconds*1000) ) # Calculate frames per second fps = ( fps + (1/seconds) ) / 2 cv2.putText(output_image,'{:.2f}'.format(fps)+' fps',(10,50),cv2.FONT_HERSHEY_SIMPLEX,0.8,(255,0,0),3) cv2.imshow('Video', output_image)
out.write(output_image)
# Press Q to stop! if cv2.waitKey(1) & 0xFF == ord('q'): break

video_capture.release()
out.release()
cv2.destroyAllWindows()

 

実行結果

サンプル動画に対して推論を行った結果がこちらです。 


DETR(End-to-End Object Detection with Transformers)を動かしてみた

 

人物、車を正確に検知できているようです。処理時間はVGA解像度の入力で1フレーム70-80ms程度でした。

動画はこちらのフリー素材を使用させていただきました。
https://pixabay.com/ja/videos/%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A8%E3%83%BC%E3%82%AF%E5%B8%82-%E3%83%9E%E3%83%B3%E3%83%8F%E3%83%83%E3%82%BF%E3%83%B3-%E4%BA%BA-1044/

 

まとめ

革新的な物体検知の手法とされているDETR(End-to-End Object Detection with Transformers)を実際に動かしてみました。今回は試してみただけで、SSDなど従来手法との比較ができていないため、もう少し従来手法も勉強して性能面でどのような変化があるのかまで踏み込むことができたらと思っています。

 

Tensorflow 2.X でTensorflow 1.Xで書かれたコードを動かす方法

前回Tensorflowの使い方について記事にしました。

masaeng.hatenablog.com

しかし、よく調べてみると、この記事で書いたことはTensorflow 1.Xについての話で、Tensorflow 2.Xでは考え方が変わっていることが分かりました。

具体的にはTensorflow 1.XはDefine and Run(グラフを定義した後にまとめて実行)だったのに対し、Tensorflow 2.XはDefine by Run(グラフの構築とデータを渡して実行するのを同時に行う)になっています。Define by RunはEager Executionとも言うそうです。

例えば、Tensorflow 1.Xで使われていたPlaceholderはTensorflow 2.Xではなくなっており、定義するとエラーとなります。また、計算実行時に使っていたSessionも使う必要がなくなっています。

となると、疑問が湧きます。
Tensorflowのバージョンを2.Xに上げた後に、1.Xで書かれたコードを実行したいときはどうすればいいのか?
これについて調べてみたので、備忘録として書いておきます。

 

Tensorflow 1系で動かすには?

結論としては import tensorflow as tfとしているところを以下のように置き換えればOKです。

import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

他のpythonスクリプトをimportしている場合はそちらでもtensorflowをimportしている場合があるため、すべてこのように修正してください。

 

Kerasの場合は?

Tensorflow2.XではKerasはTensorflowの高レベルAPIとして組み込まれており、スタンドアローンとして使用する(import Keras)使い方だとエラーが発生しました。

以下はエラーの一例

RuntimeError: It looks like you are trying to use a version of multi-backend Keras that does not support TensorFlow 2.0. We recommend using `tf.keras`,or alternatively, downgrading to TensorFlow 1.14.

 

TensorflowのAPIとしてKerasを使用するには以下のようにします。

from tensorflow import keras
from tensorflow.keras import ***

TensorflowのAPIとしてのKeras

 

高レベルAPIとしての使い方であればTensorflow1.X, 2.Xともに同じ記述で動くとのことです。もしTensorflow 2系でKerasを動かしたときにエラーがでればスタンドアローンKerasを使っていないか確認してみてください。スタンドアローンとTensorflowのAPIで関数名が若干変わっているものがあるようです。

 

 まとめ

Tensorflow1系で書かれたコードをTensorflow2系で動かすための方法を記載しました。もし同じことで悩んだ方がおられたときに助けになれば幸いです。