mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-14 13:30:55 +02:00
Add content-adaptive Google floor for chroma-zimage: face content resolves to 0.125 instead of 0.40
The four-fixture Google calibration showed a clean YuNet face-count split: both zero-face text cards need 0.25 first-clean, both face fixtures clear at 0.12 with zero cross-source spread. The adaptive arm routes face content (face_count > 0) to the measured 0.125 rung, saving 6.4 dB PSNR and doubling face identity cosine vs the flat 0.40 floor. The split is Google-SynthID-specific: OpenAI's face fixture was harder than its text fixture, so no other cohort gets an adaptive arm.
This commit is contained in:
6 files changed
+90
-14
No files matched your search
@@ -287,8 +287,10 @@ Both need the seed-sensitivity and face-stage composition checks called out
|
||||
above before shipping. Both checks have now been run (Pre-ship validation
|
||||
above): the Meta boundary is seed-stable across seeds 0-2 and the full
|
||||
profile path stays clean through the face stage on the shipped Google floor.
|
||||
The profile ships with option 1's floors; option 2's adaptive policy remains
|
||||
the documented follow-up.
|
||||
The profile ships with option 1's floors plus a content-adaptive Google arm
|
||||
(face content resolves to 0.125 instead of 0.40, keyed on YuNet detection).
|
||||
Option 2's full adaptive policy for the remaining cohorts remains the
|
||||
documented follow-up.
|
||||
|
||||
## Oracle session notes
|
||||
|
||||
|
||||
@@ -215,9 +215,11 @@ established for that stage.
|
||||
- OpenAI: `0.09` (below qwen's `0.07675` is false economy neither way -- the
|
||||
matched-strength fidelity favors Chroma1 on this cohort);
|
||||
- Microsoft InvisMark: `0.125` (below qwen's `0.15`);
|
||||
- Google: `0.40` (above qwen's `0.27`; at this floor the regeneration destroys
|
||||
dense text and collapses face identity -- the tradeoff that motivates the
|
||||
documented content-adaptive follow-up);
|
||||
- Google: `0.40` for zero-face content (above qwen's `0.27`; at this floor the
|
||||
regeneration destroys dense text and collapses face identity -- the tradeoff
|
||||
that motivates the adaptive arm below), or `0.125` when YuNet detects at
|
||||
least one face (both measured face fixtures clear at 0.12 with zero
|
||||
cross-source spread; oracle-verified 2026-08-30);
|
||||
- Meta Content Seal: `0.17` (above qwen's `0.1`);
|
||||
- unknown: `0.40`, following the strictest measured cohort.
|
||||
|
||||
|
||||
@@ -1174,13 +1174,17 @@ conditioning: the floors were measured on a plain strength pass.
|
||||
|
||||
The flat vendor floors (`CHROMA_ZIMAGE_*_STRENGTH` in watermark_profiles):
|
||||
OpenAI 0.09 and Microsoft 0.125 (both BELOW qwen's, with better
|
||||
matched-strength fidelity), Google 0.40 and Meta 0.17 (both ABOVE qwen's,
|
||||
because Chroma1's per-fixture boundaries scatter wider). The
|
||||
matched-strength addendum in the research doc is the honest read: at the
|
||||
strength each image actually needs, Chroma1 regenerates better almost
|
||||
everywhere except face identity (which the inherited Z-Image face stage
|
||||
supplies); at the flat worst-case floors it destroys dense text and face
|
||||
identity. A content-adaptive strength policy is the documented follow-up.
|
||||
matched-strength fidelity), Google 0.40 for zero-face content / 0.125 when
|
||||
faces are detected (a content-adaptive arm from the clean face-count split
|
||||
in the calibration: both text cards need 0.25, both face fixtures clear at
|
||||
0.12, so the YuNet detector -- already loaded for the face stage -- routes
|
||||
the operating point), and Meta 0.17 (ABOVE qwen's, because Chroma1's
|
||||
per-fixture boundaries scatter wider). The matched-strength addendum in the
|
||||
research doc is the honest read: at the strength each image actually needs,
|
||||
Chroma1 regenerates better almost everywhere except face identity (which the
|
||||
inherited Z-Image face stage supplies); at the flat worst-case floors it
|
||||
destroys dense text and face identity. The Google face-content arm is the
|
||||
first shipped piece of the content-adaptive policy.
|
||||
|
||||
### SDXL plus Z-Image
|
||||
|
||||
|
||||
@@ -153,6 +153,19 @@ CHROMA_ZIMAGE_GOOGLE_STRENGTH = 0.40
|
||||
CHROMA_ZIMAGE_META_STRENGTH = 0.17
|
||||
CHROMA_ZIMAGE_UNKNOWN_STRENGTH = CHROMA_ZIMAGE_GOOGLE_STRENGTH
|
||||
|
||||
# Content-adaptive Google floor: the four-fixture calibration showed a clean
|
||||
# face-count split. Both zero-face fixtures (dense-text cards 633uuy and akdbei)
|
||||
# need 0.25 first-clean; both face fixtures (y48j3c with 17 detected faces and
|
||||
# 3mc4t9 with 7) clear at 0.12. The flat 0.40 floor is the zero-face policy
|
||||
# (worst 0.25 + spread 0.13 = 0.38, rounded to 0.40). Face content can use the
|
||||
# measured 0.125 rung instead: both face fixtures first-cleaned at 0.12, and
|
||||
# with identical boundaries the cross-source spread is zero, so 0.125 (the next
|
||||
# measured rung above 0.12) is the operating point. This split is
|
||||
# Google-SynthID-specific: OpenAI's face fixture was HARDER than its text
|
||||
# fixture (0.075 vs 0.06), so no other cohort gets an adaptive arm.
|
||||
# Oracle-verified 2026-08-30; see docs/chroma1-engine-research.md.
|
||||
CHROMA_ZIMAGE_GOOGLE_FACE_STRENGTH = 0.125
|
||||
|
||||
_CHROMA_ZIMAGE_STRENGTH_BY_VENDOR: dict[str, float] = {
|
||||
"openai": CHROMA_ZIMAGE_OPENAI_STRENGTH,
|
||||
"google": CHROMA_ZIMAGE_GOOGLE_STRENGTH,
|
||||
@@ -204,6 +217,7 @@ def resolve_strength(
|
||||
pipeline: str | None = None,
|
||||
*,
|
||||
size: tuple[int, int] | None = None,
|
||||
face_count: int | None = None,
|
||||
) -> float:
|
||||
"""Resolve a user override or the calibrated policy for a profile and vendor.
|
||||
|
||||
@@ -222,7 +236,10 @@ def resolve_strength(
|
||||
if normalized == SDXL_ZIMAGE_PROFILE:
|
||||
return _SDXL_ZIMAGE_STRENGTH_BY_VENDOR.get((vendor or "").casefold(), SDXL_ZIMAGE_UNKNOWN_STRENGTH)
|
||||
if normalized == CHROMA_ZIMAGE_PROFILE:
|
||||
return _CHROMA_ZIMAGE_STRENGTH_BY_VENDOR.get((vendor or "").casefold(), CHROMA_ZIMAGE_UNKNOWN_STRENGTH)
|
||||
vendor_key = (vendor or "").casefold()
|
||||
if vendor_key == "google" and face_count is not None and face_count > 0:
|
||||
return CHROMA_ZIMAGE_GOOGLE_FACE_STRENGTH
|
||||
return _CHROMA_ZIMAGE_STRENGTH_BY_VENDOR.get(vendor_key, CHROMA_ZIMAGE_UNKNOWN_STRENGTH)
|
||||
if size is None:
|
||||
raise ValueError("qwen-zimage resolves strength from image area, so size is required")
|
||||
vendor_strength = _QWEN_ZIMAGE_FLAT_STRENGTH_BY_VENDOR.get((vendor or "").casefold())
|
||||
|
||||
@@ -188,6 +188,26 @@ class WatermarkRemover:
|
||||
|
||||
remove_ai_metadata(output_path, output_path, keep_standard=True)
|
||||
|
||||
def _resolve_chroma_strength(
|
||||
self,
|
||||
strength: float | None,
|
||||
vendor: str | None,
|
||||
source: Image.Image,
|
||||
) -> float:
|
||||
"""Resolve strength, running face detection for the chroma Google arm.
|
||||
|
||||
The content-adaptive Google floor needs a face count. YuNet is fast
|
||||
(~50 ms) and the model download is already managed by the shared base.
|
||||
"""
|
||||
if self.model_profile != CHROMA_ZIMAGE_PROFILE or (vendor or "").casefold() != "google":
|
||||
return resolve_strength(strength, vendor, self.model_profile, size=source.size)
|
||||
if strength is not None:
|
||||
return strength
|
||||
from remove_ai_watermarks._internal.two_stage_pipeline import detect_faces
|
||||
|
||||
face_count = len(detect_faces(source))
|
||||
return resolve_strength(strength, vendor, self.model_profile, size=source.size, face_count=face_count)
|
||||
|
||||
def remove_watermark(
|
||||
self,
|
||||
image_path: Path,
|
||||
@@ -213,7 +233,7 @@ class WatermarkRemover:
|
||||
with Image.open(image_path) as opened:
|
||||
source = opened.convert("RGB")
|
||||
|
||||
resolved_strength = resolve_strength(strength, vendor, self.model_profile, size=source.size)
|
||||
resolved_strength = self._resolve_chroma_strength(strength, vendor, source)
|
||||
if not 0.0 <= resolved_strength <= 1.0:
|
||||
raise ValueError(f"Strength must be between 0.0 and 1.0, got {resolved_strength}")
|
||||
if text_manifest is not None and self.model_profile in (SDXL_ZIMAGE_PROFILE, CHROMA_ZIMAGE_PROFILE):
|
||||
|
||||
@@ -1204,3 +1204,34 @@ def test_chroma_vae_roundtrip_raises_not_implemented():
|
||||
pipeline = ChromaZImagePipeline(device="cuda", torch_dtype=torch.bfloat16)
|
||||
with pytest.raises(NotImplementedError, match="does not expose a VAE donor"):
|
||||
pipeline._vae_roundtrip(Image.new("RGB", (32, 32)))
|
||||
|
||||
|
||||
def test_chroma_google_face_content_gets_the_lower_adaptive_floor():
|
||||
"""Face content on Google SynthID clears at 0.125, not 0.40.
|
||||
|
||||
The four-fixture calibration showed a clean face-count split: both zero-face
|
||||
text cards need 0.25, both face fixtures clear at 0.12. The adaptive arm
|
||||
saves face identity (0.611 vs 0.279 cosine) and 6.4 dB PSNR on face content.
|
||||
"""
|
||||
from remove_ai_watermarks._internal.watermark_profiles import (
|
||||
CHROMA_ZIMAGE_GOOGLE_FACE_STRENGTH,
|
||||
CHROMA_ZIMAGE_GOOGLE_STRENGTH,
|
||||
resolve_strength,
|
||||
)
|
||||
|
||||
assert pytest.approx(0.125) == CHROMA_ZIMAGE_GOOGLE_FACE_STRENGTH
|
||||
# Face content gets the lower adaptive floor.
|
||||
assert resolve_strength(None, "google", "chroma-zimage", face_count=17) == pytest.approx(0.125)
|
||||
assert resolve_strength(None, "google", "chroma-zimage", face_count=1) == pytest.approx(0.125)
|
||||
# Zero-face content gets the flat floor (text/graphic cohort).
|
||||
assert resolve_strength(None, "google", "chroma-zimage", face_count=0) == pytest.approx(0.40)
|
||||
assert pytest.approx(0.40) == CHROMA_ZIMAGE_GOOGLE_STRENGTH
|
||||
# No face_count hint: conservative (flat floor).
|
||||
assert resolve_strength(None, "google", "chroma-zimage") == pytest.approx(0.40)
|
||||
# Other vendors ignore face_count: the split is Google-SynthID-specific.
|
||||
assert resolve_strength(None, "openai", "chroma-zimage", face_count=5) == pytest.approx(0.09)
|
||||
assert resolve_strength(None, "meta", "chroma-zimage", face_count=5) == pytest.approx(0.17)
|
||||
# An explicit strength still wins over the adaptive arm.
|
||||
assert resolve_strength(0.20, "google", "chroma-zimage", face_count=17) == pytest.approx(0.20)
|
||||
# Other profiles are untouched by the face_count parameter.
|
||||
assert resolve_strength(None, "google", "qwen-zimage", size=(2000, 1850), face_count=17) == pytest.approx(0.27)
|
||||
Reference in new issue
Block a user