# Camera ROI-based exposure and focus

This example lets user select a region of interest (ROI) on the frame (on the host computer), and after selection is complete, it
will set the auto-exposure (AE) and auto-focus (AF) of the camera to the selected ROI.

Similar application could be to set AF/AE based on object detection results, so the camera would automatically refocus and adjust
exposure when a specific object is detected.

> **OAK4 OS version**
> If you're using OAK4 cameras, make sure to use the latest version of Luxonis OS (1.6 or newer), otherwise ROI won't be mapped
correctly.

## Demo

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

## Pipeline

### examples/camera_roi_exposure_focus.pipeline.json

```json
{
  "pipeline": {
    "connections": [
      {
        "node1Id": 4,
        "node1Output": "out",
        "node1OutputGroup": "",
        "node2Id": 0,
        "node2Input": "inputControl",
        "node2InputGroup": ""
      },
      {
        "node1Id": 0,
        "node1Output": "0",
        "node1OutputGroup": "dynamicOutputs",
        "node2Id": 5,
        "node2Input": "in",
        "node2InputGroup": ""
      }
    ],
    "globalProperties": {
      "calibData": null,
      "cameraTuningBlobSize": null,
      "cameraTuningBlobUri": "",
      "leonCssFrequencyHz": 700000000.0,
      "leonMssFrequencyHz": 700000000.0,
      "pipelineName": null,
      "pipelineVersion": null,
      "sippBufferSize": 18432,
      "sippDmaBufferSize": 16384,
      "xlinkChunkSize": -1
    },
    "nodes": [
      [
        4,
        {
          "alias": "",
          "id": 4,
          "ioInfo": [
            [
              [
                "",
                "out"
              ],
              {
                "blocking": false,
                "group": "",
                "id": 3,
                "name": "out",
                "queueSize": 8,
                "type": 0,
                "waitForMessage": false
              }
            ]
          ],
          "logLevel": 3,
          "name": "XLinkIn",
          "parentId": -1,
          "properties": {
            "maxDataSize": 5242880,
            "numFrames": 8,
            "streamName": "__x_0__inputControl"
          }
        }
      ],
      [
        5,
        {
          "alias": "",
          "id": 5,
          "ioInfo": [
            [
              [
                "",
                "in"
              ],
              {
                "blocking": true,
                "group": "",
                "id": 4,
                "name": "in",
                "queueSize": 3,
                "type": 3,
                "waitForMessage": false
              }
            ]
          ],
          "logLevel": 3,
          "name": "XLinkOut",
          "parentId": -1,
          "properties": {
            "maxFpsLimit": -1.0,
            "metadataOnly": false,
            "streamName": "__x_0_0"
          }
        }
      ],
      [
        0,
        {
          "alias": "",
          "id": 0,
          "ioInfo": [
            [
              [
                "",
                "raw"
              ],
              {
                "blocking": false,
                "group": "",
                "id": 1,
                "name": "raw",
                "queueSize": 8,
                "type": 0,
                "waitForMessage": false
              }
            ],
            [
              [
                "dynamicOutputs",
                "0"
              ],
              {
                "blocking": false,
                "group": "dynamicOutputs",
                "id": 2,
                "name": "0",
                "queueSize": 8,
                "type": 0,
                "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,
            "calibAlpha": null,
            "cameraName": "",
            "colorOrder": 0,
            "fp16": false,
            "fps": -1.0,
            "imageOrientation": -1,
            "initialControl": {
              "aeLockMode": false,
              "aeMaxExposureTimeUs": 0,
              "aeRegion": {
                "height": 0,
                "priority": 0,
                "width": 0,
                "x": 0,
                "y": 0
              },
              "afRegion": {
                "height": 0,
                "priority": 0,
                "width": 0,
                "x": 0,
                "y": 0
              },
              "antiBandingMode": 0,
              "autoFocusMode": 3,
              "awbLockMode": false,
              "awbMode": 0,
              "brightness": 0,
              "captureIntent": 0,
              "chromaDenoise": 0,
              "cmdMask": 0,
              "contrast": 0,
              "controlMode": 0,
              "effectMode": 0,
              "expCompensation": 0,
              "expManual": {
                "exposureTimeUs": 0,
                "frameDurationUs": 0,
                "sensitivityIso": 0
              },
              "frameSyncMode": 0,
              "lensPosAutoInfinity": 0,
              "lensPosAutoMacro": 0,
              "lensPosition": 0,
              "lensPositionRaw": 0.0,
              "lowPowerNumFramesBurst": 0,
              "lowPowerNumFramesDiscard": 0,
              "lumaDenoise": 0,
              "miscControls": [],
              "saturation": 0,
              "sceneMode": 0,
              "sharpness": 0,
              "strobeConfig": {
                "activeLevel": 0,
                "enable": 0,
                "gpioNumber": 0
              },
              "strobeTimings": {
                "durationUs": 0,
                "exposureBeginOffsetUs": 0,
                "exposureEndOffsetUs": 0
              },
              "wbColorTemp": 0
            },
            "interleaved": true,
            "isp3aFps": 0,
            "ispScale": {
              "horizDenominator": 0,
              "horizNumerator": 0,
              "vertDenominator": 0,
              "vertNumerator": 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": [
                      1920,
                      1080
                    ]
                  }
                },
                "type": null
              }
            ],
            "previewHeight": 300,
            "previewKeepAspectRatio": false,
            "previewWidth": 300,
            "rawPacked": null,
            "resolutionHeight": -1,
            "resolutionWidth": -1,
            "sensorCropX": -1.0,
            "sensorCropY": -1.0,
            "sensorType": -1,
            "stillHeight": -1,
            "stillWidth": -1,
            "videoHeight": -1,
            "videoWidth": -1,
            "warpMeshHeight": 0,
            "warpMeshSource": -1,
            "warpMeshStepHeight": 32,
            "warpMeshStepWidth": 32,
            "warpMeshUri": "",
            "warpMeshWidth": 0
          }
        }
      ]
    ]
  }
}
```

