Discovering, detecting, and surgically removing Google's AI watermark through spectral analysis
Visit us on PitchHut
This project reverse-engineers Google's SynthID watermarking system - the invisible watermark embedded into every image generated by Google Gemini. Using only signal processing and spectral analysis (no access to the proprietary encoder/decoder), we:
- Discovered the watermark's resolution-dependent carrier frequency structure
- Built a detector that identifies SynthID watermarks with 90% accuracy
- Developed a multi-resolution spectral bypass (V3) that achieves 75% carrier energy drop, 91% phase coherence drop, and 43+ dB PSNR on any image resolution
VT-OxFF built a really cool visualizer to view the process of how SynthID watermark is added to images here (also available in repo description)!
We're actively collecting pure black and pure white images generated by Nano Banana Pro to improve multi-resolution watermark extraction.
If you can generate these:
- Resolution: any (higher variety = better)
- Content: fully black (#000000) or fully white (#FFFFFF)
- Source: Nano Banana Pro outputs only
- Generate a batch of black/white images by attaching a pure black/white image into Gemini and prompting it to "recreate this as it is"
- Upload them to our Hugging Face dataset: aoxo/reverse-synthid
gemini_black_nb_pro/(for black)gemini_white_nb_pro/(for white)
- Open a Pull Request on the HF dataset repo
These reference images are critical for:
- Carrier frequency discovery
- Phase validation
- Improving cross-resolution robustness
Even 150–200 images at a new resolution can significantly improve detection and removal.
Reference images are hosted on Hugging Face to keep the git repo lightweight:
pip install huggingface_hub
python scripts/download_images.py # download all
python scripts/download_images.py gemini_black # download specific folderDataset: huggingface.co/datasets/aoxo/reverse-synthid
Unlike brute-force approaches (JPEG compression, noise injection), our V3 bypass uses a multi-resolution SpectralCodebook - a collection of per-resolution watermark fingerprints stored in a single file. At bypass time, the codebook auto-selects the matching resolution profile, enabling surgical frequency-bin-level removal on any image size.
SynthID embeds carrier frequencies at different absolute positions depending on image resolution. A codebook built at 1024x1024 cannot directly remove the watermark from a 1536x2816 image - the carriers are at completely different bins.
| Resolution | Top Carrier (fy, fx) | Coherence | Source |
|---|---|---|---|
| 1024x1024 | (9, 9) | 100.0% | 100 black + 100 white refs |
| 1536x2816 | (768, 704) | 99.6% | 88 watermarked content images |
This is why the V3 codebook stores separate profiles per resolution and auto-selects at bypass time.
The watermark's phase template is identical across all images from the same Gemini model:
- Green channel carries the strongest watermark signal
- Cross-image phase coherence at carriers: >99.5%
- Black/white cross-validation confirms true carriers via |cos(phase_diff)| > 0.90
At 1024x1024 (from black/white refs), top carriers lie on a low-frequency grid:
| Carrier (fy, fx) | Phase Coherence | B/W Agreement |
|---|---|---|
| (9, 9) | 100.00% | 1.000 |
| (5, 5) | 100.00% | 0.993 |
| (10, 11) | 100.00% | 0.997 |
| (13, 6) | 100.00% | 0.821 |
At 1536x2816 (from random watermarked content), carriers are at much higher frequencies:
| Carrier (fy, fx) | Phase Coherence |
|---|---|
| (768, 704) | 99.55% |
| (672, 1056) | 97.46% |
| (480, 1408) | 96.55% |
| (384, 1408) | 95.86% |
| Version | Approach | PSNR | Watermark Impact | Status |
|---|---|---|---|---|
| V1 | JPEG compression (Q50) | 37 dB | ~11% phase drop | Baseline |
| V2 | Multi-stage transforms (noise, color, frequency) | 27-37 dB | ~0% confidence drop | Quality trade-off |
| V3 | Multi-resolution spectral codebook subtraction | 43+ dB | 91% phase coherence drop | Best |
Input Image (any resolution)
│
▼
codebook.get_profile(H, W) ──► exact match? ──► FFT-domain subtraction
│ (fast path)
└─ no exact match ──────► spatial-domain resize + subtraction
(fallback path)
│
▼
Multi-pass iterative subtraction (aggressive → moderate → gentle)
│
▼
Anti-alias → Output
- SpectralCodebook stores resolution-specific profiles (carrier positions, magnitudes, phases)
- Auto resolution selection picks the exact profile or the closest match
- Direct known-signal subtraction weighted by phase consistency and cross-validation confidence
- Multi-pass schedule catches residual watermark energy missed by previous passes
- Per-channel weighting (G=1.0, R=0.85, B=0.70) matches SynthID's embedding strength
| Metric | Value |
|---|---|
| PSNR | 43.5 dB |
| SSIM | 0.997 |
| Carrier energy drop | 75.8% |
| Phase coherence drop (top-5 carriers) | 91.4% |
| Resolution | Match | PSNR | SSIM |
|---|---|---|---|
| 1536x2816 | exact | 44.9 dB | 0.996 |
| 1024x1024 | exact | 39.8 dB | 0.977 |
| 768x1024 | fallback | 40.6 dB | 0.994 |
git clone https://github.com/aloshdenny/reverse-SynthID.git
cd reverse-SynthID
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtFrom the CLI:
python src/extraction/synthid_bypass.py build-codebook \
--black gemini_black \
--white gemini_white \
--watermarked gemini_random \
--output artifacts/spectral_codebook_v3.npzOr from Python:
from src.extraction.synthid_bypass import SpectralCodebook
codebook = SpectralCodebook()
# Profile 1: from black/white reference images (1024x1024)
codebook.extract_from_references(
black_dir='gemini_black',
white_dir='gemini_white',
)
# Profile 2: from watermarked content images (1536x2816)
codebook.build_from_watermarked('gemini_random')
codebook.save('artifacts/spectral_codebook_v3.npz')
# Saved with profiles: [1024x1024, 1536x2816]from src.extraction.synthid_bypass import SynthIDBypass, SpectralCodebook
codebook = SpectralCodebook()
codebook.load('artifacts/spectral_codebook_v3.npz')
bypass = SynthIDBypass()
result = bypass.bypass_v3(image_rgb, codebook, strength='aggressive')
print(f"PSNR: {result.psnr:.1f} dB")
print(f"Profile used: {result.details['profile_resolution']}")
print(f"Exact match: {result.details['exact_match']}")From the CLI:
python src/extraction/synthid_bypass.py bypass input.png output.png \
--codebook artifacts/spectral_codebook_v3.npz \
--strength aggressiveStrength levels: gentle (minimal, ~45 dB) > moderate > aggressive (recommended) > maximum
python src/extraction/robust_extractor.py detect image.png \
--codebook artifacts/codebook/robust_codebook.pklreverse-SynthID/
├── src/
│ ├── extraction/
│ │ ├── synthid_bypass.py # V1/V2/V3 bypass + multi-res SpectralCodebook
│ │ ├── robust_extractor.py # Multi-scale watermark detection
│ │ ├── watermark_remover.py # Frequency-domain watermark removal
│ │ ├── benchmark_extraction.py # Benchmarking suite
│ │ └── synthid_codebook_extractor.py # Legacy codebook extractor
│ └── analysis/
│ ├── deep_synthid_analysis.py # FFT / phase analysis scripts
│ └── synthid_codebook_finder.py # Carrier frequency discovery
│
├── scripts/
│ └── download_images.py # Download reference images from HF
│
├── artifacts/
│ ├── spectral_codebook_v3.npz # Multi-res V3 codebook [1024x1024, 1536x2816]
│ ├── codebook/ # Detection codebooks (.pkl)
│ └── visualizations/ # FFT, phase, carrier visualizations
│
├── assets/ # README images and early analysis artifacts
├── watermark_investigation/ # Early-stage Nano-150k analysis (archived)
└── requirements.txt
┌──────────────────────────────────────────────────────────────┐
│ SynthID Encoder (in Gemini) │
├──────────────────────────────────────────────────────────────┤
│ 1. Select resolution-dependent carrier frequencies │
│ 2. Assign fixed phase values to each carrier │
│ 3. Neural encoder adds learned noise pattern to image │
│ 4. Watermark is imperceptible — spread across spectrum │
├──────────────────────────────────────────────────────────────┤
│ SynthID Decoder (in Google) │
├──────────────────────────────────────────────────────────────┤
│ 1. Extract noise residual (wavelet denoising) │
│ 2. FFT → check phase at known carrier frequencies │
│ 3. If phases match expected values → Watermarked │
└──────────────────────────────────────────────────────────────┘
The codebook captures watermark profiles at each available resolution:
- 1024x1024 profile: from 100 black + 100 white pure-color Gemini outputs
- Black images: watermark is nearly the entire pixel content
- White images (inverted): confirms carriers via cross-validation
- Black/white agreement (|cos(phase_diff)|) filters out generation bias
- 1536x2816 profile: from 88 diverse watermarked content images
- Content averages out across images; fixed watermark survives in phase coherence
- Watermark magnitude estimated as
avg_mag x coherence^2
The bypass uses direct known-signal subtraction (not a Wiener filter):
- Confidence = phase_consistency x cross_validation_agreement
- DC exclusion — soft ramp suppresses low-frequency generation biases
- Per-bin subtraction = wm_magnitude x confidence x removal_fraction x channel_weight
- Safety cap — subtraction never exceeds 90-95% of the image's energy at any bin
- Multi-pass — decreasing-strength schedule (aggressive → moderate → gentle) catches residual energy
SpectralCodebook — multi-resolution watermark fingerprint:
codebook = SpectralCodebook()
codebook.extract_from_references('gemini_black', 'gemini_white') # adds 1024x1024 profile
codebook.build_from_watermarked('gemini_random') # adds 1536x2816 profile
codebook.save('codebook.npz')
# Later:
codebook.load('codebook.npz')
profile, res, exact = codebook.get_profile(1536, 2816) # auto-selectSynthIDBypass — three bypass generations:
bypass = SynthIDBypass()
result = bypass.bypass_simple(image, jpeg_quality=50) # V1
result = bypass.bypass_v2(image, strength='aggressive') # V2
result = bypass.bypass_v3(image, codebook, strength='aggressive') # V3 (best)Multi-scale watermark detector (90% accuracy):
from robust_extractor import RobustSynthIDExtractor
extractor = RobustSynthIDExtractor()
extractor.load_codebook('artifacts/codebook/robust_codebook.pkl')
result = extractor.detect_array(image)
print(f"Watermarked: {result.is_watermarked}, Confidence: {result.confidence:.4f}")Alosh Denny AI Watermarking Research · Signal Processing
📧 Email: aloshdenny@gmail.com 🔗 GitHub: https://github.com/aloshdenny
For collaborations, research discussions, or contributions, feel free to reach out or open an issue/PR.
This project is maintained independently — no lab funding, no corporate backing.
If this work was useful to you or your team, consider supporting continued development:
Funds go toward compute costs (GPU hours for new resolution profiles), dataset expansion, and ongoing bypass research.
This project is for research and educational purposes only. SynthID is proprietary technology owned by Google DeepMind. These tools are intended for:
- Academic research on watermarking robustness
- Security analysis of AI-generated content identification
- Understanding spread-spectrum encoding methods
Do not use these tools to misrepresent AI-generated content as human-created.
