Hello AI Worldを試す - JETSON NANO 開発者キット その2

JETSON NANO 開発者キット を試す その1 の続きです

とりあえずなにかしたいわけですが、Hello AI World として紹介されているやつが便利そう。

github.com

jetson-inference

ドキュメントに従って、Using the Console Program on Jetson まで実行。

Classifying Images with ImageNet

何やら、ImageNet を使用した Classifying らしいです。

~/jetson-inference/build/aarch64/bin$ ./imagenet-console orange_0.jpg output_0.jpg
imagenet-console
  args (3):  0 [./imagenet-console]  1 [orange_0.jpg]  2 [output_0.jpg]  


imageNet -- loading classification network model from:
         -- prototxt     networks/googlenet.prototxt
         -- model        networks/bvlc_googlenet.caffemodel
         -- class_labels networks/ilsvrc12_synset_words.txt
         -- input_blob   'data'
         -- output_blob  'prob'
         -- batch_size   2

[TRT]  TensorRT version 5.0.6

 ・・・

[TRT]  layer network time - 141.518982 ms
class 0950 - 0.978908  (orange)
class 0951 - 0.020961  (lemon)
imagenet-console:  'orange_0.jpg' -> 97.89076% class #950 (orange)
loaded image  fontmapA.png  (256 x 512)  2097152 bytes
[cuda]  cudaAllocMapped 2097152 bytes, CPU 0x1048a0000 GPU 0x1048a0000
[cuda]  cudaAllocMapped 8192 bytes, CPU 0x100f62000 GPU 0x100f62000
imagenet-console:  attempting to save output image to 'output_0.jpg'
imagenet-console:  completed saving 'output_0.jpg'

shutting down...

f:id:pongsuke:20190419153340j:plain

97%の確率で、オレンジだそうですよ。

Locating Object Coordinates using DetectNet

つぎは、学習済みの DetectNet-COCO-Dog model を使用した、ディテクションです。

$ ./detectnet-console dog_1.jpg output_1.jpg coco-dog
detectnet-console
  args (4):  0 [./detectnet-console]  1 [dog_1.jpg]  2 [output_1.jpg]  3 [coco-dog]  


detectNet -- loading detection network model from:
          -- prototxt     networks/DetectNet-COCO-Dog/deploy.prototxt
          -- model        networks/DetectNet-COCO-Dog/snapshot_iter_38600.caffemodel
          -- input_blob   'data'
          -- output_cvg   'coverage'
          -- output_bbox  'bboxes'
          -- mean_pixel   0.000000
          -- class_labels networks/DetectNet-COCO-Dog/class_labels.txt
          -- threshold    0.500000
          -- batch_size   2

[TRT]  TensorRT version 5.0.6
 ・・・
[TRT]  layer network time - 371.817322 ms
detectnet-console:  finished processing network  (1555656574352)
2 bounding boxes detected
detected obj 0  class #0 (dog)  confidence=0.886617
bounding box 0  (1258.875000, 240.310547)  (1672.687500, 563.941406)  w=413.812500  h=323.630859
detected obj 1  class #0 (dog)  confidence=0.502083
bounding box 1  (594.562500, 328.482422)  (1016.437500, 584.718750)  w=421.875000  h=256.236328
detectnet-console:  writing 1920x1080 image to 'output_1.jpg'
detectnet-console:  successfully wrote 1920x1080 image to 'output_1.jpg'

shutting down...

f:id:pongsuke:20190419155014j:plain

Dog を2つ検出していて、その位置を矩形で出していますね。

Multi-class Object Detection Models

$ ./detectnet-console peds-003.jpg output-3.jpg multiped

 ...
detectnet-console:  finished processing network  (1555657903767)
6 bounding boxes detected
detected obj 0  class #0 (person)  confidence=0.547767
bounding box 0  (382.875000, -127.316528)  (837.625000, 583.328979)  w=454.750000  h=710.645508
detected obj 1  class #0 (person)  confidence=0.671255
bounding box 1  (-6.250000, -5.668457)  (218.250000, 577.884277)  w=224.500000  h=583.552734
detected obj 2  class #0 (person)  confidence=0.554531
bounding box 2  (870.500000, 72.794922)  (989.937500, 452.805298)  w=119.437500  h=380.010376
detected obj 3  class #1 (baggage)  confidence=0.718298
bounding box 3  (681.246094, 263.098450)  (761.109375, 410.105408)  w=79.863281  h=147.006958
detected obj 4  class #1 (baggage)  confidence=0.967225
bounding box 4  (925.593750, 269.661926)  (998.937500, 432.219849)  w=73.343750  h=162.557922
detected obj 5  class #1 (baggage)  confidence=0.979315
bounding box 5  (148.437500, 288.233582)  (323.406250, 522.094727)  w=174.968750  h=233.861145
detectnet-console:  writing 1024x611 image to 'output-3.jpg'
detectnet-console:  successfully wrote 1024x611 image to 'output-3.jpg'

