blind_watermark: DWT-DCT-SVD blind watermarking in Python
Blind&Invisible Watermark ,图片盲水印,提取水印无须原图!
At a glance
- What is it?
- A Python library that embeds a string, an image or a bit array into a picture and extracts it without the original file. The documentation claims survival of rotation, cropping, masks, resize and noise; the trade-offs sit in the required watermark shape and the frequency-domain maths.
- Who is it for?
- Adopt blind_watermark when you need to prove an image passed through your pipeline and you can keep the two passwords and the watermark shape on record. Skip it if you need a watermark that survives a re-encode by a hostile party, or if video is the target: the README covers still images only.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What blind_watermark solves, and for whom
Most watermarking tools write a visible mark over the pixels. blind_watermark writes into the frequency coefficients instead, so the picture looks unchanged and the mark can be read back later. The word blind in the name refers to extraction: the original image is not needed, only the passwords and the shape of the watermark. That matters when the original is gone, when it lives on someone else's disk, or when you only receive the suspect file.
The audience is narrow but real. Python developers who need to tag images leaving a service, researchers comparing frequency-domain embedding, and anyone building a provenance check into a pipeline. The README's own example embeds the string '@guofei9987 开源万岁!' and prints it back. There is no GUI in the repository, no server component, and no video path. If you want a desktop app or a video watermark, this is not the project, and the related searches for a GUI or for video describe something the README does not offer.
How DWT-DCT-SVD embedding actually works
The pipeline is three transforms stacked. A discrete wavelet transform splits the image into frequency sub-bands; the discrete cosine transform works on blocks inside one of those bands; a singular value decomposition on each block gives a set of singular values. The watermark bits are added to those singular values, and the inverse transforms rebuild the image. Because the change lands in singular values rather than pixel values, the visible difference stays small while the mark stays recoverable after some geometric edits.
The README's attack table is the honest part of the documentation. It lists rotate 45 degrees, random crop, masks, vertical cut, horizontal cut, resize, pepper noise and a 10 percent brightness reduction, and for each it shows the extracted string coming back intact. Those are the author's figures on the author's images. Nothing in the repository describes a threshold at which extraction fails, and no test image set is published, so treat the table as a demonstration rather than a guarantee.
Two arguments govern everything. password_img seeds the embedding, password_wm protects the watermark itself. Extraction needs both, plus wm_shape. That shape is not optional: the extractor has to know how many bits to pull out of the coefficients, and the README states plainly that wm_shape is necessary. Lose the length and you cannot read the mark back.
Installing blind_watermark and embedding a first image
Installation is one pip command. The package pulls numpy, opencv-python and PyWavelets as dependencies, and it declares support for Python 3.5 and above on Windows, Linux and macOS.
pip install blind_watermarkThe README also documents a developer install from a clone: run git clone on the repository, cd into it, and run pip install . inside. The console entry point is named blind_watermark and is registered in setup.py.
For a first run in Python, the string workflow is the shortest path. The example below reads an image, reads a string as the watermark, embeds it, and prints the bit length, which you must keep for extraction.
from blind_watermark import WaterMark
bwm1 = WaterMark(password_img=1, password_wm=1)
bwm1.read_img('pic/ori_img.jpg')
wm = '@guofei9987 开源万岁!'
bwm1.read_wm(wm, mode='str')
bwm1.embed('output/embedded.png')
len_wm = len(bwm1.wm_bit)
print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))The printed length is the value you pass as wm_shape later. Extraction constructs a new WaterMark with the same two passwords and calls extract with that length and mode='str'.
bwm1 = WaterMark(password_img=1, password_wm=1)
wm_extract = bwm1.extract('output/embedded.png', wm_shape=len_wm, mode='str')
print(wm_extract)You should see the original string printed. If you get garbage, the shape is wrong, the passwords are wrong, or the image was altered beyond what the coefficients tolerate. The README shows a bash path as well, with the same two passwords and a --wm_shape flag on extraction.
blind_watermark --embed --pwd 1234 examples/pic/ori_img.jpeg "watermark text" examples/output/embedded.png
blind_watermark --extract --pwd 1234 --wm_shape 111 examples/output/embedded.pngFor an image watermark, read_wm takes a path instead of a string, and extraction needs the watermark's dimensions as a tuple, for example wm_shape=(128, 128), plus out_wm_name for the recovered file. For raw bits, mode='bit' takes a list such as [True, False, True, True, True, False]; extraction returns floats, and the README advises a threshold around 0.5.
The wm_shape requirement and other failure modes
The design pushes bookkeeping onto you. wm_shape must be supplied at extraction, so the watermark length becomes metadata you have to store alongside the passwords. In a production pipeline that means a database column or a sidecar file. Forget it and the image is unreadable by the library, even though the bits are still sitting in the coefficients.
Bit mode returns floats, not booleans. The README tells you to pick a threshold such as 0.5. That threshold is a judgement call you make per image class, and the repository does not publish guidance on how much separation to expect. On a heavily compressed or resampled image the recovered values will drift toward the middle, and a fixed 0.5 will start flipping bits.
The attack table is also a fixed set. It covers rotation, cropping, masking, cutting, resizing, pepper noise and a brightness change. It does not cover JPEG re-encoding at low quality, colour-space conversion, screenshotting, or a determined adversary who knows the scheme and can estimate and subtract the embedded signal. A watermark that is invisible by construction is also a watermark that a motivated party can attempt to remove, and the README makes no claim otherwise.
Concurrency is exposed through a single argument: WaterMark(..., processes=None). The README says None means using all processes, and an integer sets the count. There is no documented queue, batching API or streaming mode, so throughput work is on you.
Compared with visible watermarking and with text_blind_watermark
Visible watermarking, the kind done with a logo overlay, is a different job. It deters casual reuse because a human sees the mark, and it needs no extraction step, no passwords and no shape metadata. Its weakness is that it is trivially cropped or painted over, and it damages the image. blind_watermark trades that visibility for recoverability: the mark is not a deterrent, it is evidence. If your goal is to stop people from reposting a photo, an overlay does more work than this library.
The author's own related projects make the boundary clearer. text_blind_watermark embeds a message into text, and HideInfo hides data in images, sounds and text. Those target different carriers. Within image watermarking, the meaningful comparison is against spatial-domain LSB-style embedding, which writes into low-order pixel bits. LSB is simpler and faster, but any resize or lossy re-encode destroys it, which is exactly the class of edit the DWT-DCT-SVD pipeline is built to survive. The cost is complexity: three transforms per block, a slower embed, and that mandatory shape parameter.
Maintenance, licence and upgrade cost
The repository is not archived, and the last push was on 2026-03-25. That is recent enough that the codebase is not abandoned, but the release history is thin: PyPI shows 0.2.1 from 2022-01-11, with 0.0.5 and 0.0.2 from 2020. Between the 2022 release and the 2026 push there is no published release, so the installable version and the repository head have diverged for years. If you pip install, you get 0.2.1. If you need a fix that landed after that, you are on the developer install path from the README: clone, cd, pip install .
Dependencies are numpy, opencv-python and PyWavelets. opencv-python is the one to watch, since it ships binary wheels and its major versions have changed APIs before. The setup.py pins numpy>=1.17.0 and leaves the others unpinned, so a fresh install today can resolve to versions the 2022 release was never tested against. Pin them in your own requirements if the pipeline is long-lived.
The licence is MIT, which is permissive: it allows commercial use and modification with the copyright notice retained. That is a statement about the licence text, not legal advice. If you plan to ship watermarked images as evidence in a dispute, the licence question is separate from the evidentiary one, and the README offers no guidance on the latter.
Editorial conclusion
Adopt blind_watermark when you need to prove an image passed through your pipeline and you can keep the two passwords and the watermark shape on record. Skip it if you need a watermark that survives a re-encode by a hostile party, or if video is the target: the README covers still images only. Before committing, run the pip install, embed one string of your own, extract it with wm_shape set to the length printed by wm_bit, then repeat the extraction after rotating the file 45 degrees and after a random crop. If the recovered string holds up on your own images, the library is doing what it claims.
Frequently asked questions
What is a blind watermark in blind_watermark?
The project describes it as a watermark based on DWT-DCT-SVD where extraction does not need the original image. You supply the two passwords and the watermark shape, and the library reads the mark out of the frequency coefficients.
How does an invisible watermark work in blind_watermark?
The image goes through a discrete wavelet transform, then a discrete cosine transform on blocks, then a singular value decomposition. Watermark bits are added to the singular values and the inverse transforms rebuild the picture, so the change is not visible.
How can I add an invisible watermark to an image with blind_watermark?
Install with pip install blind_watermark, create a WaterMark with password_img and password_wm, call read_img and read_wm, then call embed with an output path. Keep the length of wm_bit, because extraction needs it as wm_shape.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/guofei9987-blind-watermark)