MotoJapan's Tech-Memo

技術めも

【opencv 基礎知識 #5】透視投影変換行列に関連する関数まとめ (getPerspectiveTransform, calibrateCamera, warpPerspective, perspectiveTransform, undistort, remap)

忘れやすい透視投影変換行列周りの関数をメモ。(python)

具体的に前回の投稿でも使っている。
motojapan.hateblo.jp

1. 透視投影変換行列を求める方法

1.1. 台形補正の場合 [getPerspectiveTransform]

#pts_src 変換前座標 ※台形補正であれば4点
#pts_dst 変換先座標 ※台形補正であれば4点
M = cv2.getPerspectiveTransform(pts_src, pts_dst)

1.2. レンズ歪み補正の場合 [calibrateCamera]

# object_points  チェスボードのパターンの点群
# image_points   チェスボードのコーナーの点群
rms, camera_M, dist_coef, r, t = cv2.calibrateCamera(object_points,image_points,(img.shape[1],img.shape[0]),None,None)

2. 透視投影変換行列による変換方法

2.1. 画像を変換する [warpPerspective]

#img       変換前画像
#img_trans 変換後画像
img_trans = cv2.warpPerspective(img, M, (w, h))

2.2. 座標を変換する [perspectiveTransform]

#pts       変換前座標
#pts_trans 変換後座標
pts_trains = cv2.perspectiveTransform(pts, M)

2.3. 画像を変換する1[undistort](レンズ歪み補正)

img_undistort = cv2.undistort(img, camera_M, dist_coef)

2.4. 画像を変換する2[remap](レンズ歪み補正)

cam_M_ = cv2.getOptimalNewCameraMatrix(camera_M, dist_coef, (img.shape[1], img.shape[0]), 1)[0]
mapx, mapy = cv2.initUndistortRectifyMap(camera_M, dist_coef, np.eye(3), cam_M_, (img.shape[1], img.shape[0]), cv2.cv.CV_32FC1)
img_undistort = cv2.remap(img, mapx, mapy, cv2.cv.CV_INTER_AREA)

おまけ

逆行列について

透視投影変換行列Mの逆行列は、np.linalg.inv(M)
これでimage <-> iamge_trans間を行き来できる

レンズ歪み補正について

  • 歪み種類
    • 放射状歪み
    • 接線歪み
      • この2つはdist_coefで補正する

  • 補正に必要なパラメータ
    • 上記dist_coef
    • カメラの内部パラメータ
      • カメラ固有の焦点距離(fx, fy)や光学中心(cx, cy)
      • camera_Mで補正する
    • カメラの外部パラメータ
      • ある座標系における3次元点の座標を別の座標系での座標に変換するための回転と並進のパラメータ
  • 補正に必要な画像数
    • チェスボードが写った画像10枚程度

camera_M、dist_coefの定義

camera\_M = \begin{pmatrix} f_x&0&c_x \\ 0&f_y&c_y \\ 0&0&1 \end{pmatrix}
dist\_coef = \begin{pmatrix} k_1&k_2&p_1&p_2&p_3  \end{pmatrix}

詳細はここ
カメラ・キャリブレーション — OpenCV-Python Tutorials

具体的な camera_M、dist_coefの中身の例

 In [1]: camera_M
 Out[1]:
 array([[ 452.30622078,    0.        ,  332.44877655],
        [   0.        ,  453.36588503,  267.73560929],
        [   0.        ,    0.        ,    1.        ]])

 In [2]: dist_coef
 Out[2]: array([-0.19224447,  0.07401813,  0.0044831 ,  0.00214658, -0.02052423])

以上。

【opencv 基礎知識 #4】動画の手ぶれ補正をpython実装 (AKAZE, KNN, RANSAC)

行動認識が多かったので、半日くらいで動画の手ぶれ補正を作ってみた。

実装は数多あるので、そのうちコードをリファクタリングしたらGithubに載せようかと思う。
(すぐほしい人がいたら、コメントください)

すぐ忘れることをメモ。

結果

動画の通り、チューニングしなくても結構いい感じになっている。

上が補正前/下が補正後。
www.youtube.com

今年GWに山登りした時に撮った動画ですが、手ぶれを気にせず雫が落ちていくのが見れます。(最高です)
これもっと精度良くして無限ループすると、湯水のように時間が費やせそうです。

アルゴリズム

