Detectron2のModel Zooで物体検出、インスタンスセグメンテーション、姿勢推定
概要
Detectron2のModel Zooにある訓練済みを使って、物体検出やインスタンスセグメンテーション、姿勢推定等を行う。 多くのモデルに対して一括で処理できるコードを作った。便利。
Detectron2
FacebookのAI研究グループ(FAIR)が開発している物体検出アルゴリズムを実装のためのソフトウェア。
環境
- ubuntu 18.04
- GeForce GTX TITAN X
インストール
詳細は省略。ほぼ公式の通りやった。Windowsでやろうとしたら公式対応してないから大変そうな雰囲気。
メモ代わりに大雑把な手順を載せるが公式を読んでやったほうがいい。
- OS、cuda等:dockerコンテナ利用
- nvidia/cuda:10.1-cudnn7-devel
- Python環境
- Anaconda 2019.10でpython3.7環境構築
- pytorch
conda install pytorch torchvision cudatoolkit=10.1 -c pytorch
detectron2
git clone https://github.com/facebookresearch/detectron2.git cd detectron2 python setup.py build develop
Detectron2 Model Zoo
学習済みのモデルが置いてある。 以下のようなものがあるが、詳細はウェブサイト参照。
- タスク
- COCO Object Detection
- COCO Instance Segmentation
- COCO Person Keypoint Detection
- COCO Panoptic Segmentation
- LVIS Instance Segmentation
- Cityscapes
- Pascal VOC
- アルゴリズム
確認用コード
Model Zooにある学習済みモデルをロードするモジュールと、予測して可視化するためのモジュールがあったのでそれを使った。
以下のコードを実行するとModel Zooにあるモデルの42個について予測を行える。
from detectron2.data.detection_utils import read_image from detectron2.model_zoo.model_zoo import ModelZooUrls from detectron2.config import get_cfg from demo.predictor import VisualizationDemo img_path = 'input.jpg' img = read_image(img_path, format="BGR") for i, config_path in enumerate(ModelZooUrls.CONFIG_PATH_TO_URL_SUFFIX.keys()): # rpnとfast_rcnnは可視化対応していないので飛ばす if 'rpn' in config_path or 'fast_rcnn' in config_path: continue # config設定 cfg = get_cfg() cfg.merge_from_file(f'configs/{config_path}') cfg.MODEL.WEIGHTS = ModelZooUrls.get(config_path) score_thresh = 0.5 cfg.MODEL.RETINANET.SCORE_THRESH_TEST = score_thresh cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = score_thresh cfg.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH = score_thresh cfg.freeze() # 検出&可視化 demo = VisualizationDemo(cfg) predictions, visualized_output = demo.run_on_image(img) # ファイル出力 dataset_name, algorithm = config_path.split("/") algorithm = algorithm.split('.')[0] visualized_output.save(f'out/{i:02d}-{dataset_name}-{algorithm}.jpg')
結果
denseposeでU.S.A.
完全に出遅れたけど、DensePose使ってみたのでメモ。 Facebook等が発表した、2次元画像から人体の3Dサーフェスモデルを推定しちゃうすごい手法。
詳細とか使い方は下記のページあたりを参照。
環境
- ubuntu 16.04
- nvidia-docker導入済み(nvidia-dockerインストール、コンテナ等メモ - whoopsidaisies's diary)
導入
偉い人がdockerhubでイメージ公開してくれていたので活用。 動画ファイルをffmpegとかで画像にばらしておいた。
# ubuntu 16.04の端末で nvidia-docker run -it -d --name densepose garyfeng/densepose docker exec -it densepose bash # コンテナ内 # 実行時の引数でフォルダを指定するとフォルダ内の画像を一気に処理してくれる tools/infer_simple.py \ --cfg configs/DensePose_ResNet101_FPN_s1x-e2e.yaml \ --output-dir /root/data/infer_out/ \ --image-ext png \ --wts https://s3.amazonaws.com/densepose/DensePose_ResNet101_FPN_s1x-e2e.pkl \ /root/data/frame
以上、簡単にできてすごい。
OpenCV 3.4.1で背景差分
背景差分
画像の前景と背景を分離する手法。2013年にOpenCV 2.4.7での背景差分の記事を書いたが、2018年になったいまOpenCV 3.4.1で背景差分を行おうとしたら使えるアルゴリズムが増えていたのでまとめておく。
アルゴリズム
MOG, MOG2, GMG
OpenCV 2.4.7でも使えた。以下のページに日本語でわかりやすい説明がある。
背景差分 — OpenCV-Python Tutorials
KNN
K近傍方に基づく背景差分。前景の画素数が少ない場合は効率が良いらしい。
CNT
低スペックな計算機でもほかのアルゴリズムより高速に処理ができる。CNTという名前は「CouNT」の省略らしい。Raspberry Pi3でのベンチマークではMOG2が41秒に対し、CNTは18秒。
https://sagi-z.github.io/BackgroundSubtractorCNT/
LSBP
Local SVG Binary Pattern。注目画素とその周辺画素の大小関係を符号化するLBP(Local Binary Pattern)は、高速に計算可能な画像特徴量として知られている。局所的なノイズや隣接画素が類似しているような場合にもロバストにするようにSVD(特異値分解)を使った特徴量で背景差分を行っているそう。ちゃんと勉強してないのでよくわらからない。
GSOC
LSBP特徴を使ってる。ノイズ除去とか穴埋めといった後処理をしているらしい。GSOC(Google Summer of Code)2017中で開発されたとかで元論文等はないらしいのでソースコードを読もう。
コード
実行にはopencv_contribが必要。
C++
opencv_contrib
をダウンロードし、opencv
のcmake
時に例えば以下のようにOPENCV_EXTRA_MODULES_PATH
オプションを指定してビルドする。
$ cmake -DOPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules -DBUILD_opencv_legacy=OFF <opencv_source_directory>
ソースコード
#include <opencv2/opencv.hpp> #include <opencv2/core/utility.hpp> #include <opencv2/bgsegm.hpp> int main() { // 動画ファイルの読み込み cv::VideoCapture cap = cv::VideoCapture("video.mp4"); // 背景差分器の生成 cv::Ptr<cv::BackgroundSubtractor> bgfs = cv::bgsegm::createBackgroundSubtractorGSOC(); //cv::Ptr<cv::BackgroundSubtractor> bgfs = cv::bgsegm::createBackgroundSubtractorCNT(); //cv::Ptr<cv::BackgroundSubtractor> bgfs = cv::bgsegm::createBackgroundSubtractorGMG(); //cv::Ptr<cv::BackgroundSubtractor> bgfs = cv::bgsegm::createBackgroundSubtractorLSBP(); //cv::Ptr<cv::BackgroundSubtractor> bgfs = cv::bgsegm::createBackgroundSubtractorMOG(); //cv::Ptr<cv::BackgroundSubtractor> bgfs = cv::createBackgroundSubtractorMOG2(); //cv::Ptr<cv::BackgroundSubtractor> bgfs = cv::createBackgroundSubtractorKNN(); while (cv::waitKey(1) == -1) { cv::Mat frame, foreGroundMask, segm; cap >> frame; if (frame.empty()) break; bgfs->apply(frame, foreGroundMask); frame.convertTo(segm, 0, 0.5); cv::add(frame, cv::Scalar(100, 100, 0), segm, foreGroundMask); cv::imshow("output", segm); } return 0; }
Python
pip
で管理している場合、以下のようにopencv-python
をアンインストール後にopencv-contrib-python
をインストールすればopencv_contrib
が使える。
pip uninstall opencv-python pip install opencv-contrib-python
ソースコード
import cv2 cap = cv2.VideoCapture('video.mp4') bgs = cv2.bgsegm.createBackgroundSubtractorLSBP() while(cap.isOpened()): ret, frame = cap.read() mask = bgs.apply(frame) bg = bgs.getBackgroundImage() cv2.imshow('mask', mask) cv2.imshow('bg', bg) if cv2.waitKey(1) != -1: break cap.release() cv2.destroyAllWindows()
適用結果
比較のために各アルゴリズムを並べてみた。パラメータ調整もしてないから比較もくそもない気はするけど。GSOCが綺麗。LSBPはあんまり
あと、背景差分やるだけだったらたぶん以下のページで紹介されているBGSLibraryを使ったほうが良さそうではある。
tensorflowでMASK R-CNNによるSemantic Segmentation
セマンティックセグメンテーション
下の写真みたいに、入力画像を物体ごとに領域分割する技術。
https://wiki.tum.de/display/lfdv/Image+Semantic+Segmentation
Mask R-CNN
数あるセマンティックセグメンテーションを実現する手法の中で、2018年2月現在有力とされているものの一つ(たぶん)。
アルゴリズムの詳細についての説明は他に譲る。以下のページとかを参考にする。
- https://arxiv.org/pdf/1703.06870.pdf
- http://kaiminghe.com/iccv17tutorial/maskrcnn_iccv2017_tutorial_kaiminghe.pdf
- http://deeplearning.csail.mit.edu/instance_ross.pdf
TensorFlowのインストール
Google先生が公開している機械学習用オープンソースライブラリ。インストール方法は巷にあふれているので適当にググってインストールする。
Tensorflow Object Detection APIのインストール
以下のGitHubのレポジトリで様々なTensorfFlowのモデルが公開されている。公式サポートではないが物体検出とセマンティックセグメンテーションのモデルも数多く公開されているので、今回はそれを使う。
インストールは公式の手順通りだが以下に適当にメモ。 models/installation.md at master · tensorflow/models · GitHub
- 依存パッケージのインストール
apt install protobuf-compiler pip install pillow lxml jupyter matplotlib
- Protobufファイルをコンパイル
cd [PATH to models]/research protoc object_detection/protos/*.proto --python_out=.
- PYTHONPATHの追加
cd [PATH to models]/research export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
モデルファイルのダウンロード
Tensorflow detection model zooから物体検出やセマンティックセグメンテーションのトレーニング済みモデルをダウンロード出来る。とりあえず一番mAPの高いmask_rcnn_inception_resnet_v2_atrous_cocoというモデルを使う。
wget http://download.tensorflow.org/models/object_detection/mask_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz tar -zxvf mask_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz
セマンティックセグメンテーションのpythonコード
import tensorflow as tf import numpy as np from PIL import Image from object_detection.utils import ops as utils_ops import sys sys.path.append('[modelsのパス]/research/object_detection') from utils import label_map_util from utils import visualization_utils as vis_util # 学習済モデルの読み込み PATH_TO_CKPT = 'mask_rcnn_inception_resnet_v2_atrous_coco_2018_01_28/frozen_inference_graph.pb' detection_graph = tf.Graph() with detection_graph.as_default(): od_graph_def = tf.GraphDef() with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid: serialized_graph = fid.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name='') # ラベルの読み込み PATH_TO_LABELS = '[modelsへのパス]/research/object_detection/data/mscoco_label_map.pbtxt' NUM_CLASSES = 90 label_map = label_map_util.load_labelmap(PATH_TO_LABELS) categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True) category_index = label_map_util.create_category_index(categories) # 画像の読み込みとnumpy配列への変換 def load_image_into_numpy_array(image): (im_width, im_height) = image.size return np.array(image.getdata()).reshape( (im_height, im_width, 3)).astype(np.uint8) filename = '[画像ファイルのパス]' image = Image.open(filename) image_np = load_image_into_numpy_array(image) # セマンティックセグメンテーションの処理 with detection_graph.as_default(): with tf.Session() as sess: # 入出力用テンソルのハンドルを取得 image_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0') tensor_dict = {} tensor_dict['num_detections'] = tf.get_default_graph().get_tensor_by_name('num_detections:0') tensor_dict['detection_boxes'] = tf.get_default_graph().get_tensor_by_name('detection_boxes:0') tensor_dict['detection_scores'] = tf.get_default_graph().get_tensor_by_name('detection_scores:0') tensor_dict['detection_classes'] = tf.get_default_graph().get_tensor_by_name('detection_classes:0') tensor_dict['detection_masks'] = tf.get_default_graph().get_tensor_by_name('detection_masks:0') # バッチ内の最初の画像の結果を取り出す detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0]) detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0]) # 各検出ボックスのマスクを画像全体上のマスクへ変換 real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32) detection_boxes = tf.slice(detection_boxes, [0, 0], [real_num_detection, -1]) detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1]) detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks( detection_masks, detection_boxes, image_np.shape[0], image_np.shape[1]) detection_masks_reframed = tf.cast( tf.greater(detection_masks_reframed, 0.5), tf.uint8) # バッチ分の次元を追加 tensor_dict['detection_masks'] = tf.expand_dims( detection_masks_reframed, 0) # 実行 output_dict = sess.run(tensor_dict, feed_dict={image_tensor: np.expand_dims(image_np, 0)}) # バッチ分の次元の削除と型変換 output_dict['num_detections'] = int(output_dict['num_detections'][0]) output_dict['detection_classes'] = output_dict[ 'detection_classes'][0].astype(np.uint8) output_dict['detection_boxes'] = output_dict['detection_boxes'][0] output_dict['detection_scores'] = output_dict['detection_scores'][0] output_dict['detection_masks'] = output_dict['detection_masks'][0] # 画像にマスクとバウンディングボックスを書き込んで出力 vis_util.visualize_boxes_and_labels_on_image_array( image_np, output_dict['detection_boxes'], output_dict['detection_classes'], output_dict['detection_scores'], category_index, instance_masks=output_dict.get('detection_masks'), use_normalized_coordinates=True, line_thickness=8) Image.fromarray(image_np).save('out.png')
結果
拾ってきたフリー写真に適用した結果が以下。手だけ写ってても人間って判断してくれるのね。
nvidia-dockerインストール、コンテナ等メモ
基本的に公式サイトの説明とかの通りにやるだけなんだけど自分用にメモ。
ホストOS:ubuntu 16.04LTS
dockerのインストール
参考: Get Docker for Ubuntu - Docker
# https通信で公開鍵ダウンロードするため apt-get update apt-get install curl ca-certificates # dockerレポジトリの公開鍵の登録 curl -fsSL https://yum.dockerproject.org/gpg | apt-key add - # add-apt-repositoryコマンドのインストール apt-get install software-properties-common # dockerレポジトリ追加 add-apt-repository "deb https://apt.dockerproject.org/repo/ ubuntu-$(lsb_release -cs) main" # aptでhttps通信するため apt-get install apt-transport-https # dockerのインストール apt-get update apt-get install docker-engine
NVIDIAドライバのインストール
これが一番ハマって大変な場合が多い気がするが、ここでは省略。どうにかうまいことインストールする。
NVIDIA Dockerのインストール
# debパッケージのダウンロード(2017/1/22現在で最新のもの) curl -LO https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.0/nvidia-docker_1.0.0-1_amd64.deb # インストール dpkg -i nvidia-docker_1.0.0-1_amd64.deb rm nvidia-docker_1.0.0-1_amd64.deb # 動作確認(GPU情報が表示されると良い) nvidia-docker run --rm nvidia/cuda nvidia-smi
バージョンについては下記ページでチェックして適宜ダウンロードするものを確認する。
Releases · NVIDIA/nvidia-docker · GitHub
NVIDIA Dockerコンテナメモ
便利なものがあれば随時追記予定。
- https://hub.docker.com/u/nvidia/
nvidia公式コンテナ。CUDAやCuDNNのバージョン、OSの種類を選べる。 - https://hub.docker.com/u/kaixhin/
イギリスの研究者作成のコンテナ。色々あって便利。
C#で3次元グラフを表示する ILNumerics
ILNumericsは.NETで使用可能な数値計算等のライブラリである.グラフ表示機能も備わっているので,それを使ってC#のWindowsフォーム上に3次元グラフを表示する.
ILNumericsのインストール
現在最新バージョン(4.x系)のILNumericsは14日間のトライアル版以外は有償だが,Community Editionという無償版が提供されていた3.x系のバージョンがNuGetでインストール可能なのでそれを使う.
まず,Visual StudioでWindowsフォームアプリケーションを作成する.
そして,プロジェクトを右クリックして「NuGetパッケージの管理」を選択する.オンラインから「ILNumerics」を検索・インストールする.
グラフ表示用コントロールの配置
フォームデザイナを開きツールボックスの適当なところで右クリックして「アイテムの選択(I)...」を選択する.
「.NET Framework コンポーネント」タブの「参照(B)...」ボタンを押して,「(ソリューションのパス)\packages\ILNumerics.3.3.3.0\lib\ILNumerics.dll」を選択したらOK.
するとツールボックスIL○○というコントロールが追加される.その中の,ILPanelコントロールをフォーム上に配置する.
使ってみる
曲面
例として,以下の2変数関数を表示させてみる.
まず,コードの適当な場所に以下のusingを書いておく.
using ILNumerics.Drawing; using ILNumerics.Drawing.Plotting;
そして,フォームロードイベントとかの適当なイベントに以下のコードを書く.
// グラフを格納するオブジェクトの生成 var scene = new ILScene { // 3次元モードでプロット領域を生成 new ILPlotCube(twoDMode: false) { // 曲面の生成 new ILSurface( (x,y) => (float)( Math.Sin(Math.Sqrt(x*x + y*y))/Math.Sqrt(x*x + y*y))) } }; // 描画用コントロールに生成したグラフをセット ilPanel1.Scene = scene;
実行すると以下のように3次元グラフが表示される.マウスドラッグとかホイールとかで視点や拡大率を変えられる.
等高線
サンプル用の地形データも用意されていて,以下のように使うことが出来る.
var scene = new ILScene { new ILPlotCube(twoDMode: false){ new ILSurface(ILNumerics.ILMath.tosingle(ILNumerics.ILSpecialData.terrain)) } }; ilPanel1.Scene = scene;
ここまでは曲面表示のためILSurfaceクラスを使ってきたが,ILContourPlotクラスを使えば以下のように等高線表示も出来る.
var scene = new ILScene { new ILPlotCube{ new ILContourPlot(ILNumerics.ILMath.tosingle(ILNumerics.ILSpecialData.terrain)) } }; ilPanel1.Scene = scene;
散布図
ILPointsクラスで散布図を表示させることも出来る.以下はランダムに点を打って対数軸にして表示した例.
ILNumerics.ILArray<float> p = ILNumerics.ILMath.tosingle(ILNumerics.ILMath.rand(3, 10000)); var scene = new ILScene{ new ILPlotCube(twoDMode:false){ new ILPoints { Positions = p, Color = null, Colors = p, Size = 1, } } }; var scaleModes = scene.First<ILPlotCube>().ScaleModes; scaleModes.XAxisScale = AxisScale.Logarithmic; scaleModes.YAxisScale = AxisScale.Logarithmic; scaleModes.ZAxisScale = AxisScale.Logarithmic; ilPanel1.Scene = scene;
Python+OpenCVで特徴量記述・アルゴリズムまとめ
Python+OpenCVで画像の特徴点の特徴量を記述する.OpenCV2.4.9およびOpenCV3.0 alphaについて使用できるアルゴリズムをまとめる.
Python+OpenCVで特徴点抽出・使えるアルゴリズムまとめ OpenCV2.4.9と3.0 alpha - whoopsidaisies's diary
の続き.
特徴量記述器の使い方
特徴点抽出のときのFeatureDetectorと同じように,特徴量記述についてもDescriptorExtractorという共通インターフェースが用意されている.
使い方は以下の通り.
# 引数でどの特徴量記述子を使うか指定 extractor = cv2.DescriptorExtractor_create(extractor_name) # 引数として画像と特徴点を渡す # 戻り値は特徴点と特徴量 keypoints, descriptors = extractor.compute(img, keypoints)
DescriptorExtractor_createメソッドで指定出来る特徴量記述子を以下にまとめた.
OpenCVバージョン | 実数ベクトル | バイナリコード |
---|---|---|
2.4.9 | SIFT, SURF | BRIEF, BRISK, |
3.0 alpha | KAZE | AKAZE, BRISK, ORB |
(2.4.9でORBがなぜかランタイムエラーが出るので追々調査予定)
特徴量の中身
descriptorsの中身は2次元のarrayになっており,特徴点ごとに特徴量のベクトルが格納されている.
KAZEで記述した特徴量を表示してみるとこんな感じ(0番目の点).
>>descriptors[0] array([ -7.38840317e-04, -6.34267868e-04, 2.61819176e-03, 3.50267510e-03, -1.59906253e-04, -4.56994586e-03, 4.39605350e-03, 9.44076758e-03, -1.75689301e-03, 8.05667194e-04, 3.55383591e-03, 4.12554527e-03, -1.54193444e-03, 1.58668880e-03, 2.78847502e-03, 3.14337271e-03, -1.62520558e-02, 1.65848620e-02, 3.65739577e-02, 2.07063138e-01, 2.64649391e-02, -6.13974873e-03, 9.64385048e-02, 2.91850477e-01, 5.54781593e-02, 6.74537197e-03, 6.74520805e-02, 8.45865980e-02, -2.12559430e-03, 1.18403265e-03, 5.09992335e-03, 4.04977519e-03, -1.28035052e-02, 6.93068025e-04, 3.93203236e-02, 4.54802722e-01, -2.96720478e-04, 6.83288351e-02, 8.15773308e-02, 6.17026806e-01, -1.08468741e-01, 5.95125668e-02, 1.92962006e-01, 2.17324436e-01, -1.21422755e-02, 2.50244630e-03, 1.34693161e-02, 6.25377288e-03, 2.55310140e-03, -4.21544649e-02, 2.98030730e-02, 1.50349155e-01, 4.27789390e-02, -9.73087847e-02, 1.63991615e-01, 1.90487400e-01, 3.39032081e-03, -2.32313443e-02, 1.98113456e-01, 6.58137947e-02, -1.16498368e-02, 2.66303378e-03, 1.25482017e-02, 4.14769724e-03], dtype=float32)
グラフ表示だとこんな感じ.
import matplotlib.pyplot as plt plt.plot(descriptors[0])
KAZEは64次元の実数ベクトルで特徴量記述をするので以上のようになるが,記述子によってデータ量が違うため配列の長さや型が変わってくる.
- .NET (28)
- Accord.NET (3)
- AForge.NET (4)
- Android (1)
- AR (1)
- C# (35)
- C++ (17)
- d3.js (1)
- Deep Learning (3)
- Excel (2)
- javascript (1)
- LINQ (2)
- NuGet (20)
- OpenCV (17)
- OpenCV 3.0 (5)
- Programming (46)
- python (5)
- SVG (1)
- VBA (1)
- Windows フォーム アプリケーション (10)
- コンピュータビジョン (13)
- ディジタル信号処理 (2)
- ディジタル画像処理 (2)
- 可視化 (1)
- 機械学習 (4)
- 統計 (1)