Nodes/ComfyUI CV/cv2.drawFrameAxes
ComfyUI Node

cv2.drawFrameAxes

See where a solved camera pose actually points

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
cv2.drawFrameAxes
  • source
  • cameraMatrix
  • distCoeffs
  • rvec
  • tvec
  • result
◄length0.0000►
◄thickness1►

You've detected a chessboard, run solvePnP, and now you have a rotation vector and a translation vector that describe where that board sits in 3D. Numbers. Unreadable numbers. This node projects the X, Y and Z axes of that pose into the image so you can look at it and go "no, that's rotated ninety degrees."

It's the last step of every pose pipeline and the only step that gives you a visual answer.

The inputs, all of which must agree with each other

source is the image you draw on - IMAGE, MASK or NPARRAY, with the result output echoing the input's format.

cameraMatrix is the 3×3 intrinsics K. distCoeffs is the distortion vector from calibration; a five-element zeros vector is the "no lens distortion" case. Both are NPARRAY, both come straight out of the pack's calibration nodes or CV Camera Matrix for a hand-built K.

rvec and tvec are the pose. They come from solvePnP (the pack has a curated CV SolvePnPPose wrapper because the raw one returns three values and plumbing it by hand is annoying). Feed the (3, 1) vectors it gives you, unchanged.

length is the one that will stop you cold: it's required and it defaults to 0, and cv2.drawFrameAxes asserts that length is greater than zero. So a fresh node with the default raises (-215:Assertion failed) length > 0 on the first run. Nothing is wrong with your install; the widget just needs a real number.

What number? length is expressed in the same units as tvec. If you built your object points in board-square units - the normal convention when you hand solvePnP a chessboard grid - then a length of 3 to 5 draws axes three to five squares long, which reads well in a photo. If your object points are in millimetres, length is in millimetres.

thickness is optional and defaults to 1.

What you get, and how to read a bad result

Three coloured arrows from the pose origin: X, Y and Z, drawn with the perspective of your camera. Z points away from the camera along the board's normal - that's the axis to look at first, because it tells you immediately whether the pose flipped.

Common readings:

  • Axes centred on the board, Z pointing up out of it. Correct. The pose agrees with what you see.
  • Axes tiny, or at the frame edge. Your translation is in different units than you assumed, or the pose origin isn't where you think it is.
  • Nothing drawn. Check length first - a zero-length axes call raises rather than drawing nothing, so if you got a clean run and no axes, you're looking at an image you didn't save or a preview upstream of this node.
  • Axes pointing backwards through the image plane. The classic sign-flip or rotation-convention problem, which looks like a mathematical mystery until you see it drawn.

The pack's 57_camera_pose_3d_preview.json workflow does exactly this: corners → pose → drawFrameAxes for the overlay, plus its own three.js viewer for the full 3D scene. If you want the flat version, this node is the flat version.

Why it's worth wiring even when you trust the math

Because pose pipelines fail consistently. solvePnP will happily return a plausible rotation and translation for a set of points it mismatched, or a board whose object points were built in the wrong order. The reprojection error looks acceptable; the pose is wrong. Drawing the axes costs one node and turns a silent wrongness into something you spot in half a second. Same principle as the sibling cv2.drawChessboardCorners: visualise the intermediate step, don't just trust the last number in the chain.

Installing comfyui_cv

One of ~470 auto-generated raw cv2.* wrappers in bmad4ever/comfyui_cv, plus curated nodes - GPL-3.0, forked from Gerold Meisinger's opencv-comfyui. Manager: search ComfyUI CV. Or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Restart. Python ≥ 3.12 and a recent ComfyUI built on the V3 node API; one dependency, opencv-contrib-python-headless~=5.0.0.93. Keep the contrib wheel - all four OpenCV distributions share one site-packages/cv2 and the last install wins, so a non-contrib opencv-python removes the contrib nodes silently. tools/repair_opencv_contrib.py --check / --apply fixes that.

Common issues

(-215:Assertion failed) length > 0. The default. Type a length.

(-215:Assertion failed) on cameraMatrix or rvec. Wrong shape. K is 3×3 float, pose vectors are 3×1 or 1×3 - pass them through from solvePnP/calibration rather than retyping.

Axes drawn in the wrong corner of the frame. tvec units or a mismatched cameraMatrix (a K from a different resolution is the classic).

Wrong arrow order. OpenCV draws X red, Y green, Z blue. If you expected a different axis order, that's the convention to re-check - not the node.

Categoryimage/CV/low-level/cv2 D

Inputs (7)

NameTypeDefaultDescription
sourceCOMFY_MATCHTYPE_V3 The image output(s) echo this input's format. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size.
cameraMatrixNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
distCoeffsNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
rvecNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
tvecNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
lengthFLOAT0.0000-1e+38–1e+38 - - -
thicknessoptINT1-2147483648–2147483647 - - - Preset to the OpenCV default (1).

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the 'source' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.