これを実装すれば基本的には動く。

  1. 動画を読み込み、フレームを読み出す
  2. 特徴量抽出する(AKAZE)
  3. キーポイントマッチングする(Brute-Force, KNN)
  4. 透視投影変換行列を求める(RANSAC)
  5. 画像を回転する

メモは以下。

1. 動画を読み込み、フレームを読み出す
cap = cv2.VideoCapture(mov_path)
ret, frame1 = cap.read()
2. 特徴量抽出する(AKAZE)

今回は権利が緩やかなAKAZEを用いる。

  • その他の選択肢
    • SHIFT, ORB, FAST, BRISK, KAZE, etc...
detector = cv2.AKAZE_create()
gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
keypoints1, descriptors1 = detector.detectAndCompute(gray1, None)
#frame2, keypoints2, descriptors2も同様に生成

frame1をquery画像、frame2をtrain画像とする。
query、trainの説明は、次に記載。

keypointsはkeypoint型である。
詳細は下記が分かりやすい。
Common Interfaces of Feature Detectors — OpenCV 2.4.13.3 documentation

3. キーポイントマッチングする(Brute-Force, KNN)

ここでハマったのは、queryとtrainの概念
ウェブの記事を見てもなかなか説明無かったが、一般的にこういうことらしい。

説明
query 探したいターゲット画像 犬のクロップ画像
train queryを探し出したいシーン画像 犬を散歩している風景画像

今回はqueryを基準フレーム(初期フレームなど)、trainは毎フレームとしてみた。
ここからBrute-Force(総当たり)で最近傍の特徴量を検索し、キーポイントマッチングする。

  • その他の選択肢
    • FLANN: Fast Library for Approximate Nearest Neighbors (高速近似近傍探索法)
#Brute-Force
bf = cv2.BFMatcher()
#matchesの要素数は、queryDescriptorsの要素数に一致
#上位k個の特徴点を返す
matches = bf.knnMatch(queryDescriptors = descriptors1, trainDescriptors = descriptors2, k=2)

matchesはDMatch型オブジェクトのリスト。
DMatch型の詳細は下記が分かりやすい。
特徴点のマッチング — OpenCV-Python Tutorials 1 documentation

(箸休め)

どんなマッチングをしているかは、下記コードでvisualizeして確認できる。

#good_matches = matchesから選び出したもの
frame3 = cv2.drawMatchesKnn(frame1, keypoints1, frame2, keypoints2, good_matches, None, flags=2)

たとえばこんなの(左画像がquery、右画像がtrain)
f:id:motojapan:20170816235844p:plain

4. 透視投影変換行列を求める(RANSAC)

マッチングが分かったので次のどういう一致になるか具体的に透視投影変換行列 Mを求める。
Mが分かればそれでtrain画像を回したり、query画像を回せば、互いに一致する画像が得られる。

具体的には、cv2.findHomographyで行列を得られるが、外れ値をいい感じに無視して変数推定をするRANSACを用いる。

#src_points/dst_pointsは、good_matchesからindexでアクセスしたquery/trainのkeypoint座標である
#(求めたいMの順にsrc_points/dst_pointsの入力順を決める)
M, mask = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0)
5. 画像を回転する

上記Mは、query画像座標系→train画像座標系とする行列である。
手ぶれ補正をするなら、Mの逆行列が必要であるので、linalg.inv(M)で得ると楽。

frame2_trans = cv2.warpPerspective(frame2, np.linalg.inv(M), size)
#frame1_trans = cv2.warpPerspective(frame1, M, size)

これで、frame2_transは、「train画像座標系がquery画像座標系にいい感じでキーポイントに一致する形で透視投影された画像」となる。


あとは各フレームごりごりやればうまくいく。

めでたしめでたし。

【行動認識 #6】tensorflowでCAE(Convolutional Auto-Encoder)を実装してみた

これの続き
【行動認識 #5】データ量別でCNNの性能差 - MotoJapan's Tech-Memo


今回はCAEの実装をしてみた。

CAEってなに?

CAEとは、Convolutional Auto-Encoderの略で、次元削減/圧縮テクニックの1つである。
AE(Auto-Encoder)のConvolutional版ということで、Convolution層を使い、情報量を少なくして特徴量を抽出する半教師学習。
特にCAEが画像などの多次元データを扱うにはそのままテンソル変換せずに使えるので筋が良い気がするのでこれで今回はやってみる。