shutting down...

f:id:pongsuke:20190419161342j:plain

GPU 使用率

GPU使用率については、tegrastats Utility を使えとのこと。

$ tegrastats 
RAM 1097/3963MB (lfb 271x4MB) CPU [11%@102,10%@102,6%@102,3%@102] EMC_FREQ 0% GR3D_FREQ 0% PLL@40C CPU@44C PMIC@100C GPU@42C AO@48.5C thermal@43C POM_5V_IN 1248/1248 POM_5V_GPU 0/0 POM_5V_CPU 166/166
RAM 1110/3963MB (lfb 271x4MB) CPU [9%@1428,3%@1428,1%@1428,2%@1428] EMC_FREQ 0% GR3D_FREQ 2% PLL@40.5C CPU@44C PMIC@100C GPU@40C AO@49C thermal@43C POM_5V_IN 2514/1881 POM_5V_GPU 82/41 POM_5V_CPU 823/494
RAM 1342/3963MB (lfb 263x4MB) CPU [8%@1428,97%@1428,7%@1428,6%@1428] EMC_FREQ 0% GR3D_FREQ 6% PLL@41C CPU@45C PMIC@100C GPU@40.5C AO@49.5C thermal@42.5C POM_5V_IN 3317/2359 POM_5V_GPU 163/81 POM_5V_CPU 1226/738
RAM 1474/3963MB (lfb 263x4MB) CPU [8%@1428,96%@1428,6%@1428,6%@1428] EMC_FREQ 0% GR3D_FREQ 0% PLL@41C CPU@44.5C PMIC@100C GPU@40.5C AO@49.5C thermal@42.75C POM_5V_IN 2716/2448 POM_5V_GPU 82/81 POM_5V_CPU 903/779
RAM 1692/3963MB (lfb 250x4MB) CPU [7%@1428,8%@1428,12%@1428,86%@1428] EMC_FREQ 0% GR3D_FREQ 33% PLL@41C CPU@45C PMIC@100C GPU@40.5C AO@49C thermal@43C POM_5V_IN 2917/2542 POM_5V_GPU 123/90 POM_5V_CPU 1025/828
RAM 1711/3963MB (lfb 241x4MB) CPU [8%@403,71%@403,7%@403,5%@403] EMC_FREQ 0% GR3D_FREQ 99% PLL@41C CPU@45.5C PMIC@100C GPU@41.5C AO@50C thermal@43.25C POM_5V_IN 4429/2856 POM_5V_GPU 1706/359 POM_5V_CPU 528/778
RAM 1098/3963MB (lfb 268x4MB) CPU [14%@102,59%@102,0%@102,3%@102] EMC_FREQ 0% GR3D_FREQ 0% PLL@40.5C CPU@44.5C PMIC@100C GPU@40.5C AO@49C thermal@42.5C POM_5V_IN 1904/2720 POM_5V_GPU 82/319 POM_5V_CPU 165/690
RAM 1098/3963MB (lfb 268x4MB) CPU [13%@102,4%@102,4%@102,1%@102] EMC_FREQ 0% GR3D_FREQ 0% PLL@40C CPU@44.5C PMIC@100C GPU@42C AO@49.5C thermal@43.25C POM_5V_IN 1206/2531 POM_5V_GPU 0/279 POM_5V_CPU 166/625

・・・情報多すぎ。

ドキュメント

https://developer.ridgerun.com/wiki/index.php?title=Xavier/JetPack_4.1/Performance_Tuning/Evaluating_Performance

GR3D_FREQ 99%

って行があるから、GPU使用率 99% に達したという意味かな?

カメラを使用する

note: by default, the Jetson's onboard CSI camera will be used as the video source. If you wish to use a USB webcam instead, change the DEFAULT_CAMERA define at the top of imagenet-camera.cpp to reflect the /dev/video V4L2 device of your USB camera. The model it's tested with is Logitech C920.

ということで、デフォルトでは、オンボードCSIカメラを使用する設定になっている。これは意外。

USBカメラ設定で再コンパイル

USBカメラを使用する場合には、設定ファイルを書き換えて、再コンパイルが必要です。

