We’re using an Oak-D Wide (IMX378) in production and encountering overcurrent warnings after extended use. In testing, the internal chip temperature climbs to around 70 °C after ~2 hours.
From my investigation, this seems to be due to the expensive warp mesh computation at full ISP resolution for undistorting the RGB stream. I need undistortion because without it, the depth–RGB alignment produces wildly inaccurate spatial coordinates.
Setup:
Oak-D Wide (IMX378 RGB)
1080p RGB ISP output (scaled to ~810p via ISP)
Warp mesh generated from calibration and applied with ImageManip.setWarpMesh()
Face detection model running on undistorted, resized RGB frames (minimal repro script attached below)
USB3 connection, depthai==2.30.0
Question:
Is there a better out-of-the-box way to get correct RGB–depth alignment with the Oak-D Wide (IMX378) that doesn’t require running a high-resolution warp mesh on every frame?
Minimal reproducible script:
# Minimal Repro: Warp mesh + face detection only
# - Reads calibration, builds warp mesh
# - Undistorts RGB ISP->ImageManip
# - Resizes to 300x300
# - Runs MobileNetDetectionNetwork (face-detection-retail-0004)
# - Outputs detections over XLink (stream: "face_dets")
import os
from pathlib import Path
import depthai as dai
import numpy as np
import cv2
class OakD_Camera:
def __init__(self, fps: int = 15):
# Grab calibration once from a temporary device
with dai.Device() as temp_device:
calib = temp_device.readCalibration()
self.pipeline = dai.Pipeline()
# ---- RGB camera ----
cam = self.pipeline.create(dai.node.ColorCamera)
cam.setBoardSocket(dai.CameraBoardSocket.RGB)
cam.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)
cam.setIspScale(3, 4) # 1080p -> 810p (keeps FOV)
cam.setInterleaved(False)
cam.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR)
cam.setFps(fps)
# ISP size for mesh generation (matches output of cam.isp)
isp_size = (cam.getIspWidth(), cam.getIspHeight())
# ---- Build warp mesh from calibration ----
mesh, meshW, meshH = self._get_mesh(calib, isp_size)
undistort = self.pipeline.create(dai.node.ImageManip)
undistort.setWarpMesh(mesh, meshW, meshH)
undistort.setMaxOutputFrameSize(isp_size[0] * isp_size[1] * 3)
undistort.initialConfig.setFrameType(dai.RawImgFrame.Type.BGR888p)
cam.isp.link(undistort.inputImage)
# ---- Resize for NN (300x300) ----
resize = self.pipeline.create(dai.node.ImageManip)
resize.initialConfig.setResize(300, 300)
resize.initialConfig.setKeepAspectRatio(False)
resize.setMaxOutputFrameSize(300 * 300 * 3)
resize.initialConfig.setFrameType(dai.RawImgFrame.Type.BGR888p)
undistort.out.link(resize.inputImage)
# ---- Face detection NN (no depth) ----
face_nn = self.pipeline.create(dai.node.MobileNetDetectionNetwork)
face_nn.setBlobPath(self._get_model_path("face-detection-retail-0004"))
face_nn.setConfidenceThreshold(0.5)
resize.out.link(face_nn.input)
# ---- Output detections ----
xout = self.pipeline.create(dai.node.XLinkOut)
xout.setStreamName("face_dets")
face_nn.out.link(xout.input)
# ---- Device / queues ----
self.device = dai.Device(self.pipeline) # default USB speed is fine for MRE
self.q_dets = self.device.getOutputQueue(name="face_dets", maxSize=1, blocking=False)
def _get_model_path(self, name: str) -> Path:
# models/face-detection-retail-0004.blob relative to this file
here = Path(__file__).resolve()
return (here.parent / "models" / f"{name}.blob").resolve()
def _get_mesh(self, calib: dai.CalibrationHandler, isp_size: tuple[int, int]):
cam = dai.CameraBoardSocket.RGB
M1 = np.array(calib.getCameraIntrinsics(cam, isp_size[0], isp_size[1]))
d1 = np.array(calib.getDistortionCoefficients(cam))
R1 = np.eye(3)
mapX, mapY = cv2.initUndistortRectifyMap(M1, d1, R1, M1, isp_size, cv2.CV_32FC1)
meshCell = 16
mesh0 = []
for y in range(mapY.shape[0] + 1):
if y % meshCell == 0:
row = []
for x in range(mapX.shape[1] + 1):
if x % meshCell == 0:
yy = min(y, mapY.shape[0] - 1)
xx = min(x, mapX.shape[1] - 1)
row.append(mapX[yy, xx])
row.append(mapY[yy, xx])
# ensure even count
if (mapX.shape[1] % meshCell) % 2 != 0:
row.extend([0, 0])
mesh0.append(row)
mesh0 = np.array(mesh0)
meshW = mesh0.shape[1] // 2
meshH = mesh0.shape[0]
mesh0.resize(meshW * meshH, 2)
mesh = list(map(tuple, mesh0))
return mesh, meshW, meshH
def get_detections(self):
"""
Non-blocking read of NN detections.
Returns a list of dai.ImgDetections (or empty list if none).
"""
pkt = self.q_dets.tryGet()
return [] if pkt is None else pkt.detections
if __name__ == "__main__":
cam = OakD_Camera()
try:
while True:
dets = cam.get_detections()
if dets:
# Print the first detection for brevity
d = dets[0]
print(f"det: conf={d.confidence:.2f} x={d.xmin:.3f}-{d.xmax:.3f} y={d.ymin:.3f}-{d.ymax:.3f}")
except KeyboardInterrupt:
pass
Thanks,
Josh