Hi,
I’m building an RGB pipeline that works perfectly on RVC2 (OAK-D-PoE), but behaves very differently on RVC4 (OAK-4D).
On RVC2, my pipeline uses an ImageManip node after the RGB output to apply an ROI crop. When I change roi_width and roi_height, the per-frame processing time changes accordingly — meaning the device is effectively processing fewer pixels when the ROI is smaller, which is expected.
However, on RVC4, this behavior no longer occurs. Whether I use 48MP or 4000×3000, and whether the pipeline uses the full frame or a small ROI, the iteration time is almost identical. It appears that RVC4 is always processing the full sensor image internally, and the ImageManip ROI only crops after the heavy processing stage.
So my questions are:
What is the architectural or behavior difference between RVC2 and RVC4 that causes ImageManip ROI to affect processing time on RVC2, but not on RVC4?
I tested many configurations, and it seems like RVC4 always performs the full-resolution ISP + frame generation first, regardless of ROI.
My Plan B is to set camRgb->requestOutput(roi_width, roi_height, …) so the camera node performs a center crop at the output stage. This actually reduces processing time on RVC4 — but it loses the ability to specify roi_x and roi_y, meaning I can’t move the ROI region dynamically at runtime.
Is there any supported method on RVC4 to:
process only an ROI region at the camera/ISP level,
while also allowing roi_x/roi_y to be adjusted at runtime,
similar to how ImageManip behaves on RVC2?
Any insight into the internal differences or recommended patterns for ROI-based performance optimization on RVC4 would be greatly appreciated.
Here's my pipeline code:
width = 8000,_height = 6000//48MP in RVC4
width = 4056,_height = 3040//12MP in RVC2,
dai::DeviceInfo info(poe_connection);
device = std::make_shared<dai::Device>(info);
dai::Pipeline pipeline(device);
try {
auto camRgb = pipeline.create<dai::node::Camera>()->build(
dai::CameraBoardSocket::CAM_A,
std::pair<uint32_t, uint32_t>(_width, _height),
setCamfps
);
auto rgbOut = camRgb->requestOutput(
std::make_pair<uint32_t, uint32_t>(_width, _height),
dai::ImgFrame::Type::BGR888i,
dai::ImgResizeMode::CROP,
setCamfps,
true
);
auto camQIn = camRgb->inputControl.createInputQueue();
auto ctrlMsg = std::make_shared<dai::CameraControl>();
if (configChecker.getCameraOptics().exposure_time == -1) {
ctrlMsg->setAutoExposureEnable();
std::cout << "Use auto exposure now" << std::endl;
}
else {
ctrlMsg->setManualExposure(configChecker.getCameraOptics().exposure_time, configChecker.getCameraOptics().iso);
std::cout << "Use manual exposure: " << configChecker.getCameraOptics().exposure_time
<< "us, ISO " << configChecker.getCameraOptics().iso << std::endl;
}
ctrlMsg->setContrast(configChecker.getCameraOptics().contrast);
camQIn->send(ctrlMsg);
std::shared_ptr<dai::node::ImageManip> manipRgb;
std::shared_ptr<dai::InputQueue> manipRgbCfgQ;
std::shared_ptr<dai::MessageQueue> outQ;
if (configChecker.getROISettings().roi_setting == 1) {
std::cout << "[2D ROI] Using full frame (roi_setting=1 or invalid ROI)\n";
manipRgb = nullptr;
manipRgbCfgQ = nullptr;
outQ = rgbOut->createOutputQueue(4, false);
}
else {
manipRgb = pipeline.create<dai::node::ImageManip>();
manipRgb->initialConfig->addCrop(configChecker.getROISettings().roi_x, configChecker.getROISettings().roi_y, configChecker.getROISettings().roi_width, configChecker.getROISettings().roi_height);
manipRgb->initialConfig->setFrameType(dai::ImgFrame::Type::BGR888i);
manipRgb->initialConfig->setOutputSize(configChecker.getROISettings().roi_width, configChecker.getROISettings().roi_height);
manipRgb->setMaxOutputFrameSize(_width * _height * 3 + 4096);
rgbOut->link(manipRgb->inputImage);
outQ = manipRgb->out.createOutputQueue(4, false);
manipRgbCfgQ = manipRgb->inputConfig.createInputQueue(4, false);
}
// ---------- Start pipeline ----------
pipeline.start();
My version is DepthAI 3.1.0