Skip to content
Snippets Groups Projects
main.cpp 9.57 KiB
Newer Older
/*
 * Copyright 2019 Nicolas Pope. All rights reserved.
 *
 * See LICENSE.
 */

#include <glog/logging.h>
#include <ftl/config.h>
#include <ftl/configuration.hpp>
Sebastian Hahta's avatar
Sebastian Hahta committed

Nicolas Pope's avatar
Nicolas Pope committed
// #include <zlib.h>

#include <string>
#include <vector>
#include <thread>
#include <chrono>
#include <mutex>

#include <opencv2/opencv.hpp>
#include <ftl/net/universe.hpp>
#include <ftl/display.hpp>
Nicolas Pope's avatar
Nicolas Pope committed
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utility.hpp>

#include <ftl/utility/opencv_to_pcl.hpp>
#include <ftl/registration.hpp>
Nicolas Pope's avatar
Nicolas Pope committed
#ifdef HAVE_PCL
#include <pcl/point_cloud.h>
#include <pcl/common/transforms.h>
#include <pcl/filters/uniform_sampling.h>
#endif
Sebastian Hahta's avatar
Sebastian Hahta committed

using ftl::config;
Sebastian Hahta's avatar
Sebastian Hahta committed

using json = nlohmann::json;
using std::this_thread::sleep_for;
using std::chrono::milliseconds;
using std::mutex;
using std::unique_lock;

Nicolas Pope's avatar
Nicolas Pope committed
using std::vector;

#ifdef HAVE_PCL
using pcl::PointCloud;
using pcl::PointXYZ;
using pcl::PointXYZRGB;
#endif

using cv::Mat;
Sebastian Hahta's avatar
Sebastian Hahta committed