## Source code

#### Python

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

import cv2
import depthai as dai

# Create pipeline
with dai.Pipeline() as pipeline:
    # Define source and output
    cam = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_A)
    cam_input_q = cam.inputControl.createInputQueue()
    stream_q = cam.requestOutput((1920, 1080)).createOutputQueue()

    cam_q_in = cam.inputControl.createInputQueue()

    # Connect to device and start pipeline
    pipeline.start()

    # ROI selection variables
    start_points = []
    roi_rect = None
    scale_factors = None
    # Mouse callback function for ROI selection
    def select_roi(event, x, y, flags, param):
        global start_points, roi_rect
        def set_roi_rect():
            global roi_rect
            x1, y1 = start_points
            x2, y2 = (x, y)
            roi_rect = (min(x1, x2), min(y1, y2), abs(x2-x1), abs(y2-y1))

        if event == cv2.EVENT_LBUTTONDOWN:
            roi_rect = None
            start_points = (x, y)
        elif event == cv2.EVENT_MOUSEMOVE and start_points:
            set_roi_rect()
        elif event == cv2.EVENT_LBUTTONUP and start_points:
            set_roi_rect()
            roi_rect_scaled = (
                int(roi_rect[0] * scale_factors[0]),
                int(roi_rect[1] * scale_factors[1]),
                int(roi_rect[2] * scale_factors[0]),
                int(roi_rect[3] * scale_factors[1])
            )
            print(f"ROI selected: {roi_rect}")
            ctrl = dai.CameraControl()
            print(f"Scaled ROI selected: {roi_rect_scaled}. Setting exposure and focus to this region.")
            ctrl.setAutoExposureRegion(*roi_rect_scaled)
            ctrl.setAutoFocusRegion(*roi_rect_scaled)
            cam_q_in.send(ctrl)
            start_points = None

    # Create a window and set the mouse callback
    cv2.namedWindow("video")
    cv2.setMouseCallback("video", select_roi)

    while pipeline.isRunning():
        img_hd: dai.ImgFrame = stream_q.get()
        if scale_factors is None:
            print(img_hd.getTransformation().getSourceSize(), img_hd.getTransformation().getSize())
            scale_factors = (img_hd.getTransformation().getSourceSize()[0] / img_hd.getTransformation().getSize()[0],
                            img_hd.getTransformation().getSourceSize()[1] / img_hd.getTransformation().getSize()[1])
        frame = img_hd.getCvFrame()

        # Draw the ROI rectangle if it exists
        if roi_rect is not None:
            x, y, w, h = roi_rect
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)

        cv2.imshow("video", frame)

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