構造はencoderとdecoderで構成され、

  • encoderの入力は画像等のデータ、出力は圧縮特徴量
  • decoderの入力は圧縮特徴量、出力は、画像等のデータ
  • decoderの出力とencoderへの入力が一致するように学習していく


活用方法は、次などがある。

  • 圧縮した特徴量を抽出できる
  • データが少ない場合の事は前学習として用い、encoder部分を再利用する
    • モデルの局所最適への落ち込みを回避できるという点。(最近だとデータが多いなら不要という話)

概要イメージは下記。

f:id:motojapan:20170815034208p:plain

詳細はいろいろなとこで記事になっているので他に譲ります。

実践

今回は前回に引き続き6行動のCAEを行う。
入力は、6ODF(加速度3軸、角速度3軸)の復号化。

今回のモデルは、Convolution + maxpooling 1 layer (=encoder) + unpooling + Deconvolution (=decoder) 1 layerで実装。
tensorflowだと、decoderは、tf.nn.conv2d_transposeが使える。
注意としては、ピクセル毎に回帰するので最後はsigmoidで0~1に収める必要がある。

結果

結果は例えばWALKを学習してみると、次の正解信号に対して、

f:id:motojapan:20170815032531p:plain

学習epochを回す毎にどんどん近づいていく。
(見てておもしろい)

f:id:motojapan:20170815032558g:plain

100[epoch]目以降はかなりオーバフィットさせ過ぎている感じもあるのでちょうどよいところ、
(WALKで言えば周期性が見えてくるところで)で打ち切る必要がありそうだ。

他の行動についてもすべてが並べてみるとなかなかの光景。

左上から右下までの順は次の通り。(横方向)
WALKING, WALKING_UPSTAIRS, WALKING_DOWNSTAIRS, SITTING, STANDING, LAYING
f:id:motojapan:20170815035856g:plain

学習曲線はこんな感じで学習できている。
f:id:motojapan:20170815104840p:plain

今後やりたいこと

  • 学習データが少ない状況で、CAEで事前学習
    • これで精度が上がるのであればいいなと。
  • 生成モデルとしてVAEの実装

以上。

【行動認識 #5】データ量別でCNNの性能差

これの続き
【行動認識 #4】Subjectを考慮してCNN層数別で性能差を確認 - MotoJapan's Tech-Memo


学習データ量別で確認してみる。

概要

もともとの総データ数は、7344件。
7344件 ⇒ ( 7344 x 128 [samples] / 50 [Hz] ) + 0.5 [overwrap rate] ⇒ 2.6 [hour]
これが100%⇒50%⇒25%と変化したときの精度差を確認。

結果

最終精度は下記

condition accuracy loss
full (100%) 0.9276 0.9687
half (50%) 0.9008 0.9108
quarter (25%) 0.8597 1.2324

tensorboardで学習進捗を書き出すとこんな感じ


f:id:motojapan:20170811170435p:plain

  • 学習データ量が多いほど精度向上がわかる
  • 大体80 [epoch]目でどれも精度的な収束は落ち着きがわかる
    • 比較的データが多いほうが初期精度も高い


f:id:motojapan:20170811170803p:plain

  • データ量が少ないとOverfitによる乖離傾向が強くなる


confusion matrixはどうだろう。

testデータの左からfull/half/quarterの順

f:id:motojapan:20170811174843p:plain

  • 特に姿勢系のSITTING/LAYINGの精度劣化が著しい(これは結構面白い)

SITTING/LAYINGは、0.87->0.77に落ち込み、誤検出の漏れ先はほとんどが姿勢系

まとめ

  • 学習データ量が多いほど、汎化性能は高くなっている
    • いろいろな行動パターンが学習できている
  • 学習データ量が多いほど、収束タイミングは早くなっている
    • epochを回すのもいいがデータが多ければepochをそこまで回さなくてもよいかも
  • 姿勢系は遷移系に比べてデータが必要
    • 繰り返し動作と単発動作だと、繰り返しの方が特徴量抽出しやすいのかも

以上。

【行動認識 #4】Subjectを考慮してCNN層数別で性能差を確認

これの続き
【行動認識 #3】機械学習/深層学習で人間行動認識 ~CNNしてみる~ - MotoJapan's Tech-Memo

前回はtrainデータでSubject未考慮で精度を検証した。(trainデータ内)
その精度は、93.43%

今回はtrain/testのそれぞれを使って評価
加えて、Convolution+MaxPoolingのペア層の数を変えて評価

