みらいテックラボ

音声・画像認識や機械学習など, 週末プログラマである管理人が興味のある技術の紹介や実際にトライしてみた様子などメモしていく.

ピープルカウンタを考えてみる(6)

Code for Naraがらみで, ピープルカウンタの開発に取り組んではや一年が過ぎた.
昨年は, 12月に第5回「HUG²祭り2018」でプロトタイプによる実証実験をさせていただいたこともあり, 今年はCode for Naraに「HUG²祭り2019」で人数カウントをしてほしいとの要望が来ているようだ.

www.volunt-info.jp

そこで, 今年はDeep Learningを用いた人物検出を試してみようと思い, NDIVIA Jetson Nano + Intel RealSense D415でトライすることにした.


関連記事:
ピーブルカウンタを考えてみる(1)
ピープルカウンタを考えてみる(2)
ピープルカウンタを考えてみる(3)
ピープルカウンタを考えてみる(4)
ピープルカウンタを考えてみる(5)
・ピープルカウンタを考えてみる(6)
ピープルカウンタを考えてみる(7)
ピープルカウンタを考えてみる(8)


1. システム準備
システムの構成は, 以下の通り.
(1) ハードウェア
 - NDIVIA Jetson Nano
 - Intel RealSense D415
(2) ソフトウェア
 - 人物検出:YOLOv3
 - 人物カウント:独自ソフト

Jetson NanoでD415やYOLOv3を動かすところについては, 別記事「Jetson NanoでIntel RealSenseを試してみる(1) ~ (4)」[1]を参照ください.


2. 学習
昨年の実証実験で収集したDepth画像をアノテーションして学習データを作成し, 学習する.

2.1 アノテーションツール
人物領域をアノテーションするにあたり,

などを検討したが, 結局YOLOv3への変換も容易にできそうなMS社のVoTT(Visual Object Tagging Tool)[2]を使用することにした.
VoTTの使い方については, ネット上にいくつか使い方の記事[3]があるので, そちらを参照ください.

2.2 アノテーション
昨年の実証実験で収集したDepth画像から, 以下の手順でアノテーション作業を行った.
(1) フレーム画像保存
昨年の人物検出を利用して, 人物領域を含むフレーム画像をカラー化及びグレースケール化して画像(2種類)を保存する.
1時間程度のDepth画像から約1万枚のフレーム画像が...
(これでも数フレームに一回保存と間引きやってる)
(2) アノテーション
VoTTで, 各画像内の人物のバウンディングボックスおよびラベルを設定する.
こんな感じ!!
f:id:moonlight-aska:20190918231848p:plain:w500
(3) VOC形式で出力
VoTTでは, YOLOv3の学習で使用するためのフォーマットで直接出力することができないので, 一旦VOC形式で出力する.

2.3 YOLOv3フォーマットへコンバート
こちらのkeras-yolo3[4]のvoc_annotation.pyは, VOC形式からkeras-yolo3で学習するためのフォーマットに変換することができるのだが, 本家のYOLOv3[5]の学習で使用するフォーマットとは異なる.
そこで, voc_annotation.pyベースに修正し, VOC形式をYOLOv3本家形式に変換するツールを作成した.

import xml.etree.ElementTree as ET
from os import getcwd
import sys,os

sets=[('AM9', 'passer_train'), ('AM9', 'passer_val')]

export_path = 'labels/Hug2-2018-PascalVOC-export'

classes = ["passer"]

if len(sys.argv) > 1:
    classes = sys.argv[1:]

with open('model_data/passer_classes.txt','w') as f:
    f.write('\n'.join(classes))


def convert_annotation(hour, image_id):
    in_file = open('%s/%s/Annotations/%s.xml'%(hour, export_path, image_id.replace(".jpg","")))
    tree=ET.parse(in_file)
    root = tree.getroot()

    out_file = open('%s/%s/Annotations/%s.txt'%(hour, export_path, image_id.replace(".jpg","")), "w")
    size = root.find('size')
    width = float(size.find('width').text)
    height = float(size.find('height').text)
    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult)==1:
            continue
        cls_id = classes.index(cls)

        xmlbox = obj.find('bndbox')
        xmin = float(xmlbox.find('xmin').text)
        ymin = float(xmlbox.find('ymin').text)
        xmax = float(xmlbox.find('xmax').text)
        ymax = float(xmlbox.find('ymax').text)
        cx = (xmax + xmin) / (width * 2)
        cy = (ymax + ymin) / (height * 2)
        w = (xmax - xmin) / width
        h = (ymax - ymin) / height
        out_file.write('{} {} {} {} {}\n'.format(cls_id, cx, cy, w, h))
    out_file.close()
    
wd = getcwd()

