I'm using an ImageManip node to rotate an image frame. I'm getting the original image from Camera node, which gives an output of width 1280 and height 800. The documentation says that rotation is possible only when the width is multiple of 16. This is true for the resolution I'm using.
When I try to rotate the frame by 90 degrees, the resultant frame is of width 896 and height 1280. The output frame has 96px padding along the width. Is this a result of optimizing image manipulation at hardware level? I saw this change in output dimensions on OAK-4 devices, but not on OAK-D-Pro or OAK-FFC-4P.
Due to the change in width, I'm getting the following error from StereoDepth node:
[StereoDepth(6)] [error] Node threw exception, stopping the node. Exception message: Input stride '896' not being equal to width '800' is not supported! Please use widths divisible by 128 (1280, 640, ...)
I tried to set the output size to 1280x800 using ImageManipConfig, but it didn't work. I'm sharing a minimal example below. I appreciate any help in resolving this error.
Thank you.
import depthai as dai
import numpy as np
import cv2
from datetime import timedelta
ROTATE_ANGLE = 90.0
DEFAULT_FPS = 15
COLOR_WIDTH = 1920
COLOR_HEIGHT = 1200
COLOR_OUT_TYPE = dai.ImgFrame.Type.NV12
MONO_WIDTH = 1280
MONO_HEIGHT = 800
MONO_OUT_TYPE = dai.ImgFrame.Type.GRAY8
pipeline = dai.Pipeline()
colorNode = pipeline.create(dai.node.Camera).build(boardSocket=dai.CameraBoardSocket.CAM_A)
colorOut = colorNode.requestOutput((COLOR_WIDTH, COLOR_HEIGHT), fps = DEFAULT_FPS, type=COLOR_OUT_TYPE)
colorManipNode = pipeline.create(dai.node.ImageManip)
colorManipNode.setRunOnHost(False)
colorManipNode.setMaxOutputFrameSize(COLOR_WIDTH * COLOR_HEIGHT * 3)
colorManipNode.initialConfig.setFrameType(COLOR_OUT_TYPE)
colorManipNode.initialConfig.addRotateDeg(ROTATE_ANGLE)
colorOut.link(colorManipNode.inputImage)
monoLeft = pipeline.create(dai.node.Camera).build(boardSocket=dai.CameraBoardSocket.CAM_B)
monoLeftOut = monoLeft.requestOutput((MONO_WIDTH, MONO_HEIGHT), fps=DEFAULT_FPS, type=MONO_OUT_TYPE)
monoLeftManip = pipeline.create(dai.node.ImageManip)
monoLeftManip.setRunOnHost(False)
monoLeftManip.setMaxOutputFrameSize(MONO_WIDTH * MONO_HEIGHT * 3)
monoLeftManip.initialConfig.setFrameType(MONO_OUT_TYPE)
monoLeftManip.initialConfig.addRotateDeg(ROTATE_ANGLE)
monoLeftManip.initialConfig.setOutputSize(MONO_HEIGHT, MONO_WIDTH, dai.ImageManipConfig.ResizeMode.CENTER_CROP)
monoLeftOut.link(monoLeftManip.inputImage)
monoRight = pipeline.create(dai.node.Camera).build(boardSocket=dai.CameraBoardSocket.CAM_C)
monoRightOut = monoRight.requestOutput((MONO_WIDTH, MONO_HEIGHT), fps=DEFAULT_FPS, type=MONO_OUT_TYPE)
monoRightManip = pipeline.create(dai.node.ImageManip)
monoRightManip.setRunOnHost(False)
monoRightManip.setMaxOutputFrameSize(MONO_WIDTH * MONO_HEIGHT * 3)
monoRightManip.initialConfig.setFrameType(MONO_OUT_TYPE)
monoRightManip.initialConfig.addRotateDeg(ROTATE_ANGLE)
monoRightManip.initialConfig.setOutputSize(MONO_HEIGHT, MONO_WIDTH, dai.ImageManipConfig.ResizeMode.CENTER_CROP)
monoRightOut.link(monoRightManip.inputImage)
stereoNode = pipeline.create(dai.node.StereoDepth)
monoLeftManip.out.link(stereoNode.left)
monoRightManip.out.link(stereoNode.right)
sync = pipeline.create(dai.node.Sync)
sync.setSyncThreshold(timedelta(milliseconds=80))
colorManipNode.out.link(sync.inputs["color"])
stereoNode.depth.link(sync.inputs["depth"])
syncQueue = sync.out.createOutputQueue()
with pipeline:
pipeline.start()
cv2.namedWindow("color", cv2.WINDOW_NORMAL)
cv2.resizeWindow("color", 640, 400)
cv2.namedWindow("depth", cv2.WINDOW_NORMAL)
cv2.resizeWindow("depth", 640, 400)
while pipeline.isRunning():
syncGroup = syncQueue.tryGet()
if syncGroup is None:
continue
assert isinstance(syncGroup, dai.MessageGroup)
if syncGroup.getNumMessages() == 0:
continue
print(f"MSG Group timestamp: {syncGroup.getTimestamp()}")
for name, payload in syncGroup:
assert isinstance(payload, dai.ImgFrame)
print(f"\t[{name}] size: [{payload.getWidth()} x {payload.getHeight()}]")
if name == "color":
cv2.imshow(name, payload.getCvFrame())
elif name == "depth":
depthMap = payload.getFrame()
normalizedMap = (depthMap * (255 / stereoNode.initialConfig.getMaxDisparity())).astype(np.uint8)
colorizedDepth = cv2.applyColorMap(normalizedMap, cv2.COLORMAP_JET)
cv2.imshow(name, colorizedDepth)
key = cv2.waitKey(1)
if key == ord('q'):
pipeline.stop()
break