Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ if(DEPTHAI_DYNAMIC_CALIBRATION_SUPPORT)
endif()

set(TARGET_OPENCV_SOURCES
src/opencv/ColorizeDepthFrame.cpp
src/opencv/ImgFrame.cpp
src/pipeline/node/host/Display.cpp
src/pipeline/node/host/HostCamera.cpp
Expand Down
28 changes: 27 additions & 1 deletion bindings/python/src/pipeline/datatype/ImgFrameBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
// depthai
#include "depthai/common/ImgTransformations.hpp"
#include "depthai/pipeline/datatype/ImgFrame.hpp"
#include "depthai/utility/ColorizeDepthFrame.hpp"
#include "ndarray_converter.h"
// pybind
#include <pybind11/cast.h>
#include <pybind11/chrono.h>
#include <pybind11/numpy.h>

void bind_imgframe(pybind11::module& m, void* pCallstack) {
using namespace dai;
Expand Down Expand Up @@ -324,4 +324,30 @@ void bind_imgframe(pybind11::module& m, void* pCallstack) {
// add aliases dai.ImgFrame.Type and dai.ImgFrame.Specs
// m.attr("ImgFrame").attr("Type") = m.attr("RawImgFrame").attr("Type");
// m.attr("ImgFrame").attr("Specs") = m.attr("RawImgFrame").attr("Specs");

#ifdef DEPTHAI_HAVE_OPENCV_SUPPORT
auto utility = m.def_submodule("utility", "Utility functions");
utility.def(
"colorizeDepthFrame",
[](const ImgFrame& frame, float minDepth, float maxDepth, int colormap, bool useLog) {
return dai::utility::colorizeDepthFrame(frame, minDepth, maxDepth, static_cast<cv::ColormapTypes>(colormap), useLog);
},
py::arg("frame"),
py::arg("minDepth") = 300.0f,
py::arg("maxDepth") = 12000.0f,
py::arg("colormap") = static_cast<int>(cv::COLORMAP_JET),
py::arg("useLog") = true,
DOC(dai, utility, colorizeDepthFrame));
utility.def(
"colorizeDepthFrame",
[](const cv::Mat& frame, float minDepth, float maxDepth, int colormap, bool useLog) {
return dai::utility::colorizeDepthFrame(frame, minDepth, maxDepth, static_cast<cv::ColormapTypes>(colormap), useLog);
},
py::arg("frame"),
py::arg("minDepth") = 300.0f,
py::arg("maxDepth") = 12000.0f,
py::arg("colormap") = static_cast<int>(cv::COLORMAP_JET),
py::arg("useLog") = true,
DOC(dai, utility, colorizeDepthFrame, 2));
#endif
}
4 changes: 4 additions & 0 deletions bindings/python/tests/imgframe_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def assert_images_close(expected, recovered, tolerance, msg):
assert max_diff <= tolerance, f"{msg} max abs diff too high: {max_diff} > {tolerance}"


def test_colorize_depth_frame_is_in_utility_namespace():
assert callable(dai.utility.colorizeDepthFrame)
assert not hasattr(dai, "colorizeDepthFrame")


COLOR_TYPES = [
pytest.param(dai.ImgFrame.Type.BGR888p, 0.5, id="BGR888p"),
Expand Down
23 changes: 2 additions & 21 deletions examples/cpp/AutoCalibration/auto_calibration_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,6 @@

#include "depthai/depthai.hpp"

// Visualization helper
void showDepth(const cv::Mat& depthFrame, const std::string& windowName = "Depth", int minDistance = 500, int maxDistance = 5000) {
if(maxDistance <= minDistance) return;

cv::Mat clipped = depthFrame.clone();
clipped.setTo(minDistance, depthFrame < minDistance);
clipped.setTo(maxDistance, depthFrame > maxDistance);

cv::Mat displayFrame;
double scale = 255.0 / (maxDistance - minDistance);
double offset = -minDistance * scale;
clipped.convertTo(displayFrame, CV_8UC1, scale, offset);

cv::Mat colorMap;
cv::applyColorMap(displayFrame, colorMap, cv::COLORMAP_TURBO);

cv::imshow(windowName, colorMap);
}

std::tuple<double, double, double> rotationMatrixToEulerAngles(const cv::Matx33d& rotationMatrix) {
constexpr double kPi = 3.14159265358979323846;
const double sy = std::sqrt(rotationMatrix(0, 0) * rotationMatrix(0, 0) + rotationMatrix(1, 0) * rotationMatrix(1, 0));
Expand Down Expand Up @@ -143,8 +124,8 @@ int main() {
}
}

auto depthFrame = stereoOut->get<dai::ImgFrame>();
showDepth(depthFrame->getCvFrame(), "Depth", 500, 5000);
auto depth = stereoOut->get<dai::ImgFrame>();
cv::imshow("Depth", dai::utility::colorizeDepthFrame(*depth, 500.0f, 12000.0f, cv::COLORMAP_TURBO, true).getCvFrame());

if(cv::waitKey(1) == 'q') break;
}
Expand Down
58 changes: 1 addition & 57 deletions examples/cpp/Depth/depth_rgb_align.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,62 +66,6 @@ class FPSCounter {
std::deque<std::chrono::steady_clock::time_point> frameTimes;
};

cv::Mat colorizeDepth(const cv::Mat& frameDepth) {
if(frameDepth.empty() || frameDepth.channels() != 1) {
return cv::Mat::zeros(frameDepth.size(), CV_8UC3);
}

cv::Mat depth32f;
frameDepth.convertTo(depth32f, CV_32F);

const cv::Mat nonZeroMask = depth32f != 0.0f;
const int nz = cv::countNonZero(nonZeroMask);
if(nz == 0) {
return cv::Mat::zeros(frameDepth.size(), CV_8UC3);
}

std::vector<float> values;
values.reserve(static_cast<size_t>(nz));
for(int r = 0; r < depth32f.rows; ++r) {
const float* d = depth32f.ptr<float>(r);
const uchar* m = nonZeroMask.ptr<uchar>(r);
for(int c = 0; c < depth32f.cols; ++c) {
if(m[c]) {
values.push_back(d[c]);
}
}
}

std::sort(values.begin(), values.end());
auto pct = [&](double p) {
const size_t idx = static_cast<size_t>(std::round((p / 100.0) * (values.size() - 1)));
return values[idx];
};

const float minDepth = pct(3.0);
const float maxDepth = pct(95.0);

cv::Mat logDepth;
depth32f.copyTo(logDepth);
logDepth.setTo(minDepth, ~nonZeroMask);
cv::log(logDepth, logDepth);

const float logMinDepth = std::log(minDepth);
const float logMaxDepth = std::log(maxDepth);

cv::min(logDepth, logMaxDepth, logDepth);
cv::max(logDepth, logMinDepth, logDepth);
logDepth = (logDepth - logMinDepth) * (255.0f / (logMaxDepth - logMinDepth));

cv::Mat depth8U;
logDepth.convertTo(depth8U, CV_8U);

cv::Mat depthFrameColor;
cv::applyColorMap(depth8U, depthFrameColor, cv::COLORMAP_JET);
depthFrameColor.setTo(cv::Scalar::all(0), ~nonZeroMask);
return depthFrameColor;
}

float rgbWeight = 0.4f;
float depthWeight = 0.6f;

Expand Down Expand Up @@ -190,7 +134,7 @@ int main() {

if(frameDepth != nullptr) {
cv::Mat cvFrame = frameRgb->getCvFrame();
cv::Mat alignedDepthColorized = colorizeDepth(frameDepth->getFrame());
cv::Mat alignedDepthColorized = dai::utility::colorizeDepthFrame(*frameDepth).getCvFrame();
cv::imshow("Depth aligned", alignedDepthColorized);

if(cvFrame.channels() == 1) {
Expand Down
58 changes: 1 addition & 57 deletions examples/cpp/Depth/unified_depth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,62 +30,6 @@

namespace {

cv::Mat colorizeDepth(const cv::Mat& frameDepth) {
if(frameDepth.empty() || frameDepth.channels() != 1) {
return cv::Mat::zeros(frameDepth.size(), CV_8UC3);
}

cv::Mat depth32f;
frameDepth.convertTo(depth32f, CV_32F);

const cv::Mat nonZeroMask = depth32f != 0.0f;
const int nz = cv::countNonZero(nonZeroMask);
if(nz == 0) {
return cv::Mat::zeros(frameDepth.size(), CV_8UC3);
}

std::vector<float> values;
values.reserve(static_cast<size_t>(nz));
for(int r = 0; r < depth32f.rows; ++r) {
const float* d = depth32f.ptr<float>(r);
const uchar* m = nonZeroMask.ptr<uchar>(r);
for(int c = 0; c < depth32f.cols; ++c) {
if(m[c]) {
values.push_back(d[c]);
}
}
}

std::sort(values.begin(), values.end());
auto pct = [&](double p) {
const size_t idx = static_cast<size_t>(std::round((p / 100.0) * (values.size() - 1)));
return values[idx];
};

const float minDepth = pct(3.0);
const float maxDepth = pct(95.0);

cv::Mat logDepth;
depth32f.copyTo(logDepth);
logDepth.setTo(minDepth, ~nonZeroMask);
cv::log(logDepth, logDepth);

const float logMinDepth = std::log(minDepth);
const float logMaxDepth = std::log(maxDepth);

cv::min(logDepth, logMaxDepth, logDepth);
cv::max(logDepth, logMinDepth, logDepth);
logDepth = (logDepth - logMinDepth) * (255.0f / (logMaxDepth - logMinDepth));

cv::Mat depth8U;
logDepth.convertTo(depth8U, CV_8U);

cv::Mat depthFrameColor;
cv::applyColorMap(depth8U, depthFrameColor, cv::COLORMAP_JET);
depthFrameColor.setTo(cv::Scalar::all(0), ~nonZeroMask);
return depthFrameColor;
}

cv::Mat colorizeConfidence(const cv::Mat& frame) {
if(frame.empty() || frame.channels() != 1) {
return cv::Mat::zeros(frame.size(), CV_8UC3);
Expand Down Expand Up @@ -351,7 +295,7 @@ int main(int argc, char** argv) {
auto confidenceFrame = confidenceQueue->get<dai::ImgFrame>();

if(depthFrame != nullptr) {
cv::imshow("depth", colorizeDepth(depthFrame->getFrame()));
cv::imshow("depth", dai::utility::colorizeDepthFrame(*depthFrame).getCvFrame());
}
if(confidenceFrame != nullptr) {
cv::imshow("confidence", colorizeConfidence(confidenceFrame->getFrame()));
Expand Down
71 changes: 2 additions & 69 deletions examples/cpp/DetectionNetwork/detection_network_remap.cpp
Original file line number Diff line number Diff line change
@@ -1,84 +1,17 @@
#include <algorithm> // Required for std::sort and std::unique
#include <cmath> // Required for std::log, std::isnan, std::isinf
#include <csignal>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <string>
#include <vector>

#include "depthai/depthai.hpp"
#include "xtensor/containers/xadapt.hpp"
#include "xtensor/core/xmath.hpp"

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

void signalHandler(int) {
quitEvent = true;
}

cv::Mat colorizeDepth(cv::Mat frameDepth) {
cv::Mat invalidMask = frameDepth == 0;
cv::Mat depthFrameColor;

try {
cv::Mat frameDepthFloat;
frameDepth.convertTo(frameDepthFloat, CV_32F);
xt::xtensor<float, 2> depth =
xt::adapt((float*)frameDepthFloat.data, {static_cast<size_t>(frameDepthFloat.rows), static_cast<size_t>(frameDepthFloat.cols)});

// Get valid depth values (non-zero)
std::vector<float> validDepth;
validDepth.reserve(depth.size());
std::copy_if(depth.begin(), depth.end(), std::back_inserter(validDepth), [](float x) { return x != 0; });

if(validDepth.size() == 0) {
return cv::Mat::zeros(frameDepth.rows, frameDepth.cols, CV_8UC3);
}

// Calculate percentiles
std::sort(validDepth.begin(), validDepth.end());
float minDepth = validDepth[static_cast<size_t>(validDepth.size() * 0.03)];
float maxDepth = validDepth[static_cast<size_t>(validDepth.size() * 0.95)];

// Take log of depth values
auto logDepth = xt::eval(xt::log(depth));
float logMinDepth = std::log(minDepth);
float logMaxDepth = std::log(maxDepth);

auto logDepthData = logDepth.data();
auto depthData = depth.data();
const size_t size = depth.size();
// Replace invalid values with logMinDepth using a naive implementation
for(size_t i = 0; i < size; i++) {
if(std::isnan(logDepthData[i]) || std::isinf(logDepthData[i]) || depthData[i] == 0.0f) {
logDepthData[i] = logMinDepth;
}
}

// Clip values
logDepth = xt::clip(logDepth, logMinDepth, logMaxDepth);

// Normalize to 0-255 range
auto normalizedDepth = (logDepth - logMinDepth) / (logMaxDepth - logMinDepth) * 255.0f;

// Convert to CV_8UC1
cv::Mat depthMat(frameDepth.rows, frameDepth.cols, CV_8UC1);
std::transform(normalizedDepth.begin(), normalizedDepth.end(), depthMat.data, [](float x) { return static_cast<uchar>(x); });

// Apply colormap
cv::applyColorMap(depthMat, depthFrameColor, cv::COLORMAP_JET);

// Set invalid pixels to black
depthFrameColor.setTo(cv::Scalar(0, 0, 0), invalidMask);

} catch(const std::exception& e) {
std::cerr << "Error in colorizeDepth: " << e.what() << std::endl;
return cv::Mat::zeros(frameDepth.rows, frameDepth.cols, CV_8UC3);
}

return depthFrameColor;
}

// Helper function to display frames with detections
void displayFrame(const std::string& name,
std::shared_ptr<dai::ImgFrame> frame,
Expand All @@ -88,7 +21,7 @@ void displayFrame(const std::string& name,
cv::Mat cvFrame;

if(frame->getType() == dai::ImgFrame::Type::RAW16) {
cvFrame = colorizeDepth(frame->getFrame());
cvFrame = dai::utility::colorizeDepthFrame(*frame).getCvFrame();
} else {
cvFrame = frame->getCvFrame();
}
Expand Down Expand Up @@ -182,4 +115,4 @@ int main() {
}

return 0;
}
}
14 changes: 7 additions & 7 deletions examples/cpp/DynamicCalibration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ This folder contains minimal, end-to-end **C++** examples that use **`dai::node:
**Flow:**
1. Create mono cameras → request **full-res NV12** (unrectified) → link to:
- `DynamicCalibration.left/right`
- `StereoDepth.left/right` (for live disparity view)
- `StereoDepth.left/right` (for live depth view)
2. Start the pipeline, give AE a moment to settle.
3. **Start calibration** by sending `DynamicCalibrationControl::Commands::StartCalibration{}`.
4. In the loop:
- Show `left`, `right`, and `disparity`.
- Show `left`, `right`, and `depth`.
- Poll `coverageOutput` for progress.
- Poll `calibrationOutput` for a result.
5. When a result arrives:
Expand Down Expand Up @@ -110,10 +110,10 @@ This folder contains minimal, end-to-end **C++** examples that use **`dai::node:
**File:** `calibration_integration.cpp`

**What it does:**
Runs one loop that periodically refreshes coverage, executes calibration, and applies a new calibration automatically when the returned metrics indicate drift — while showing `left`, `right`, and a colorized `disparity` preview.
Runs one loop that periodically refreshes coverage, executes calibration, and applies a new calibration automatically when the returned metrics indicate drift — while showing `left`, `right`, and a colorized `depth` preview.

**Flow:**
1. Create mono cameras → request **full-res NV12** (unrectified) → link to `dai::node::DynamicCalibration` and `dai::node::StereoDepth` for live disparity. Read the device’s current calibration as the baseline.
1. Create mono cameras → request **full-res NV12** (unrectified) → link to `dai::node::DynamicCalibration` and `dai::node::StereoDepth` for live depth. Read the device’s current calibration as the baseline.
2. On a fixed interval (e.g., ~3 seconds), send on the control queue:
- `DynamicCalibrationControl::Commands::LoadImage{}` to compute coverage on the current frames, and
- `DynamicCalibrationControl::Commands::Calibrate{true}` to compute a new candidate calibration and return metrics on `calibrationOutput`.
Expand All @@ -125,7 +125,7 @@ Runs one loop that periodically refreshes coverage, executes calibration, and ap
5. Exit on `q` keypress or window close.

**Notes & defaults:**
- Disparity preview can be auto-scaled to the observed maximum; zero disparity can be rendered black for clarity.
- Depth preview uses the shared colorization helper with a 500–12000 mm range and logarithmic scaling.
- The 0.05 px Sampson threshold is a simple heuristic — tune to your tolerance and noise profile.

**Example console output:**
Expand All @@ -146,7 +146,7 @@ Mono CAM_B ──▶ [Camera] ── NV12 (full-res) ──▶ DynamicCalibratio

Mono CAM_C ──▶ [Camera] ── NV12 (full-res) ──▶ DynamicCalibration.right
└───────────▶ StereoDepth.right ──▶ disparity
└───────────▶ StereoDepth.right ──▶ depth
```

---
Expand Down Expand Up @@ -230,7 +230,7 @@ If you previously read fields from `CalibrationQuality::qualityData`, read the s
- **No quality data returned**
Ensure the target is sharp, well-lit, and covers diverse parts of the image. Increase lighting or steady the rig.

- **Disparity looks worse after apply**
- **Depth preview looks worse after apply**
Collect more diverse samples (tilt/translate the target), or try a performance mode tuned for robustness. Clean lenses; verify focus.

- **Nothing happens after StartCalibration**
Expand Down
Loading
Loading