前回との差

学習データ Subject CNN層数
前回 trainデータを7:3で分割して評価 (5136件:2194件) 14名 分割時に未考慮 (Conv + Pooling) x 2
今回 trainデータ(7344件) / testデータ(2944件) 20名 予めSubject単位で分割(14名/6名) (Conv + Pooling) x 1~3

結果

200[epoch]回した結果を載せる

model accuracy loss epoch throughput
(Conv + Pooling) x 1 90.86% 0.8690 165 1070 [fps]
(Conv + Pooling) x 2 92.76% 0.9687 184 314 [fps]
(Conv + Pooling) x 3 92.23% 0.9871 95 130 [fps]

f:id:motojapan:20170811004015p:plain

精度としては、0.67%程悪化したが、想像より精度劣化は無い印象

まとめ

  • 6 Subjects-vs-restで評価したがCNN2層で92.76%は出る
  • CNN3層だと収束が早いOverfitもしやすい
  • CNN層を減らすほど、スループット非線形(指数的?)に改善していく
  • 動きの再現性が低い可能性がある
    • 前回との差分を見ると極端な精度劣化かがないことから同じSubject内の同じ行動であっても毎回同じような動きではない可能性がある

【行動認識 #3】機械学習/深層学習で人間行動認識 ~CNNしてみる~

これの続き
【行動認識 #2】機械学習/深層学習で人間行動認識 ~データ確認~ - MotoJapan's Tech-Memo

やりたいことの1つでもあるtensorflowを使った。

結果

1. 精度

下記条件で検証して、精度としてはCNN(Convolutional Neural Network)で93.43%程度でた。

testセットの結果(93.25%時点)
f:id:motojapan:20170807231813p:plain

条件

  • データは、trainデータをtrainセット/testセットに7:3で分割 (Subject未考慮)
  • 精度は、testセットに対して分類した結果

2. tensorflowを使って良かった点

  • 実行速度が速い(いちいちpython層まで帰ってこない)
  • tensorboardがめちゃ使える
  • tensorflow mobileにも使える(まだやってないけど)
  • コミュニティが大きく、文献が多い

3. 逆に辛かった点

  • chainer上がりなので計算グラフを先に作るという概念なくて最初辛かった(慣れればOK)
  • 動的に計算グラフを更新できない(とりあえず簡単にはできなそう)
  • 学習中のネットワークのデバックができない(やり方がわからない)

Data

入力データは、前回の通り、下記の計6軸。

# 重力未分離加速度情報
total_acc_x_train、total_acc_y_train、total_acc_z_train
# 角速度情報
body_gyro_x_train、body_gyro_y_train、body_gyro_z_train

ラベルデータは、次の6行動。
WALKING(0), WALKING_UPSTAIRS(1), WALKING_DOWNSTAIRS(2), SITTING(3), STANDING(4), LAYING(5)

1データは、特に前処理せずに1軸あたり128 [samples]ずつ突っ込む。

Network

まず下記の感じで、[Convolution層 + pooling層] x 2で作って回す。
tensorboardでvisualize!

f:id:motojapan:20170808003624p:plain

忘れそうだらtensorboardの実行方法を記載。

>tensorboard --logdir=[target_logdir]

ちなみに精度やロスを出すときに、trainとtestのディレクトリを分けておくと、
tensorboardが色分けしてくれる。

Accuracy/loss

精度とロスはこんな感じになりました。
一応、学習はちゃんと進んでいる。

200 epoch 回したが、189 epoch目でtestがbestに。

epoch=189 train test
accuracy 0.9998 0.9343
loss 0.0004 0.5572

学習曲線は以下。(ありがとうtensorboard)
(※ただ、結局細かいところは自分でダンプした方がいい。)

Accuracy graph
f:id:motojapan:20170808003927p:plain
Loss graph
f:id:motojapan:20170808004420p:plain

analysis

実行速度(throughput)

環境は、Ubuntu 16.04 LTS / RAM 4G / Core-i7 CPU
このnetworkで 314.039 frame/secでした。

混合行列 (confusion matrix)

200 epoch目の結果で考察
bestの189 epochと比較してもそこまで大きく違わないので。

f:id:motojapan:20170808002615p:plain

trainから分かること
  • ちゃんとOverfitするだけの表現力がある