Nicolas Pope's avatar
Nicolas Pope committed
class InputStereo {
Sebastian Hahta's avatar
Sebastian Hahta committed
private:
Nicolas Pope's avatar
Nicolas Pope committed
	string uri_;
	Mat rgb_, disp_;
	Mat q_;
	std::mutex m_;

#ifdef HAVE_PCL
	static PointCloud<PointXYZRGB>::Ptr _getPointCloud(Mat rgb, Mat disp, Mat q) {
		cv::Mat_<cv::Vec3f> XYZ(disp.rows, disp.cols);
		reprojectImageTo3D(disp, XYZ, q, false);
		return ftl::utility::matToPointXYZ(XYZ, rgb);
	}
#endif
Sebastian Hahta's avatar
Sebastian Hahta committed

public:
Nicolas Pope's avatar
Nicolas Pope committed
	InputStereo(string uri, const Mat Q): uri_(uri), q_(Q) {};
Sebastian Hahta's avatar
Sebastian Hahta committed
	
Nicolas Pope's avatar
Nicolas Pope committed
	void set(Mat &rgb, Mat &disp) {
		unique_lock<mutex> lk(m_);
		rgb_ = rgb;
		disp_ = disp;
Nicolas Pope's avatar
Nicolas Pope committed
	string getURI() { return uri_; }
	Mat getQ() { return q_; }

Sebastian Hahta's avatar
Sebastian Hahta committed
	/* thread unsafe, use lock() */
Nicolas Pope's avatar
Nicolas Pope committed
#ifdef HAVE_PCL	
	PointCloud<PointXYZRGB>::Ptr getPointCloud() { return _getPointCloud(rgb_, disp_, q_); }
#endif
	Mat getRGB() { return rgb_; }
	Mat getDisparity() { return disp_; }
	/* Mat getDepth() {} */
	/* Mat getLeftRGB() {} */
	/* Mat getRightRGB() {} */
	unique_lock<mutex> lock() { return unique_lock<mutex>(m_); } // use recursive mutex instead (and move locking to methods)?
};

#ifdef HAVE_PCL
#include <pcl/filters/uniform_sampling.h>

/*
class InputMerged {
private:
	// todo: Abstract input systems, can also use other 3D inputs (like InputMerged)
	vector<InputStereo> inputs_;
	vector<Eigen::Matrix4f> T_;
Nicolas Pope's avatar
Nicolas Pope committed
public:
	PointCloud<PointXYZRGB>::Ptr getPointCloud() {
		PointCloud<PointXYZRGB>::Ptr result(new PointCloud<PointXYZRGB>);
		for (size_t i = 0; i < T_.size(); i++) {
			inputs_[i].lock();
			PointCloud<PointXYZRGB>::Ptr cloud = inputs_[i].getPointCloud();
			// Documentation: Can be used with cloud_in equal to cloud_out 
			pcl::transformPointCloud(*cloud, *cloud, T_[i]);
			*result += *cloud;
		}
		
		PointCloud<PointXYZRGB>::Ptr result_sampled(new PointCloud<PointXYZRGB>);
		pcl::UniformSampling<PointXYZRGB> uniform_sampling;
		uniform_sampling.setInputCloud(result);
		uniform_sampling.setRadiusSearch(0.1f); // todo parametrize
		uniform_sampling.filter(*result_sampled);
		
		return result_sampled;
Sebastian Hahta's avatar
Sebastian Hahta committed
	}
Nicolas Pope's avatar
Nicolas Pope committed
};
*/
std::map<string, Eigen::Matrix4f> loadRegistration() {
	std::map<string, Eigen::Matrix4f> registration;
	std::ifstream file(string(FTL_LOCAL_CONFIG_ROOT) + "/registration.json");
	nlohmann::json load;
	file >> load;
Nicolas Pope's avatar
Nicolas Pope committed
	for (auto it = load.begin(); it != load.end(); ++it) {
		Eigen::Matrix4f m;
		auto data = m.data();
		
		for(size_t i = 0; i < 16; i++) {;
			data[i] = it.value()[i];
		}
Nicolas Pope's avatar
Nicolas Pope committed
		registration[it.key()] = m;
		
	}
	return registration;
}

void saveRegistration(std::map<string, Eigen::Matrix4f> registration) {
	nlohmann::json save;
	for (auto &item : registration) {
		auto val = nlohmann::json::array();
		for(size_t j = 0; j < 16; j++) { val.push_back((float) item.second.data()[j]); }
		save[item.first] = val;
Sebastian Hahta's avatar
Sebastian Hahta committed
	}
Nicolas Pope's avatar
Nicolas Pope committed

	std::ofstream file(string(FTL_LOCAL_CONFIG_ROOT) + "/registration.json");
	file << save;
}

template <template<class> class Container>
std::map<string, Eigen::Matrix4f> runRegistration(Container<InputStereo> &inputs) {
Nicolas Pope's avatar
Nicolas Pope committed
	std::map<string, Eigen::Matrix4f> registration;
	
	// NOTE: uses config["registration"]
	
	if (!config["registration"].is_object()) {
		LOG(FATAL) << "Configuration missing \"registration\" entry!";
		return registration;
Nicolas Pope's avatar
Nicolas Pope committed
	int iter = (int) config["registration"]["calibration"]["iterations"];
	int delay = (int) config["registration"]["calibration"]["delay"];
	string ref_uri = (string) config["registration"]["reference-source"];
	cv::Size pattern_size(	(int) config["registration"]["calibration"]["patternsize"][0],
							(int) config["registration"]["calibration"]["patternsize"][1]);
	
	// config["registration"] done
	
	size_t ref_i;
	bool found = false;
	for (size_t i = 0; i < inputs.size(); i++) {
		if (inputs[i].getURI() == ref_uri) {
			ref_i = i;
			found = true;
			break;
		}
Nicolas Pope's avatar
Nicolas Pope committed
	if (!found) { LOG(ERROR) << "Reference input not found!"; return registration; }
	
	for (auto &input : inputs) { 
		cv::namedWindow("Registration: " + input.getURI(), cv::WINDOW_KEEPRATIO|cv::WINDOW_NORMAL);
Nicolas Pope's avatar
Nicolas Pope committed
	// vector for every input: vector of point clouds of patterns
	vector<vector<PointCloud<PointXYZ>::Ptr>> patterns(inputs.size());
	
	while (iter > 0) {
		bool retval = true; // set to false if pattern not found in one of the sources
		vector<PointCloud<PointXYZ>::Ptr> patterns_iter(inputs.size());
		
		for (size_t i = 0; i < inputs.size(); i++) {
			InputStereo &input = inputs[i];
			Mat rgb, disp, Q;
			
			while(true) {
				auto lk = input.lock();
				rgb = input.getRGB();
				disp = input.getDisparity();
				Q = input.getQ();
				lk.unlock();
				
				// todo solve this somewhere else
				if ((rgb.cols > 0) && (disp.cols > 0)) { break; }
				else { sleep_for(milliseconds(500)); }
			}
			
			retval &= ftl::registration::findChessboardCorners(rgb, disp, Q, pattern_size, patterns_iter[i]);
			
			cv::imshow("Registration: " + input.getURI(), rgb);
		}
		cv::waitKey(delay);
		
		// every camera found the pattern
		if (retval) {
			for (size_t i = 0; i < patterns_iter.size(); i++) {
				patterns[i].push_back(patterns_iter[i]);
			}
			iter--;
		}
		else { LOG(WARNING) << "Pattern not detected on all inputs";}
	}
	
	for (auto &input : inputs) { cv::destroyWindow("Registration: " + input.getURI()); }
	
	for (size_t i = 0; i < inputs.size(); i++) {
		Eigen::Matrix4f T;
		if (i == ref_i) {
			T.setIdentity();
		}
		else {
			T = ftl::registration::findTransformation(patterns[i], patterns[ref_i]);
		}
		registration[inputs[i].getURI()] = T;
	}
	saveRegistration(registration);
	return registration;
}
#endif

bool getCalibration(Universe &net, string src, Mat &Q) {
	Q = Mat(cv::Size(4,4), CV_32F);
	while(true) {
		auto buf = net.findOne<vector<unsigned char>>((string) src +"/calibration");
		if (buf) {
			memcpy(Q.data, (*buf).data(), (*buf).size());
			
			if (Q.step*Q.rows != (*buf).size()) {
				LOG(ERROR) << "Corrupted calibration";
				return false;
			}
			
			return true;
		}
		else {
			LOG(INFO) << "Could not get calibration, retrying";
			sleep_for(milliseconds(500));
		}
	}
}
Sebastian Hahta's avatar
Sebastian Hahta committed
static void run() {
	Universe net(config["net"]);
	
	// Make sure connections are complete
	sleep_for(milliseconds(500));
	
	if (!config["sources"].is_array()) {
		LOG(ERROR) << "No sources configured!";
		return;
	}
	
Nicolas Pope's avatar
Nicolas Pope committed
	std::deque<InputStereo> inputs;
	std::vector<Display> displays;

	// todo networking and initilization for input should possibly be implemented in their own class
	//      with error handling etc.
Sebastian Hahta's avatar
Sebastian Hahta committed
	//
	
	for (auto &src : config["sources"]) {
Nicolas Pope's avatar
Nicolas Pope committed
		Mat Q;
		if (!getCalibration(net, src, Q)) {	continue; } // skips input if calibration bad
		
		InputStereo &in = inputs.emplace_back(src, Q);
		displays.emplace_back(config["display"], src);
		
		LOG(INFO) << src << " loaded";
Sebastian Hahta's avatar
Sebastian Hahta committed
		net.subscribe(src, [&in](const vector<unsigned char> &jpg, const vector<unsigned char> &d) {
Nicolas Pope's avatar
Nicolas Pope committed
			Mat rgb, disp;
			cv::imdecode(jpg, cv::IMREAD_COLOR, &rgb);
			Mat(rgb.size(), CV_16UC1);
			cv::imdecode(d, cv::IMREAD_UNCHANGED, &disp);
			disp.convertTo(disp, CV_32FC1, 1.0f/16.0f);
			in.set(rgb, disp);
Sebastian Hahta's avatar
Sebastian Hahta committed
		});
	}
Nicolas Pope's avatar
Nicolas Pope committed
	
	// Displays and Inputs configured
	
	// load point cloud transformations
	
#ifdef HAVE_PCL
	std::map<string, Eigen::Matrix4f> registration;
	if (config["registration"]["calibration"]["run"]) {
		registration = runRegistration(inputs);
	}
	else {
		registration = loadRegistration();
	}
	vector<Eigen::Matrix4f> T;
	for (auto &input : inputs) { T.push_back(registration[input.getURI()]); }
	
	//
	vector<PointCloud<PointXYZRGB>::Ptr> clouds(inputs.size());
	Display display_merged(config["display"], "Merged"); // todo
Sebastian Hahta's avatar
Sebastian Hahta committed

	int active = displays.size();
	while (active > 0) {
		active = 0;

		net.broadcast("grab");  // To sync cameras
Nicolas Pope's avatar
Nicolas Pope committed
		PointCloud<PointXYZRGB>::Ptr cloud(new PointCloud<PointXYZRGB>);
Nicolas Pope's avatar
Nicolas Pope committed
		for (size_t i = 0; i < inputs.size(); i++) {
Sebastian Hahta's avatar
Sebastian Hahta committed
			Display &display = displays[i];
Nicolas Pope's avatar
Nicolas Pope committed
			InputStereo &input = inputs[i];
Sebastian Hahta's avatar
Sebastian Hahta committed
			
			if (!display.active()) continue;
			active += 1;
			
Nicolas Pope's avatar
Nicolas Pope committed
			auto lk = input.lock();
			//Mat rgb = input.getRGB();
			//Mat disparity = input.getDisparity();
			clouds[i] = input.getPointCloud();
Sebastian Hahta's avatar
Sebastian Hahta committed
			lk.unlock();
			
Nicolas Pope's avatar
Nicolas Pope committed
			display.render(clouds[i]);
			//display.render(rgb, disparity);
Sebastian Hahta's avatar
Sebastian Hahta committed
			display.wait(50);
Nicolas Pope's avatar
Nicolas Pope committed
		for (size_t i = 0; i < clouds.size(); i++) {
			pcl::transformPointCloud(*clouds[i], *clouds[i], T[i]);
			*cloud += *clouds[i];
		}
		
		pcl::UniformSampling<PointXYZRGB> uniform_sampling;
		uniform_sampling.setInputCloud(cloud);
Sebastian Hahta's avatar
Sebastian Hahta committed
		uniform_sampling.setRadiusSearch(0.1f);
Nicolas Pope's avatar
Nicolas Pope committed
		uniform_sampling.filter(*cloud);
		
		display_merged.render(cloud);
		display_merged.wait(50);
Nicolas Pope's avatar
Nicolas Pope committed
#endif
	// TODO non-PCL (?)
	ftl::configure(argc, argv, "representation"); // TODO(nick) change to "reconstruction"
	run();
Nicolas Pope's avatar
Nicolas Pope committed
}