From 0be0297485ff4e2090e2fcb63ce911da7404ce68 Mon Sep 17 00:00:00 2001 From: Sebastian Gomez-Gonzalez <sgomez@tue.mpg.de> Date: Wed, 11 Oct 2017 13:50:29 +0200 Subject: [PATCH] Full stereo tracking pipeline seems to work (not well tested yet) --- CMakeLists.txt | 7 + examples/CMakeLists.txt | 8 ++ examples/tracking/gpu_track.cpp | 13 +- examples/tracking/server3d.cpp | 47 +++++++ examples/tracking/server_3d_conf.json | 29 ++++ examples/tracking/track.cpp | 12 +- include/ball_tracking/stereo.hpp | 22 +++ src/stereo.cpp | 185 ++++++++++++++++++++++++++ src/tracker.cpp | 58 +++++++- 9 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 examples/tracking/server3d.cpp create mode 100644 examples/tracking/server_3d_conf.json create mode 100644 include/ball_tracking/stereo.hpp create mode 100644 src/stereo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c678eb5..2b072e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,11 +6,14 @@ find_package( Boost 1.58 COMPONENTS log REQUIRED ) find_package(OpenCV REQUIRED) find_package(CUDA 8.0) +find_package(Armadillo REQUIRED) find_library(ZMQPP NAMES zmqpp) find_path(ZMQPP_INCLUDES NAMES zmqpp/zmqpp.hpp) find_library(CAMLIB NAMES camera) find_path(CAMLIB_INC NAMES camera.hpp) +find_library(ROBOTICS NAMES robotics) +find_path(ROBOTICS_INC NAMES robotics.hpp) if (CUDA_FOUND) option (WITH_CUDA "Compile the library with GPU implementations" ON) @@ -38,6 +41,7 @@ include_directories(include ${OpenCV_INCLUDES} ${ZMQPP_INCLUDES} ${CAMLIB_INC} + ${ROBOTICS_INC} ) add_library(ball_tracking SHARED @@ -45,6 +49,7 @@ add_library(ball_tracking SHARED src/utils.cpp src/tracker.cpp src/server.cpp + src/stereo.cpp ${GPU_CPP_SRC} ) target_link_libraries(ball_tracking @@ -53,6 +58,8 @@ target_link_libraries(ball_tracking ${ZMQPP} ${CAMLIB} ${Boost_LIBRARIES} + ${ARMADILLO_LIBRARIES} + ${ROBOTICS} ) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a7994aa..2e49770 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,6 +35,14 @@ target_link_libraries(track_server ${Boost_LIBRARIES} ) +add_executable(track_server3d + tracking/server3d.cpp + ) +target_link_libraries(track_server3d + ball_tracking + ${Boost_LIBRARIES} + ) + if (WITH_CUDA) add_executable(log_reg_approach img_proc/log_reg_approach.cpp diff --git a/examples/tracking/gpu_track.cpp b/examples/tracking/gpu_track.cpp index b5ef35f..457ef28 100644 --- a/examples/tracking/gpu_track.cpp +++ b/examples/tracking/gpu_track.cpp @@ -65,11 +65,13 @@ int main(int argc, char** argv) { Binarizer bin(jobj.at("binarizer")); ball_tracking::cuda::Binarizer gpu_bin(jobj.at("binarizer")); FindBallBlob bf(jobj.at("blob_detection")); + int subsample = 0; + if (jobj.count("subsample")) subsample = jobj.at("subsample"); string fname = vm["input"].as<string>(); VideoCapture in_video(fname); - Size in_size((int)in_video.get(CV_CAP_PROP_FRAME_WIDTH), - (int)in_video.get(CV_CAP_PROP_FRAME_HEIGHT)); + Size in_size((int)in_video.get(CV_CAP_PROP_FRAME_WIDTH) >> subsample, + (int)in_video.get(CV_CAP_PROP_FRAME_HEIGHT) >> subsample); int ex = static_cast<int>(in_video.get(CV_CAP_PROP_FOURCC)); // Use same input codec // Transform from int to char via Bitwise operators char codec[] = {(char)(ex & 0XFF) , @@ -106,6 +108,13 @@ int main(int argc, char** argv) { gpu_img.upload(img); auto end_t = std::chrono::steady_clock::now(); in_time.push_back(std::chrono::duration_cast<std::chrono::microseconds>(end_t - start_t).count()); + //1.1) Subsample + for (int i=0; i<subsample; i++) { + cv::cuda::GpuMat gpu_tmp; + cv::cuda::pyrDown(gpu_img, gpu_tmp); + gpu_img = gpu_tmp; + } + gpu_img.download(img); //2) Compute log likelihood image (In GPU) start_t = std::chrono::steady_clock::now(); diff --git a/examples/tracking/server3d.cpp b/examples/tracking/server3d.cpp new file mode 100644 index 0000000..636ce07 --- /dev/null +++ b/examples/tracking/server3d.cpp @@ -0,0 +1,47 @@ + +#include <ball_tracking/stereo.hpp> +#include <ball_tracking/utils.hpp> +#include <iostream> +#include <boost/program_options.hpp> +#include <json.hpp> + +using namespace std; +using namespace ball_tracking; +using json = nlohmann::json; + +int main(int argc, char** argv) { + try { + namespace po = boost::program_options; + po::options_description desc("Options"); + desc.add_options() + ("help", "Produce help message") + ("conf,c", po::value<string>(), "path to the JSON configuration"); + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + cout << desc << endl; + return 0; + } + if (!vm.count("conf")) { + cerr << "Error: You should provide the video to process" << endl; + return 1; + } + + json server_conf = load_json(vm.at("conf").as<string>()); + cout << "Loading server configuration" << endl; + StereoServer srv; + srv.start(server_conf); + cout << "Tracking 3D started. Type q to quit" << endl; + char c; + do { + cin >> c; + } while (c != 'q'); + cout << "Stopping" << endl; + srv.stop(); + } catch (std::exception& ex) { + cerr << "Exception: " << ex.what() << endl; + return 1; + } +} diff --git a/examples/tracking/server_3d_conf.json b/examples/tracking/server_3d_conf.json new file mode 100644 index 0000000..4fa65a4 --- /dev/null +++ b/examples/tracking/server_3d_conf.json @@ -0,0 +1,29 @@ +{ + "publisher": "tcp://*:7660", + "obs2d": ["tcp://localhost:7650"], + "num_cams": 2, + "stereo": { + "calib": [ + { + "ID": 0, + "val": [ + [330.818029777, -20.0612367361, 4.36051841227, 267.094073072], + [3.18143132853, -129.47891137, -292.718044206, -202.284356759], + [0.0636413872527, 0.200475112774, -0.114193103147, 1.0] + ] + }, + { + "ID": 1, + "val": [ + [226.81497789, 157.971113188, -122.040734067, 342.535383641], + [-34.3055014233, -132.114683094, -281.490884508, -123.382602225], + [-0.138614275474, 0.161602493631, -0.110710339962, 1.0 ] + ] + } + ] + }, + "log": { + "auto_flush": true, + "file": "/tmp/ball_tracking.log" + } +} diff --git a/examples/tracking/track.cpp b/examples/tracking/track.cpp index 5ad4dd9..c95458a 100644 --- a/examples/tracking/track.cpp +++ b/examples/tracking/track.cpp @@ -62,11 +62,13 @@ int main(int argc, char** argv) { BallLogLikelihood bll(jobj.at("ball_log_lh")); Binarizer bin(jobj.at("binarizer")); FindBallBlob bf(jobj.at("blob_detection")); + int subsample = 0; + if (jobj.count("subsample")) subsample = jobj.at("subsample"); string fname = vm["input"].as<string>(); VideoCapture in_video(fname); - Size in_size((int)in_video.get(CV_CAP_PROP_FRAME_WIDTH), - (int)in_video.get(CV_CAP_PROP_FRAME_HEIGHT)); + Size in_size((int)in_video.get(CV_CAP_PROP_FRAME_WIDTH)>>subsample, + (int)in_video.get(CV_CAP_PROP_FRAME_HEIGHT)>>subsample); int ex = static_cast<int>(in_video.get(CV_CAP_PROP_FOURCC)); // Use same input codec // Transform from int to char via Bitwise operators char codec[] = {(char)(ex & 0XFF) , @@ -100,6 +102,12 @@ int main(int argc, char** argv) { if (img.empty()) break; auto end_t = std::chrono::steady_clock::now(); in_time.push_back(std::chrono::duration_cast<std::chrono::microseconds>(end_t - start_t).count()); + //1.1) Subsample + for (int i=0; i<subsample; i++) { + Mat tmp; + pyrDown(img, tmp, Size(img.cols/2, img.rows/2)); + img = tmp; + } //2) Compute log likelihood image start_t = std::chrono::steady_clock::now(); diff --git a/include/ball_tracking/stereo.hpp b/include/ball_tracking/stereo.hpp new file mode 100644 index 0000000..8a34495 --- /dev/null +++ b/include/ball_tracking/stereo.hpp @@ -0,0 +1,22 @@ +#ifndef BALL_TRACKING_STEREO +#define BALL_TRACKING_STEREO + +#include <memory> +#include <json.hpp> + +namespace ball_tracking { + + class StereoServer { + private: + class Impl; + std::shared_ptr<Impl> _impl; + public: + StereoServer(); + ~StereoServer(); + + void start(const nlohmann::json& conf); + void stop(); + }; +}; + +#endif diff --git a/src/stereo.cpp b/src/stereo.cpp new file mode 100644 index 0000000..200e25e --- /dev/null +++ b/src/stereo.cpp @@ -0,0 +1,185 @@ + +#include <ball_tracking/stereo.hpp> +#include <ball_tracking/utils.hpp> +#include <robotics/utils.hpp> +#include <zmqpp/zmqpp.hpp> +#include <string> +#include <unordered_map> +#include <thread> +#include <chrono> +#include <armadillo> +#include <thread> + +#include <boost/log/core.hpp> +#include <boost/log/trivial.hpp> +#include <boost/log/expressions.hpp> +#include <boost/log/utility/setup/file.hpp> +#include <boost/log/utility/setup/console.hpp> +#include <boost/log/utility/setup/common_attributes.hpp> + +namespace logging = boost::log; +using namespace std; +using namespace std::chrono; +using json = nlohmann::json; +using namespace arma; + +namespace ball_tracking { + + class MultiObs2D { + private: + unsigned int num; //!< ID of the observation + double _time; //!< A consolidated time of the observation + unsigned int _num_obs; + public: + vector<robotics::pt2d> obs2d; //!< List of observations + vector<bool> is_obs; //!< Weather or not the observation was actually made + + MultiObs2D(unsigned int num_cams) { + obs2d = vector<robotics::pt2d>(num_cams); + is_obs = vector<bool>(num_cams, false); + _num_obs = 0; + } + + unsigned int num_obs() const { + return _num_obs; + } + + double time() const { + return _time; + } + + void add_obs(unsigned int cam_id, double time, bool is_obs, const robotics::pt2d& x) { + if (!this->is_obs[cam_id]) { + this->is_obs[cam_id] = is_obs; + _time = time; + _num_obs++; + } + obs2d[cam_id] = x; + } + }; + + class Stereo { + public: + unordered_map<unsigned int, arma::mat> calib; + + Stereo(const json& conf) { + json jcalib = conf.at("calib"); + for (auto elem : jcalib) { + unsigned int id = elem.at("ID"); + mat val = robotics::json2mat( elem.at("val") ); + calib[id] = val; + } + } + + bool operator()(robotics::pt3d& ans, const MultiObs2D& obs) { + vector<pair<unsigned int,robotics::pt2d>> all_obs; + for (unsigned int i=0; i<obs.obs2d.size(); i++) { + if (obs.is_obs[i]) { + all_obs.push_back(make_pair(i, obs.obs2d[i])); + } + } + if (all_obs.size()<2) return false; + ans = robotics::stereo_vision(calib, all_obs); + return true; + } + }; + + class StereoServer::Impl { + public: + unordered_map<unsigned int, mat> calib_mats; + unordered_map<unsigned int, MultiObs2D> observations; + thread srv_thread; + bool running; + unsigned int num_cams; + + void start(const json& conf) { + running = true; + auto method = [this](const json& conf) -> void { + this->start_server(conf); + }; + srv_thread = thread(method, conf); + } + + void start_server(const json& conf) { + // 1) Create client and server sockets + zmqpp::context context; + zmqpp::socket pos_recv(context, zmqpp::socket_type::sub); + zmqpp::socket pos_send(context, zmqpp::socket_type::pub); + // 1.1) Subscribe to all position publisher in 2 dimensions (in config) + cout << "Creating sockets" << endl; + for (const string& url : conf.at("obs2d")) { + pos_recv.connect(url); + } + pos_recv.set(zmqpp::socket_option::subscribe, "a"); + // 1.2) And create a publisher of 3 dimensional positions + string pos_send_url = conf.at("publisher"); + pos_send.bind(pos_send_url); + // 1.3) Read additional configuration + cout << "Reading additional configuration" << endl; + num_cams = conf.at("num_cams"); + Stereo stereo(conf.at("stereo")); + while (running) { + //2) Read the next 2 dim position + zmqpp::message msg; + pos_recv.receive(msg); + string topic, body; + msg >> topic >> body; + json jobs = json::parse(body); + + //3) Create a new MultiObs2D or modify an existing one + unsigned int num = jobs.at("num"); + unsigned int cam_id = jobs.at("cam_id"); + double time = jobs.at("time"); + robotics::pt2d x; bool is_obs = false; + if (observations.count(num) == 0) { + observations.insert({num, MultiObs2D(num_cams)}); + } + if (!jobs.at("obs").is_null()) { + is_obs = true; + json jx = jobs.at("obs"); + x[0] = jx[0]; x[1] = jx[1]; + } + MultiObs2D& curr = observations.at(num); + curr.add_obs(cam_id, time, is_obs, x); + + //4) If we have all observations, run stereo + if (curr.num_obs() == num_cams) { + robotics::pt3d pos3d; + bool success = stereo(pos3d, curr); + zmqpp::message out; + json out_obs; + if (success) { + for (int i=0; i<3; i++) out_obs.push_back(pos3d[i]); + } + json out_msg { + {"num", num}, + {"time", time}, + {"obs", out_obs} + }; + cout << out_msg.dump() << endl; + out << "b" << out_msg.dump(); + } + } + } + + void stop() { + running = false; + srv_thread.join(); + } + }; + + StereoServer::StereoServer() { + _impl = make_shared<Impl>(); + } + + StereoServer::~StereoServer() = default; + + void StereoServer::start(const nlohmann::json& conf) { + _impl->start(conf); + } + + void StereoServer::stop() { + _impl->stop(); + } + +}; diff --git a/src/tracker.cpp b/src/tracker.cpp index 59ab004..21aa71b 100644 --- a/src/tracker.cpp +++ b/src/tracker.cpp @@ -301,15 +301,40 @@ namespace ball_tracking { private: BallLogLikelihood llh; FindBallBlob blob_detect; + unsigned int subsample; public: - CPUTracker(const json& conf) { + CPUTracker(const json& conf) : subsample(0) { llh = BallLogLikelihood(conf.at("ball_log_lh")); blob_detect = FindBallBlob(conf.at("blob_detection")); + if (conf.count("subsample")) subsample = conf.at("subsample"); } - vector<KeyPoint> operator()(cv::InputArray img) { + vector<KeyPoint> operator()(cv::InputArray _img) { + Mat img = _img.getMat(); + + //1) Subsample if necessary + double fx=1.0, fy=1.0; + for (unsigned int i=0; i<subsample; ++i) { + Mat tmp; + pyrDown(img, tmp); + fx *= ((double)img.cols)/tmp.cols; + fy *= ((double)img.rows)/tmp.rows; + img = tmp; + } + + //2) Process image and get the blobs Mat llh_img = llh(img); - return blob_detect(llh_img); + auto ans = blob_detect(llh_img); + + //3) Recover the answer to the original problem + if (subsample != 0) { + for (auto& kpt : ans) { + kpt.pt.x *= fx; + kpt.pt.y *= fy; + kpt.size *= (fx + fy)/2.0; + } + } + return ans; } }; @@ -322,16 +347,29 @@ namespace ball_tracking { ball_tracking::cuda::BallLogLikelihood llh; //!< Fully implemented in GPU FindBallBlob blob_detect; //!< For the moment only implemented in CPU cv::cuda::Stream stream; + unsigned int subsample; public: - GPUTracker(const json& conf) { + GPUTracker(const json& conf) : subsample(0) { BOOST_LOG_TRIVIAL(debug) << "Creating a log-likelihood object in the GPU"; llh = ball_tracking::cuda::BallLogLikelihood(conf.at("ball_log_lh")); BOOST_LOG_TRIVIAL(debug) << "GPU log-likelihood creation was successful. Now creating CPU blob"; blob_detect = FindBallBlob(conf.at("blob_detection")); + if (conf.count("subsample")) subsample = conf.at("subsample"); } vector<KeyPoint> operator()(cv::InputArray _img) { Mat img = _img.getMat(); + //1) Subsample if necessary (in CPU) + double fx=1.0, fy=1.0; + for (unsigned int i=0; i<subsample; ++i) { + Mat tmp; + cv::pyrDown(img, tmp); + fx *= ((double)img.cols)/tmp.cols; + fy *= ((double)img.rows)/tmp.rows; + img = tmp; + } + + //2) Process image (in GPU) and get blobs (in CPU) GpuMat gpu_img; GpuMat gpu_llh_img; gpu_img.upload(img, stream); @@ -340,7 +378,17 @@ namespace ball_tracking { Mat llh_img; gpu_llh_img.download(llh_img, stream); stream.waitForCompletion(); - return blob_detect(llh_img); + auto ans = blob_detect(llh_img); + + //3) Recover the answer to the original problem + if (subsample != 0) { + for (auto& kpt : ans) { + kpt.pt.x *= fx; + kpt.pt.y *= fy; + kpt.size *= (fx + fy)/2.0; + } + } + return ans; } }; #else -- GitLab