pranjal-pravesh's picture
Update README.md
6107ffa verified
|
Raw
History Blame Contribute Delete
5.64 kB
---
language:
- en
tags:
- image-watermarking
- onnx
- computer-vision
---
# Watermark Anything Model (WAM) - ONNX
This repository contains the ONNX conversion of the **Watermark Anything Model (WAM)**. To ensure optimal performance and flexibility, the monolithic PyTorch model has been decomposed into three distinct ONNX computational graphs.
## Model Components
The watermarking pipeline is divided into three `.onnx` files, ensuring that image scaling and heavy computations (like Just Noticeable Difference - JND attenuation) can be performed optimally.
1. **`embedder.onnx`**
- **Inputs:**
- `image`: Float32 `[batch_size, 3, 256, 256]` (Normalized image)
- `message`: Float32 `[batch_size, nbits]` (Binary payload to embed)
- **Outputs:**
- `watermark`: Float32 `[batch_size, 3, 256, 256]` (Watermark signal/deltas)
2. **`blender.onnx`**
- **Inputs:**
- `imgs`: Float32 `[batch_size, 3, height, width]` (Original normalized image at any resolution)
- `deltas`: Float32 `[batch_size, 3, height, width]` (Watermark signal resized to original image dimensions)
- **Outputs:**
- `watermarked_image`: Float32 `[batch_size, 3, height, width]` (Final normalized image with JND applied)
3. **`extractor.onnx`**
- **Inputs:**
- `image`: Float32 `[batch_size, 3, 256, 256]` (Watermarked image, resized and normalized)
- **Outputs:**
- `mask`: Float32 `[batch_size, 1 + nbits, 256, 256]` (Detected watermark mask and extracted message bits)
---
## Inference Guide (Python)
You will need `onnxruntime`, `numpy`, `torch`, `torchvision`, and `Pillow`.
### 1. Installation
```bash
pip install onnxruntime numpy torch torchvision pillow
```
### 2. End-to-End Example
Below is a complete Python script to load an image, embed a watermark, save the result, and extract the watermark mask.
```python
import numpy as np
import onnxruntime as ort
import torch
from torchvision import transforms
from PIL import Image
# ---------------------------------------------------------
# 1. Initialization
# ---------------------------------------------------------
# Load ONNX Runtime sessions
embedder_sess = ort.InferenceSession("embedder.onnx")
blender_sess = ort.InferenceSession("blender.onnx")
extractor_sess = ort.InferenceSession("extractor.onnx")
# Define ImageNet normalization stats
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
normalize = transforms.Normalize(mean=mean, std=std)
unnormalize = transforms.Normalize(
mean=[-m/s for m, s in zip(mean, std)],
std=[1/s for s in std]
)
resize_256 = transforms.Resize((256, 256), antialias=True)
to_tensor = transforms.ToTensor()
# Configure watermark bits (0 means only localizer mask, no payload)
nbits = 0
# ---------------------------------------------------------
# 2. Image Preprocessing
# ---------------------------------------------------------
img_path = "input_image.jpg"
pil_img = Image.open(img_path).convert("RGB")
img_tensor = to_tensor(pil_img).unsqueeze(0) # [1, 3, H, W]
# Normalize for the model
img_normalized = normalize(img_tensor)
original_h, original_w = img_tensor.shape[2], img_tensor.shape[3]
# ---------------------------------------------------------
# 3. Embedding the Watermark Signal
# ---------------------------------------------------------
img_resized = resize_256(img_normalized)
msg = np.random.randint(0, 2, (1, nbits)).astype(np.float32)
embedder_inputs = {
'image': img_resized.numpy(),
'message': msg
}
# Output is [1, 3, 256, 256]
watermark_deltas = embedder_sess.run(None, embedder_inputs)[0]
# ---------------------------------------------------------
# 4. Blending & JND Attenuation
# ---------------------------------------------------------
# Resize the watermark signal back to the original image resolution
inverse_resize = transforms.Resize((original_h, original_w), antialias=True)
watermark_deltas_full = inverse_resize(torch.from_numpy(watermark_deltas)).numpy()
blender_inputs = {
'imgs': img_normalized.numpy(),
'deltas': watermark_deltas_full
}
# Output is [1, 3, H, W] normalized
watermarked_img_normalized = blender_sess.run(None, blender_inputs)[0]
# ---------------------------------------------------------
# 5. Postprocessing and Saving
# ---------------------------------------------------------
watermarked_img_tensor = torch.from_numpy(watermarked_img_normalized)
watermarked_img_unnorm = unnormalize(watermarked_img_tensor)
watermarked_img_clamped = torch.clamp(watermarked_img_unnorm, 0, 1)
output_img = transforms.ToPILImage()(watermarked_img_clamped.squeeze(0))
output_img.save("watermarked_image.png")
print("✅ Watermarked image saved!")
# ---------------------------------------------------------
# 6. Extraction Detection
# ---------------------------------------------------------
# Resize the watermarked image to 256x256 before feeding into the extractor
extractor_input = resize_256(watermarked_img_tensor).numpy()
extractor_inputs = {
'image': extractor_input
}
extracted_mask = extractor_sess.run(None, extractor_inputs)[0]
print(f"✅ Extracted mask shape: {extracted_mask.shape}")
```
## Performance & Optimization
Replacing the native monolithic implementation with these ONNX models offers massive portability, meaning it can be run seamlessly on edge devices using ONNX Runtime (CPU, CUDA, TensorRT, CoreML).
*Note: The `blender.onnx` relies on **dynamic axes** for image resolution, allowing you to avoid resizing your pristine original images. The `embedder` and `extractor` graphs operate strictly at `256x256` to align with their training resolutions.*
---
license: mit
---