ここで注意が必要なのは、detectnet-cameraimagenet-camera の2スクリプトそれぞれに、設定ファイルがあるので、両方変えないとだめです。
最初、片方だけ直したせいで、片やWEBカメラ、片やカメラモジュールが起動するという、挙動になり、なぜだろう・・・?と、相当悩みました。

~/jetson-inference/imagenet-camera/imagenet-camera.cpp
~/jetson-inference/detectnet-camera/detectnet-camera.cpp

カメラモジュールについて

Raspberry pi 用の公式カメラモジュールが動きます。

V2.1 を刺したところ正常に認識し、動きました。

V1.4 を刺したところ、認識しませんでした。
ドキュメントには、Version2は動くよーと書いてあるので、V1.4は非対応なのかも。
ただし、手元の v1.4 が壊れていた可能性もあります。

セットアップ - JETSON NANO 開発者キット その1

JETSON NANO 開発者キット を試します。

www.nvidia.com

Jetson nano の情報

技術仕様
GPU 128 基の NVIDIA CUDA® コアを実装した NVIDIA Maxwell™ アーキテクチャ
CPU クアッドコア ARM® Cortex®-A57 MPCore プロセッサ
メモリ   4 GB 64 ビット LPDDR4
ストレージ 16 GB eMMC 5.1 フラッシュ
ビデオ エンコード   4K @ 30 (H.264/H.265)
ビデオ デコード  4K @ 60 (H.264/H.265)
カメラ   12 レーン (3x4 または 4x2) MIPI CSI-2 DPHY 1.1 (1.5 Gbps)
コネクティビティ    ギガビット イーサネット
ディスプレイ  HDMI 2.0 または DP1.2 | eDP 1.4 | DSI (1 x2) の 2 つ同時
UPHY    1 x1/2/4 PCIE、1x USB 3.0、3x USB 2.0
I/O 1x SDIO / 2x SPI / 6x I2C / 2x I2S / GPIOs
サイズ   69.6 mm x 45 mm
コネクタ    260 ピン エッジ コネクタ

開発者キット

開発者キット I/O
USB 4x USB 3.0、USB 2.0 Micro-B
カメラ用コネクタ    1x MIPI CSI-2 DPHY レーン
コネクティビティ    ギガビット イーサネット、M.2 Key E
ストレージ microSD (別売)
ディスプレイ  HDMI 2.0 および eDP 1.4
I/O GPIO、I2C、I2S、SPI、UART

インストール

ガイドはこちら

Getting Started With Jetson Nano Developer Kit | NVIDIA Developer

必須

microSD Card

the minimum recommended is a 16GB UHS-1 card.

Micro-USB Power Supply

5V⎓2A

Write Image to the microSD Card

Download the Jetson Nano Developer Kit SD Card Image, and note where it was saved on the computer.
Write the image to your microSD card by following the instructions below according to the type of computer you are using: Windows, Mac, or Linux.

zipのファイルサイズは 5.5G あります。
解凍後の .img は 12.5G あります

焼く

以下の2つのセットが紹介されています。
Raspberry pi と一緒ですね。

SD Memory Card Formatter for Windows.
  1. Download, install, and launch SD Memory Card Formatter for Windows.
  2. Select card drive
  3. Select “Quick format”
  4. Leave “Volume label” blank
  5. Click “Format” to start formatting, and “Yes” on the warning dialog

Etcher.

初期設定

起動

GUI ログイン画面になっていました。

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

いろいろ設定

root のパスワード sudo su - で root になって、パスワード設定

VNC

いつもトラブル発生のVNC

Vino という VNC server?が入っている。 ログインし、画面共有を Active にして、パスワードを設定する。

WIndows10 の VNC viewer でコネクトするとエラーが出る。

$ gsettings set org.gnome.Vino require-encryption false

f:id:pongsuke:20190419122254p:plain

電源について

8. DC Barrel jack for 5V power input となっているが、5V4Aを指しても通電しない模様。

ぐぐったら、横のジャンパーピンを繋ぐ必要があるらしい。
適当につないだら、確かに起動した。

Ubuntu 16.04 lts + CUDA9.0 + cuDNN 7.1.3 Tensorflow1.6 コンパイルまで

Ubuntu14.04 LTS を dist upgrade で、16.04 にしたので、CUDA と cuDNN を入れ直しすことにしました。

NVIDIA Driver を入れ直す

Before installing the CUDA Toolkit on Linux, please ensure that you have the latest NVIDIA driver R390 installed. The latest NVIDIA R390 driver is available at: www.nvidia.com/drivers

を発見。

