ラズパイで動画配信
前回から大分間が開いてしまいましたが、見守りカメラ作成に向けた第三回目をやっていきます。
今回はUSBカメラで撮影した動画をWebサイト上でリアルアイムに表示する方法について書いていきたいと思います。
⓪USBカメラをラズパイに接続
事前準備として、ラズパイにUSBカメラを接続しておきます。カメラは何でもいいのですが、私はこちらのカメラを見守りカメラ作成用に購入しました。
ロジクール ウェブカメラ C270n ブラック HD 720P ウェブカム ストリーミング 小型 シンプル設計 国内正規品 2年間メーカー保証
①MJPG-streamerをラズパイにインストール
動画をストリーミング配信するのにMJPG-streamerというソフトウェアを使用します。ラズパイ上で以下のコマンドを入力し、インストールを行います。
$ cd <work_space> #mjpg-streamerをインストールするフォルダに移動 $ sudo apt-get install subversion libjpeg-dev imagemagick $ svn co https://mjpg-streamer.svn.sourceforge.net/svnroot/mjpg-streamer mjpg-streamer $ cd mjpg-streamer/mjpg-streamer $ make
②MJPG-streamerを起動
以下のコマンドでMJPG-streamerを起動させます。
cd mjpg-streamer sudo ./mjpg_streamer -i "./input_uvc.so -f 10 -r 320x240 -d /dev/video0 -y -n"\ -o "./output_http.so -w ./www -p 8080"
オプションのうち、-fはフレームレート、-rは解像度です。配信の解像度、フレームレートを変えたい場合はこれらの引数を変更します。上記のコマンドの場合はフレームレート10fps, 解像度QVGA(320x240)としています。
コマンドを実行するとターミナル上に以下のように表示され、MJPG-streamerが起動することを確認できます。
MJPG Streamer Version: svn rev: 3:172
i: Using V4L2 device.: /dev/video0
i: Desired Resolution: 320 x 240
i: Frames Per Second.: 10
i: Format............: YUV
i: JPEG Quality......: 80
o: www-folder-path...: ./www/
o: HTTP TCP port.....: 8080
o: username:password.: disabled
o: commands..........: enabled
➂ストリーム動画の表示
カメラの映像がうまく配信されているかを確認するため、ブラウザから以下のURLにアクセスします。
http://"ラズパイのIPアドレス":8080/stream.html
配信がうまくいっていれば以下のような画面が表示されます。カメラの画面は動画となっており、リアルアイムに動いていることが確認できると思います。動画のフレームレートは10fpsですが、個人的には特に違和感は感じませんでした。見守り用途としては十分だと思います。
④動画を自作htmlファイルに埋め込む
➂のサイトはMJPG-streamerのデモページでしたので、自作のhtmlファイル上で動画を見れるようにしていきます。試しにraspi_movie.htmlというhtmlファイルを作ってみました。埋め込んだ動画はimgタグの部分です。
raspi_movie.html
<html> <head> <title>Raspberry pi stream</title> </head> <body> <h1>Raspberry Pi</h1> <img src="http://"ラズパイのIPアドレス":8080/?action=stream"> </body> </html>
このファイルを前回と同様に/var/www/html/に保存します。すると別のデバイスから以下のURLでアクセスできるようになります。
http://"ラズパイのIPアドレス"/raspi_movie.html
実際にアクセスした結果は以下の通りです。
⑤配信を停止
配信を停止するときはラズパイのターミナル上でCtrl+Cを実行します。配信を停止すると、raspi_movie.htmlは以下のように参照先のimgが見つからないときと同じ状態なります。
まとめ
ラズパイを使ってUSBカメラの映像をリアルアイムに配信する方法について調べてみました。ライブラリを使えばとても簡単に実現することができました。次はこのサイトに温度センサーの測定温度をグラフで表示していきたいと思います。
参考サイト
https://www.hiramine.com/physicalcomputing/raspberrypi3/webcamstreaming.html
http://shokai.org/blog/archives/6896
JDLA G検定 2020#3 の総括
本業の方がバタバタしており、なかなかブログの更新ができておりませんでした。「ラズパイで見守りカメラを作る」がまだ完成していませんが、先日G検定を受験したので、当日までの勉強方法、試験内容、感想等をまとめておきたいと思います。
ちなみに結果の方は無事に合格できました。よかった^^
受験するに至った経緯
G検定を受けようと思ったのが2020年8月ごろ。これまでは資格だけ取っても実務ができないと意味ないと思い、別に取る必要ないと思っていたのですが、やっぱり気が変わって受けてみることに。2020年第二回の試験が7月に終わったばかりだったので、11月の第三回をターゲットにした。ちなみに、第二回はコロナによる緊急事態宣言が発令されたことを受けて、受験料が半額になっていた。どうせ受けるんなら、もっと早く思い立っておけば。。。
当時のスキル
・機械学習やディープラーニングの基礎的なところは講座や書籍で一通り勉強
・画像関連タスクの実務経験 1~2年
といったところ。過去問をサラッとやっておけば簡単に受かるだろうと思っていました。
勉強方法
●8月中旬(試験まであと三ヶ月)
まずはJDLAのホームページを見て参考図書を確認。公式テキストを買おうか迷ったのですが、Amazonのレビューを見るとこの範囲からあまり出ないというものが多かったので、結局問題集(通称、黒本)のみ購入しました。
徹底攻略 ディープラーニングG検定 ジェネラリスト 問題集 徹底攻略シリーズ
- 作者:スキルアップAI株式会社 明松真司,スキルアップAI株式会社 田原眞一
- 発売日: 2019/02/08
- メディア: Kindle版
●8月後半(試験まであと2ヶ月半)
問題集を解き始める。Deep Learningの分野は解けるが、Deep Learningの歴史、機械学習、法律の問題がズタボロ。これはまずいと焦る。
●9月前半(試験まであと2ヶ月)
問題集を1周したタイミングでネットの情報を探す。こちらの動画を見つけ、解説を聞きながら問題集の2周目を行う。以前勉強していたことを復習でき、忘れていたことも思い出すことができた。2周目が完了した時点で巻末の模擬試験を受ける。あっさり合格点が取れた。
●9月後半(試験まであと1ヶ月半)
ネットでG検定の情報を集めていたときに、前回2020年第二回の感想を書いたブログを見つける。「白本、黒本の範囲からの内容は3-4割」、「法律やDeep Learningに関する最新動向からの問題が多い」という記述を見てまた焦る。
そんな中、こちらのサイトを見つける。先人がG検定の勉強のためにまとめたサイトのリンク集です。これを隙間時間に読むことにする。
●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サーバーです。
カメラの映像や過去の温度データを参照するため、ラズパイにWebサーバーを立てて、スマホやPCからこのサーバーにアクセスするようにします。
今回はWebサーバーを立てるところまでをやってみましたので、手順を書いていきます。
①Apatch2のインストール
まず、WebサーバーソフトウェアのApache HTTP Serverを利用するために「apache2」パッケージをインストールします。
$ sudo apt install apache2
インストールするとApacheのWebサービスが自動的に開始されます。ラズパイと同じネットワークにつながったPCやスマホのブラウザで「http://ラズパイのIPアドレス」と入力すると以下のようにApacheのデフォルトページが表示されます。
②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」にアクセスすると以下の画面が表示されます。
うまく表示できました!!
➂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/実行ファイル名」にアクセスすると、以下のように表示されます。
こちらもうまくいきました!
まとめ
ラズパイにWebサーバーを立てて自作のWebページを表示させることができました。これで見守りカメラ完成に向けてまた一歩前進です。次はこのWebページにカメラの映像を表示してみたいと思います。簡単にできるかわかりませんが、いろいろ調べてみて、結果を記事にしてみます。
【Keras】Triplet lossを使って学習する
以前、顔認識を行うAIモデルである、Facenetを動かしたときにTriplet lossについて少し触れました。
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
これにより、うまく学習を行うことができました。
まとめ
Triplet lossを使った学習方法についてまとめてきました。高位APIを使うと簡単に実装ができる一方で、問題が起きたときに何が起きているかわかりにくいというデメリットがあります。できるだけ内部処理も理解して、低位APIでも書けるようになっていることが重要かなと思います。
また、今回の学習結果を使って、距離による分類がうまくいくかも機会があれば試してみたいと思います。
ラズパイからLINE通知
今回から見守りカメラに必要な機能を作っていきたいと思います。
まず初めはラズパイからLINE通知をする方法です。
温度センサーをラズパイにつないで、測定した温度を定期的にLINEに通知するプログラムを作っていきます。
LINE notifyに登録
LINEから自分のアカウントにメッセージを送信するにはLINE notifyというサービスを利用します。早速登録してみましょう。
①右上の"ログイン"ボタンからログイン
②ログイン後にマイページを開く
➂アクセストークンの発行から"トークンを発行する"をクリック
④トークン名を入力し、通知を送信するトークルームを選択後、"発行する"をクリック
トークルームは「1:1でLINE Notifyから通知を受け取る」でOKです。
⑤トークンが発行されるため、控えておく
⑥自分のスマホに"LINE notify"を招待しておく
これで登録は完了です。
ラズパイから通知コマンドを送信
発行されたトークンを使って自分のアカウントにラズパイからメッセージを送信します。次のコマンドで実現できます。
$ curl https://notify-api.line.me/api/notify \
-X POST \
-H "Authorization: Bearer xxxxxxx" \ # "xxxxxxx"にトークンを入力
-F "message=Hello world!" # Hello world!の部分が送信されるメッセージ
送信結果
一番下に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の部分には何か入れる必要があります。
送信結果
送信した"test"というメッセージとラズパイのアイコンが届いていることが確認できます。
温度センサーの測定データをLINEで通知
最後に温度センサーの測定データを通知してみます。これまではcurlコマンドを使ってきましたが、プログラムを作る上ではPython上で命令を出せた方がいいので、pythonで通知をさせる方法を調べてみました。
温度センサーとラズパイの接続
今回使用した温度センサーはBosch製のBMP280です。
メーカーサイトはこちら。データシートもここから参照できます。
購入サイトはこちら。
インターフェースはI2CとSPIのいずれかを選択できます。ピン数が少なくて済むので、今回はI2Cを使用します。回路図は以下の通りです。
CSBpinのレベルでI2C, SPIを切り替えます。I2Cを使う場合はHigh, SPIの場合はLowにしてください。SDOはH/LでI2Cのスレーブアドレスを変更できます。モジュール内部でpull-downされているので、変更の必要がなければオープンでOKです。
PythonスクリプトでLINE通知
Pythonでcurlコマンドを使いたい場合、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
このサイトで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!')
このスクリプトを動かすと、以下のようになります。おお~、うまくいっていますね!
終わりに
ラズパイからLINEにメッセージや写真を投稿する方法について、実際に動かしてみた結果を書いてみました。これで見守りカメラの機能が1つできました。次はラズパイにWebサーバーを立てる方法を勉強します。
参考サイト
https://qiita.com/iitenkida7/items/576a8226ba6584864d95
https://www.itmedia.co.jp/news/articles/1912/01/news009_3.html
ラズパイで見守りカメラを作ってみる
DETR(End-to-End Object Detection with Transformers)を動かしてみた
先日、AIに関するニュース記事を見ていて、物体検出の新しい手法が発表されたと、大々的に報じられていたものがあったので、非常に気になっていました。
DETR(End-to-End Object Detection with Transformers)というタイトルで、NMSなどの人手による設計をなくし、End-to-Endで物体検出を実現できる手法とのことです。
今回は久しぶりにこちらのモデルを動かしてみたいと思います。
論文 : 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など従来手法との比較ができていないため、もう少し従来手法も勉強して性能面でどのような変化があるのかまで踏み込むことができたらと思っています。