# AprilTags from camera (12MP)

Utilizes the [AprilTag](https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes/april_tag.md) node to detect
apriltag markers on the camera stream at 12MP. It outlines detected tags and their IDs on the screen.

To preserve bandwidth, instead of streaming 12MP video stream to the host computer, the example uses
[ImageManip](https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes/image_manip.md) to downscale 12MP frames to
1332x1000 (3x smaller) before sending it to the host computer.

## Demo

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

## Pipeline

### examples/april_tags_12mp.pipeline.json

```json
{"pipeline": {"connections": [{"node1Id": 2, "node1Output": "out", "node1OutputGroup": "", "node2Id": 5, "node2Input": "in", "node2InputGroup": ""}, {"node1Id": 0, "node1Output": "0", "node1OutputGroup": "dynamicOutputs", "node2Id": 3, "node2Input": "in", "node2InputGroup": ""}, {"node1Id": 0, "node1Output": "0", "node1OutputGroup": "dynamicOutputs", "node2Id": 2, "node2Input": "inputImage", "node2InputGroup": ""}], "globalProperties": {"calibData": null, "cameraTuningBlobSize": null, "cameraTuningBlobUri": "", "leonCssFrequencyHz": 700000000.0, "leonMssFrequencyHz": 700000000.0, "pipelineName": null, "pipelineVersion": null, "sippBufferSize": 18432, "sippDmaBufferSize": 16384, "xlinkChunkSize": -1}, "nodes": [[5, {"alias": "", "id": 5, "ioInfo": [[["", "in"], {"blocking": true, "group": "", "id": 8, "name": "in", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "XLinkOut", "parentId": -1, "properties": {"maxFpsLimit": -1.0, "metadataOnly": false, "streamName": "__x_2_out"}}], [3, {"alias": "", "id": 3, "ioInfo": [[["", "in"], {"blocking": true, "group": "", "id": 7, "name": "in", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "XLinkOut", "parentId": -1, "properties": {"maxFpsLimit": -1.0, "metadataOnly": false, "streamName": "__x_0_dynamicOutputs_0"}}], [2, {"alias": "", "id": 2, "ioInfo": [[["", "out"], {"blocking": false, "group": "", "id": 6, "name": "out", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "inputImage"], {"blocking": true, "group": "", "id": 5, "name": "inputImage", "queueSize": 3, "type": 3, "waitForMessage": false}], [["", "inputConfig"], {"blocking": true, "group": "", "id": 4, "name": "inputConfig", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "ImageManip", "parentId": -1, "properties": {"backend": 0, "initialConfig": {"base": {"background": 0, "backgroundB": 0, "backgroundG": 0, "backgroundR": 0, "center": true, "colormap": 0, "operations": [], "outputHeight": 1000, "outputWidth": 1332, "resizeMode": 1, "undistort": false}, "outputFrameType": 33, "reusePreviousImage": false, "skipCurrentImage": false}, "numFramesPool": 4, "outputFrameSize": 2162688, "performanceMode": 0}}], [0, {"alias": "", "id": 0, "ioInfo": [[["dynamicOutputs", "0"], {"blocking": false, "group": "dynamicOutputs", "id": 3, "name": "0", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "raw"], {"blocking": false, "group": "", "id": 2, "name": "raw", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "mockIsp"], {"blocking": true, "group": "", "id": 1, "name": "mockIsp", "queueSize": 8, "type": 3, "waitForMessage": false}], [["", "inputControl"], {"blocking": true, "group": "", "id": 0, "name": "inputControl", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "Camera", "parentId": -1, "properties": {"boardSocket": 0, "cameraName": "", "fps": -1.0, "imageOrientation": -1, "initialControl": {"aeLockMode": false, "aeMaxExposureTimeUs": 65544, "aeRegion": {"height": 0, "priority": 0, "width": 0, "x": 0, "y": 0}, "afRegion": {"height": 43, "priority": 131077, "width": 12, "x": 0, "y": 0}, "antiBandingMode": 0, "autoFocusMode": 3, "awbLockMode": false, "awbMode": 0, "brightness": 0, "captureIntent": 0, "chromaDenoise": 136, "cmdMask": 0, "contrast": -116, "controlMode": 12, "effectMode": 0, "enableHdr": false, "expCompensation": 0, "expManual": {"exposureTimeUs": 0, "frameDurationUs": 0, "sensitivityIso": 0}, "frameSyncMode": 6, "lensPosAutoInfinity": 0, "lensPosAutoMacro": 0, "lensPosition": 0, "lensPositionRaw": 0.0, "lowPowerNumFramesBurst": 1, "lowPowerNumFramesDiscard": 0, "lumaDenoise": 0, "miscControls": [], "saturation": 0, "sceneMode": 0, "sharpness": 2, "strobeConfig": {"activeLevel": 110, "enable": 0, "gpioNumber": 111}, "strobeTimings": {"durationUs": 2949132, "exposureBeginOffsetUs": 6649189, "exposureEndOffsetUs": 1704760}, "wbColorTemp": 1}, "isp3aFps": 0, "mockIspHeight": -1, "mockIspWidth": -1, "numFramesPoolIsp": 3, "numFramesPoolPreview": 4, "numFramesPoolRaw": 3, "numFramesPoolStill": 4, "numFramesPoolVideo": 4, "outputRequests": [{"enableUndistortion": null, "fps": {"value": null}, "resizeMode": 0, "size": {"value": {"index": 0, "value": [4000, 3000]}}, "type": null}], "resolutionHeight": -1, "resolutionWidth": -1}}]]}}
```

