diff --git a/CMakeLists.txt b/CMakeLists.txt
index 42198650d9b9835f14a0b72fb6f82050f6f2ed3c..54256ba0e0746e5a48a1df20c0d2ee841326e0b0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -487,6 +487,8 @@ if (HAVE_NANOGUI)
 	add_subdirectory(applications/gui2)
 endif()
 
+add_subdirectory(applications/aruco)
+
 ### Generate Build Configuration Files =========================================
 
 configure_file(${CMAKE_SOURCE_DIR}/components/common/cpp/include/ftl/config.h.in
diff --git a/applications/aruco/CMakeLists.txt b/applications/aruco/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0975581448490624c343ef55a30d6e9496fc2a12
--- /dev/null
+++ b/applications/aruco/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(FTLARUCOSRC
+	src/main.cpp
+)
+
+add_executable(ftl-aruco ${FTLARUCOSRC})
+
+target_include_directories(ftl-aruco PRIVATE src)
+
+target_link_libraries(ftl-aruco ftlcommon Threads::Threads ${OpenCV_LIBS})
+
+
diff --git a/applications/aruco/src/main.cpp b/applications/aruco/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c1daf03147dc7660239bff46eb5c2f26062e570
--- /dev/null
+++ b/applications/aruco/src/main.cpp
@@ -0,0 +1,50 @@
+#include <ftl/timer.hpp>
+#include <ftl/configuration.hpp>
+
+#include <opencv2/highgui.hpp>
+#include <opencv2/aruco.hpp>
+
+#include <vector>
+#include <string>
+
+int main(int argc, char** argv) {
+	std::vector<cv::Mat> tags;
+	unsigned int ntags = 10;
+	cv::Ptr<cv::aruco::Dictionary> dict =
+		cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
+	unsigned int size = 1024;
+	unsigned int margin = 1;
+	unsigned int delay = 100;
+
+	argc--;
+	argv++;
+	auto opts = ftl::config::read_options(&argv, &argc);
+
+	if (opts.count("delay"))
+		delay = std::stoi(opts["delay"]);
+	if (opts.count("dict"))
+		dict = cv::aruco::getPredefinedDictionary(std::stoi(opts["dict"]));
+	if (opts.count("ntags"))
+		ntags = std::stoi(opts["ntags"]);
+	if (opts.count("size"))
+		size = std::stoi(opts["size"]);
+
+	for (unsigned int i = 0; i < ntags; i++) {
+		cv::aruco::drawMarker(dict, i, size, tags.emplace_back(), margin);
+	}
+
+	int id = 0;
+	ftl::timer::setInterval(delay);
+	ftl::timer::setHighPrecision(true);
+	auto h = ftl::timer::add(ftl::timer::kTimerMain, [&](uint64_t){
+
+		cv::imshow("ArUco", tags[id]);
+		if (cv::waitKey(1) == 27) { ftl::timer::stop(false); }
+		id = (id + 1) % ntags;
+		return true;
+	});
+
+	ftl::timer::start(true);
+
+	return 0;
+}
\ No newline at end of file