EISATS
EISATS | Reinhard Klette: CCVというコンピュータビジョン向けのデータセットがありまして
EISATS
The .enpeda.. Image Sequence Analysis Test Site (EISATS) offers sets of image sequences for the purpose of comparative performance evaluation of stereo vision, optic flow, motion analysis, or further techniques in computer vision.
そのうちSET2は車載シーンが合成画像で作成されており
オプティカルフロー、視差、エゴモーション等の正解データが取得できます
ときどきOpenCV上で利用するのですが、毎度データの読み込み方法を忘れるので
自分へのメモとして各データを読み込んで表示するサンプルプログラムを作成しました
github.com
使用する際は前述のSET2から
SEQUENCE2のデータを適当なディレクトリにダウンロード・解凍して
/home/hoge/EISATS/SET2/S2/ ├── colour-left-S2 ├── disparityGT-S2 ├── egoMotion ├── flowX-S2 ├── flowY-S2
そのディレクトリをプログラムに渡します
./eisats-synthesized-sequences /home/hoge/EISATS/SET2/S2/
ソースファイルは1個だけなので全部載せておきます
#include <iostream> #include <iomanip> #include <string> #include <sstream> #include <fstream> #include <opencv2/opencv.hpp> namespace { std::string numberString(int n) { std::stringstream ss; ss << std::setw(3) << std::setfill('0') << n; return ss.str(); } cv::Mat readRaw(const std::string& filename) { std::ifstream ifs(filename, std::ios::binary); if (ifs.fail()) return cv::Mat(); std::string line; while (getline(ifs, line) && line[0] == '#') {} int width, height, depth; sscanf(line.c_str(), "%d %d %d", &width, &height, &depth); CV_Assert(depth == 32); cv::Mat img(height, width, CV_32F); ifs.read((char *)img.data, sizeof(float) * width * height); return img; } void readEgoMotion(const std::string& filename, cv::Matx33d& R, cv::Matx31d& t) { std::ifstream ifs(filename); if (ifs.fail()) return; std::string line; while (getline(ifs, line) && line[0] == '#') {} sscanf(line.c_str(), "%lf %lf %lf %lf", &R(0, 0), &R(0, 1), &R(0, 2), &t(0)); getline(ifs, line); sscanf(line.c_str(), "%lf %lf %lf %lf", &R(1, 0), &R(1, 1), &R(1, 2), &t(1)); getline(ifs, line); sscanf(line.c_str(), "%lf %lf %lf %lf", &R(2, 0), &R(2, 1), &R(2, 2), &t(2)); } void drawOpticalFlow(cv::Mat& img, const cv::Mat1f& flowx, const cv::Mat1f& flowy) { float maxnorm = 0; for (int i = 0; i < flowx.rows; ++i) { for (int j = 0; j < flowx.cols; ++j) { float x = flowx(i, j); float y = flowy(i, j); maxnorm = std::max(maxnorm, sqrtf(x * x + y * y)); } } img.create(flowx.size(), CV_8UC3); for (int i = 0; i < flowx.rows; ++i) { for (int j = 0; j < flowx.cols; ++j) { float x = flowx(i, j); float y = flowy(i, j); // Convert flow angle to hue float angle = atan2f(y, x); if (angle < 0.f) angle += (float)(2 * CV_PI); angle /= (float)(2 * CV_PI); uchar hue = static_cast<uchar>(180 * angle); // Convert flow norm to saturation float norm = sqrtf(x * x + y * y) / maxnorm; uchar sat = static_cast<uchar>(255 * norm); img.at<cv::Vec3b>(i, j) = cv::Vec3b(hue, sat, 255); } } cv::cvtColor(img, img, cv::COLOR_HSV2BGR); } } /* namespace */ int main(int argc, char *argv[]) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " [data path]" << std::endl; return -1; } std::string dataPath(argv[1]); for (int frameNo = 2; ; ++frameNo) { // Read left image cv::Mat leftImg = cv::imread(dataPath + "/colour-left-S2/img_c0_" + numberString(frameNo) + ".ppm"); if (leftImg.empty()) break; // Read optical flow map cv::Mat flowx = readRaw(dataPath + "/flowX-S2/flowU_from_" + numberString(frameNo - 1) + "_to_" + numberString(frameNo) + ".raw"); cv::Mat flowy = readRaw(dataPath + "/flowY-S2/flowV_from_" + numberString(frameNo - 1) + "_to_" + numberString(frameNo) + ".raw"); if (flowx.empty() || flowy.empty()) break; // Read disparity map cv::Mat disp = readRaw(dataPath + "/disparityGT-S2/stereo_" + numberString(frameNo) + ".raw"); if (disp.empty()) break; // Read ego-motion cv::Matx33d R; cv::Matx31d t, r; readEgoMotion(dataPath + "/egoMotion/from_" + numberString(frameNo - 1) + "_to_" + numberString(frameNo) + ".txt", R, t); cv::Rodrigues(R, r); // Draw and display image cv::Mat flowImg, dispImg; drawOpticalFlow(flowImg, flowx, flowy); cv::normalize(disp, dispImg, 0.0, 1.0, cv::NORM_MINMAX); cv::putText(leftImg, "Rotation: " + std::to_string(r(0)) + " " + std::to_string(r(1)) + " " + std::to_string(r(2)), cv::Point(10, 20), 0, 0.5, cv::Scalar(0, 255, 255)); cv::putText(leftImg, "Translation: " + std::to_string(t(0)) + " " + std::to_string(t(1)) + " " + std::to_string(t(2)), cv::Point(10, 40), 0, 0.5, cv::Scalar(0, 255, 255)); cv::imshow("Left image", 16 * leftImg); cv::imshow("Flow image", flowImg); cv::imshow("Disp image", dispImg); char c = cv::waitKey(100); if (c == 27) // ESC break; } return 0; }
6D-Visionについて調べてみた
車載ステレオカメラによる画像認識技術「6D-Vision」について調べてみました
6D-Visionとは
6D-Visionとは自動車メーカーのダイムラー(Daimler AG)が開発している
自動運転のための画像認識技術です
メルセデス・ベンツの資料に
2012 年からは、メルセデス・ ベンツによる高度
支援システムの総称として、「インテリジェントド
ライブ」という名称が導入されました。その基
盤となる革新的な 6D ビジョンは、車載ステレオ
カメラで撮影した映像を処理・解析する技術で
す。車両センサーでクルマの周囲の状況を瞬時
に検知。レーダーセンサーと連動したステレオ
カメラによって、車両や歩行者などの動く対象物
を認識し、その位置や運動方向(速度も含む)
を測定することで、その後の行動を予測します。
危険な状況が発生すると、車載支援システムが
適切に、しかも瞬時に対処します。
とあるように、6D-Visionの特徴は
- ステレオカメラを使用
- 車両や歩行者などの動く対象物を認識
- その位置や運動方向(速度も含む)を測定することで行動を予測
とのことです
6D-Visionの詳細
6D-Visionはどのように実現されているのか
6D-Visionには公式ページがあり、研究の紹介や論文へのリンクが載ってます
www.6d-vision.com
うーん、とても勉強になる
Stixel World
公式ページの中で興味を引いたのが「Stixel」と呼ばれるシーンの表現手法です
こちらのデモ動画をご覧下さい
物体を覆う棒状のSuperpixel(ピクセルをグループ化したもの)がStixelと呼ばれます
路面から物体を覆う高さまで伸びています
また距離の情報も備えており、動画ではカメラからの距離によって色付けされています
物体から伸びる白い矢印は、物体の移動方向と0.5秒後に到達する位置を示しています
これにより自動車が危険な状況を察知できるというわけですね
6D-Visionの6Dとは、物体の3次元位置(X,Y,Z)と速度(Vx,Vy,Vz)を合わせた
6次元のパラメータに由来し、ここでは各Stixelについて運動を推定しています
アルゴリズム
まだ理解できてないのでちゃんと説明できませんが
わかったことを処理の流れにそって書いておきます
読んだ論文はこれです
Dence Stereo
まずステレオ画像から密な(全ピクセルの)視差を求めます
論文ではSGM(Semi-Global Matching)と呼ばれるアルゴリズムを使用しており
FPGA上に実装して高速化しているようです
Stixelの構築
まず視差画像を路面と物体の領域に分け(動的計画法を使うらしい)
次に物体の高さを推定、最後に物体の領域をStixelの幅で区切っているようです
おわりに
今車載関連の仕事をしてるのですが
最近6D-Visionについて耳にすることがあり
気になったので調べてみました
アルゴリズムをちゃんと理解したいです
Mean-shift Trackingを実装してみた
勉強のためにMean-shift Trackingを実装してみました
ソースコード
以下に公開してあります
github.com
参考
実装の際に参考にしたのはOpenCVのCamShiftのサンプルコードと
こちらのintelの論文です
http://opencv.jp/opencv-1.0.0_org/docs/papers/camshift.pdf
URLから分かるようにCamShiftのオリジナルっぽいですね
アルゴリズム
アルゴリズムを簡単に紹介します
追跡ウィンドウの更新
枚フレーム以下の処理を行います
- 追跡対象の2次元確率分布を計算
- 追跡ウィンドウ内の確率分布の重心を求める
- ウィンドウの中心が重心の位置に来るようウィンドウを更新
- 収束するまで2、3を繰り返す
2次元確率分布は要するに「追跡対象らしさマップ」みたいなものです
最初に計算したヒストグラムを2次元に逆投影(back projection)することで求められます
2、3がMean-shiftの本処理です
確率分布のピークに向かってウインドウを移動させます
さらにウィンドウのサイズや傾きを計算する処理が加わると
CamShiftと呼ばれる手法になります
あとはこことかも参考になりました
OpenCV: Meanshift and Camshift
ソースの解説
MeanShiftTrackerクラス
start()で追跡の開始、update()で追跡ウィンドウの更新を行います
勉強用ということでメンバは公開にしました
class MeanShiftTracker { public: MeanShiftTracker(); void start(const cv::Mat& img, const cv::Rect& window); int update(const cv::Mat& img, cv::Rect& window); int vmin_, vmax_, smin_; cv::Mat hist_, backProject_; };
追跡の開始
ウィンドウ内の色ヒストグラムを計算します
純粋に色だけに着目したい(輝度や彩度の影響を無視したい)ので
RGBをHSVに変換しそのHue成分だけを使用します
また輝度や彩度が低い画素では色の表現力も弱くなるので
そのような画素もマスクによって計算から除外します
void MeanShiftTracker::start(const cv::Mat& img, const cv::Rect& window) { CV_Assert(img.type() == CV_8UC3); // ROIの設定 cv::Mat roi = img(window); // HSVに変換 cv::Mat hsv; cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV); // マスクの作成 cv::Mat mask; cv::inRange(hsv, cv::Scalar(0, smin_, vmin_), cv::Scalar(180, 256, vmax_), mask); // Hue成分の抽出 cv::Mat hue(hsv.size(), hsv.depth()); int fromTo[2] = { 0, 0 }; cv::mixChannels({ hsv }, { hue }, fromTo, 1); // ヒストグラムの計算 int histSize = 64; float range[] = { 0, 180 }; calcHist(hue, mask, hist_, histSize, range); }
ヒストグラムの計算では値がrangeの範囲内にある画素を
0≦x<1となるよう正規化し、histSizeをかけることでビンを算出しています
void calcHist(const cv::Mat& img, const cv::Mat& mask, cv::Mat& hist, int histSize, const float *range) { CV_Assert(img.type() == CV_8U); CV_Assert(mask.type() == CV_8U); CV_Assert(mask.size() == img.size()); hist.create(cv::Size(1, histSize), CV_32F); hist = cv::Scalar::all(0); float minv = range[0]; float maxv = range[1]; for (int i = 0; i < img.rows; ++i) { for (int j = 0; j < img.cols; ++j) { float v = img.at<uchar>(i, j); if (v < minv || v >= maxv) continue; float nv = (v - minv) / (maxv - minv); int bin = static_cast<int>(histSize * nv); CV_Assert(bin < histSize); if (mask.at<uchar>(i, j)) hist.at<float>(bin) += 1.0f; } } }
追跡ウィンドウの更新
全体の流れはこんな感じ
前半部分は開始処理とほとんど同じですね
int MeanShiftTracker::update(const cv::Mat& img, cv::Rect& window) { // HSVに変換 cv::Mat hsv; cv::cvtColor(img, hsv, cv::COLOR_BGR2HSV); // マスクの作成 cv::Mat mask; cv::inRange(hsv, cv::Scalar(0, smin_, vmin_), cv::Scalar(180, 256, vmax_), mask); // Hue成分の抽出 cv::Mat hue(hsv.size(), hsv.depth()); int fromTo[2] = { 0, 0 }; cv::mixChannels({ hsv }, { hue }, fromTo, 1); // ヒストグラムの逆投影 float range[] = { 0, 180 }; calcBackProject(hue, hist_, backProject_, range); // 逆投影画像のマスキング cv::bitwise_and(backProject_, mask, backProject_); // ウィンドウの更新 return meanShift(backProject_, window); }
追跡対象の2次元確率分布を求めるため、ヒストグラムの逆投影を行います
各画素に対しヒストグラムを計算した時と同じ方法でビンを計算し
ビンに対応するヒストグラムの値(度数)を逆投影画像に格納します
追跡対象に近い色を持つならば、大きな値になるはずです
見やすいように値を0~255に正規化してあります
void calcBackProject(const cv::Mat& img, const cv::Mat& hist, cv::Mat& backProject, const float *range) { CV_Assert(img.type() == CV_8U); CV_Assert(hist.type() == CV_32F); backProject.create(img.size(), CV_8U); backProject = cv::Scalar::all(0); cv::Mat _hist = hist; cv::normalize(_hist, _hist, 0, 255, cv::NORM_MINMAX); int histSize = hist.rows; float minv = range[0]; float maxv = range[1]; for (int i = 0; i < img.rows; ++i) { for (int j = 0; j < img.cols; ++j) { float v = img.at<uchar>(i, j); if (v < minv || v >= maxv) continue; float nv = (v - minv) / (maxv - minv); int bin = static_cast<int>(histSize * nv); CV_Assert(bin < histSize); backProject.at<uchar>(i, j) = cv::saturate_cast<uchar>(_hist.at<float>(bin)); } } }
追跡ウィンドウ内の確率分布の重心を求めます
分布の0次モーメントを
1次モーメントを
とすると、重心は以下のように求まります
ウィンドウの中心が重心の位置に来るようウィンドウ位置を更新します
反復が既定回数に達するか、ウィンドウの移動量が小さくなるまで繰り返します
int meanShift(const cv::Mat& probImage, cv::Rect& window) { CV_Assert(probImage.type() == CV_8U); int maxiter = 20; for (int iter = 0; iter < maxiter; ++iter) { // 重心の計算 unsigned int mz = 0, mx = 0, my = 0; for (int i = 0; i < window.height; ++i) { for (int j = 0; j < window.width; ++j) { int y = i + window.y; int x = j + window.x; mz += probImage.at<uchar>(y, x); mx += x * probImage.at<uchar>(y, x); my += y * probImage.at<uchar>(y, x); } } if (mz == 0) return 0; int cx = mx / mz; int cy = my / mz; // ウィンドウの位置を更新 int winx = cx - window.width/2; int winy = cy - window.height/2; winx = std::min(probImage.cols - 1 - window.width, std::max(0, winx)); winy = std::min(probImage.rows - 1 - window.height, std::max(0, winy)); // 移動量が小さい場合は終了 if (abs(winx - window.x) < 1 && abs(winy - window.y) < 1) break; window.x = winx; window.y = winy; } return 1; }
サンプル
入力データはこちらを利用させていただきました
David Ross - Incremental Visual Tracking
OpenCVのCamShiftのデモと同様に
マウスのドラッグで追跡対象を指定できるようになってます
キーボードの'b'を押すと逆投影画像に表示が切り替わります
MeanShiftTracker Demo
最初は追跡に成功していますが
途中でウィンドウが背景の方に外れてしまいました
おわりに
以前の記事ではOpenCVのサンプルの紹介で終わってしまったので
今回は理解のために自分で中身を実装してみました
WindowsにOpenCV 3 + Visual Studio 2015の環境を構築する
WindowsにOpenCVとVisual Studioをインストールして画像処理を始めましょう
OpenCVとVisual Studioのバージョンは2016年7月時点で最新のものを選びました
- やること
- OpenCV 3.1のインストール・設定
- Visual Studio 2015のインストール・設定
- CMake 3.5.2のインストール・設定
- CMakeLists.txtとソースの準備
- プロジェクトの作成・ビルド・実行
- おわりに
やること
- WindowsにOpenCV 3 + Visual Studio 2015の環境を構築
- OpenCVを使った簡単なプログラムのビルド・実行
OpenCV 3.1のインストール・設定
まずOpenCVをインストールします
- DOWNLOADS | OpenCVからOpenCV for Windows VERSION3.1をダウンロード
- opencv-3.1.0.exe実行
- ファイルを適当なディレクトリに展開
ここではDドライブ直下に展開したとして説明を続けます
D:\opencv
次に環境変数を設定します
OPENCV_DIRでCMakeにOpenCVのインストール場所を教えていますが
CMakeを使わないのであれば設定は不要です
Visual Studio 2015のインストール・設定
結構時間がかかりました(ファイルサイズ的に)
- Downloads | Visual Studio Official Siteから無償のVisual Studio Communityをダウンロード
- vs_community_ENU.exeを実行してインストール
インストール完了と思いきや、C++の開発ツールがまだ入っていないので入れます
- Visual Studioを立ち上げる
- New Projectからプロジェクト作成画面を表示
- Visual C++ -> Install|Visual C++| Install Visual C++ 2015 Tools for Windows Desktopを選択
- OKを押してインストール
- 参考:C++/Visual Studio 2015 プログラミング
CMake 3.5.2のインストール・設定
Visual Studioのプロジェクト作成ごとにOpenCVのincludeやライブラリの設定をするのは面倒です
CMakeを使うと自動で設定してくれますし、再利用がきくのでおススメです
CMakeLists.txtとソースの準備
いよいよプログラムの作成に入ります
プロジェクト用ディレクトリを作成し、CMakeLists.txtとソースコードを置きます
HelloCV |- CMakeLists.txt |- main.cpp
CMakeLists.txtの中身はこんな感じです
これでOpenCVが使えるプロジェクトを作成することができます
cmake_minimum_required(VERSION 2.8) find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) file(GLOB srcs ./*.cpp ./*.h*) add_executable(HelloCV ${srcs}) target_link_libraries(HelloCV ${OpenCV_LIBS})
main.cppの中身はこんな感じです
画像に「Hello OpenCV World.」というテキストを描画して表示するプログラムです
#include <opencv2\opencv.hpp> int main() { cv::Mat img = cv::Mat::zeros(cv::Size(512, 256), CV_8UC3); cv::putText(img, "Hello OpenCV World.", cv::Point(100, 100), 0, 1, cv::Scalar(255, 255, 255)); cv::imshow("HelloCV", img); cv::waitKey(0); return 0; }
プロジェクトの作成・ビルド・実行
コマンドラインでプロジェクトを作成しますので、まずはコンソールを立ち上げましょう
コマンドプロンプトでも良いですが、さすがにつらいという方にはコンソールエミュレータを入れることをおすすめします(私は現在cmder | Console Emulatorを使用しています)
- プロジェクトのディレクトリに移動
$ls CMakeLists.txt main.cpp
- プロジェクトの作成
$mkdir build $cd build $cmake .. -G "Visual Studio 14 Win64"
成功するとbuild以下にProject.slnというソリューションファイルができてるはずです
これをダブルクリックするとVisual Studio 2015が起動します
- プロジェクトのビルド
cmake --build . --config Release
コマンドラインでビルドできて便利!
この方法はこちらのブログを参考にさせていただきました
CMakeプロジェクトをVisual Studioでビルドするには - kumar8600の日記
- 実行
./Release/HelloCV.exe
無事動きました
おわりに
WindowsにOpenCV 3 + Visual Studio 2015の環境を構築する手順を紹介しました
手軽な方法を選んだつもりですが、何だかんだやること多いですね
ちなみにVisual Studio 2015でソースを表示してみましたが、文字が柔らかくなった印象です
Visual Studio 2013よりもいいかも…!
Google Code Jamに参加してみた
Googleが開催するプログラミングコンテスト「Google Code Jam」の予選ラウンドに参加しました
全4問のうち、正解率の高かった2問は何とか解くことができたのですが
3問目で解答が思いつかず降参
プログラミングコンテスト、興味はあって取り組むようにしてるのですが
問題を読んでるだけで頭が痛くなってきます…
向いてないのかな…
字下げスタイルとわたし
「字下げスタイル?コーディング作法って何?」
って感じで学生の頃はこんなコードを書いてました
void func(float *a,float *b,int n){ for(int i=0; i<n; i++){ b[i] = a[i] ; } }
いま思うと、いろいろと気になるところがある
- 変数名適当
- 基本的にスペースが無い
- なのに何故かセミコロンの前だけスペース!?
「会社に入って」
他人とコードを共有したり、参考書を読むようになってからは、ちょっとましになった思います
- 関数名とか、変数名を適当にしない
- コメント書く
- 既存のスタイルに従う(K&Rスタイルとか)
// n要素コピーする void copy(const float *src, float *dst, int n) { for (int i = 0; i < n; i++) { dst[i] = src[i]; } }
最近は
なんとなくBSDスタイルいいんじゃね?と思い始めてます
全て中括弧を次の行に置く一貫性と、if-else句の見易さが良いです
コードが長くなりがちだけど
void copy(const float *src, float *dst, int n) { for (int i = 0; i < n; i++) { dst[i] = src[i]; } if (hoge) { hogehoge(); } else { hogehogehoge(); } }
皆さんはどのスタイルを使いますか
OpenCVのMean-shift Trackingを試してみた
OpenCVで使える、色ベースのトラッキング「meanShift」と「CamShift」を紹介します!
何ができるの?
追跡したい対象の領域をはじめに指定することで、
その色情報(ヒストグラム)をもとに、対象を追跡できます
探索窓が固定なMeanShiftに対し、CamShiftは物体サイズに合わせて窓サイズを調整してくれます
試してみよう
OpenCVにCamShiftのサンプルコードがあります
(OpenCVのインストール先)/sources/samples/cpp/camshiftdemo.cpp
実行例:YouTubeから拾った動画で失礼します
OpenCV + Camshift Demonstration - YouTube
meanShiftも試してみたい
camshiftdemo.cppを少し修正すると、meanShiftも試すことができます
CamShift()の呼び出し箇所
cv::RotatedRect trackBox = cv::CamShift(backproj, trackWindow, cv::TermCriteria(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 10, 1));
を以下のように修正します
cv::meanShift(backproj, trackWindow, cv::TermCriteria(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 10, 1));
また、すぐ下の領域を描画する箇所
cv::ellipse(_image, trackBox, cv::Scalar(0, 0, 255), 3, cv::LINE_AA);
を以下のように修正します
cv::rectangle(_image, trackWindow.tl(), trackWindow.br(), cv::Scalar(0,0,255), 3, cv::LINE_AA);
終わりに
今回は「meanShift」と「CamShift」の表面だけ紹介しましたが
今後、追跡の仕組みについても解説したいと思います