for hour, image_set in sets:
    image_ids = open('%s/%s/ImageSets/Main/%s.txt'%(hour, export_path, image_set)).read().strip().split()
    list_file = open('model_data/%s_%s.txt'%(hour, image_set), 'w')
    for image_id in image_ids:
        if image_id == '1': continue
        if image_id == '-1': continue
        image_file_path = '%s/%s/%s/JPEGImages/%s'%(wd, hour, export_path, image_id)
        list_file.write(image_file_path)
        list_file.write('\n')
        convert_annotation(hour, image_id)
    list_file.close()

(注) 今回ツールは汎用的には作ってません.

[変換後の例]
・データリスト
[dataset path]/model_data/AM9_passer_train.txt

<dataset path>/AM9/labels/Hug2-2018-PascalVOC-export/JPEGImages/FCam9_00007330.jpg
<dataset path>/AM9/labels/Hug2-2018-PascalVOC-export/JPEGImages/FCam9_00007328.jpg
<dataset path>/AM9/labels/Hug2-2018-PascalVOC-export/JPEGImages/FCam9_00007326.jpg
<dataset path>/AM9/labels/Hug2-2018-PascalVOC-export/JPEGImages/FCam9_00007324.jpg
     :

・正解データ
[dataset path]/AM/labels/Hug2-2018-PascalVOC-export/Annotations/FCam9_00007330.txt

0 0.5995575221238938 0.6106194690265487 0.23230088495575227 0.3038348082595871


2.4 お試し学習
ラベル付けした約550サンプル(train 439, val 110)で, モデルのお試し学習をした.
(大量にラベル付けした後, 学習してもまったく認識しない, なんてことになっても嫌なので...)

(1) cfg/yolov3-tiny-passer.cfg
今回は, グレースケール画像, クラス数1(通行人のみ)で学習するため, yolov3-tiny.cfgをベースに以下のように修正した.

channels=3 
⇒ channels=1

filters=255
⇒ filters=18

classes=80
⇒ classes=1

(2) cfg/passer.data

classes= 1
train  = <dataset path>/model_data/AM9_passer_train.txt
valid  = <dataset path>/model_data/AM9_passer_val.txt
names = data/passer.names
backup = backup

(3) data/passer.names

Passer

(4) ラベルデータ配置
ラベルのあるディレクトリ名を変更する.
[dataset path]/AM9/labels/Hug2-2018-PascalVOC-export/Annotations
/AM9/labels/Hug2-2018-PascalVOC-export/labels

(5) 学習
channels=1とすると, オリジナルのdarknetでは学習ができなかったので, こちらのサイト[6]のdarknetを用いた.

./darknet detector train cfg/passer.data cfg/yolov3-tiny-passer.cfg -dont_show darknet53.conv.74 

学習を回すこと3日ほど, 約45万batchsで一旦認識を試すことに.


3. Jetson Nanoでお試し
PeopleCounterの人物検出部分をYOLOv3に置き換えて, お試しで動かしてみた.

PeopleCounterUsingYOLOv3

数フレーム/秒とYOLOv3による人物検出に時間がかかって, カクカクした動きにはなっているが一応人物検出してトラッキングできていそうだ!!
ただ, 以下のような問題もみられる.

  • 明らかに人物である部分を人物として検出できていない.
  • フレーム処理が遅いことで, フレーム間の人物の移動距離が大きくなり, トラッキングに失敗しているところもある.

いくつか課題もあるが, アノテーションやYOLOv3の学習等に大きな誤りはなさそうなので,

  • 継続してアノテーションを行い, 学習データを増やす.
  • YOLOv3による人物検出処理を高速化する.

といった取り組みを, 実証実験(12/1)まで引き続きやっていこうと思う.

----
参照URL:
[1] Jetson NanoでIntel RealSenseを試してみる(1) ~ (4)
[2] GitHub - microsoft/VoTT: Visual Object Tagging Tool: An electron app for building end to end Object Detection Models from Images and Videos.
[3] 【物体検出】アノテーションツールVoTTの使い方|エンジニアの眠れない夜
[4] GitHub - qqwweee/keras-yolo3: A Keras implementation of YOLOv3 (Tensorflow backend)
[5] YOLO: Real-Time Object Detection - Joseph Redmon
[6] GitHub - AlexeyAB/darknet: Windows and Linux version of Darknet Yolo v3 & v2 Neural Networks for object detection (Tensor Cores are used)






物体・画像認識と時系列データ処理入門

物体・画像認識と時系列データ処理入門


オリジナルの画像認識AIを簡単に作ろう!

オリジナルの画像認識AIを簡単に作ろう!

  • 作者:安田 恒
  • 発売日: 2018/03/01
  • メディア: 単行本