現在のバージョンを確認。

$ nvidia-smi 
Tue May  1 21:25:53 2018       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.111                Driver Version: 384.111                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:06:00.0  On |                  N/A |
|  0%   39C    P8     9W / 120W |     52MiB /  6071MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      1135      G   /usr/lib/xorg/Xorg                            50MiB |
+-----------------------------------------------------------------------------+

とのことなので、Driver を先に入れよう。

$ dpkg -l | grep nvidia
$ dpkg -l | grep cuda

$ sudo add-apt-repository ppa:graphics-drivers/ppa
$ sudo apt-get update

$ sudo apt-get install nvidia-390

$ sudo reboot

$ nvidia-smi 
Tue May  1 22:00:43 2018       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.48                 Driver Version: 390.48                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:06:00.0  On |                  N/A |
|  0%   43C    P0    29W / 120W |     61MiB /  6077MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      1049      G   /usr/lib/xorg/Xorg                            59MiB |
+-----------------------------------------------------------------------------+

CUDA インストール

sudo dpkg -i cuda-repo-ubuntu1604_9.1.85-1_amd64.deb sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub sudo apt-get update sudo apt-get install cuda

なお、Tensorflow が、 CUDA9.0 を求めてきたので、

$ sudo apt-get install cuda-9-0

となりました。

邪魔していたのを対処

$ sudo aptitude install cuda

path を通す

cuDNN インストール

tensorflow インストール

sudo pip install tensorflow

で、1.8 かなにかが入ったんですが、tensorflow Illegal instruction core dump でてしまって、困りました。

回避策として、

$ sudo pip install tensorflow-gpu==1.5

となりました。

pipでバイナリをインストールした場合、1.6以降ではAVX がONになっていて、CPU が AVX に対応していないと、エラーに成るようです。

Note: Starting from 1.6 release, our prebuilt binaries will use AVX instructions. Older CPUs may not be able to execute these binaries.

回避策としては、1.5を使用するか、pip ではなく、自分で AVX OFF でコンパイルすることらしいです。

Tensorflow を、コンパイルする

Installing TensorFlow from Sources  |  TensorFlow

git で取得

$ git clone https://github.com/tensorflow/tensorflow
$ cd tensorflow
$ git checkout r1.6

$ ./configure

build

$ bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package
$ bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg

$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.8.0-py2-none-any.whl

Tensorflow で SSD

動作環境

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="14.04.5 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.5 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"

$ nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Tue_Jan_10_13:22:03_CST_2017
Cuda compilation tools, release 8.0, V8.0.61

$ ls /usr/local/cuda/lib64/libcudnn.so.*
/usr/local/cuda/lib64/libcudnn.so.5  /usr/local/cuda/lib64/libcudnn.so.5.1.10


$ pip list | grep tensorflow
tensorflow-gpu                1.4.1             
tensorflow-tensorboard        0.4.0

$ python
Python 3.4.3 (default, Nov 28 2017, 16:41:13) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.

SSD の取得と、設定?

$ git clone https://github.com/balancap/SSD-Tensorflow.git

$ cd SSD-Tensorflow
$ cd checkpoints
$ unzip ssd_300_vgg.ckpt.zip

サンプルコードを改変して、動かす

$ cd ../notebooks/

$ jupyter nbconvert --to python ssd_notebook.ipynb

matplotlib の設定

$ cat ~/.config/matplotlib/matplotlibrc 
font.family : IPAexGothic
backend      : tkagg

サンプルコードを改変

せっかくなので、すべての画像を処理することと、ラベルを設定しました。

./datasets/pascalvoc_common.py

の中身を、使用しやすいように書き換えてます。

SSDによる物体検出を試してみた - TadaoYamaokaの日記

こちら様のデータを使用しています。

ssd_notebook.py
# coding: utf-8

import os
import math
import random

import numpy as np
import tensorflow as tf
import cv2

slim = tf.contrib.slim

#get_ipython().magic('matplotlib inline')
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import sys
sys.path.append('../')

from nets import ssd_vgg_300, ssd_common, np_methods
from preprocessing import ssd_vgg_preprocessing
from notebooks import visualization

# TensorFlow session: grow memory when needed. TF, DO NOT USE ALL MY GPU MEMORY!!!
gpu_options = tf.GPUOptions(allow_growth=True)
config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options)
isess = tf.InteractiveSession(config=config)


