cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

OpenCV 3.1のsfmをVS2015とopenFrameworksで動かしてみる(その2) -AKAZE特徴点の追跡からのtrackデータの生成

重要 出力結果の座標系について確認

qiita.com

未反映

OpenCV3.1のsfmの入力データは動画像ではなく、追跡済みの特徴点情報です。
そして、データ形式に合わせて成型してやらないといけません。結構、面倒です。以前書いたAKAZE特徴点追跡のプログラムを改造して、適当に書きなぐってみます。
ちゃんとデバッグしてないので、適当に直してください。(そして、もっときれいな書き方教えてください。。。)

# frame1 frame2 frameN
# track1 | (x11,y11) | -> | (x12,y12) | -> | (x1N,y1N) |
# track2 | (x21,y11) | -> | (x22,y22) | -> | (x2N,y2N) |
# trackN | (xN1,yN1) | -> | (xN2,yN2) | -> | (xNN,yNN) |
特徴点が無いフレームは(-1,-1)で埋める。

動画を入力すると、opencv3.1のSfMの入力形式に成形された特徴点の追跡結果のtxtファイルを吐きます。

ofApp.cpp

#include "ofApp.h"
#include "opencv_lib.hpp"

#define CERES_FOUND 1

#include <opencv2/core.hpp>
#include <opencv2/sfm.hpp>
//#include <opencv2/viz.hpp>
#include <opencv2/core/affine.hpp>

#include <iostream>
#include <fstream>
#include <string>

#ifdef _DEBUG
#pragma comment(lib, "ceres_d.lib")
#pragma comment(lib, "libglog_d.lib")
#pragma comment(lib, "gflags_d.lib")
#pragma comment(lib, "correspondence_d.lib")
#pragma comment(lib, "multiview_d.lib")
#pragma comment(lib, "numeric_d.lib")
#pragma comment(lib, "simple_pipeline_d.lib")
#else
#pragma comment(lib, "ceres.lib")
#pragma comment(lib, "libglog.lib")
#pragma comment(lib, "gflags.lib")
#pragma comment(lib, "correspondence.lib")
#pragma comment(lib, "multiview.lib")
#pragma comment(lib, "numeric.lib")
#pragma comment(lib, "simple_pipeline.lib")
#endif

#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>

const double akaze_thresh = 3e-4; // AKAZE detection threshold set to locate about 1000 keypoints
const double ransac_thresh = 2.5f; // RANSAC inlier threshold
const double nn_match_ratio = 0.8f; // Nearest-neighbour matching ratio

int frame_steps = 10;			// 画像処理するフレームのステップ数
int threshold_track_no = 5;	// 採用する特徴点の連続フレーム数
bool flag_video_out = true;		// 追跡結果を動画で保存

class LastFrame {
public:
	LastFrame(int i1, float x_1, float y_1, int i2, float x_2, float y_2, int t)
		: idx1(i1), x1(x_1), y1(y_1), idx2(i2), x2(x_2), y2(y_2), track_no(t)
	{}

public:
	int idx1, idx2, track_no;
	float x1, y1, x2, y2;
};
std::vector<std::map<int, LastFrame> > last_frame;

int track_no = 0;

class Tracker
{
public:
	Tracker(cv::Ptr<cv::Feature2D> _detector, cv::Ptr<cv::DescriptorMatcher> _matcher) :
		detector(_detector),
		matcher(_matcher)
	{}

	void setFirstFrame(const cv::Mat frame);
	cv::Mat process(const cv::Mat frame, int frame_no);
	cv::Ptr<cv::Feature2D> getDetector() {
		return detector;
	}
protected:
	cv::Ptr<cv::Feature2D> detector;
	cv::Ptr<cv::DescriptorMatcher> matcher;
	cv::Mat first_frame, first_desc;
	vector<cv::KeyPoint> first_kp;
	vector<cv::Point2f> object_bb;
};

void Tracker::setFirstFrame(const cv::Mat frame)
{
	first_frame = frame.clone();
	detector->detectAndCompute(first_frame, cv::noArray(), first_kp, first_desc);
}

vector<cv::Point2f> Points(vector<cv::KeyPoint> keypoints)
{
	vector<cv::Point2f> res;
	for (unsigned i = 0; i < keypoints.size(); i++) {
		res.push_back(keypoints[i].pt);
	}
	return res;
}

cv::Mat Tracker::process(const cv::Mat frame, int frame_no)
{
	vector<cv::KeyPoint> kp;
	cv::Mat desc;
	detector->detectAndCompute(frame, cv::noArray(), kp, desc);

	vector< vector<cv::DMatch> > matches;
	vector<cv::KeyPoint> matched1, matched2;
	vector<int> matched1_idx, matched2_idx;
	matched1_idx.clear();
	matched2_idx.clear();
	matcher->knnMatch(first_desc, desc, matches, 2);
	for (unsigned i = 0; i < matches.size(); i++) {
		if (matches[i][0].distance < nn_match_ratio * matches[i][1].distance) {
			matched1.push_back(first_kp[matches[i][0].queryIdx]);
			matched2.push_back(kp[matches[i][0].trainIdx]);

			// 入力画像とアルゴリズムが同じなら、オリジナルの特徴点のインデックスは同じなので保存。
			matched1_idx.push_back(matches[i][0].queryIdx);
			matched2_idx.push_back(matches[i][0].trainIdx);
		}
	}

	cv::Mat inlier_mask, homography;
	vector<cv::KeyPoint> inliers1, inliers2;
	vector<cv::DMatch> inlier_matches;

	if (matched1.size() >= 4) {
		homography = cv::findHomography(Points(matched1), Points(matched2),
			cv::RANSAC, ransac_thresh, inlier_mask);
	}

	vector<int> inliers_idx;
	inliers_idx.clear();
	for (unsigned i = 0; i < matched1.size(); i++) {
		if (inlier_mask.at<uchar>(i)) {
			int new_i = static_cast<int>(inliers1.size());
			inliers1.push_back(matched1[i]);
			inliers2.push_back(matched2[i]);
			inlier_matches.push_back(cv::DMatch(new_i, new_i, 0));

			// インデックスを整理した後もオリジナルのインデックスを参照できるように保存。
			inliers_idx.push_back(i);
		}
	}

	cv::Mat res;
	if (flag_video_out) {
		drawMatches(first_frame, inliers1, frame, inliers2,
			inlier_matches, res,
			cv::Scalar(255, 0, 0), cv::Scalar(255, 0, 0));
	}

	map<int, LastFrame> current_frame;
	for (int i = 0; i < inlier_matches.size(); i++) {
		int idx1 = matched1_idx[inliers_idx[i]];
		int idx2 = matched2_idx[inliers_idx[i]];

		float x1 = inliers1[i].pt.x;
		float y1 = inliers1[i].pt.y;
		float x2 = inliers2[i].pt.x;
		float y2 = inliers2[i].pt.y;

		if (!last_frame.empty()) {
			auto itr = last_frame.back().find(idx1);        // 前フレームでidx1と同じ値のidx2が設定されているか?
			if (itr != last_frame.back().end()) {
				//設定されている場合の処理
				current_frame.insert(make_pair(idx2, LastFrame(idx1, x1, y1, idx2, x2, y2, itr->second.track_no)));
			}
			else {
				//設定されていない場合の処理
				current_frame.insert(make_pair(idx2, LastFrame(idx1, x1, y1, idx2, x2, y2, track_no++)));
			}
		}
		else {
			current_frame.insert(make_pair(idx2, LastFrame(idx1, x1, y1, idx2, x2, y2, track_no++)));
		}
	}
	last_frame.push_back(current_frame);

	first_frame = frame.clone();
	first_kp = kp;
	first_desc = desc;
	return res;
}

