π Extract Text Watermark
Read the invisible copyright message out of an image
- image
- extracted_text
This is the payoff node for the whole text-watermarking idea. WatermarkEmbedText wrote a string invisibly into an image; this node pulls it back out and gives it to you as text. If you embedded a copyright notice into everything you post, this is how you check whether that leaked copy on someone's blog still carries your claim on it.
The workflow is deliberately simple: embed β save the PNG β (days later, after it's been re-encoded and cropped) load it β extract. But "simple" hides one non-negotiable detail: you must feed it the same wm_length and the same two passwords the embed node used. Get any of those wrong and you get garbage.
The inputs that matter
- image - the image you believe is watermarked.
- wm_length - the watermark's length in bits, straight from the embed node's
wm_lengthoutput. The tooltip says it: "get from embed node." There's no way to derive it from the image, so if you didn't keep it, you're stuck - which is exactly why the embed node exposes it as an output you can wire directly across. In a saved workflow, run the embed once and drag the wire fromwm_lengthinto this node's input and it's locked in. - password_img / password_wm - must match the embed exactly. These are the key; there's no guessing them from the image.
- processes - CPU pool setting; "auto" is correct for everyone.
The single output is extracted_text (STRING). Wire it into any ShowText / text display node. Expect the recovered string to be exact if the image hasn't been mangled too hard - the library recovers the bits, not a fuzzy guess.
How it works
Same reverse-transform mechanism as the pack's other extractor, but for text: the carrier goes through DWT-DCT-SVD, each block's singular values are read to recover a bit, the bits are un-shuffled with password_wm, and the result is decoded from UTF-8 back into the string. The node also enforces a minimum image size based on wm_length (roughly max(int(sqrt(wm_length)*4), 128)) and refuses to run on images too small to plausibly hold the message.
Two failure behaviors to know. On invalid inputs it returns a string starting with ERROR: rather than crashing, so an ERROR: output is the node talking, not it breaking. And because the library decodes with error-tolerant UTF-8 (errors='replace'), a mangled watermark can come back as a string with replacement characters - which is your cue that the signal was damaged, not that the node is broken.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/TianDongL/Blind_watermark_Comfyui.git
pip install -r requirements.txt
# restart ComfyUI
Or "Blind Watermark" via ComfyUI Manager. CPU-only, no model files. Actual requirements: numpy, opencv-python, setuptools, PyWavelets, qrcode, pyzbar. Ignore the README's suggestion to install blind-watermark from pip - the pack vendors its own copy of that library.
Troubleshooting
- Garbled text - passwords mismatch, wrong
wm_length, or the image was re-encoded (JPEG saves are cumulative damage; save the watermarked file as PNG and don't re-save). ERROR:output - the node's guardrails fired, usually the "image too small" check. Bigger carrier or shorter message.- Works on the fresh image but not the copy you found - that's the watermark's resilience being tested, and it lost. Heavy cropping or aggressive filtering will do that. Nothing to fix except expectations: this survives scaling, mild rotation and normal re-encoding, not a repaint.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | β | |
| wm_length | INT | 00β999999 | Watermark length (get from embed node) |
| password_img | INT | 11β999999 | β |
| password_wm | INT | 11β999999 | β |
| processes | COMBO | auto | 7 options: auto, 1, 2, 4, 8, multiprocessing, +1 |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| extracted_text | STRING | β |