testから分かること
  • WALK系[WALKING(0), WALKING_UPSTAIRS(1), WALKING_DOWNSTAIRS(2)]と、WALK系以外[SITTING(3), STANDING(4), LAYING(5)]は分離できている。(予想通り)
    • ちなみに、WALK系は遷移系統、SITTING/STANDING/LAYINGは姿勢系統の動き。
  • WALKING_DOWNSTAIRSの推定はまず間違えない。
  • WALKINGは、UPSTAIRS, DOWNSTAIRSの一部であるから、間違える可能性がある。
  • 姿勢系統は、ぼちぼちお互いに勘違いするケースがあるが、0.87は精度が出せている。
    • 姿勢は、動きが少なく、特徴的な変化が少ないかもしれない。

同一Subjectが含まれる学習でも上記のような結果。

他の考察もそのうち。

【行動認識 #2】機械学習/深層学習で人間行動認識 ~データ確認~

この続き
【行動認識 #1】機械学習/深層学習で人間行動認識 ~事始め~ - MotoJapan's Tech-Memo

データセットは下記
UCI Machine Learning Repository: Human Activity Recognition Using Smartphones Data Set

ライセンスは次の通り。

This dataset is distributed AS-IS and no responsibility implied or explicit can be addressed to the authors or their institutions for its use or misuse. Any commercial use is prohibited.


「Human Activity Recognition Using Smartphones Data Set」『UCI Machine Learning Repository』データセット内 README.txt より。
URL: https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones

データセットについて構造確認

train側だけだとこんなイメージ(test側もあり)

  • body_acc_x_train # 重力分離済み加速度情報 (★1)
  • body_acc_y_train
  • body_acc_z_train
  • body_gyro_x_train # 角速度情報 (★1)
  • body_gyro_y_train
  • body_gyro_z_train
  • total_acc_x_train # 重力未分離加速度情報 (★1)
  • total_acc_y_train
  • total_acc_z_train
  • X_train # 特徴量 (★1)
  • y_train # 行動ラベル (★2)

分類問題を解くのであれば、★2が正解ラベルであり、★1を駆使して分類する。
今回はより生に近い値でやりたいので、入力は重力未分離加速度情報(ax,ay,az)と角速度情報(gx, gy, gz)のみを利用。

ではデータの中身を眺める。

データセットの中身確認

trainデータだけでも、7352件/21名分あるのでまずこれを味見。

同一被験者/行動違いのデータを確認(時間領域)

被験者1(Subject1)内でのランダムに取り出した各Actionのノルム違い

total_norm_acc_train
f:id:motojapan:20170805005540p:plain
body_norm_gyro_train
f:id:motojapan:20170805005534p:plain

  • 明らかにWALK系とそれ以外で分離可能
  • この分離は、最大値、最小値、標準偏差を考察したが、標準偏差は加速度/角速度共に10^{-1}10^{-2}のorder差が見える

異なる被験者/同一行動のデータを確認

シンプルそうなSTANDINGで確認。

total_norm_acc_train
f:id:motojapan:20170805012236p:plain
body_norm_gyro_train
f:id:motojapan:20170805012300p:plain

  • total_acc_trainは、Subject3,5はオフセットしているように見える
    • これは最初から平均値等でキャンセルした方が良さそう。
  • body_gyro_trainは、かなり小さい値なので、その辺りで安定している
    • Subject11が持つ0.14[rad/s]付近のデータでも8.02[dps]と考えるとかなり小さい。

同一被験者/異なる動きのデータを確認(周波数領域)

周波数領域についても見たい。


・total_norm_acc_train

SITTING/STANDING/WALKING
f:id:motojapan:20170805015949p:plain

[SITTING/STANDING] [WALKING]という分離ができそう

・body_norm_gyro_train

SITTING/STANDING/WALKING
f:id:motojapan:20170805020155p:plain

[SITTING] [[STANDING] [WALKING]]という分離ができそう
・SITTINGはかなり限られた低周波が反応
・WALKが高周波の部分が良く反応している

他の分析

同一被験者/異なる試行のデータを確認

SITTING (周期性なし)
f:id:motojapan:20170805014848p:plain

WALKING (周期性あり)
f:id:motojapan:20170805014921p:plain

  • 傾向が見えるものもあるが、たまに特異な動きをするものもある(個人内ばらつき
  • 目視だが、行動によって周期性の無い動き周期性のある動き、その再現性が確認できる

以上。

簡単なデータ分析だが、組み合わせ次第で分離可能性は確認できる。

次回は学習。