CV WeChat QR Detect
Read every QR code in a frame, including the tiny ones
- image
- bboxes
- corners
- connections
- texts
- labels
- qr_count
OpenCV's stock QRCodeDetector is fine on a clean, flat, well-lit code and gives up on everything else - a code at an angle, a code at 40 pixels wide in the corner of a product photo, a code on a crumpled page. CV WeChat QR Detect wraps the WeChat detector instead: a small learned detect network plus a super-resolution network, and it finds and decodes all the codes in an image in one shot.
It also hands you the geometry, not just the string, which is the part that makes it useful in a ComfyUI graph.
How it works
cv2.wechat_qrcode.WeChatQRCode takes two ONNX models at construction: a detect model that finds candidate code regions, and an SR model that upscales small ones before decoding. That second model is the whole reason it beats the built-in detector on hard inputs - a QR code needs enough pixels per module to decode, and the SR network manufactures them.
Both models come from ComfyUI/models/onnx and are chosen with the detect_model and sr_model dropdowns. They're Apache-2.0 packages from the OpenCV contribution repo (detect_2026april.onnx and sr_2026april.onnx are the reference pair), with the usual caveat that the repo is Apache-2.0 as a whole while asking contributors to keep per-model licences - so read before you redistribute. The pack's model_sources.txt is the licensing record, and this is the kind of node where you actually want to read it: it's the difference between a hobby graph and shipping a product.
The node processes one image - the first frame of a batch, or an NPARRAY treated as a single frame.
The outputs, which are the interesting bit
Everything is DATA. Nothing here draws anything; you wire the data to drawing nodes, which is why you can put the same detection on the image, in a report, and in an overlay at the same time.
- bboxes - a
BOUNDING_BOXper code, derived from the four corners, with the decoded text as its label. Straight into the core Draw BBoxes. - corners -
(N*4, 2)float32, the four corner points of every code in order. - connections -
(N*4, 2)int32 index pairs closing each code's quad, already offset per code so they indexcornersdirectly. Feedcorners+connectionsintoCV Draw Connectionsfor the outline. - texts -
(N,), the decoded string of each code, one entry per code. An empty string means the code was located but not decoded - which is a genuinely useful distinction and worth branching on rather than treating as success. - labels -
(N*4,)strings aligned row-for-row withcorners: the code's text on its first corner and blanks on the other three, socorners+labelsintoCV Draw Labelswrites each string exactly once per code. - qr_count - how many were found.
Zero codes is a valid result with empty outputs, not an error.
Installing it
Manager → ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart. Python ≥ 3.12, recent ComfyUI on the V3 node API, opencv-contrib-python-headless~=5.0.0.93. Contrib is mandatory here - wechat_qrcode is a contrib module, so a plain opencv-python wheel on top of it means the module isn't there and the node fails on execute.
The two .onnx models do not ship with the pack and aren't in example_inputs/. Drop them in ComfyUI/models/onnx yourself; the pack registers that folder with ComfyUI so the dropdowns populate (they'll be empty until the files are there, which is the #1 reason this node "doesn't work"). GPL-3.0, forked from opencv-comfyui.
Where people get burned
Empty model dropdowns. Nothing in models/onnx, nothing to pick. Download both files, restart, and the widgets fill in.
module 'cv2' has no attribute 'wechat_qrcode'. Evidence that something replaced the contrib wheel with a non-contrib one - they share a single site-packages/cv2, so the swap is silent until a contrib node runs. The pack ships tools/repair_opencv_contrib.py; run it with --check first, then --apply.
Tiny or blurry codes. This is where the SR model earns its keep, so if small codes are consistently located-but-not-decoded, check the SR model is actually selected - a blank or wrong sr_model removes exactly the capability you needed.
A batch of video frames. Only the first frame is read. Loop the frames and run one detection per frame if you're scanning a sequence; there's no built-in iteration here.
Expecting grayscale handling to be automatic. The node accepts gray, BGR and BGRA NPARRAYs and any dtype, but decoding needs what it needs - a 30-pixel code in a heavily downscaled image is gone before any model sees it. Crop and upscale the region if you know roughly where the code is.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE | Input image. An IMAGE batch uses its first frame; an NPARRAY (gray/BGR/BGRA, 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. | |
| detect_model | COMBO | WeChat QR DETECT .onnx model from ComfyUI/models/onnx (detect_2026april.onnx). | |
| sr_model | COMBO | WeChat QR SUPER-RESOLUTION .onnx model from ComfyUI/models/onnx (sr_2026april.onnx) - upsamples small codes so they decode. |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| bboxes | BOUNDING_BOX | One {x, y, width, height, score} dict per QR code (the axis-aligned box around its 4 corners, label = decoded text) - feed the core 'Draw BBoxes' node. |
| corners | NPARRAY | (N*4, 2) float32: the 4 corner points of every code, in order. Feed 'CV Draw Points', or 'CV Draw Connections'/'Draw Labels' with the matching outputs below. Empty (0, 2) when no codes. |
| connections | NPARRAY | (N*4, 2) int32 edge index-pairs closing each code's quad, already offset per code to index 'corners' directly. Feed 'corners' + this into 'CV Draw Connections' to outline the codes. Empty (0, 2) when no codes. |
| texts | NPARRAY | (N,) string array: the decoded text of each code (one entry per code). Feed 'Preview as Text'/'Inspect CV Data'. An empty string means the code was located but not decoded. |
| labels | NPARRAY | (N*4,) string array aligned row-for-row with 'corners': each code's decoded text on its FIRST corner, blank on the other three. Feed 'corners' + this into 'CV Draw Labels' to write the text once per code. Empty (0,) when no codes. |
| qr_count | INT | Number of QR codes detected. |