diff --git a/components/common/cpp/include/ftl/utility/msgpack.hpp b/components/common/cpp/include/ftl/utility/msgpack.hpp
index 993e068a8b102f4742dd8666f423d1901a00a5d9..6d733f1fb520d60e2e021759fa6ea9fbffdbef46 100644
--- a/components/common/cpp/include/ftl/utility/msgpack.hpp
+++ b/components/common/cpp/include/ftl/utility/msgpack.hpp
@@ -13,12 +13,12 @@ MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) {
 namespace adaptor {
 
 ////////////////////////////////////////////////////////////////////////////////
-// cv::Size
+// cv::Size_<T>
 
-template<>
-struct pack<cv::Size> {
+template<typename T>
+struct pack<cv::Size_<T>> {
 	template <typename Stream>
-	packer<Stream>& operator()(msgpack::packer<Stream>& o, cv::Size const& v) const {
+	packer<Stream>& operator()(msgpack::packer<Stream>& o, cv::Size_<T> const& v) const {
 
 		o.pack_array(2);
 		o.pack(v.width);
@@ -28,22 +28,22 @@ struct pack<cv::Size> {
 	}
 };
 
-template<>
-struct convert<cv::Size> {
-	msgpack::object const& operator()(msgpack::object const& o, cv::Size& v) const {
+template<typename T>
+struct convert<cv::Size_<T>> {
+	msgpack::object const& operator()(msgpack::object const& o, cv::Size_<T>& v) const {
 		if (o.type != msgpack::type::ARRAY) { throw msgpack::type_error(); }
 		if (o.via.array.size != 2) { throw msgpack::type_error(); }
 		
-		int width = o.via.array.ptr[0].as<int>();
-		int height = o.via.array.ptr[1].as<int>();
-		v = cv::Size(width, height);
+		T width = o.via.array.ptr[0].as<T>();
+		T height = o.via.array.ptr[1].as<T>();
+		v = cv::Size_<T>(width, height);
 		return o;
 	}
 };
 
-template <>
-struct object_with_zone<cv::Size> {
-	void operator()(msgpack::object::with_zone& o, cv::Size const& v) const {
+template <typename T>
+struct object_with_zone<cv::Size_<T>> {
+	void operator()(msgpack::object::with_zone& o, cv::Size_<T> const& v) const {
 		o.type = type::ARRAY;
 		o.via.array.size = 2;
 		o.via.array.ptr = static_cast<msgpack::object*>(
@@ -55,6 +55,56 @@ struct object_with_zone<cv::Size> {
 	}
 };
 
+////////////////////////////////////////////////////////////////////////////////
+// cv::Rect_<T>
+
+template<typename T>
+struct pack<cv::Rect_<T>> {
+	template <typename Stream>
+	packer<Stream>& operator()(msgpack::packer<Stream>& o, cv::Rect_<T> const& v) const {
+
+		o.pack_array(4);
+		o.pack(v.height);
+		o.pack(v.width);
+		o.pack(v.x);
+		o.pack(v.y);
+
+		return o;
+	}
+};
+
+template<typename T>
+struct convert<cv::Rect_<T>> {
+	msgpack::object const& operator()(msgpack::object const& o, cv::Rect_<T> &v) const {
+		if (o.type != msgpack::type::ARRAY) { throw msgpack::type_error(); }
+		if (o.via.array.size != 4) { throw msgpack::type_error(); }
+		
+		T height = o.via.array.ptr[0].as<T>();
+		T width = o.via.array.ptr[1].as<T>();
+		T x = o.via.array.ptr[2].as<T>();
+		T y = o.via.array.ptr[3].as<T>();
+
+		v = cv::Rect_<T>(x, y, width, height);
+		return o;
+	}
+};
+
+template <typename T>
+struct object_with_zone<cv::Rect_<T>> {
+	void operator()(msgpack::object::with_zone& o, cv::Rect_<T> const& v) const {
+		o.type = type::ARRAY;
+		o.via.array.size = 4;
+		o.via.array.ptr = static_cast<msgpack::object*>(
+			o.zone.allocate_align(	sizeof(msgpack::object) * o.via.array.size,
+									MSGPACK_ZONE_ALIGNOF(msgpack::object)));
+
+		o.via.array.ptr[0] = msgpack::object(v.heigth, o.zone);
+		o.via.array.ptr[1] = msgpack::object(v.width, o.zone);
+		o.via.array.ptr[2] = msgpack::object(v.x, o.zone);
+		o.via.array.ptr[3] = msgpack::object(v.y, o.zone);
+	}
+};
+
 ////////////////////////////////////////////////////////////////////////////////
 // cv::Mat
 
@@ -118,7 +168,6 @@ struct object_with_zone<cv::Mat> {
 	}
 };
 
-
 }
 }
 }
diff --git a/components/common/cpp/test/msgpack_unit.cpp b/components/common/cpp/test/msgpack_unit.cpp
index a45ad3d1b4e98c0c7da7601c18ef46eca8a7a87a..996c10cd2784fa9fe198b0190d8f87184b870620 100644
--- a/components/common/cpp/test/msgpack_unit.cpp
+++ b/components/common/cpp/test/msgpack_unit.cpp
@@ -19,17 +19,18 @@ std::string msgpack_pack(T v) {
 	return std::string(buffer.str());
 }
 
-Mat msgpack_unpack_mat(std::string str) {
+template<typename T>
+T msgpack_unpack(std::string str) {
 	msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
 	msgpack::object obj = oh.get();
-	Mat M;
-	return obj.convert<Mat>(M);
+	T res;
+	return obj.convert<T>(res);
 }
 
 TEST_CASE( "msgpack cv::Mat" ) {
 	SECTION( "Mat::ones(Size(5, 5), CV_64FC1)" ) {
 		Mat A = Mat::ones(Size(5, 5), CV_64FC1);
-		Mat B = msgpack_unpack_mat(msgpack_pack(A));
+		Mat B = msgpack_unpack<Mat>(msgpack_pack(A));
 
 		REQUIRE(A.size() == B.size());
 		REQUIRE(A.type() == B.type());
@@ -38,7 +39,7 @@ TEST_CASE( "msgpack cv::Mat" ) {
 
 	SECTION( "Mat::ones(Size(1, 5), CV_8UC3)" ) {
 		Mat A = Mat::ones(Size(1, 5), CV_8UC3);
-		Mat B = msgpack_unpack_mat(msgpack_pack(A));
+		Mat B = msgpack_unpack<Mat>(msgpack_pack(A));
 		
 		REQUIRE(A.size() == B.size());
 		REQUIRE(A.type() == B.type());
@@ -48,7 +49,7 @@ TEST_CASE( "msgpack cv::Mat" ) {
 	SECTION ( "Mat 10x10 CV_64FC1 with random values [-1000, 1000]" ) {
 		Mat A(Size(10, 10), CV_64FC1);
 		cv::randu(A, -1000, 1000);
-		Mat B = msgpack_unpack_mat(msgpack_pack(A));
+		Mat B = msgpack_unpack<Mat>(msgpack_pack(A));
 		
 		REQUIRE(A.size() == B.size());
 		REQUIRE(A.type() == B.type());
@@ -62,7 +63,7 @@ TEST_CASE( "msgpack cv::Mat" ) {
 		msgpack::zone z;
 		auto obj = msgpack::object(A, z);
 		
-		Mat B = msgpack_unpack_mat(msgpack_pack(obj));
+		Mat B = msgpack_unpack<Mat>(msgpack_pack(obj));
 		
 		REQUIRE(A.size() == B.size());
 		REQUIRE(A.type() == B.type());
@@ -75,7 +76,7 @@ TEST_CASE( "msgpack cv::Mat" ) {
 			A = A(Rect(2, 2, 3,3));
 			A.setTo(0);
 
-			Mat B = msgpack_unpack_mat(msgpack_pack(A));
+			Mat B = msgpack_unpack<Mat>(msgpack_pack(A));
 		
 			REQUIRE(A.size() == B.size());
 			REQUIRE(A.type() == B.type());
@@ -85,4 +86,9 @@ TEST_CASE( "msgpack cv::Mat" ) {
 			// if not supported, throws exception
 		}
 	}
+
+	SECTION( "Rect_<T>" ) {
+		auto res = msgpack_unpack<cv::Rect2d>(msgpack_pack(cv::Rect2d(1,2,3,4)));
+		REQUIRE(res == cv::Rect2d(1,2,3,4));
+	}
 }
diff --git a/components/operators/CMakeLists.txt b/components/operators/CMakeLists.txt
index 3bff44a4cb44ecf14b71a0f1e3b12af74ddd2ca3..cd9d674f828ffafcc79d3c590e1792d474ff336f 100644
--- a/components/operators/CMakeLists.txt
+++ b/components/operators/CMakeLists.txt
@@ -23,9 +23,9 @@ set(OPERSRC
 	src/correspondence.cu
 	src/clipping.cpp
 	src/depth.cpp
+	src/detectandtrack.cpp
 )
 
-
 if (LIBSGM_FOUND)
 	list(APPEND OPERSRC src/disparity/fixstars_sgm.cpp)
 endif (LIBSGM_FOUND)
diff --git a/components/operators/include/ftl/operators/detectandtrack.hpp b/components/operators/include/ftl/operators/detectandtrack.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d5b325a29e1c243b0db4088fcb1b5d877d422bdf
--- /dev/null
+++ b/components/operators/include/ftl/operators/detectandtrack.hpp
@@ -0,0 +1,73 @@
+#ifndef _FTL_OPERATORS_CASCADECLASSIFIER_HPP_
+#define _FTL_OPERATORS_CASCADECLASSIFIER_HPP_
+
+#include "ftl/operators/operator.hpp"
+#include <opencv2/objdetect.hpp>
+#include <opencv2/tracking.hpp>
+
+namespace ftl {
+namespace operators {
+
+/**
+ * Object detection and tracking. 
+ * 
+ * cv::CascadeClassifier used in detection
+ * https://docs.opencv.org/master/d1/de5/classcv_1_1CascadeClassifier.html
+ * 
+ * cv::TrackerKCF used in tracking
+ * https://docs.opencv.org/master/d2/dff/classcv_1_1TrackerKCF.html
+ * 
+ */
+class DetectAndTrack : public ftl::operators::Operator {
+	public:
+	explicit DetectAndTrack(ftl::Configurable*);
+	~DetectAndTrack() {};
+
+	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
+
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
+
+	protected:
+	bool init();
+	
+	bool detect(const cv::Mat &im);
+	bool track(const cv::Mat &im);
+
+	private:
+	ftl::codecs::Channel channel_in_;
+	ftl::codecs::Channel channel_out_;
+
+	struct Object {
+		cv::Ptr<cv::Tracker> tracker;
+		cv::Rect2d object;
+		int fail_count;
+	};
+	std::vector<Object> tracked_;
+
+	// detection: if detected object is farther than max_distance_, new tracked
+	// object is added
+	double max_distance_;
+	// maximum number of tracked objects (performance)
+	int max_tracked_;
+	// maximum number of successive failed trackings before object is removed
+	int	max_fail_;
+	// how often detection is performed
+	int detect_n_frames_;
+
+	// cascade classifier parameters, see OpenCV documentation
+	std::string fname_;
+	double scalef_;
+	int min_neighbors_;
+	// min_size_ and max_size_ relative
+	std::vector<double> min_size_;
+	std::vector<double> max_size_;
+
+	int n_frame_;
+	cv::Mat gray_;
+	cv::CascadeClassifier classifier_;
+};
+
+}
+}
+
+#endif // _FTL_OPERATORS_CASCADECLASSIFIER_HPP_
diff --git a/components/operators/src/detectandtrack.cpp b/components/operators/src/detectandtrack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5d3ac46753ce63fab088d6ce370abc1695f9656
--- /dev/null
+++ b/components/operators/src/detectandtrack.cpp
@@ -0,0 +1,184 @@
+#include <cmath>
+
+#include "loguru.hpp"
+#include "ftl/operators/detectandtrack.hpp"
+
+using std::string;
+using std::vector;
+using std::map;
+
+using cv::Mat;
+using cv::Size;
+using cv::Rect;
+using cv::Rect2d;
+using cv::Point2i;
+using cv::Point2d;
+
+using ftl::rgbd::Frame;
+using ftl::operators::DetectAndTrack;
+
+DetectAndTrack::DetectAndTrack(ftl::Configurable *cfg) : ftl::operators::Operator(cfg) {
+	init();
+}
+
+bool DetectAndTrack::init() {
+	fname_ = config()->value<string>("filename", "");
+	
+	detect_n_frames_ = config()->value<int>("n_frames", 10);
+	detect_n_frames_ = detect_n_frames_ < 0.0 ? 0.0 : detect_n_frames_;
+
+	max_distance_ = config()->value<double>("max_distance", 100.0);
+	max_distance_ = max_distance_ < 0.0 ? 0.0 : max_distance_;
+
+	max_fail_ = config()->value<int>("max_fail", 10);
+	max_fail_ = max_fail_ < 0 ? 10 : max_fail_;
+
+	max_tracked_ = config()->value<int>("max_tracked", 3);
+	max_tracked_ = max_tracked_ < 0 ? 10 : max_tracked_;
+
+	scalef_ = config()->value<double>("scalef", 1.1);
+	min_neighbors_ = config()->value<int>("min_neighbors", 3);
+
+	auto min_size = config()->get<vector<double>>("min_size");
+	auto max_size = config()->get<vector<double>>("max_size");
+
+	if (min_size && min_size->size() == 2) { min_size_ = *min_size; }
+	else { min_size_ = {0.0, 0.0}; }
+	if (max_size && max_size->size() == 2) { max_size_ = *max_size; }
+	else { max_size_ = {1.0, 1.0}; }
+	
+	min_size_[0] = max(min(1.0, min_size_[0]), 0.0);
+	min_size_[1] = max(min(1.0, min_size_[1]), 0.0);
+	min_size_[0] = max(min(1.0, max_size_[0]), 0.0);
+	min_size_[1] = max(min(1.0, max_size_[1]), 0.0);
+	if (min_size_[0] > max_size_[0]) { min_size_[0] = max_size_[0]; }
+	if (min_size_[1] > max_size_[1]) { min_size_[1] = max_size_[1]; }
+
+	channel_in_ = ftl::codecs::Channel::Colour;
+
+	bool retval = false;
+
+	try {
+		retval = classifier_.load(fname_);
+	}
+	catch (cv::Exception &ex)
+	{
+		retval = false;
+		LOG(ERROR) << ex.what();
+	}
+
+	if (!retval) {
+		LOG(ERROR) << "can't load: " << fname_;
+		return false;
+	}
+
+	return true;
+}
+
+static double distance(Point2i p, Point2i q) {
+	double a = (p.x-q.x);
+	double b = (p.y-q.y);
+	return sqrt(a*a+b*b);
+}
+
+static Point2d center(Rect2d obj) {
+	return Point2d(obj.x+obj.width/2.0, obj.y+obj.height/2.0);
+}
+
+bool DetectAndTrack::detect(const Mat &im) {
+	Size min_size(im.size().width*min_size_[0], im.size().height*min_size_[1]);
+	Size max_size(im.size().width*max_size_[0], im.size().height*max_size_[1]);
+
+	vector<Rect> objects;
+
+	classifier_.detectMultiScale(im, objects,
+								 scalef_, min_neighbors_, 0, min_size, max_size);
+	
+	LOG(INFO) << "Cascade classifier found " << objects.size() << " objects";
+
+	for (const Rect2d &obj : objects) {
+		Point2d c = center(obj);
+
+		bool found = false;
+		for (auto &tracker : tracked_) {
+			if (distance(center(tracker.object), c) < max_distance_) {
+				// update? (bounding box can be quite different)
+				// tracker.object = obj;
+				found = true;
+				break;
+			}
+		}
+
+		if (!found && (tracked_.size() < max_tracked_)) {
+			cv::Ptr<cv::Tracker> tracker = cv::TrackerKCF::create();
+			tracker->init(im, obj);
+			tracked_.push_back({ tracker, obj, 0 });
+		}
+	}
+
+	return true;
+}
+
+bool DetectAndTrack::track(const Mat &im) {
+	for (auto it = tracked_.begin(); it != tracked_.end();) {
+		if (!it->tracker->update(im, it->object)) {
+			it->fail_count++;
+		}
+		else {
+			it->fail_count = 0;
+		}
+
+		if (it->fail_count > max_fail_) {
+			tracked_.erase(it);
+		}
+		else { it++; }
+	}
+
+	return true;
+}
+
+bool DetectAndTrack::apply(Frame &in, Frame &out, cudaStream_t stream) {
+	if (classifier_.empty()) {
+		LOG(ERROR) << "classifier not loaded";
+		return false;
+	}
+
+	if (!in.hasChannel(channel_in_)) {
+		LOG(ERROR) << "input channel missing";
+		return false;
+	}
+
+	in.download(channel_in_);
+	Mat im = in.get<Mat>(channel_in_);
+
+	track(im);
+
+	if ((n_frame_++ % detect_n_frames_ == 0) && (tracked_.size() < max_tracked_)) {
+		if (im.channels() == 1) {
+			gray_ = im;
+		}
+		else if (im.channels() == 4) {
+			cv::cvtColor(im, gray_, cv::COLOR_BGRA2GRAY);
+		}
+		else if (im.channels() == 3) {
+			cv::cvtColor(im, gray_, cv::COLOR_BGR2GRAY);
+		}
+		else {
+			LOG(ERROR) << "unsupported number of channels in input image";
+			return false;
+		}
+
+		detect(gray_);
+	}
+
+	// TODO: save results somewhere
+	std::vector<Rect2d> result;
+	result.reserve(tracked_.size());
+	for (auto const &tracked : tracked_) { result.push_back(tracked.object); }
+	in.create(ftl::codecs::Channel::Data, result);
+
+	// TODO: should be uploaded by operator which requires data on GPU
+	in.upload(channel_in_);
+
+	return true;
+}
diff --git a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
index 57f84ea03aa74817f13f12cfec87ce7c0ab50c22..708d0a2e8b6a456d4d58c9425805c8fd36c3a1b5 100644
--- a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
+++ b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
@@ -36,8 +36,6 @@ class Calibrate : public ftl::Configurable {
 
 	/**
 	 * @brief	Rectify and undistort stereo pair images (CPU)
-	 * @todo	Uses same rectification maps as GPU version, according to OpenCV
-	 * 			documentation for remap(), fixed point versions faster for CPU
 	 */
 	void rectifyStereo(cv::Mat &l, cv::Mat &r);
 
diff --git a/components/rgbd-sources/src/sources/stereovideo/local.cpp b/components/rgbd-sources/src/sources/stereovideo/local.cpp
index f4e11d6debca52b9049fba491c06a9cbd266fb0d..b571f35f46c519c60df9745c082db8820c01ab5b 100644
--- a/components/rgbd-sources/src/sources/stereovideo/local.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/local.cpp
@@ -56,21 +56,24 @@ LocalSource::LocalSource(nlohmann::json &config)
 		camera_b_ = nullptr;
 		stereo_ = false;
 		LOG(WARNING) << "Not able to find second camera for stereo";
-	} else {
-		camera_a_->set(cv::CAP_PROP_FRAME_WIDTH, value("width", 640));
-		camera_a_->set(cv::CAP_PROP_FRAME_HEIGHT, value("height", 480));
+	}
+	else {
 		camera_b_->set(cv::CAP_PROP_FRAME_WIDTH, value("width", 640));
 		camera_b_->set(cv::CAP_PROP_FRAME_HEIGHT, value("height", 480));
 
-		Mat frame;
-		camera_a_->grab();
-		camera_a_->retrieve(frame);
-		LOG(INFO) << "Video size : " << frame.cols << "x" << frame.rows;
-		width_ = frame.cols;
-		height_ = frame.rows;
 		stereo_ = true;
 	}
 
+	camera_a_->set(cv::CAP_PROP_FRAME_WIDTH, value("width", 640));
+	camera_a_->set(cv::CAP_PROP_FRAME_HEIGHT, value("height", 480));
+	
+	Mat frame;
+	camera_a_->grab();
+	camera_a_->retrieve(frame);
+	LOG(INFO) << "Video size : " << frame.cols << "x" << frame.rows;
+	width_ = frame.cols;
+	height_ = frame.rows;
+
 	dwidth_ = value("depth_width", width_);
 	dheight_ = value("depth_height", height_);
 
@@ -248,66 +251,30 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda
 
 	if (!camera_a_) return false;
 
-	if (camera_b_ || !stereo_) {
-		// TODO: Use threads here?
-		if (!camera_a_->retrieve(lfull)) {
-			LOG(ERROR) << "Unable to read frame from camera A";
-			return false;
-		}
-		if (camera_b_ && !camera_b_->retrieve(rfull)) {
-			LOG(ERROR) << "Unable to read frame from camera B";
-			return false;
-		}
-	} else {
-		LOG(FATAL) << "Stereo video no longer supported";
-		/*Mat frame;
-		if (!camera_a_->retrieve(frame)) {
-			LOG(ERROR) << "Unable to read frame from video";
-			return false;
-		}
-
-		int resx = frame.cols / 2;
-		//if (flip_) {
-		//	r = Mat(frame, Rect(0, 0, resx, frame.rows));
-		//	l = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows));
-		//} else {
-			l = Mat(frame, Rect(0, 0, resx, frame.rows));
-			r = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows));
-		//}*/
+	// TODO: Use threads here?
+	if (!camera_a_->retrieve(lfull)) {
+		LOG(ERROR) << "Unable to read frame from camera A";
+		return false;
 	}
 
-	/*if (downsize_ != 1.0f) {
-		// cv::cuda::resize()
-
-		cv::resize(left_, left_, cv::Size((int)(left_.cols * downsize_), (int)(left_.rows * downsize_)),
-				0, 0, cv::INTER_LINEAR);
-		cv::resize(r, r, cv::Size((int)(r.cols * downsize_), (int)(r.rows * downsize_)),
-				0, 0, cv::INTER_LINEAR);
-	}*/
-
-	// Note: this seems to be too slow on CPU...
-	/*cv::Ptr<cv::xphoto::WhiteBalancer> wb;
-	wb = cv::xphoto::createSimpleWB();
-	wb->balanceWhite(l, l);
-	wb->balanceWhite(r, r);*/
-
-	/*if (flip_v_) {
-		Mat tl, tr;
-		cv::flip(left_, tl, 0);
-		cv::flip(r, tr, 0);
-		left_ = tl;
-		r = tr;
-	}*/
+	if (camera_b_ && !camera_b_->retrieve(rfull)) {
+		LOG(ERROR) << "Unable to read frame from camera B";
+		return false;
+	}
 
-	c->rectifyStereo(lfull, rfull);
+	if (stereo_) {
+		c->rectifyStereo(lfull, rfull);
+		
+		// Need to resize
+		if (hasHigherRes()) {
+			// TODO: Use threads?
+			cv::resize(rfull, r, r.size(), 0.0, 0.0, cv::INTER_CUBIC);
+		}
+	}
 
-	// Need to resize
 	if (hasHigherRes()) {
-		// TODO: Use threads?
 		cv::resize(lfull, l, l.size(), 0.0, 0.0, cv::INTER_CUBIC);
-		cv::resize(rfull, r, r.size(), 0.0, 0.0, cv::INTER_CUBIC);
 		hres_out.upload(hres, stream);
-		//LOG(INFO) << "Early Resize: " << l.size() << " from " << lfull.size();
 	} else {
 		hres_out = cv::cuda::GpuMat();
 	}
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
index c380a4a210ff86304f1d4e19b0d41560d8e6673a..f27270e1a5be5c47e71c436d92ae7d4f14963b0a 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
@@ -18,6 +18,7 @@
 #include "ftl/operators/segmentation.hpp"
 #include "ftl/operators/disparity.hpp"
 #include "ftl/operators/mask.hpp"
+#include "ftl/operators/detectandtrack.hpp"
 
 #include "ftl/threads.hpp"
 #include "calibrate.hpp"
@@ -46,25 +47,6 @@ StereoVideoSource::~StereoVideoSource() {
 	delete lsrc_;
 }
 
-template<typename T>
-static std::pair<std::vector<int>, std::vector<T>> MatToVec(cv::Mat M) {
-	std::pair<std::vector<int>, std::vector<T>> res;
-	res.first = std::vector<int>(3);
-	res.first[0] = M.type();
-	res.first[1] = M.size().width;
-	res.first[2] = M.size().height;
-	res.second = std::vector<T>(M.begin<T>(), M.end<T>());
-	return res;
-}
-
-template<typename T>
-static cv::Mat VecToMat(std::pair<std::vector<int>, std::vector<T>> data) {
-	return cv::Mat(	data.first[1],
-					data.first[2],
-					data.first[0],
-					data.second.data());
-}
-
 void StereoVideoSource::init(const string &file) {
 	capabilities_ = kCapVideo | kCapStereo;
 
@@ -97,6 +79,7 @@ void StereoVideoSource::init(const string &file) {
 	#ifdef HAVE_OPTFLOW
 	pipeline_input_->append<ftl::operators::NVOpticalFlow>("optflow", Channel::Colour, Channel::Flow);
 	#endif
+	pipeline_input_->append<ftl::operators::DetectAndTrack>("facedetection")->set("enabled", false);
 
 	calib_ = ftl::create<Calibrate>(host_, "calibration", cv::Size(lsrc_->fullWidth(), lsrc_->fullHeight()), stream_);
 
diff --git a/components/streams/src/netstream.cpp b/components/streams/src/netstream.cpp
index f8857d5c8ec6ca6a8e65344d9438347f4855333b..d9533de4958646edc3e0b03bbfbb04f1023cdfb3 100644
--- a/components/streams/src/netstream.cpp
+++ b/components/streams/src/netstream.cpp
@@ -196,7 +196,9 @@ bool Net::begin() {
 				for (size_t i=0; i<size(); ++i) {
 					select(i, selected(i) + spkt.channel);
 				}
-				reqtally_[static_cast<int>(spkt.channel)] = static_cast<int>(pkt.frame_count)*kTallyScale;
+				if (static_cast<int>(spkt.channel) < 32) {
+					reqtally_[static_cast<int>(spkt.channel)] = static_cast<int>(pkt.frame_count)*kTallyScale;
+				}
 			} else {
 				select(spkt.frameSetID(), selected(spkt.frameSetID()) + spkt.channel);
 			}