mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-07-28 10:18:49 +02:00
fix(invisible): raise default strength 0.10 -> 0.30 (current SynthID threshold); flag ctrlregen experimental
An oracle-verified GPU strength study (Modal A100, native res, Gemini-app 'Verify with SynthID', n=3 fresh Gemini images, protect_text/faces off) found the current Google SynthID survives strength 0.10/0.15/0.2 and is removed only at 0.3. The previous 0.10 default (set from an n=1 result) no longer clears it -- Google hardened SynthID and the threshold has climbed 0.05 -> 0.10 -> ~0.3. Bump DEFAULT_STRENGTH to 0.30; OpenAI/ChatGPT carry C2PA not SynthID, so 0.10 is plenty there (pass --strength 0.10). Note protect_text shields the text regions SynthID hides in (use --no-protect-text for full removal on text-heavy images). The same study found ctrlregen at clean-noise strength DESTROYS real images (hallucinated micro-text in smooth regions), with no usable middle setting, so the literature's 'clean-noise is the lever' did not hold empirically. Flag ctrlregen EXPERIMENTAL in the CLI --pipeline help, README, and watermark_profiles; SDXL img2img at ~0.3 stays the shippable path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
729f5f2ecd
commit
cddbaf6413
@@ -433,14 +433,14 @@ def cmd_erase(
|
||||
"--strength",
|
||||
type=float,
|
||||
default=None,
|
||||
help="Denoising strength (0.0-1.0). Default: 0.10 (SDXL), 1.0 clean-noise for ctrlregen.",
|
||||
help="Denoising strength (0.0-1.0). Default: 0.30 (SDXL SynthID threshold); ctrlregen uses 1.0.",
|
||||
)
|
||||
@click.option("--steps", type=int, default=50, help="Number of denoising steps. Default: 50.")
|
||||
@click.option(
|
||||
"--pipeline",
|
||||
type=click.Choice(["default", "ctrlregen"]),
|
||||
default="default",
|
||||
help="Pipeline profile (default=SDXL, ctrlregen=CtrlRegen).",
|
||||
help="Pipeline profile (default=SDXL; ctrlregen=CtrlRegen, EXPERIMENTAL/destructive at clean-noise).",
|
||||
)
|
||||
@click.option(
|
||||
"--device",
|
||||
@@ -680,14 +680,14 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo
|
||||
"--strength",
|
||||
type=float,
|
||||
default=None,
|
||||
help="Invisible watermark denoising strength. Default: 0.10 (SDXL), 1.0 clean-noise for ctrlregen.",
|
||||
help="Invisible watermark denoising strength. Default: 0.30 (SDXL); ctrlregen uses 1.0.",
|
||||
)
|
||||
@click.option("--steps", type=int, default=50, help="Number of denoising steps for invisible removal.")
|
||||
@click.option(
|
||||
"--pipeline",
|
||||
type=click.Choice(["default", "ctrlregen"]),
|
||||
default="default",
|
||||
help="Pipeline profile (default=SDXL, ctrlregen=CtrlRegen).",
|
||||
help="Pipeline profile (default=SDXL; ctrlregen=CtrlRegen, EXPERIMENTAL/destructive at clean-noise).",
|
||||
)
|
||||
@click.option("--model", type=str, default=None, help="HuggingFace model ID for invisible removal.")
|
||||
@click.option(
|
||||
@@ -979,7 +979,7 @@ def _process_batch_image(
|
||||
"--pipeline",
|
||||
type=click.Choice(["default", "ctrlregen"]),
|
||||
default="default",
|
||||
help="Pipeline profile (default=SDXL, ctrlregen=CtrlRegen).",
|
||||
help="Pipeline profile (default=SDXL; ctrlregen=CtrlRegen, EXPERIMENTAL/destructive at clean-noise).",
|
||||
)
|
||||
@click.option(
|
||||
"--device",
|
||||
|
||||
@@ -70,9 +70,10 @@ class InvisibleEngine:
|
||||
to break watermark patterns, and reconstructs via reverse diffusion.
|
||||
"""
|
||||
|
||||
# SDXL base is the default since May 2026: empirically defeats SynthID v2
|
||||
# at strength=0.10 / steps=50 / native ~1024px. See CLAUDE.md "Known
|
||||
# limitations" for the regression evidence ruling out SD-1.5 pipelines.
|
||||
# SDXL base is the default since May 2026; the current Google SynthID is
|
||||
# removed at strength ~0.30 / steps=50 / native res (oracle-verified, n=3 fresh
|
||||
# Gemini -- 0.10/0.15/0.2 still detected). See CLAUDE.md "Known limitations" for
|
||||
# the strength study and the regression evidence ruling out SD-1.5 pipelines.
|
||||
DEFAULT_MODEL_ID = "stabilityai/stable-diffusion-xl-base-1.0"
|
||||
CTRLREGEN_MODEL_ID = "yepengliu/ctrlregen"
|
||||
|
||||
|
||||
@@ -9,17 +9,21 @@ DEFAULT_MODEL_ID = "stabilityai/stable-diffusion-xl-base-1.0"
|
||||
CTRLREGEN_MODEL_ID = "yepengliu/ctrlregen"
|
||||
|
||||
# Single default denoising strength for the SDXL img2img scrub, overridable from
|
||||
# the CLI (`--strength`). Raised from the old 0.04/0.05 because that no longer
|
||||
# removes the CURRENT Google SynthID (Nano Banana / Gemini 3): verified 2026-05-30
|
||||
# via the Gemini "Verify with SynthID" oracle on a real image -- 0.05 still
|
||||
# detected, 0.10 not detected (OpenAI's SynthID was already cleared at 0.05). 0.10
|
||||
# keeps the visible change modest while removing both. CAVEAT: confirmed on n=1
|
||||
# Google + n=1 OpenAI image; broad oracle validation across the corpus is pending
|
||||
# (different images may need a different strength). At this higher strength small
|
||||
# text deforms more -- which is exactly why text protection (`_run_region_hires`)
|
||||
# runs by default. (Fixed LOW/MEDIUM/HIGH presets were removed -- unused; the one
|
||||
# knob is this default plus the per-call `--strength` override.)
|
||||
DEFAULT_STRENGTH = 0.10
|
||||
# the CLI (`--strength`). Raised 0.10 -> 0.30 after an oracle-verified GPU strength
|
||||
# study (2026-05-31, Modal A100, native res, Gemini-app "Verify with SynthID", n=3
|
||||
# FRESH Gemini images + protect_text/faces OFF): the CURRENT Google SynthID survives
|
||||
# 0.10/0.15/0.2 and is only REMOVED at 0.3 (0.3 is the threshold; 0.2 still present).
|
||||
# This supersedes the earlier n=1 "0.10 removes it" note, which is now stale -- Google
|
||||
# has hardened SynthID and the threshold has climbed 0.05 -> 0.10 -> ~0.3 over time, so
|
||||
# treat this as a moving target and re-test against fresh Gemini output periodically.
|
||||
# Cost of 0.3: SSIM ~0.97 vs original (modest), but fine/dense typography softens, and
|
||||
# it is OVERKILL for non-SynthID sources (OpenAI/ChatGPT carry C2PA, not Google SynthID
|
||||
# -- 0.10 is plenty there). Two known tensions, documented but not auto-handled here:
|
||||
# (1) higher strength deforms text more (why text protection runs by default), and
|
||||
# (2) `protect_text` SHIELDS the text regions where SynthID hides, so text-region
|
||||
# SynthID can survive at 0.3 unless `--no-protect-text` is passed. (Fixed LOW/MEDIUM/
|
||||
# HIGH presets were removed -- the one knob is this default + the per-call override.)
|
||||
DEFAULT_STRENGTH = 0.30
|
||||
|
||||
# CtrlRegen removes watermarks by regenerating from (near) clean Gaussian noise,
|
||||
# NOT by the light-touch partial-noise img2img the SDXL default uses. The research
|
||||
@@ -29,9 +33,19 @@ DEFAULT_STRENGTH = 0.10
|
||||
# clean noise. With the StableDiffusionControlNetImg2ImgPipeline that maps to a high
|
||||
# strength (~1.0 = full noise at the first timestep, structure held by the canny
|
||||
# ControlNet + DINOv2 IP-Adapter, not by the watermarked latent). So the ctrlregen
|
||||
# profile must NOT inherit the SDXL 0.10 default -- at 0.10 it loads ControlNet +
|
||||
# DINOv2-giant and then barely changes the image (a no-op for removal). Tunable via
|
||||
# profile must NOT inherit the SDXL default (`DEFAULT_STRENGTH`, a partial-noise
|
||||
# value) -- at that low strength it loads ControlNet + DINOv2-giant and then barely
|
||||
# changes the image (a no-op for removal). Tunable via
|
||||
# `--strength`; lower it to trade removal strength for fidelity (the CtrlRegen+ regime).
|
||||
#
|
||||
# EXPERIMENTAL -- NOT recommended for production. The same GPU study that set the 0.3
|
||||
# SDXL threshold tested ctrlregen at its clean-noise strength and found it DESTROYS
|
||||
# images: smooth/background regions fill with hallucinated micro-text garbage, and it
|
||||
# is heavy (~8.5 min / ~$0.30 vs ~25 s / ~$0.02 for SDXL on a large image). The pipeline
|
||||
# is effectively binary -- low strength = no-op, high strength = destroys -- with no
|
||||
# usable middle, so the literature's "clean-noise is the lever" (arXiv:2410.05470) did
|
||||
# NOT survive empirical testing on real content. SDXL img2img at ~0.3 is the shippable
|
||||
# path; ctrlregen stays opt-in and flagged experimental.
|
||||
CTRLREGEN_DEFAULT_STRENGTH = 1.0
|
||||
|
||||
|
||||
@@ -39,10 +53,10 @@ def resolve_strength(strength: float | None, profile: str) -> float:
|
||||
"""Resolve the denoising strength, applying the profile-specific default when unset.
|
||||
|
||||
``None`` means "the user did not pass ``--strength``": the SDXL default profile
|
||||
resolves to ``DEFAULT_STRENGTH`` (a light SynthID-tuned touch), while ``ctrlregen``
|
||||
resolves to ``CTRLREGEN_DEFAULT_STRENGTH`` (clean-noise regeneration). An explicit
|
||||
value always wins. Shared by the CLI (for display) and the engine (for execution)
|
||||
so the two never disagree.
|
||||
resolves to ``DEFAULT_STRENGTH`` (the SynthID-removal default, ~0.3), while
|
||||
``ctrlregen`` resolves to ``CTRLREGEN_DEFAULT_STRENGTH`` (clean-noise regeneration).
|
||||
An explicit value always wins. Shared by the CLI (for display) and the engine (for
|
||||
execution) so the two never disagree.
|
||||
"""
|
||||
if strength is not None:
|
||||
return strength
|
||||
|
||||
Reference in New Issue
Block a user