# ## SSD 300 Model
# 
# The SSD 300 network takes 300x300 image inputs. In order to feed any image, the latter is resize to this input shape (i.e.`Resize.WARP_RESIZE`). Note that even though it may change the ratio width / height, the SSD model performs well on resized images (and it is the default behaviour in the original Caffe implementation).
# 
# SSD anchors correspond to the default bounding boxes encoded in the network. The SSD net output provides offset on the coordinates and dimensions of these anchors.

# Input placeholder.
net_shape = (300, 300)
data_format = 'NHWC'
img_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
# Evaluation pre-processing: resize to SSD net shape.
image_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval( img_input, None, None, net_shape, data_format, resize=ssd_vgg_preprocessing.Resize.WARP_RESIZE)
image_4d = tf.expand_dims(image_pre, 0)

# Define the SSD model.
reuse = True if 'ssd_net' in locals() else None
ssd_net = ssd_vgg_300.SSDNet()
with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
        predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)

# Restore SSD model.
ckpt_filename = '../checkpoints/ssd_300_vgg.ckpt'
# ckpt_filename = '../checkpoints/VGG_VOC0712_SSD_300x300_ft_iter_120000.ckpt'
isess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(isess, ckpt_filename)

# SSD default anchor boxes.
ssd_anchors = ssd_net.anchors(net_shape)


# ## Post-processing pipeline
# 
# The SSD outputs need to be post-processed to provide proper detections. Namely, we follow these common steps:
# 
# * Select boxes above a classification threshold;
# * Clip boxes to the image shape;
# * Apply the Non-Maximum-Selection algorithm: fuse together boxes whose Jaccard score > threshold;
# * If necessary, resize bounding boxes to original image shape.

# Main image processing routine.
def process_image(img, select_threshold=0.5, nms_threshold=.45, net_shape=(300, 300)):
        # Run SSD network.
        rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img], feed_dict={img_input: img})

        # Get classes and bboxes from the net outputs.
        rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select( rpredictions, rlocalisations, ssd_anchors, select_threshold=select_threshold, img_shape=net_shape, num_classes=21, decode=True)

        rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)
        rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k=400)
        rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold=nms_threshold)
        # Resize bboxes to original image shape. Note: useless for Resize.WARP!
        rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)
        return rclasses, rscores, rbboxes


# Test on some demo image and visualize output.
path = '../demo/'
image_names = sorted(os.listdir(path))

for i in range(12):
        img = mpimg.imread(path + image_names[i])
        rclasses, rscores, rbboxes =  process_image(img)
visualization.py
# Copyright 2017 Paul Balanca. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import sys
import cv2
import random

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import matplotlib.cm as mpcm

VOC_LABELS = {
    0: 'none',
    1: 'aeroplane',
    2: 'bicycle',
    3: 'bird',
    4: 'boat',
    5: 'bottle',
    6: 'bus',
    7: 'car',
    8: 'cat',
    9: 'chair',
    10: 'cow',
    11: 'diningtable',
    12: 'dog',
    13: 'horse',
    14: 'motorbike',
    15: 'person',
    16: 'pottedplant',
    17: 'sheep',
    18: 'sofa',
    19: 'train',
    20: 'tvmonitor',
}

# =========================================================================== #
# Some colormaps.
# =========================================================================== #
def colors_subselect(colors, num_classes=21):
        dt = len(colors) // num_classes
        sub_colors = []
        for i in range(num_classes):
                color = colors[i*dt]
                if isinstance(color[0], float):
                        sub_colors.append([int(c * 255) for c in color])
                else:
                        sub_colors.append([c for c in color])
        return sub_colors

colors_plasma = colors_subselect(mpcm.plasma.colors, num_classes=21)
colors_tableau = [(255, 255, 255), (31, 119, 180), (174, 199, 232), (255, 127, 14), (255, 187, 120),
                                (44, 160, 44), (152, 223, 138), (214, 39, 40), (255, 152, 150),
                                (148, 103, 189), (197, 176, 213), (140, 86, 75), (196, 156, 148),
                                (227, 119, 194), (247, 182, 210), (127, 127, 127), (199, 199, 199),
                                (188, 189, 34), (219, 219, 141), (23, 190, 207), (158, 218, 229)]


# =========================================================================== #
# OpenCV drawing.
# =========================================================================== #
def draw_lines(img, lines, color=[255, 0, 0], thickness=2):
        """Draw a collection of lines on an image.
        """
        for line in lines:
                for x1, y1, x2, y2 in line:
                        cv2.line(img, (x1, y1), (x2, y2), color, thickness)


def draw_rectangle(img, p1, p2, color=[255, 0, 0], thickness=2):
        cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)


