Stable Diffusion Invisible Watermarking: Implementation, Customization, and Disabling Guide

Stable Diffusion embeds an invisible watermark into every generated image using the imwatermark library with a default payload of "StableDiffusionV1", which can be customized by modifying the encoder payload or algorithm, or completely disabled by passing None to the watermarking function.

The CompVis/stable-diffusion repository automatically applies an invisible watermark to all generated images using DWT-DCT steganography techniques. This feature helps track image provenance by embedding a hidden byte payload that survives compression and minor edits. Understanding the invisible watermarking implementation allows developers to customize the embedded metadata or disable it entirely for privacy-sensitive applications.

How Invisible Watermarking Works in Stable Diffusion

The implementation relies on the third-party imwatermark library (installable via pip install invisible-watermark) and consists of three main components defined in scripts/txt2img.py.

Initializing the WatermarkEncoder

The encoder is instantiated once per generation run. In scripts/txt2img.py, the code creates a WatermarkEncoder object and sets the default payload:

from imwatermark import WatermarkEncoder

wm = "StableDiffusionV1"
wm_encoder = WatermarkEncoder()
wm_encoder.set_watermark('bytes', wm.encode('utf-8'))

This converts the UTF-8 string into a byte array that will be distributed across the image's frequency domain.

The put_watermark Function

The put_watermark helper function handles the actual embedding immediately before saving. It converts the PIL image to a NumPy BGR array, applies the DWT-DCT algorithm, and converts back to RGB:

import cv2
import numpy as np
from PIL import Image

def put_watermark(img: Image.Image, encoder=None):
    if encoder is not None:
        img_bgr = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
        img_bgr = encoder.encode(img_bgr, 'dwtDct')
        img = Image.fromarray(img_bgr[:, :, ::-1])
    return img

The 'dwtDct' parameter specifies the discrete wavelet transform plus discrete cosine transform method, which provides robustness against JPEG compression.

Verification in the Test Suite

The repository includes a verification mechanism in scripts/tests/test_watermark.py that uses WatermarkDecoder to extract the payload from saved images and confirm the embedding succeeded.

Customizing the Watermark Payload

Developers can modify both the embedded message and the encoding algorithm in scripts/txt2img.py.

Changing the Text Payload

Replace the default "StableDiffusionV1" string with any custom UTF-8 message before calling set_watermark:

wm_encoder = WatermarkEncoder()
custom_payload = "MyCustomTag2024"
wm_encoder.set_watermark('bytes', custom_payload.encode('utf-8'))

Short strings (under 32 bytes) work best for reliability.

Selecting Alternative Algorithms

The imwatermark library supports multiple encoding methods. While the default uses 'dwtDct', you can specify 'dwt' (wavelet only) or 'dct' (cosine only) by modifying the second argument in the encode call:

img_bgr = encoder.encode(img_bgr, 'dwt')  # Wavelet-only encoding

Disabling Watermarking Entirely

The watermarking step is optional and can be bypassed without modifying the underlying diffusion model.

Method 1: Skip Encoder Creation

Comment out or remove the encoder initialization block (lines 61-66 in scripts/txt2img.py), then ensure put_watermark receives None:

wm_encoder = None  # Encoder not initialized

# In the save loop:

img = put_watermark(img, wm_encoder)  # Returns original image unchanged

Method 2: Direct Image Saving

Alternatively, modify the save loop to call img.save() directly, bypassing the put_watermark function entirely:


# Instead of: img = put_watermark(img, wm_encoder)

img.save("output.png")

Complete Code Examples

Standard Implementation

from imwatermark import WatermarkEncoder
import cv2
import numpy as np
from PIL import Image

# Initialize with default payload

wm_encoder = WatermarkEncoder()
wm_encoder.set_watermark('bytes', b'StableDiffusionV1')

def put_watermark(img: Image.Image, encoder=None) -> Image.Image:
    if encoder is not None:
        img_bgr = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
        img_bgr = encoder.encode(img_bgr, 'dwtDct')
        img = Image.fromarray(img_bgr[:, :, ::-1])
    return img

# Usage

pil_img = Image.open("generated.png")
watermarked = put_watermark(pil_img, wm_encoder)
watermarked.save("output.png")

Custom Payload and Algorithm

wm_encoder = WatermarkEncoder()
wm_encoder.set_watermark('bytes', "ProjectAlpha".encode('utf-8'))

def put_watermark_custom(img, encoder):
    if encoder is not None:
        img_bgr = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
        # Use DWT-only instead of DWT-DCT

        img_bgr = encoder.encode(img_bgr, 'dwt')
        return Image.fromarray(img_bgr[:, :, ::-1])
    return img

Disabling Watermarking


# Disable by passing None

wm_encoder = None

def put_watermark(img, encoder=None):
    # No-op when encoder is None

    return img

# Save without watermark

pil_img = Image.open("generated.png")
clean_img = put_watermark(pil_img, wm_encoder)
clean_img.save("clean.png")

Summary

  • Stable Diffusion uses the imwatermark library in scripts/txt2img.py to embed an invisible watermark via the DWT-DCT algorithm.
  • The default payload is "StableDiffusionV1", but any UTF-8 string can be substituted by modifying the set_watermark call.
  • Alternative encoding methods include 'dwt' and 'dct' instead of the default 'dwtDct'.
  • Watermarking can be disabled by setting the encoder to None or bypassing the put_watermark function entirely.
  • The WatermarkDecoder in scripts/tests/test_watermark.py validates that embedded payloads survive image processing.

Frequently Asked Questions

What library does Stable Diffusion use for invisible watermarking?

Stable Diffusion uses the imwatermark library (available as invisible-watermark on PyPI), which implements DWT-DCT steganography techniques to embed byte payloads into the frequency domain of images without visible degradation.

Can I change the watermark text in Stable Diffusion?

Yes. Modify the wm variable in scripts/txt2img.py before it is passed to wm_encoder.set_watermark('bytes', wm.encode('utf-8')). Any UTF-8 string works, though shorter payloads under 32 bytes provide better extraction reliability.

How do I completely remove the watermark from Stable Diffusion images?

Set wm_encoder = None in scripts/txt2img.py and ensure the put_watermark function receives this None value, which causes it to return the original image unchanged. Alternatively, remove the put_watermark call entirely from the image saving loop.

Does the invisible watermark affect image quality?

No. The DWT-DCT algorithm modifies high-frequency coefficients that are imperceptible to human vision. According to the imwatermark implementation, the embedding process does not introduce visible artifacts or reduce perceptual quality, though it does slightly alter the file's binary representation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →