OpenCV 3.1のsfmをVS2015とopenFrameworksで動かしてみる(その2) -AKAZE特徴点の追跡からのtrackデータの生成
重要 出力結果の座標系について確認
未反映
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) { }
表示の軸がひっくり返ってますね。直さなきゃ。