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

> Learn how Stable Diffusion implements invisible watermarking. Discover how to customize or disable watermarking for your generated images with this comprehensive guide.

- Repository: [CompVis - Computer Vision and Learning LMU Munich/stable-diffusion](https://github.com/CompVis/stable-diffusion)
- Tags: how-to-guide
- Published: 2026-03-01

---

**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`](https://github.com/CompVis/stable-diffusion/blob/main/scripts/txt2img.py).

### Initializing the WatermarkEncoder

The encoder is instantiated once per generation run. In [`scripts/txt2img.py`](https://github.com/CompVis/stable-diffusion/blob/main/scripts/txt2img.py), the code creates a `WatermarkEncoder` object and sets the default payload:

```python
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:

```python
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`](https://github.com/CompVis/stable-diffusion/blob/main/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`](https://github.com/CompVis/stable-diffusion/blob/main/scripts/txt2img.py).

### Changing the Text Payload

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

```python
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:

```python
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`](https://github.com/CompVis/stable-diffusion/blob/main/scripts/txt2img.py)), then ensure `put_watermark` receives `None`:

```python
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:

```python

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

img.save("output.png")

```

## Complete Code Examples

### Standard Implementation

```python
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

```python
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

```python

# 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`](https://github.com/CompVis/stable-diffusion/blob/main/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`](https://github.com/CompVis/stable-diffusion/blob/main/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`](https://github.com/CompVis/stable-diffusion/blob/main/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`](https://github.com/CompVis/stable-diffusion/blob/main/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.