## Source code

#### Python

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

import cv2
import depthai as dai
import time

FULL_RES = (4000, 3000) # 12MP
PREVIEW_SIZE = (1332, 1000) # 1/3 of 12MP, to preserve bandwidth

with dai.Pipeline() as pipeline:
    hostCamera = pipeline.create(dai.node.Camera).build()
    aprilTagNode = pipeline.create(dai.node.AprilTag)
    outputCam = hostCamera.requestOutput(FULL_RES)
    outputCam.link(aprilTagNode.inputImage)
    outQueue = aprilTagNode.out.createOutputQueue()

    # We use ImageManip instead of creating a new smaller output because of the syncing,
    # ATM, AprilTags don't contain timestamps, so we can't sync them with frames
    manip = pipeline.create(dai.node.ImageManip)
    manip.initialConfig.setOutputSize(PREVIEW_SIZE[0], PREVIEW_SIZE[1], dai.ImageManipConfig.ResizeMode.STRETCH)
    manip.setMaxOutputFrameSize(2162688)
    outputCam.link(manip.inputImage)
    frameQ = manip.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

        def rescale(p: dai.Point2f):
            return (int(p.x / FULL_RES[0] * PREVIEW_SIZE[0]),
                    int(p.y / FULL_RES[1] * PREVIEW_SIZE[1]))

        passthroughImage: dai.ImgFrame = frameQ.get()
        frame = passthroughImage.getCvFrame()
        for tag in aprilTags:
            topLeft = rescale(tag.topLeft)
            topRight = rescale(tag.topRight)
            bottomRight = rescale(tag.bottomRight)
            bottomLeft = rescale(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, 1, color)

        cv2.imshow("detections", frame)

        if cv2.waitKey(1) == ord("q"):
            break
```

#### C++

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

#include "depthai/depthai.hpp"

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

void signalHandler(int) {
    quitEvent = true;
}

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

    // Create device
    std::shared_ptr<dai::Device> device = std::make_shared<dai::Device>();

    // Create pipeline
    dai::Pipeline pipeline(device);

    // Constants
    const cv::Size FULL_RES(4000, 3000);      // 12MP
    const cv::Size PREVIEW_SIZE(1332, 1000);  // 1/3 of 12MP

    // Create nodes
    auto hostCamera = pipeline.create<dai::node::Camera>()->build();
    auto aprilTagNode = pipeline.create<dai::node::AprilTag>();
    auto manip = pipeline.create<dai::node::ImageManip>();

    // Configure nodes
    auto outputCam = hostCamera->requestOutput(std::make_pair(FULL_RES.width, FULL_RES.height));
    outputCam->link(aprilTagNode->inputImage);

    // Configure ImageManip
    manip->initialConfig->setOutputSize(PREVIEW_SIZE.width, PREVIEW_SIZE.height, dai::ImageManipConfig::ResizeMode::STRETCH);
    manip->setMaxOutputFrameSize(2162688);
    outputCam->link(manip->inputImage);

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

    // Start pipeline
    pipeline.start();

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

    while(pipeline.isRunning() && !quitEvent) {
        auto aprilTagMessage = outQueue->get<dai::AprilTags>();
        if(aprilTagMessage == nullptr) continue;

        // FPS calculation
        counter++;
        auto currentTime = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count();
        if(elapsed > 1000) {
            fps = counter * 1000.0f / elapsed;
            counter = 0;
            startTime = currentTime;
        }

        // Helper function to rescale points
        auto rescale = [&FULL_RES, &PREVIEW_SIZE](const dai::Point2f& p) {
            return cv::Point(static_cast<int>(p.x / FULL_RES.width * PREVIEW_SIZE.width), static_cast<int>(p.y / FULL_RES.height * PREVIEW_SIZE.height));
        };

        auto frame = frameQ->get<dai::ImgFrame>();
        if(frame == nullptr) continue;

        cv::Mat cvFrame = frame->getCvFrame();
        for(const auto& tag : aprilTagMessage->aprilTags) {
            auto topLeft = rescale(tag.topLeft);
            auto topRight = rescale(tag.topRight);
            auto bottomRight = rescale(tag.bottomRight);
            auto bottomLeft = rescale(tag.bottomLeft);

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

            // Draw tag boundaries
            cv::line(cvFrame, topLeft, topRight, color, 2, cv::LINE_AA, 0);
            cv::line(cvFrame, topRight, bottomRight, color, 2, cv::LINE_AA, 0);
            cv::line(cvFrame, bottomRight, bottomLeft, color, 2, cv::LINE_AA, 0);
            cv::line(cvFrame, bottomLeft, topLeft, color, 2, cv::LINE_AA, 0);

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

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

        cv::imshow("detections", cvFrame);

        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.
