# AprilTags on image replay

Utilizes the [AprilTag](https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes/april_tag.md) node to detect
apriltag markers on the image that's on the host computer. This example uses a custom [Host
Node](https://docs.luxonis.com/software-v3/depthai/depthai-components/host_nodes.md) called ImageReplay which reads images from
the disk and sends them to the DepthAI device for processing. One could extend this example to read video instead of images.

## Demo

This example requires the DepthAI v3 API, see [installation instructions](https://docs.luxonis.com/software-v3/depthai.md).

## Source code

#### Python

```python
#!/usr/bin/env python3

import cv2
import depthai as dai
import time
from pathlib import Path

# Get the absolute path of the current script's directory
script_dir = Path(__file__).resolve().parent
examplesRoot = (script_dir / Path('../')).resolve()  # This resolves the parent directory correctly
models = examplesRoot / 'models'
tagImage = models / 'april_tags.jpg'

class ImageReplay(dai.node.ThreadedHostNode):
    def __init__(self):
        dai.node.ThreadedHostNode.__init__(self)
        self.output =  self.createOutput()
        frame = cv2.imread(str(tagImage.resolve()))
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        imgFrame = dai.ImgFrame()
        imgFrame.setData(frame)
        imgFrame.setWidth(frame.shape[1])
        imgFrame.setHeight(frame.shape[0])
        imgFrame.setType(dai.ImgFrame.Type.GRAY8)
        self.imgFrame = imgFrame
    def run(self):
        while self.mainLoop():
            self.output.send(self.imgFrame)
            time.sleep(0.03)

with dai.Pipeline() as pipeline:
    imageReplay = pipeline.create(ImageReplay)
    aprilTagNode = pipeline.create(dai.node.AprilTag)
    imageReplay.output.link(aprilTagNode.inputImage)
    aprilTagNode.initialConfig.setFamily(dai.AprilTagConfig.Family.TAG_16H5)

    passthroughOutputQueue = aprilTagNode.passthroughInputImage.createOutputQueue()
    outQueue = aprilTagNode.out.createOutputQueue()

    color = (0, 255, 0)
    startTime = time.monotonic()
    counter = 0
    fps = 0.0

    pipeline.start()
    while pipeline.isRunning():
        aprilTagMessage = outQueue.get()
        assert(isinstance(aprilTagMessage, dai.AprilTags))
        aprilTags = aprilTagMessage.aprilTags

        counter += 1
        currentTime = time.monotonic()
        if (currentTime - startTime) > 1:
            fps = counter / (currentTime - startTime)
            counter = 0
            startTime = currentTime

        passthroughImage: dai.ImgFrame = passthroughOutputQueue.get()
        frame = passthroughImage.getCvFrame()
        frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)

        def to_int(tag):
            return (int(tag.x), int(tag.y))

        for tag in aprilTags:
            topLeft = to_int(tag.topLeft)
            topRight = to_int(tag.topRight)
            bottomRight = to_int(tag.bottomRight)
            bottomLeft = to_int(tag.bottomLeft)

            center = (int((topLeft[0] + bottomRight[0]) / 2), int((topLeft[1] + bottomRight[1]) / 2))

            cv2.line(frame, topLeft, topRight, color, 2, cv2.LINE_AA, 0)
            cv2.line(frame, topRight,bottomRight, color, 2, cv2.LINE_AA, 0)
            cv2.line(frame, bottomRight,bottomLeft, color, 2, cv2.LINE_AA, 0)
            cv2.line(frame, bottomLeft,topLeft, color, 2, cv2.LINE_AA, 0)

            idStr = "ID: " + str(tag.id)
            cv2.putText(frame, idStr, center, cv2.FONT_HERSHEY_TRIPLEX, 0.5, color)

            cv2.putText(frame, f"fps: {fps:.1f}", (200, 20), cv2.FONT_HERSHEY_TRIPLEX, 0.5, color)

        cv2.imshow("detections", frame)
        if cv2.waitKey(1) == ord("q"):
            break
```

#### C++

```cpp
#include <atomic>
#include <chrono>
#include <csignal>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <thread>

#include "depthai/depthai.hpp"

std::atomic<bool> quitEvent(false);

void signalHandler(int) {
    quitEvent = true;
}

class ImageReplay : public dai::NodeCRTP<dai::node::ThreadedHostNode, ImageReplay> {
   public:
    constexpr static const char* NAME = "ImageReplay";

    Output output{*this, {"out", DEFAULT_GROUP, {{{dai::DatatypeEnum::ImgFrame, true}}}}};

    ImageReplay() {
        // Load and prepare the image
        cv::Mat frame = cv::imread(APRIL_TAGS_PATH);
        cv::cvtColor(frame, frame, cv::COLOR_BGR2GRAY);

        // Create ImgFrame
        auto imgFrame = std::make_shared<dai::ImgFrame>();
        std::vector<uint8_t> data(frame.data, frame.data + frame.total() * frame.elemSize());
        imgFrame->setData(data);
        imgFrame->setWidth(frame.cols);
        imgFrame->setHeight(frame.rows);
        imgFrame->setType(dai::ImgFrame::Type::GRAY8);
        _imgFrame = imgFrame;
    }

    void run() override {
        while(mainLoop()) {
            output.send(_imgFrame);
            std::this_thread::sleep_for(std::chrono::milliseconds(30));
        }
    }

   private:
    std::shared_ptr<dai::ImgFrame> _imgFrame;
};

int main() {
    signal(SIGTERM, signalHandler);
    signal(SIGINT, signalHandler);

    // Create pipeline
    dai::Pipeline pipeline;

    // Create nodes
    auto imageReplay = pipeline.create<ImageReplay>();
    auto aprilTagNode = pipeline.create<dai::node::AprilTag>();

    // Link nodes
    imageReplay->output.link(aprilTagNode->inputImage);
    aprilTagNode->initialConfig->setFamily(dai::AprilTagConfig::Family::TAG_16H5);

    // Create output queues
    auto passthroughOutputQueue = aprilTagNode->passthroughInputImage.createOutputQueue();
    auto outQueue = aprilTagNode->out.createOutputQueue();

    // Start pipeline
    pipeline.start();

    // FPS calculation variables
    cv::Scalar color(0, 255, 0);
    auto startTime = std::chrono::steady_clock::now();
    int counter = 0;
    float fps = 0.0f;

    // Main loop
    while(pipeline.isRunning() && !quitEvent) {
        auto aprilTagMessage = outQueue->get<dai::AprilTags>();
        auto aprilTags = aprilTagMessage->aprilTags;

        // Calculate FPS
        counter++;
        auto currentTime = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(currentTime - startTime).count();
        if(elapsed >= 1) {
            fps = counter / static_cast<float>(elapsed);
            counter = 0;
            startTime = currentTime;
        }

        // Get passthrough image
        auto passthroughImage = passthroughOutputQueue->get<dai::ImgFrame>();
        cv::Mat frame = passthroughImage->getCvFrame();
        cv::cvtColor(frame, frame, cv::COLOR_GRAY2BGR);

        // Helper function to convert points to integers
        auto to_int = [](const dai::Point2f& p) { return cv::Point(static_cast<int>(p.x), static_cast<int>(p.y)); };

        // Draw detections
        for(const auto& tag : aprilTags) {
            auto topLeft = to_int(tag.topLeft);
            auto topRight = to_int(tag.topRight);
            auto bottomRight = to_int(tag.bottomRight);
            auto bottomLeft = to_int(tag.bottomLeft);

            auto center = cv::Point((topLeft.x + bottomRight.x) / 2, (topLeft.y + bottomRight.y) / 2);

            // Draw rectangle
            cv::line(frame, topLeft, topRight, color, 2, cv::LINE_AA, 0);
            cv::line(frame, topRight, bottomRight, color, 2, cv::LINE_AA, 0);
            cv::line(frame, bottomRight, bottomLeft, color, 2, cv::LINE_AA, 0);
            cv::line(frame, bottomLeft, topLeft, color, 2, cv::LINE_AA, 0);

            // Draw ID
            std::string idStr = "ID: " + std::to_string(tag.id);
            cv::putText(frame, idStr, center, cv::FONT_HERSHEY_TRIPLEX, 0.5, color);

            // Draw FPS
            cv::putText(frame, "fps: " + std::to_string(fps).substr(0, 4), cv::Point(200, 20), cv::FONT_HERSHEY_TRIPLEX, 0.5, color);
        }

        cv::imshow("detections", frame);
        if(cv::waitKey(1) == 'q') {
            break;
        }
    }

    pipeline.stop();
    pipeline.wait();

    return 0;
}
```

### Need assistance?

Head over to [Discussion Forum](https://discuss.luxonis.com/) for technical support or any other questions you might have.