def draw_bbox(img, bbox, shape, label, color=[255, 0, 0], thickness=2):
        p1 = (int(bbox[0] * shape[0]), int(bbox[1] * shape[1]))
        p2 = (int(bbox[2] * shape[0]), int(bbox[3] * shape[1]))
        cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)
        p1 = (p1[0]+15, p1[1])
        cv2.putText(img, str(label), p1[::-1], cv2.FONT_HERSHEY_DUPLEX, 0.5, color, 1)


def bboxes_draw_on_img(img, classes, scores, bboxes, colors, thickness=2):
        shape = img.shape
        for i in range(bboxes.shape[0]):
                bbox = bboxes[i]
                color = colors[classes[i]]
                # Draw bounding box...
                p1 = (int(bbox[0] * shape[0]), int(bbox[1] * shape[1]))
                p2 = (int(bbox[2] * shape[0]), int(bbox[3] * shape[1]))
                cv2.rectangle(img, p1[::-1], p2[::-1], color, thickness)
                # Draw text...
                s = '%s/%.3f' % (classes[i], scores[i])
                p1 = (p1[0]-5, p1[1])
                cv2.putText(img, s, p1[::-1], cv2.FONT_HERSHEY_DUPLEX, 0.4, color, 1)


# =========================================================================== #
# Matplotlib show...
# =========================================================================== #
def plt_bboxes(f_name, img, classes, scores, bboxes, figsize=(10,10), linewidth=1.5):
        """Visualize bounding boxes. Largely inspired by SSD-MXNET!
        """
        fig = plt.figure(figsize=figsize)
        plt.imshow(img)
        height = img.shape[0]
        width = img.shape[1]
        colors = dict()
        for i in range(classes.shape[0]):
                cls_id = int(classes[i])
                if cls_id >= 0:
                        score = scores[i]
                        if cls_id not in colors:
                                colors[cls_id] = (random.random(), random.random(), random.random())
                        ymin = int(bboxes[i, 0] * height)
                        xmin = int(bboxes[i, 1] * width)
                        ymax = int(bboxes[i, 2] * height)
                        xmax = int(bboxes[i, 3] * width)
                        rect = plt.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, fill=False, edgecolor=colors[cls_id], linewidth=linewidth)
                        plt.gca().add_patch(rect)
                        class_name = str(cls_id)
                        class_name = VOC_LABELS[cls_id]

                        plt.gca().text(xmin, ymin - 2, '{:s} | {:.3f}'.format(class_name, score), bbox=dict(facecolor=colors[cls_id], alpha=0.5), fontsize=12, color='white')
        plt.savefig( './results/'+f_name + '.png' )
        plt.show()

結果

f:id:pongsuke:20180420141915p:plainf:id:pongsuke:20180420141920p:plainf:id:pongsuke:20180420141926p:plainf:id:pongsuke:20180420141931p:plainf:id:pongsuke:20180420141935p:plainf:id:pongsuke:20180420141939p:plainf:id:pongsuke:20180420141943p:plainf:id:pongsuke:20180420141947p:plainf:id:pongsuke:20180420141952p:plainf:id:pongsuke:20180420141958p:plainf:id:pongsuke:20180420142003p:plainf:id:pongsuke:20180420142008p:plain

Opencv Aruco で Pose estimation する その2 estimatePoseBoard

今度は estimatePoseBoard を試します。

f:id:pongsuke:20180419112457p:plain

#!/usr/bin/env python
# -*- coding: utf-8 -*
import cv2
import numpy as np

aruco = cv2.aruco

# WEBカメラ
cap = cv2.VideoCapture(0)

dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
parameters =  aruco.DetectorParameters_create()
# CORNER_REFINE_NONE, no refinement. CORNER_REFINE_SUBPIX, do subpixel refinement. CORNER_REFINE_CONTOUR use contour-Points
parameters.cornerRefinementMethod = aruco.CORNER_REFINE_CONTOUR

cameraMatrix = np.array( [[1.42068235e+03,0.00000000e+00,9.49208512e+02],
    [0.00000000e+00,1.37416685e+03,5.39622051e+02],
    [0.00000000e+00,0.00000000e+00,1.00000000e+00]] )
distCoeffs = np.array( [1.69926613e-01,-7.40003491e-01,-7.45655262e-03,-1.79442353e-03, 2.46650225e+00] )

cap.set(cv2.CAP_PROP_FPS, 10)
#cap.set(cv2.CAP_PROP_FRAME_WIDTH, 600)
#cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 600)

board = aruco.GridBoard_create(5, 1, 0.04, 0.01, dictionary)

