mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-10 08:00:32 +02:00
fix(gemini): self-verify repair for under-removed sparkles
After reverse-alpha, re-detect the sparkle; when one survives at or above the registry fail line (conf >= 0.5) -- an alpha mismatch the per-image gain estimate could not fully correct -- inpaint the footprint and keep that only when it lowers the re-detect confidence. The footprint inpaint reconstructs the slot from its darker surroundings, so it physically removes the bright sparkle; purely additive, the common clean removal re-detects below 0.5 and is returned untouched. Measured on the spaces visible-removal audit: gemini removal-audit failures drop 15 -> 11 (4 genuine rescues), doubao 65/65 and jimeng 11/11 unchanged, zero regressions on the 468 already-clean removals. An offset+scale alignment search was prototyped on the remaining 11 fails and rejected: an audit "ceiling" suggested +4 more, but those were NCC-gaming -- the lower-scoring placement left the sparkle as bright or brighter, just reshaping the residual so the contrast-invariant shape-NCC scored lower (a5a9: first-pass slot ~76 at background level vs the "aligned win" ~164). A brightness sanity check rejected every one, so it contributed nothing and was removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6d11c11b52
commit
2c0b174dfa
@@ -346,6 +346,61 @@ class TestUnderSubtractionGain:
|
||||
assert abs(float(footprint.mean()) - 80.0) < 20.0
|
||||
|
||||
|
||||
class TestVerifyAndRepair:
|
||||
"""Self-verify fallback: a sparkle that survives reverse-alpha is inpaint-repaired,
|
||||
but only when that lowers the re-detect confidence (so it can never regress).
|
||||
|
||||
The detector NCC is degenerate on flat synthetic backgrounds, so the keep-best
|
||||
control flow is driven through a stubbed ``detect_watermark`` rather than a real
|
||||
re-detect (mirroring the reasoning in TestUnderSubtractionGain).
|
||||
"""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _setup_engine(self):
|
||||
self.engine = GeminiEngine()
|
||||
self.alpha = self.engine.get_interpolated_alpha(96)
|
||||
self.pos = (200, 200)
|
||||
|
||||
def _stub_detect(self, confidences):
|
||||
"""detect_watermark stub yielding the given confidences in order."""
|
||||
seq = iter(confidences)
|
||||
|
||||
def fake(image, force_size=None):
|
||||
return DetectionResult(detected=True, confidence=next(seq))
|
||||
|
||||
return fake
|
||||
|
||||
def _repair(self, result):
|
||||
return self.engine._verify_and_repair(result, self.alpha, self.pos, WatermarkSize.LARGE)
|
||||
|
||||
def test_clean_removal_returned_untouched(self, monkeypatch):
|
||||
"""Below the fallback threshold, the input is returned byte-identical."""
|
||||
img = np.full((600, 600, 3), 90, dtype=np.uint8)
|
||||
monkeypatch.setattr(self.engine, "detect_watermark", self._stub_detect([0.2]))
|
||||
out = self._repair(img)
|
||||
assert out is img # no copy, no inpaint
|
||||
|
||||
def test_keeps_footprint_inpaint_when_it_helps(self, monkeypatch):
|
||||
"""A surviving sparkle is footprint-inpainted when that re-detects lower; the
|
||||
footprint pixels change."""
|
||||
img = np.full((600, 600, 3), 90, dtype=np.uint8)
|
||||
# Bright residual block over the footprint so the inpaint visibly changes it.
|
||||
img[self.pos[1] : self.pos[1] + 96, self.pos[0] : self.pos[0] + 96] = 240
|
||||
# residual 0.7 (>= 0.5 triggers), candidate re-detects 0.2 (< residual -> keep).
|
||||
monkeypatch.setattr(self.engine, "detect_watermark", self._stub_detect([0.7, 0.2]))
|
||||
out = self._repair(img)
|
||||
assert out is not img
|
||||
assert not np.array_equal(out, img) # footprint was inpainted from surroundings
|
||||
|
||||
def test_repair_rejected_when_inpaint_does_not_help(self, monkeypatch):
|
||||
"""When the inpaint does not lower the re-detect confidence, keep the original."""
|
||||
img = np.full((600, 600, 3), 90, dtype=np.uint8)
|
||||
# residual 0.7, candidate re-detects 0.75 (>= residual -> reject the inpaint).
|
||||
monkeypatch.setattr(self.engine, "detect_watermark", self._stub_detect([0.7, 0.75]))
|
||||
out = self._repair(img)
|
||||
assert out is img
|
||||
|
||||
|
||||
class TestSparkleFalsePositiveGate:
|
||||
"""False-positive gate: a low-confidence shape match whose core is NOT brighter
|
||||
than its surroundings (ornate/flat content, not a white sparkle overlay) is
|
||||
|
||||
Reference in New Issue
Block a user