Nodes/ComfyUI CV/CV QR Detect
ComfyUI Node

CV QR Detect

Read every QR code in a photo, no models required

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV QR Detect
  • image
  • found
  • qr_count
  • text
  • bboxes
  • corners
  • connections
  • texts
  • labels
  • straight_codes
◄detector▾►
◄mode▾►
◄eps_x0.20►
◄eps_y0.10►
◄use_alignment_markerstrue►

Decodes QR codes with OpenCV's own detector - no ONNX files, no downloads, no WeChat model. Drop an image in, get text and geometry out.

The two detectors

This is worth understanding before you tune anything, because they fail differently.

The classic detector (cv2.QRCodeDetector) scans lines looking for the 1:1:3:1:1 finder-pattern ratio and is what the eps_x/eps_y tolerances belong to. It's fast and it's a bit fussy: raise eps_x (0.2 by default) for blurry or low-contrast codes, lower it to reject false positives. use_alignment_markers refines corner positions using the code's alignment markers - turn it off for a damaged code whose markers are unreliable.

The ArUco-based detector (cv2.QRCodeDetectorAruco) reuses the ArUco marker machinery and is, in practice, usually the one that works on small, rotated, or perspective-distorted codes - a phone photo of a poster, a code at the edge of a wide-angle frame. It ignores the classic knobs entirely, so don't tune eps values while it's selected and wonder why nothing changes.

mode then chooses multi - every code in the image, the normal case - or curved, which runs detectAndDecodeCurved for a single code wrapped around a cylinder. Curved is a specialist: it rectifies before decoding and fails on some flat codes, so don't leave it on as a general setting.

Outputs, which are all data

Nothing here draws. found (a boolean you can branch on), qr_count, and text - every payload one per line, straight into Preview as Text.

For visuals you pick: bboxes is a BOUNDING_BOX with one axis-aligned box per code plus the decoded text as its label, so core Draw BBoxes gives you a labelled overlay in one wire. corners is the exact (N4, 2) quads, and connections is an index-pair list that closes each quad - feed those two to CV Draw Connections when you want the true quad outline rather than the axis-aligned approximation. labels is a (N4,) string array carrying each code's text on its first corner and blanks elsewhere, which paired with corners and CV Draw Labels writes the payload once per code instead of four times.

texts is the (N,) payload array, where an empty entry means a code was located but not decoded - that distinction is useful, since a located-but-unreadable code usually means damage or resolution, not a detection failure. And straight_codes is an IMAGE batch of the rectified, binarised symbols the decoder read, white-padded where versions differ.

Installing

ComfyUI Manager → search "ComfyUI CV", or by hand:

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

Python ≥ 3.12, a recent ComfyUI on the V3 node API, and the pinned contrib wheel: opencv-contrib-python-headless~=5.0.0.93 alongside numpy and torch. The QR classes live in cv2's objdetect module, which the pack reaches through hand-written nodes because the generator only wraps plain functions.

Gotchas

One frame only. An IMAGE batch is processed as its first frame. To scan a clip, iterate.

Structured Append split messages decode as one joined message on the first part, with the rest blank. That's expected, not a bug - and it's how CV QR Encode's multi-code output comes back together when all parts are visible in one image.

Zero codes is a valid result, not a failure: empty outputs with found=false. If found is true but your text preview is empty, you're in the located-but-undecodable case, and straight_codes will show you why.

Broken image quality is a detection problem, not a QR problem. Heavy compression, motion blur and glare all defeat finder-pattern scanning before anything clever can help. If you're fighting codes in a pipeline that has also been through lossy steps, that's where to look first.

Categoryimage/CV/codes

Inputs (6)

NameTypeDefaultDescription
imageNPARRAY,IMAGEImage to scan. An IMAGE batch uses its first frame; an NPARRAY (gray/BGR, any dtype) is treated as one frame. 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.
detectorCOMBOclassic: cv2.QRCodeDetector, the scanline finder-pattern detector (honours eps_x/eps_y and use_alignment_markers). ArUco-based: cv2.QRCodeDetectorAruco, which reuses the ArUco marker machinery - more robust on small, rotated or warped codes, and ignores the classic knobs.
modeCOMBOmulti: detectAndDecodeMulti - every code in the image (the normal choice). curved: detectAndDecodeCurved - ONE code on a curved surface, rectified before decoding; classic detector only, and it fails on some flat codes, so only use it for genuinely curved ones.
eps_xoptFLOAT0.200–1Classic detector only: tolerance of the HORIZONTAL scan for the 1:1:3:1:1 finder-pattern ratio. Raise it for blurry/low-contrast codes, lower it to reject false finders.
eps_yoptFLOAT0.100–1Classic detector only: the same tolerance for the VERTICAL scan of the finder pattern.
use_alignment_markersoptBOOLEANtrueClassic detector only: refine the corner positions with the code's alignment markers (OpenCV's default). Turning it off is faster and can help on damaged codes whose markers are unreliable.

Outputs (9)

NameTypeDescription
foundBOOLEANTrue when at least one code was located. Feed a 'Basic data handling: IfElse' to branch instead of gating a node with a boolean widget.
qr_countINTNumber of QR codes located.
textSTRINGEvery decoded string, one per line - wire into the core 'Preview as Text' (PreviewAny) node. Empty when nothing decoded.
bboxesBOUNDING_BOXOne {x, y, width, height, score, label} dict per code (axis-aligned box around its 4 corners, label = decoded text) - feed the core 'Draw BBoxes' node.
cornersNPARRAY(N*4, 2) float32: the 4 corner points of every code, in order. Feed 'CV Draw Points', or 'CV Draw Connections'/'Draw Labels' together with the matching outputs. Empty (0, 2) when no codes.
connectionsNPARRAY(N*4, 2) int32 edge index-pairs closing each code's quad, indexed into 'corners' - feed 'CV Draw Connections' to outline the codes exactly (unlike the axis-aligned bboxes).
textsNPARRAY(N,) string array of the decoded payloads, one per code - inspect with 'Inspect CV Data' + 'Preview as Text'. An empty entry means the code was located but not decoded.
labelsNPARRAY(N*4,) string array aligned row-for-row with 'corners': each code's text on its FIRST corner, blank on the other three. Feed 'corners' + this to 'CV Draw Labels' to write the text once per code.
straight_codesIMAGEIMAGE batch of the rectified, binarized symbols (one per decoded code, perspective removed) - the picture the decoder actually read. Frames of differing versions are white-padded to a common size. Empty batch when nothing decoded.