#### C++

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

#include "depthai/depthai.hpp"

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

void signalHandler(int) {
    quitEvent = true;
}

// Global variables for ROI selection
std::vector<cv::Point> startPoints;
cv::Rect roiRect;
std::pair<float, float> scaleFactors;

// Mouse callback function for ROI selection
void selectRoi(int event, int x, int y, int flags, void* userdata) {
    auto* camQIn = static_cast<std::shared_ptr<dai::InputQueue>*>(userdata);

    if(event == cv::EVENT_LBUTTONDOWN) {
        roiRect = cv::Rect();
        startPoints.clear();
        startPoints.push_back(cv::Point(x, y));
    } else if(event == cv::EVENT_MOUSEMOVE && !startPoints.empty()) {
        cv::Point start = startPoints[0];
        roiRect = cv::Rect(std::min(start.x, x), std::min(start.y, y), std::abs(x - start.x), std::abs(y - start.y));
    } else if(event == cv::EVENT_LBUTTONUP && !startPoints.empty()) {
        cv::Point start = startPoints[0];
        roiRect = cv::Rect(std::min(start.x, x), std::min(start.y, y), std::abs(x - start.x), std::abs(y - start.y));

        // Scale ROI to original resolution
        cv::Rect roiRectScaled(static_cast<int>(roiRect.x * scaleFactors.first),
                               static_cast<int>(roiRect.y * scaleFactors.second),
                               static_cast<int>(roiRect.width * scaleFactors.first),
                               static_cast<int>(roiRect.height * scaleFactors.second));

        std::cout << "ROI selected: " << roiRect << std::endl;
        std::cout << "Scaled ROI selected: " << roiRectScaled << ". Setting exposure and focus to this region." << std::endl;

        // Create and send control message
        auto ctrl = std::make_shared<dai::CameraControl>();
        ctrl->setAutoExposureRegion(roiRectScaled.x, roiRectScaled.y, roiRectScaled.width, roiRectScaled.height);
        ctrl->setAutoFocusRegion(roiRectScaled.x, roiRectScaled.y, roiRectScaled.width, roiRectScaled.height);
        (*camQIn)->send(ctrl);

        startPoints.clear();
    }
}

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);

    // Create nodes
    auto cam = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_A);
    auto camQIn = cam->inputControl.createInputQueue();
    auto streamQ = cam->requestOutput(std::make_pair(1920, 1080))->createOutputQueue();

    // Start pipeline
    pipeline.start();

    // Create window and set mouse callback
    cv::namedWindow("video");
    cv::setMouseCallback("video", selectRoi, &camQIn);

    while(pipeline.isRunning() && !quitEvent) {
        auto imgHd = streamQ->get<dai::ImgFrame>();
        if(imgHd == nullptr) continue;

        // Calculate scale factors if not set
        if(scaleFactors.first == 0.0f) {
            auto sourceSize = imgHd->transformation.getSourceSize();
            auto targetSize = imgHd->transformation.getSize();
            scaleFactors = std::make_pair(static_cast<float>(sourceSize.first) / targetSize.first, static_cast<float>(sourceSize.second) / targetSize.second);
            std::cout << "Source size: " << sourceSize.first << "x" << sourceSize.second << std::endl;
            std::cout << "Target size: " << targetSize.first << "x" << targetSize.second << std::endl;
        }

        cv::Mat frame = imgHd->getCvFrame();

        // Draw ROI rectangle if it exists
        if(roiRect.width > 0 && roiRect.height > 0) {
            cv::rectangle(frame, roiRect, cv::Scalar(0, 255, 0), 2);
        }

        cv::imshow("video", 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.