void ofApp::setup() {

	//http://docs.opencv.org/3.0-rc1/dc/d16/tutorial_akaze_tracking.html

	string in_filename = "data/IMG_9413.avi"; // "data/blais/blais.mp4";
	string out_filename = "data/result.avi"; // data / blais / result.avi";
	string out_tracks = "data/IMG_9413_tracks.txt"; // "data/blais/blais_tracks.txt";

	cv::VideoCapture video_in(in_filename);
	cv::VideoWriter  video_out(out_filename,
		(int)video_in.get(cv::CAP_PROP_FOURCC),
		(int)video_in.get(cv::CAP_PROP_FPS),
		cv::Size(2 * (int)video_in.get(cv::CAP_PROP_FRAME_WIDTH),
			2 * (int)video_in.get(cv::CAP_PROP_FRAME_HEIGHT)));

	if (!video_in.isOpened()) {
		cerr << "Couldn't open " << in_filename << endl;
		return;

	}
	if (!video_out.isOpened()) {
		cerr << "Couldn't open " << out_filename << endl;
		return;
	}

	cv::Ptr<cv::AKAZE> akaze = cv::AKAZE::create();
	akaze->setThreshold(akaze_thresh);
	cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
	Tracker akaze_tracker(akaze, matcher);

	cv::Mat frame;
	video_in >> frame;
	akaze_tracker.setFirstFrame(frame);

	int frame_count = (int)video_in.get(cv::CAP_PROP_FRAME_COUNT);
	cv::Mat akaze_res, res_frame;

	for (int i = 1; i < frame_count / frame_steps; i++) {
		for (int j = 0; j < frame_steps; j++) {
			video_in >> frame;
		}

		akaze_res = akaze_tracker.process(frame, i);

		if (flag_video_out) {
			vconcat(akaze_res, akaze_res, res_frame);
			video_out << res_frame;
		}
		std::cout << i << "/" << (frame_count / frame_steps) - 1 << endl;
	}

	// 検索キーをトラック番号に変更
	vector<map<int, LastFrame> > find_track;
	find_track.clear();
	for (int i = 0; i < last_frame.size(); i++) {
		map<int, LastFrame> track;
		auto itr = last_frame[i].begin();
		while (itr != last_frame[i].end()) {
			track.insert(make_pair(itr->second.track_no, itr->second));
			itr++;
		}
		find_track.push_back(track);
	}

	// track番号順に書き出し
	ofstream fout(out_tracks, ios::out);
	for (int i = 0; i < track_no; i++) {
		stringstream ss;

		int count_check = 0;
		auto itr_1 = find_track[0].find(i);

		for (int j = 0; j < find_track.size(); j++) {
			bool first_flag = false;

			if (j == 0) { // Frame0の時に対応点の登録があれば
				if (itr_1 != find_track[j].end()) {
					first_flag = true;
				}
			}
			else { // Frame1以上の時、前のフレームに登録が無ければ
				if (itr_1 == find_track[j - 1].end()) {
					first_flag = true;
				}
			}

			auto itr = find_track[j].find(i);        // trackにiが設定されているか?
			if (itr != find_track[j].end()) {
				if (first_flag) {
					ss << itr->second.x1 << " " << itr->second.y1 << " ";
					count_check++;
				}
				//設定されている場合の処理
				ss << itr->second.x2 << " " << itr->second.y2 << " ";
				count_check++;
			}
			else {
				//設定されていない場合の処理
				ss << "-1.00" << " " << "-1.00" << " ";
			}
			itr_1 = itr;
		}

		if (count_check >= threshold_track_no) {
			fout << ss.str() << endl;
		}
	}
	fout.close();
}

//--------------------------------------------------------------
void ofApp::update() {

}

//--------------------------------------------------------------
void ofApp::draw() {

}

//--------------------------------------------------------------
void ofApp::keyPressed(int key) {

}

//--------------------------------------------------------------
void ofApp::keyReleased(int key) {

}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h) {

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg) {

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo) {

}

f:id:cvl-robot:20160209000027p:plain

f:id:cvl-robot:20160209133717g:plain
表示の軸がひっくり返ってますね。直さなきゃ。

ものすごーくサンプルフレーム数を減らしておかないと、sfmの計算で破たんします。。。
あとで、少し直しますかね。。。

今日のイヤホン

これが安くて良い音だと、人気らしい。人によっては30万のイヤホンに匹敵する音質だとか。ほんまかいな。小米とどっちがいいかな?
GranVela® R8 IEM はすべての金属で雑音を隔離しような分離できるケーブルと記憶ワイヤーを付きの音楽家の耳内スタイルイヤホン (銀)