Nodes/Blind_watermark_Comfyui/πŸ“– Extract Text Watermark
ComfyUI Node

πŸ“– Extract Text Watermark

Read the invisible copyright message out of an image

By TianDongLΒ·Created 11 months agoΒ·Updated 11 months agoΒ· 22
πŸ“– Extract Text Watermark
  • image
  • extracted_text
β—„wm_length0β–Ί
β—„password_img1β–Ί
β—„password_wm1β–Ί
β—„processesautoβ–Ί

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_length output. 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 from wm_length into 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.
CategoryBlindWatermark/Extract

Inputs (5)

NameTypeDefaultDescription
imageIMAGEβ€”
wm_lengthINT00–999999Watermark length (get from embed node)
password_imgINT11–999999β€”
password_wmINT11–999999β€”
processesCOMBOauto7 options: auto, 1, 2, 4, 8, multiprocessing, +1

Outputs (1)

NameTypeDescription
extracted_textSTRINGβ€”