def main():

    ret, frame = cap.read()

    # 変換処理ループ
    while ret == True:
        corners, ids, rejectedImgPoints = aruco.detectMarkers(frame, dictionary, parameters=parameters)
        #print(corners)
        #print(ids)
        #print(rejectedImgPoints)

        aruco.drawDetectedMarkers(frame, corners, ids, (0,255,0))

        for i, corner in enumerate( corners ):
            points = corner[0].astype(np.int32)
            cv2.polylines(frame, [points], True, (0,255,255))
            cv2.putText(frame, str(ids[i][0]), tuple(points[0]), cv2.FONT_HERSHEY_PLAIN, 1,(0,0,0), 1)

        # int valid = estimatePoseBoard(corners, ids, board, cameraMatrix, distCoeffs, rvec, tvec);
        retval, rvec, tvec = aruco.estimatePoseBoard(corners, ids, board, cameraMatrix, distCoeffs)

        if retval != 0:
            aruco.drawAxis(frame, cameraMatrix, distCoeffs, rvec, tvec, 0.1)

        cv2.imshow('org', frame)

        # Escキーで終了
        key = cv2.waitKey(50)
        if key == 27: # ESC
            break

        # 次のフレーム読み込み
        ret, frame = cap.read()

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass

これでも、やはり、 z axis flipping は発生します。

Opencv Aruco で Pose estimation する その1

Pose estimation ってのは、姿勢推定 なのでしょうか?

estimatePoseSingleMarkers においては、そのマーカーの向きを推定します(多分)。

なお、estimatePoseBoard も有ります。

とにかくやってみましょう。

マーカー画像の用意

f:id:pongsuke:20180419102042j:plain

今回は、マーカーを5つ(0,1,2,3,4)印刷しました。DICT_4X4_50 で作成したものです。

スクリプト

(いくつかのサイト様のコードを参考にさせていただいたのですが、混じってしまって、特定できませんでした。)

#!/usr/bin/env python
# -*- coding: utf-8 -*
import cv2
import numpy as np

aruco = cv2.aruco

# WEBカメラ
cap = cv2.VideoCapture(0)

dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
parameters =  aruco.DetectorParameters_create()
# CORNER_REFINE_NONE, no refinement. CORNER_REFINE_SUBPIX, do subpixel refinement. CORNER_REFINE_CONTOUR use contour-Points
parameters.cornerRefinementMethod = aruco.CORNER_REFINE_CONTOUR

cameraMatrix = np.array( [[1.42068235e+03,0.00000000e+00,9.49208512e+02],
    [0.00000000e+00,1.37416685e+03,5.39622051e+02],
    [0.00000000e+00,0.00000000e+00,1.00000000e+00]] )
distCoeffs = np.array( [1.69926613e-01,-7.40003491e-01,-7.45655262e-03,-1.79442353e-03, 2.46650225e+00] )

cap.set(cv2.CAP_PROP_FPS, 10)
#cap.set(cv2.CAP_PROP_FRAME_WIDTH, 600)
#cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 600)

def main():

    ret, frame = cap.read()

    # 変換処理ループ
    while ret == True:
        corners, ids, rejectedImgPoints = aruco.detectMarkers(frame, dictionary, parameters=parameters)
        #print(corners)
        #print(ids)
        #print(rejectedImgPoints)

        aruco.drawDetectedMarkers(frame, corners, ids, (0,255,0))

        for i, corner in enumerate( corners ):
            points = corner[0].astype(np.int32)
            cv2.polylines(frame, [points], True, (0,255,255))
            cv2.putText(frame, str(ids[i][0]), tuple(points[0]), cv2.FONT_HERSHEY_PLAIN, 1,(0,0,0), 1)

        # rvecs, tvecs, _objPoints =   cv.aruco.estimatePoseSingleMarkers( corners, markerLength, cameraMatrix, distCoeffs[, rvecs[, tvecs[, _objPoints]]] )
        rvecs, tvecs, _objPoints = aruco.estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs)
        if ids is not None:
            for i in range( ids.size ):
                #print( 'rvec {}, tvec {}'.format( rvecs[i], tvecs[i] ))
                #print( 'rvecs[{}] {}'.format( i, rvecs[i] ))
                #print( 'tvecs[{}] {}'.format( i, tvecs[i] ))
                aruco.drawAxis(frame, cameraMatrix, distCoeffs, rvecs[i], tvecs[i], 0.1)

        cv2.imshow('org', frame)

        # Escキーで終了
        key = cv2.waitKey(50)
        if key == 27: # ESC
            break

        # 次のフレーム読み込み
        ret, frame = cap.read()

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass

