diff --git a/bindings/python/src/pipeline/datatype/ImgFrameBindings.cpp b/bindings/python/src/pipeline/datatype/ImgFrameBindings.cpp index 4c9451a7f..6752646e8 100644 --- a/bindings/python/src/pipeline/datatype/ImgFrameBindings.cpp +++ b/bindings/python/src/pipeline/datatype/ImgFrameBindings.cpp @@ -144,9 +144,20 @@ void bind_imgframe(pybind11::module& m, void* pCallstack) { .def("getSourceIntrinsicMatrixInv", &ImgTransformation::getSourceIntrinsicMatrixInv) .def("getIntrinsicMatrix", &ImgTransformation::getIntrinsicMatrix) .def("getIntrinsicMatrixInv", &ImgTransformation::getIntrinsicMatrixInv) + .def("getDistortionModel", &ImgTransformation::getDistortionModel, DOC(dai, ImgTransformation, getDistortionModel)) + .def("getDistortionCoefficients", &ImgTransformation::getDistortionCoefficients, DOC(dai, ImgTransformation, getDistortionCoefficients)) + .def("getSrcCrops", &ImgTransformation::getSrcCrops, DOC(dai, ImgTransformation, getSrcCrops)) + .def("getSrcMaskPt", &ImgTransformation::getSrcMaskPt, py::arg("x"), py::arg("y"), DOC(dai, ImgTransformation, getSrcMaskPt)) + .def("getDstMaskPt", &ImgTransformation::getDstMaskPt, py::arg("x"), py::arg("y"), DOC(dai, ImgTransformation, getDstMaskPt)) .def("getDFov", &ImgTransformation::getDFov, py::arg("source") = false) .def("getHFov", &ImgTransformation::getHFov, py::arg("source") = false) .def("getVFov", &ImgTransformation::getVFov, py::arg("source") = false) + .def("setIntrinsicMatrix", &ImgTransformation::setIntrinsicMatrix, py::arg("intrinsicMatrix"), DOC(dai, ImgTransformation, setIntrinsicMatrix)) + .def("setDistortionModel", &ImgTransformation::setDistortionModel, py::arg("model"), DOC(dai, ImgTransformation, setDistortionModel)) + .def("setDistortionCoefficients", + &ImgTransformation::setDistortionCoefficients, + py::arg("coefficients"), + DOC(dai, ImgTransformation, setDistortionCoefficients)) .def("addTransformation", &ImgTransformation::addTransformation, py::arg("matrix"), DOC(dai, ImgTransformation, addTransformation)) .def("addCrop", &ImgTransformation::addCrop, py::arg("x"), py::arg("y"), py::arg("width"), py::arg("height"), DOC(dai, ImgTransformation, addCrop)) .def("addPadding", @@ -201,6 +212,7 @@ void bind_imgframe(pybind11::module& m, void* pCallstack) { .def("getSourceWidth", &ImgFrame::getSourceWidth, DOC(dai, ImgFrame, getSourceWidth)) .def("getSourceHeight", &ImgFrame::getSourceHeight, DOC(dai, ImgFrame, getSourceHeight)) .def("getTransformation", [](ImgFrame& msg) {return msg.transformation;}) + .def("validateTransformations", &ImgFrame::validateTransformations, DOC(dai, ImgFrame, validateTransformations)) #ifdef DEPTHAI_HAVE_OPENCV_SUPPORT // The cast function itself does a copy, so we can avoid two copies by always not copying diff --git a/cmake/Depthai/DepthaiDeviceRVC4Config.cmake b/cmake/Depthai/DepthaiDeviceRVC4Config.cmake index d2e84558e..b21e7b3a5 100644 --- a/cmake/Depthai/DepthaiDeviceRVC4Config.cmake +++ b/cmake/Depthai/DepthaiDeviceRVC4Config.cmake @@ -4,4 +4,4 @@ set(DEPTHAI_DEVICE_RVC4_MATURITY "snapshot") # "version if applicable" # set(DEPTHAI_DEVICE_RVC4_VERSION "0.0.1+93f7b75a885aa32f44c5e9f53b74470c49d2b1af") -set(DEPTHAI_DEVICE_RVC4_VERSION "0.0.1+670797e3b8cbc185c7d457e24382495200486573") +set(DEPTHAI_DEVICE_RVC4_VERSION "0.0.1+3cf298000129597b70c7f266bb70bc13647a356f") diff --git a/cmake/Depthai/DepthaiDeviceSideConfig.cmake b/cmake/Depthai/DepthaiDeviceSideConfig.cmake index 56c20c489..fed491ec8 100644 --- a/cmake/Depthai/DepthaiDeviceSideConfig.cmake +++ b/cmake/Depthai/DepthaiDeviceSideConfig.cmake @@ -2,7 +2,7 @@ set(DEPTHAI_DEVICE_SIDE_MATURITY "snapshot") # "full commit hash of device side binary" -set(DEPTHAI_DEVICE_SIDE_COMMIT "fa759809823266407a39cd7cc77d98175234a7f6") +set(DEPTHAI_DEVICE_SIDE_COMMIT "3c7d7c93a4c4c55d235ff7e406ed8824b9c18fef") # "version if applicable" set(DEPTHAI_DEVICE_SIDE_VERSION "") diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 2bfa0dc8c..887d322c8 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -273,6 +273,9 @@ dai_add_example(record_imu RVC2/Record/record_imu.cpp OFF OFF) dai_add_example(replay_video_meta RVC2/Replay/replay_video_meta.cpp OFF OFF) dai_add_example(replay_imu RVC2/Replay/replay_imu.cpp OFF OFF) +# StereoDepth +dai_add_example(depth_preview StereoDepth/depth_preview.cpp OFF OFF) + # Thermal dai_add_example(thermal RVC2/Thermal/thermal.cpp OFF OFF) diff --git a/examples/cpp/ImageManip/image_manip_v2_mod.cpp b/examples/cpp/ImageManip/image_manip_v2_mod.cpp index 76d1ae62d..2c7a75ef3 100644 --- a/examples/cpp/ImageManip/image_manip_v2_mod.cpp +++ b/examples/cpp/ImageManip/image_manip_v2_mod.cpp @@ -14,13 +14,10 @@ int main(int argc, char** argv) { } dai::Pipeline pipeline(device); - auto camRgb = pipeline.create(); + auto camRgb = pipeline.create()->build(dai::CameraBoardSocket::CAM_A); auto display = pipeline.create(); auto manip = pipeline.create(); - camRgb->setBoardSocket(dai::CameraBoardSocket::CAM_A); - camRgb->setResolution(dai::ColorCameraProperties::SensorResolution::THE_1080_P); - manip->setMaxOutputFrameSize(4000000); manip->initialConfig.setOutputSize(1280, 720, dai::ImageManipConfigV2::ResizeMode::LETTERBOX); manip->initialConfig.setBackgroundColor(100, 100, 100); @@ -29,7 +26,8 @@ int main(int argc, char** argv) { manip->initialConfig.addFlipVertical(); manip->initialConfig.setFrameType(dai::ImgFrame::Type::RGB888p); - camRgb->video.link(manip->inputImage); + auto* rgbOut = camRgb->requestOutput({1920, 1080}); + rgbOut->link(manip->inputImage); manip->out.link(display->input); pipeline.start(); diff --git a/examples/cpp/StereoDepth/depth_preview.cpp b/examples/cpp/StereoDepth/depth_preview.cpp new file mode 100644 index 000000000..0466c6e08 --- /dev/null +++ b/examples/cpp/StereoDepth/depth_preview.cpp @@ -0,0 +1,65 @@ +#include + +// Includes common necessary includes for development using depthai library +#include "depthai/depthai.hpp" +#include "depthai/pipeline/datatype/StereoDepthConfig.hpp" +#include "depthai/pipeline/node/StereoDepth.hpp" +#include "depthai/properties/StereoDepthProperties.hpp" + +// Closer-in minimum depth, disparity range is doubled (from 95 to 190): +static std::atomic extended_disparity{false}; +// Better accuracy for longer distance, fractional disparity 32-levels: +static std::atomic subpixel{false}; +// Better handling for occlusions: +static std::atomic lr_check{true}; + +int main() { + // Create pipeline + dai::Pipeline pipeline; + + // Define sources and outputs + auto monoLeft = pipeline.create()->build(dai::CameraBoardSocket::CAM_B); + auto monoRight = pipeline.create()->build(dai::CameraBoardSocket::CAM_C); + auto depth = pipeline.create(); + + // Properties + auto* lout = monoLeft->requestOutput({640, 400}); + auto* rout = monoRight->requestOutput({640, 400}); + + // Create a node that will produce the depth map (using disparity output as it's easier to visualize depth this way) + depth->build(*lout, *rout, dai::node::StereoDepth::PresetMode::HIGH_DENSITY); + // Options: MEDIAN_OFF, KERNEL_3x3, KERNEL_5x5, KERNEL_7x7 (default) + depth->initialConfig.setMedianFilter(dai::StereoDepthConfig::MedianFilter::KERNEL_7x7); + depth->setLeftRightCheck(lr_check); + depth->setExtendedDisparity(extended_disparity); + depth->setSubpixel(subpixel); + + // Output queue will be used to get the disparity frames from the outputs defined above + auto q = depth->disparity.createOutputQueue(); + auto qleft = lout->createOutputQueue(); + + pipeline.start(); + + while(true) { + auto inDepth = q->get(); + auto inLeft = qleft->get(); + auto frame = inDepth->getFrame(); + // Normalization for better visualization + frame.convertTo(frame, CV_8UC1, 255 / depth->initialConfig.getMaxDisparity()); + + std::cout << "Left type: " << inLeft->fb.str() << std::endl; + + cv::imshow("disparity", frame); + + // Available color maps: https://docs.opencv.org/3.4/d3/d50/group__imgproc__colormap.html + cv::applyColorMap(frame, frame, cv::COLORMAP_JET); + cv::imshow("disparity_color", frame); + + int key = cv::waitKey(1); + if(key == 'q' || key == 'Q') { + break; + } + } + pipeline.stop(); + return 0; +} diff --git a/examples/python/CMakeLists.txt b/examples/python/CMakeLists.txt index b53454604..c295aefba 100644 --- a/examples/python/CMakeLists.txt +++ b/examples/python/CMakeLists.txt @@ -147,6 +147,8 @@ dai_set_example_test_labels(april_tags_replay ondevice rvc2_all rvc4 ci) ## Detection network add_python_example(detection_network DetectionNetwork/detection_network.py) dai_set_example_test_labels(detection_network ondevice rvc2_all rvc4 ci) +add_python_example(detection_network_remap DetectionNetwork/detection_network_remap.py) +dai_set_example_test_labels(detection_network ondevice rvc2_all rvc4 ci) ## Host nodes add_python_example(display HostNodes/display.py) @@ -162,6 +164,9 @@ dai_set_example_test_labels(image_manip_mod ondevice rvc2_all rvc4 ci) add_python_example(image_manip_resize ImageManip/image_manip_v2_resize.py) dai_set_example_test_labels(image_manip_resize ondevice rvc2_all rvc4 ci) +add_python_example(image_manip_remap ImageManip/image_manip_v2_remap.py) +dai_set_example_test_labels(image_manip_remap ondevice rvc2_all rvc4 ci) + ## Misc add_python_example(reconnect_callback Misc/AutoReconnect/reconnect_callback.py) dai_set_example_test_labels(reconnect_callback ondevice rvc2_all rvc4 ci) diff --git a/examples/python/RVC4/DetectionNetwork/detection_network_remap.py b/examples/python/DetectionNetwork/detection_network_remap.py similarity index 73% rename from examples/python/RVC4/DetectionNetwork/detection_network_remap.py rename to examples/python/DetectionNetwork/detection_network_remap.py index d872fb737..f533475ec 100644 --- a/examples/python/RVC4/DetectionNetwork/detection_network_remap.py +++ b/examples/python/DetectionNetwork/detection_network_remap.py @@ -4,6 +4,33 @@ import depthai as dai import numpy as np +def colorizeDepth(frameDepth): + invalidMask = frameDepth == 0 + # Log the depth, minDepth and maxDepth + try: + minDepth = np.percentile(frameDepth[frameDepth != 0], 3) + maxDepth = np.percentile(frameDepth[frameDepth != 0], 95) + logDepth = np.log(frameDepth, where=frameDepth != 0) + logMinDepth = np.log(minDepth) + logMaxDepth = np.log(maxDepth) + np.nan_to_num(logDepth, copy=False, nan=logMinDepth) + # Clip the values to be in the 0-255 range + logDepth = np.clip(logDepth, logMinDepth, logMaxDepth) + + # Interpolate only valid logDepth values, setting the rest based on the mask + depthFrameColor = np.interp(logDepth, (logMinDepth, logMaxDepth), (0, 255)) + depthFrameColor = np.nan_to_num(depthFrameColor) + depthFrameColor = depthFrameColor.astype(np.uint8) + depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_JET) + # Set invalid depth pixels to black + depthFrameColor[invalidMask] = 0 + except IndexError: + # Frame is likely empty + depthFrameColor = np.zeros((frameDepth.shape[0], frameDepth.shape[1], 3), dtype=np.uint8) + except Exception as e: + raise e + return depthFrameColor + # Create pipeline with dai.Pipeline() as pipeline: cameraNode = pipeline.create(dai.node.Camera).build() @@ -35,8 +62,7 @@ def displayFrame(name: str, frame: dai.ImgFrame, imgDetections: dai.ImgDetection assert imgDetections.getTransformation() is not None cvFrame = frame.getFrame() if frame.getType() == dai.ImgFrame.Type.RAW16 else frame.getCvFrame() if(frame.getType() == dai.ImgFrame.Type.RAW16): - cvFrame = (cvFrame * (255 / stereo.initialConfig.getMaxDisparity())).astype(np.uint8) - cvFrame = cv2.applyColorMap(cvFrame, cv2.COLORMAP_JET) + cvFrame = colorizeDepth(cvFrame) for detection in imgDetections.detections: # Get the shape of the frame from which the detections originated for denormalization normShape = imgDetections.getTransformation().getSize() diff --git a/examples/python/ImageAlign/depth_align.py b/examples/python/ImageAlign/depth_align.py index 3ca4eccee..2a461e4b3 100755 --- a/examples/python/ImageAlign/depth_align.py +++ b/examples/python/ImageAlign/depth_align.py @@ -25,15 +25,7 @@ def getFps(self): return 0 return (len(self.frameTimes) - 1) / (self.frameTimes[-1] - self.frameTimes[0]) -device = dai.Device() - -calibrationHandler = device.readCalibration() -rgbDistortion = calibrationHandler.getDistortionCoefficients(RGB_SOCKET) -distortionModel = calibrationHandler.getDistortionModel(RGB_SOCKET) -if distortionModel != dai.CameraModel.Perspective: - raise RuntimeError("Unsupported distortion model for RGB camera. This example supports only Perspective model.") - -pipeline = dai.Pipeline(device) +pipeline = dai.Pipeline() platform = pipeline.getDefaultDevice().getPlatform() @@ -47,7 +39,6 @@ def getFps(self): align = pipeline.create(dai.node.ImageAlign) stereo.setExtendedDisparity(True) - sync.setSyncThreshold(timedelta(seconds=1/(2*FPS))) rgbOut = camRgb.requestOutput(size = (1280, 960), fps = FPS) @@ -142,14 +133,10 @@ def updateBlendWeights(percentRgb): # Blend when both received if frameDepth is not None: cvFrame = frameRgb.getCvFrame() - - # Undistort the rgb frame - rgbIntrinsics = calibrationHandler.getCameraIntrinsics(RGB_SOCKET, int(cvFrame.shape[1]), int(cvFrame.shape[0])) - cvFrameUndistorted = cv2.undistort( cvFrame, - np.array(rgbIntrinsics), - np.array(rgbDistortion), + np.array(frameRgb.getTransformation().getIntrinsicMatrix()), + np.array(frameRgb.getTransformation().getDistortionCoefficients()), ) # Colorize the aligned depth alignedDepthColorized = colorizeDepth(frameDepth.getFrame()) diff --git a/examples/python/ImageManip/image_manip_v2_remap.py b/examples/python/ImageManip/image_manip_v2_remap.py new file mode 100644 index 000000000..8cc543e2f --- /dev/null +++ b/examples/python/ImageManip/image_manip_v2_remap.py @@ -0,0 +1,70 @@ +import depthai as dai +import cv2 +import numpy as np + +def draw_rotated_rectangle(frame, center, size, angle, color, thickness=2): + """ + Draws a rotated rectangle on the given frame. + + Args: + frame (numpy.ndarray): The image/frame to draw on. + center (tuple): The (x, y) coordinates of the rectangle's center. + size (tuple): The (width, height) of the rectangle. + angle (float): The rotation angle of the rectangle in degrees (counter-clockwise). + color (tuple): The color of the rectangle in BGR format (e.g., (0, 255, 0) for green). + thickness (int): The thickness of the rectangle edges. Default is 2. + """ + # Create a rotated rectangle + rect = ((center[0], center[1]), (size[0], size[1]), angle) + + # Get the four vertices of the rotated rectangle + box = cv2.boxPoints(rect) + box = np.intp(box) # Convert to integer coordinates + + # Draw the rectangle on the frame + cv2.polylines(frame, [box], isClosed=True, color=color, thickness=thickness) + +with dai.Pipeline() as pipeline: + cam = pipeline.create(dai.node.Camera).build() + camOut = cam.requestOutput((640, 400), dai.ImgFrame.Type.BGR888i, fps = 30.0) + manip1 = pipeline.create(dai.node.ImageManipV2) + manip2 = pipeline.create(dai.node.ImageManipV2) + + camOut.link(manip1.inputImage) + manip1.out.link(manip2.inputImage) + + manip1.initialConfig.addRotateDeg(90) + manip1.initialConfig.setOutputSize(200, 320) + + manip2.initialConfig.addRotateDeg(90) + manip2.initialConfig.setOutputSize(320, 200) + manip2.setRunOnHost(True) + + outQcam = camOut.createOutputQueue() + outQ1 = manip1.out.createOutputQueue() + outQ2 = manip2.out.createOutputQueue() + + pipeline.start() + + while True: + camFrame: dai.ImgFrame = outQcam.get() + manip1Frame: dai.ImgFrame = outQ1.get() + manip2Frame: dai.ImgFrame = outQ2.get() + + camCv = camFrame.getCvFrame() + manip1Cv = manip1Frame.getCvFrame() + manip2Cv = manip2Frame.getCvFrame() + + rect2 = dai.RotatedRect(dai.Rect(dai.Point2f(100, 100), dai.Point2f(200, 150)), 0) + rect1 = manip2Frame.getTransformation().remapRectTo(manip1Frame.getTransformation(), rect2) + rectcam = manip1Frame.getTransformation().remapRectTo(camFrame.getTransformation(), rect1) + + draw_rotated_rectangle(manip2Cv, (rect2.center.x, rect2.center.y), (rect2.size.width, rect2.size.height), rect2.angle, (255, 0, 0)) + draw_rotated_rectangle(manip1Cv, (rect1.center.x, rect1.center.y), (rect1.size.width, rect1.size.height), rect1.angle, (255, 0, 0)) + draw_rotated_rectangle(camCv, (rectcam.center.x, rectcam.center.y), (rectcam.size.width, rectcam.size.height), rectcam.angle, (255, 0, 0)) + + cv2.imshow("cam", camCv) + cv2.imshow("manip1", manip1Cv) + cv2.imshow("manip2", manip2Cv) + if cv2.waitKey(1) == ord('q'): + break diff --git a/examples/python/StereoDepth/stereo_depth_remap.py b/examples/python/StereoDepth/stereo_depth_remap.py new file mode 100644 index 000000000..8d213a086 --- /dev/null +++ b/examples/python/StereoDepth/stereo_depth_remap.py @@ -0,0 +1,84 @@ +import depthai as dai +import cv2 +import numpy as np + +def draw_rotated_rectangle(frame, center, size, angle, color, thickness=2): + """ + Draws a rotated rectangle on the given frame. + + Args: + frame (numpy.ndarray): The image/frame to draw on. + center (tuple): The (x, y) coordinates of the rectangle's center. + size (tuple): The (width, height) of the rectangle. + angle (float): The rotation angle of the rectangle in degrees (counter-clockwise). + color (tuple): The color of the rectangle in BGR format (e.g., (0, 255, 0) for green). + thickness (int): The thickness of the rectangle edges. Default is 2. + """ + # Create a rotated rectangle + rect = ((center[0], center[1]), (size[0], size[1]), angle) + + # Get the four vertices of the rotated rectangle + box = cv2.boxPoints(rect) + box = np.intp(box) # Convert to integer coordinates + + # Draw the rectangle on the frame + cv2.polylines(frame, [box], isClosed=True, color=color, thickness=thickness) + +def processDepthFrame(depthFrame): + depth_downscaled = depthFrame[::4] + if np.all(depth_downscaled == 0): + min_depth = 0 + else: + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) + return cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) + +with dai.Pipeline() as pipeline: + color = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_A) + monoLeft = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_B) + monoRight = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_C) + stereo = pipeline.create(dai.node.StereoDepth) + + stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) + # stereo.setDepthAlign(dai.CameraBoardSocket.CAM_A) + # stereo.setOutputSize(640, 400) + + colorCamOut = color.requestOutput((640, 480)) + + monoLeftOut = monoLeft.requestOutput((640, 480)) + monoRightOut = monoRight.requestOutput((640, 480)) + + monoLeftOut.link(stereo.left) + monoRightOut.link(stereo.right) + + colorOut = colorCamOut.createOutputQueue() + rightOut = monoRightOut.createOutputQueue() + stereoOut = stereo.depth.createOutputQueue() + + pipeline.start() + while pipeline.isRunning(): + colorFrame = colorOut.get() + stereoFrame = stereoOut.get() + + assert colorFrame.validateTransformations() + assert stereoFrame.validateTransformations() + + clr = colorFrame.getCvFrame() + depth = processDepthFrame(stereoFrame.getCvFrame()) + + rect = dai.RotatedRect(dai.Point2f(300, 200), dai.Size2f(200, 100), 10) + remappedRect = colorFrame.getTransformation().remapRectTo(stereoFrame.getTransformation(), rect) + + print(f"Original rect x: {rect.center.x} y: {rect.center.y} width: {rect.size.width} height: {rect.size.height} angle: {rect.angle}") + print(f"Remapped rect x: {remappedRect.center.x} y: {remappedRect.center.y} width: {remappedRect.size.width} height: {remappedRect.size.height} angle: {remappedRect.angle}") + + draw_rotated_rectangle(clr, (rect.center.x, rect.center.y), (rect.size.width, rect.size.height), rect.angle, (255, 0, 0)) + draw_rotated_rectangle(depth, (remappedRect.center.x, remappedRect.center.y), (remappedRect.size.width, remappedRect.size.height), remappedRect.angle, (255, 0, 0)) + + cv2.imshow("color", clr) + cv2.imshow("depth", depth) + + if cv2.waitKey(1) == ord('q'): + break + pipeline.stop() diff --git a/include/depthai/common/ImgTransformations.hpp b/include/depthai/common/ImgTransformations.hpp index 40ae2aa10..55f37e55e 100644 --- a/include/depthai/common/ImgTransformations.hpp +++ b/include/depthai/common/ImgTransformations.hpp @@ -177,12 +177,12 @@ struct ImgTransformation { ImgTransformation& addCrop(int x, int y, int width, int height); /** * Add a pad transformation. Works like crop, but in reverse. - * @param x Padding on the left. The x coordinate of the top-left corner in the new image. - * @param y Padding on the top. The y coordinate of the top-left corner in the new image. - * @param width New image width - * @param height New image height + * @param top Padding on the top + * @param bottom Padding on the bottom + * @param left Padding on the left + * @param right Padding on the right */ - ImgTransformation& addPadding(int x, int y, int width, int height); + ImgTransformation& addPadding(int top, int bottom, int left, int right); /** * Add a vertical flip transformation. */ @@ -204,6 +204,11 @@ struct ImgTransformation { */ ImgTransformation& addScale(float scaleX, float scaleY); ImgTransformation& addSrcCrops(const std::vector& crops); + ImgTransformation& setSize(size_t width, size_t height); + ImgTransformation& setSourceSize(size_t width, size_t height); + ImgTransformation& setIntrinsicMatrix(std::array, 3> intrinsicMatrix); + ImgTransformation& setDistortionModel(CameraModel model); + ImgTransformation& setDistortionCoefficients(std::vector coefficients); /** * Remap a point from this transformation to another. If the intrinsics are different (e.g. different camera), the function will also use the intrinsics to diff --git a/include/depthai/utility/ImageManipV2Impl.hpp b/include/depthai/utility/ImageManipV2Impl.hpp index f145a76cb..372b604b4 100644 --- a/include/depthai/utility/ImageManipV2Impl.hpp +++ b/include/depthai/utility/ImageManipV2Impl.hpp @@ -2811,15 +2811,12 @@ void Warp::build(const FrameSpecs srcFrameSpec sourceMaxY = inHeight; for(const auto& corners : srcCorners) { auto [minx, maxx, miny, maxy] = getOuterRect(std::vector>(corners.begin(), corners.end())); - minx = std::max(minx, 0.0f); - maxx = std::min(maxx, (float)inWidth); - miny = std::max(miny, 0.0f); - maxy = std::min(maxy, (float)inHeight); - sourceMinX = std::max(sourceMinX, (size_t)std::floor(minx)); - sourceMinY = std::max(sourceMinY, (size_t)std::floor(miny)); + sourceMinX = std::max(sourceMinX, (size_t)std::floor(std::max(minx, 0.f))); + sourceMinY = std::max(sourceMinY, (size_t)std::floor(std::max(miny, 0.f))); sourceMaxX = std::min(sourceMaxX, (size_t)std::ceil(maxx)); sourceMaxY = std::min(sourceMaxY, (size_t)std::ceil(maxy)); } + if(sourceMinX >= sourceMaxX || sourceMinY >= sourceMaxY) throw std::runtime_error("Initial crop is outside the source image"); #if !DEPTHAI_IMAGEMANIPV2_OPENCV && !DEPTHAI_IMAGEMANIPV2_FASTCV || !defined(DEPTHAI_HAVE_OPENCV_SUPPORT) && !defined(DEPTHAI_HAVE_FASTCV_SUPPORT) const uint32_t outWidth = dstFrameSpecs.width; diff --git a/src/pipeline/datatype/ImgTransformations.cpp b/src/pipeline/datatype/ImgTransformations.cpp index eef90507a..de9da30e4 100644 --- a/src/pipeline/datatype/ImgTransformations.cpp +++ b/src/pipeline/datatype/ImgTransformations.cpp @@ -8,6 +8,8 @@ namespace dai { +constexpr float ROUND_UP_EPS = 1e-3f; + // Function to check if a point is inside a rotated rectangle inline bool isPointInRotatedRectangle(const dai::Point2f& p, const dai::RotatedRect& rect) { auto theta = -rect.angle * (float)M_PI / 180.0f; @@ -342,8 +344,8 @@ ImgTransformation& ImgTransformation::addRotation(float angle, dai::Point2f rota return *this; } ImgTransformation& ImgTransformation::addScale(float scaleX, float scaleY) { - width *= scaleX; - height *= scaleY; + width = width * scaleX + ROUND_UP_EPS; + height = height * scaleY + ROUND_UP_EPS; std::array, 3> scaleMatrix = {{{scaleX, 0, 0}, {0, scaleY, 0}, {0, 0, 1}}}; addTransformation(scaleMatrix); return *this; @@ -355,6 +357,30 @@ ImgTransformation& ImgTransformation::addSrcCrops(const std::vectorwidth = width; + this->height = height; + return *this; +} +ImgTransformation& ImgTransformation::setSourceSize(size_t width, size_t height) { + this->srcWidth = width; + this->srcHeight = height; + return *this; +} +ImgTransformation& ImgTransformation::setIntrinsicMatrix(std::array, 3> intrinsicMatrix) { + sourceIntrinsicMatrix = intrinsicMatrix; + sourceIntrinsicMatrixInv = getMatrixInverse(intrinsicMatrix); + return *this; +} +ImgTransformation& ImgTransformation::setDistortionModel(CameraModel model) { + distortionModel = model; + return *this; +} +ImgTransformation& ImgTransformation::setDistortionCoefficients(std::vector coefficients) { + distortionCoefficients = coefficients; + return *this; +} + bool ImgTransformation::isValid() const { return srcWidth > 0 && srcHeight > 0 && width > 0 && height > 0; } diff --git a/src/pipeline/node/ImageManipV2.cpp b/src/pipeline/node/ImageManipV2.cpp index 6312c9e24..a831e80f3 100644 --- a/src/pipeline/node/ImageManipV2.cpp +++ b/src/pipeline/node/ImageManipV2.cpp @@ -47,6 +47,7 @@ void ImageManipV2::run() { auto srcCrops = manip.getSrcCrops(); dstFrame.transformation.addSrcCrops(srcCrops); dstFrame.transformation.addTransformation(manip.getMatrix()); + dstFrame.transformation.setSize(dstSpecs.width, dstSpecs.height); }); }