diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt
index 5d2008bf0d85ef6867c7b1580aa98cc714884a49..ff7d290981c1a84c5ee211219ae0651f85c58c77 100644
--- a/components/rgbd-sources/CMakeLists.txt
+++ b/components/rgbd-sources/CMakeLists.txt
@@ -3,6 +3,7 @@ set(RGBDSRC
 	src/local.cpp
 	src/disparity.cpp
 	src/source.cpp
+	src/frame.cpp
 	src/stereovideo.cpp
 	src/middlebury_source.cpp
 	src/net.cpp
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
index e41d8d18866f4c68821a74390dea87e27f392938..ddc624d9b2dc05ee3745400d366c3017735579fc 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -36,49 +36,8 @@ public:
 	 * true.
 	 */
 
-	const cv::Mat& getChannel(const ftl::rgbd::channel_t& channel)
-	{
-		auto idx = _channelIdx(channel);
-		if (!(available_[idx] & mask_host))
-		{
-			if (available_[idx] & mask_gpu)
-			{
-				channels_gpu_[idx].download(channels_host_[idx]);
-				available_[idx] |= mask_host;
-			}
-		}
-
-		return channels_host_[idx];
-	}
-
-	cv::Mat& setChannel(const ftl::rgbd::channel_t& channel)
-	{
-		auto idx = _channelIdx(channel);
-		available_[idx] = mask_host;
-		return channels_host_[idx];
-	}
-
-	const cv::cuda::GpuMat& getChannelGpu(const ftl::rgbd::channel_t& channel)
-	{
-		auto idx = _channelIdx(channel);
-		if (!(available_[idx] & mask_gpu))
-		{
-			if (available_[idx] & mask_host)
-			{
-				channels_gpu_[idx].upload(channels_host_[idx]);
-				available_[idx] |= mask_gpu;
-			}
-		}
-		
-		return channels_gpu_[idx];
-	}
-
-	cv::cuda::GpuMat& setChannelGpu(const ftl::rgbd::channel_t& channel)
-	{
-		auto idx = _channelIdx(channel);
-		available_[idx] = mask_gpu;
-		return channels_gpu_[idx];
-	}
+	template <typename T> const T& getChannel(const ftl::rgbd::channel_t& channel);
+	template <typename T> T& setChannel(const ftl::rgbd::channel_t& channel);
 
 private:
 
@@ -112,4 +71,4 @@ private:
 };
 
 }
-}
\ No newline at end of file
+}
diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..62b3795ff79c4f728cc637b5e03dd646642a1abe
--- /dev/null
+++ b/components/rgbd-sources/src/frame.cpp
@@ -0,0 +1,52 @@
+
+#include <ftl/rgbd/frame.hpp>
+
+namespace ftl {
+namespace rgbd {
+
+template<> const cv::Mat& Frame::getChannel(const ftl::rgbd::channel_t& channel)
+{
+	auto idx = _channelIdx(channel);
+	if (!(available_[idx] & mask_host))
+	{
+		if (available_[idx] & mask_gpu)
+		{
+			channels_gpu_[idx].download(channels_host_[idx]);
+			available_[idx] |= mask_host;
+		}
+	}
+
+	return channels_host_[idx];
+}
+
+template<> cv::Mat& Frame::setChannel(const ftl::rgbd::channel_t& channel)
+{
+	auto idx = _channelIdx(channel);
+	available_[idx] = mask_host;
+	return channels_host_[idx];
+}
+
+template<> const cv::cuda::GpuMat& Frame::getChannel(const ftl::rgbd::channel_t& channel)
+{
+	auto idx = _channelIdx(channel);
+	if (!(available_[idx] & mask_gpu))
+	{
+		if (available_[idx] & mask_host)
+		{
+			channels_gpu_[idx].upload(channels_host_[idx]);
+			available_[idx] |= mask_gpu;
+		}
+	}
+	
+	return channels_gpu_[idx];
+}
+
+template<> cv::cuda::GpuMat& Frame::setChannel(const ftl::rgbd::channel_t& channel)
+{
+	auto idx = _channelIdx(channel);
+	available_[idx] = mask_gpu;
+	return channels_gpu_[idx];
+}
+
+}
+}
\ No newline at end of file
diff --git a/components/rgbd-sources/src/stereovideo.cpp b/components/rgbd-sources/src/stereovideo.cpp
index 75ed21230cfb07808d5e2b5d10e1bc0311a2a88a..f5257f5cb9c981be3a643e347ce8ba396b87c97d 100644
--- a/components/rgbd-sources/src/stereovideo.cpp
+++ b/components/rgbd-sources/src/stereovideo.cpp
@@ -173,8 +173,8 @@ bool StereoVideoSource::capture(int64_t ts) {
 bool StereoVideoSource::retrieve() {
 	auto &frame = frames_[0];
 	frame.reset();
-	auto &left = frame.setChannelGpu(ftl::rgbd::kChanLeft);
-	auto &right = frame.setChannelGpu(ftl::rgbd::kChanRight);
+	auto &left = frame.setChannel<cv::cuda::GpuMat>(ftl::rgbd::kChanLeft);
+	auto &right = frame.setChannel<cv::cuda::GpuMat>(ftl::rgbd::kChanRight);
 	lsrc_->get(left, right, calib_, stream2_);
 
 #ifdef HAVE_OPTFLOW
@@ -208,15 +208,15 @@ void StereoVideoSource::swap() {
 
 bool StereoVideoSource::compute(int n, int b) {
 	auto &frame = frames_[1];
-	auto &left = frame.getChannelGpu(ftl::rgbd::kChanLeft);
-	auto &right = frame.getChannelGpu(ftl::rgbd::kChanRight);
+	auto &left = frame.getChannel<cv::cuda::GpuMat>(ftl::rgbd::kChanLeft);
+	auto &right = frame.getChannel<cv::cuda::GpuMat>(ftl::rgbd::kChanRight);
 
 	const ftl::rgbd::channel_t chan = host_->getChannel();
 	if (left.empty() || right.empty()) return false;
 
 	if (chan == ftl::rgbd::kChanDepth) {
-		auto &depth = frame.setChannelGpu(ftl::rgbd::kChanDepth);
-		auto &disp = frame.setChannelGpu(ftl::rgbd::kChanDisparity);
+		auto &depth = frame.setChannel<cv::cuda::GpuMat>(ftl::rgbd::kChanDepth);
+		auto &disp = frame.setChannel<cv::cuda::GpuMat>(ftl::rgbd::kChanDisparity);
 
 		if (depth.empty()) depth = cv::cuda::GpuMat(left.size(), CV_32FC1);
 		if (disp.empty()) disp = cv::cuda::GpuMat(left.size(), CV_32FC1);