f:id:pongsuke:20180419104506p:plain

parameters.cornerRefinementMethod = aruco.CORNER_REFINE_CONTOUR

で、何かを設定していますが、どの様な差が出るのかわかってません。

z axis flipping 現象

f:id:pongsuke:20180419110109p:plain

z軸が反転するような現象が起こります。(再現して撮影しようとすると、変に大変だったりします。)

これは、バグなのかどうか、スレッドがいろいろ見当たりますが、どうやらバグではなく、そういうもののようです。

f:id:pongsuke:20180419110845p:plain

この画像の、白の面が、どっち向きなのか(天井から下がっているのか、地面に設置されているのか)を、判定できないことと一緒らしいです。

Iterative Pose Estimation Using Coplanar Feature Points

https://pdfs.semanticscholar.org/36bc/490d902061a27b1c598725cd61ad9f1fd4b5.pdf

の画像。

f:id:pongsuke:20180419111218p:plain

じゃあどうする?

今回のように、複数のマーカーが存在し、向きが一緒だとわかっている場合は、マーカーの向きを多数決的に決めてしまえば、ほぼOKとの指摘があります。

opencv チュートリアルチャレンジ カメラキャリブレーション

カメラキャリブレーション — OpenCV-Python Tutorials 1 documentation

Aruco で estimatePoseSingleMarkers を試したいと思ったところ、cameraMatrix と distCoeffs が必要だと判明。

OpenCV: ArUco Marker Detection

estimatePoseSingleMarkers
 Pose estimation for single markers.

cameraMatrix と distCoeffs は、カメラ行列(内部パラメータ) レンズ歪みパラメータ らしい。

Camera Calibration and 3D Reconstruction — OpenCV 3.0.0-dev documentation

Python: cv2.calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs[, rvecs[, tvecs[, flags[, criteria]]]]) → retval, cameraMatrix, distCoeffs, rvecs, tvecs

とにかく取得しましょう。

チェスボードを用意する

https://github.com/opencv/opencv/tree/master/samples/data

こちらから、chessboard.png を取得し、印刷。

曲がらないように、板に貼り付けました。

画像を用意する

キャリブレーションしたいカメラ(今回はWEBカメラ)で、チェスボード用紙を、いろんな角度から撮影します。

(背景にいろいろ写り込んでいたので、写真の掲載は控えます)

ディレクトリ calib を作成し、入れ込んでおきます。

パラメーターを算出する

こちらを参考(ほぼそのまま)にしました。

csharp/calibration.py at master · tanaka0079/csharp · GitHub

myCalib.py

# -*- coding: utf-8 -*-

import numpy as np
import cv2
from glob import glob

def main():
    square_size = 1.0      # 正方形のサイズ
    pattern_size = (7, 7)  # 交差ポイントの数
    pattern_points = np.zeros( (np.prod(pattern_size), 3), np.float32 ) #チェスボード(X,Y,Z)座標の指定 (Z=0)
    pattern_points[:,:2] = np.indices(pattern_size).T.reshape(-1, 2)
    pattern_points *= square_size
    objpoints = []
    imgpoints = []

    for fn in glob("calib/*.jpg"):
        # 画像の取得
        gray = cv2.imread(fn, 0)
        print("loading..." + fn)

        # チェスボードのコーナーを検出
        ret, corner = cv2.findChessboardCorners(gray, pattern_size)
        # コーナーがあれば
        if ret == True:
            term = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.1)
            cv2.cornerSubPix(gray, corner, (5,5), (-1,-1), term)
        else:
            print('Chessboard not found!')
            continue
        imgpoints.append(corner.reshape(-1, 2))   #appendメソッド:リストの最後に因数のオブジェクトを追加
        objpoints.append(pattern_points)
        #corner.reshape(-1, 2) : 検出したコーナーの画像内座標値(x, y)

    # 内部パラメータを計算
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
    # 計算結果を表示
    print("RMS = ", ret)
    print("mtx = \n", mtx)
    print("dist = ", dist.ravel())
    # 計算結果を保存
    np.savetxt("ret.csv", np.array([ret]), delimiter =',',fmt="%0.14f")
    np.savetxt("mtx.csv", mtx, delimiter =',',fmt="%0.14f")

if __name__ == '__main__':
    main()

私の結果は、

mtx

1420.68234847996541,0.00000000000000,949.20851179562965
0.00000000000000,1374.16684667116965,539.62205073603297
0.00000000000000,0.00000000000000,1.00000000000000

ret

1.129281781

でした。