mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-07 06:28:36 +02:00
Register RunningHub, Baidu, and LibLibAI visible marks; park Qingyan and MiniMax (measured)
New engines, each calibrated on its TC260 USCC cohort and validated by a full-corpus sweep (42009 files): - runninghub: top-left corner (new corner="tl"), faint mid-gray text via the new raw-grayscale "gray" detection front-end, anchor-position gate - baidu: text-run-only template (pill is a bright-blob magnet), load-bearing Doubao+Qwen rival margins, corner-extended footprint for the white tag - liblib: bottom-center (new corner="bc"), Arial silhouette (font is the discriminative lever against latin UI text), logo-extended footprint Qingyan parked (no clean-arm separation at any render/box), MiniMax/Hailuo parked (1 visible frame, the xinghui rule); silhouettes kept as starting points.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""Tests for the LibLibAI ("LibLibAI" wordmark) visible-watermark engine.
|
||||
|
||||
Every tuned constant in ``liblib_engine`` was measured on the 15-frame vendor
|
||||
cohort (2026-07-22); these tests pin the load-bearing ones: the bottom-CENTER
|
||||
anchor, the strict-only gate, and the match-box footprint (the blob bbox both
|
||||
bled into background structure and did not own the triangle logo).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from remove_ai_watermarks import watermark_registry as registry
|
||||
from remove_ai_watermarks.liblib_engine import (
|
||||
_ALPHA_HEIGHT_FRAC,
|
||||
_ALPHA_WIDTH_FRAC,
|
||||
LibLibEngine,
|
||||
_alpha_template,
|
||||
)
|
||||
|
||||
_MARK_FRAC = 0.10 # measured wordmark width, fraction of the frame WIDTH
|
||||
|
||||
|
||||
def _compose(w: int, h: int, bg: float = 100.0):
|
||||
"""Composite a triangle logo + the LibLibAI wordmark, bottom-center."""
|
||||
img = np.full((h, w, 3), bg, np.float32)
|
||||
at = _alpha_template()
|
||||
gw = int(_MARK_FRAC * w)
|
||||
gh = max(4, int(_MARK_FRAC * (_ALPHA_HEIGHT_FRAC / _ALPHA_WIDTH_FRAC) * w))
|
||||
ax = (w - gw) // 2
|
||||
ay = int(0.94 * h) - gh
|
||||
amap = np.zeros((h, w), np.float32)
|
||||
amap[ay : ay + gh, ax : ax + gw] = cv2.resize(at, (gw, gh))
|
||||
# the triangle logo, its own height to the LEFT of the wordmark
|
||||
lx1 = ax - int(0.3 * gh)
|
||||
lx0 = lx1 - gh
|
||||
cv2.fillPoly(amap, [np.array([(lx0, ay + gh), (lx1, ay + gh), (lx1, ay)])], 1.0)
|
||||
a3 = amap[:, :, None]
|
||||
wm = (a3 * 255.0 + (1 - a3) * img).clip(0, 255).astype(np.uint8)
|
||||
return wm, (ax, ay, gw, gh, lx0)
|
||||
|
||||
|
||||
class TestLocate:
|
||||
def test_box_horizontally_centered(self):
|
||||
eng = LibLibEngine()
|
||||
img = np.zeros((2048, 1536, 3), np.uint8)
|
||||
loc = eng.locate(img)
|
||||
assert (1536 - loc.w) // 2 == pytest.approx(loc.x, abs=2) # corner="bc"
|
||||
assert 2048 - (loc.y + loc.h) > 0 # bottom-anchored
|
||||
|
||||
def test_box_scales_with_width(self):
|
||||
eng = LibLibEngine()
|
||||
narrow = eng.locate(np.zeros((2048, 1024, 3), np.uint8))
|
||||
wide = eng.locate(np.zeros((2048, 2048, 3), np.uint8))
|
||||
assert wide.w == pytest.approx(narrow.w * 2, rel=0.05)
|
||||
|
||||
|
||||
class TestConfig:
|
||||
def test_tophat_frontend(self):
|
||||
assert LibLibEngine().config.detect_frontend == "tophat"
|
||||
|
||||
def test_strict_only_no_provenance_relaxation(self):
|
||||
assert LibLibEngine().config.provenance_ncc_factor == 1.0
|
||||
|
||||
def test_gate_above_clean_arm_max(self):
|
||||
# With the Arial silhouette the full-corpus false arm (latin UI text)
|
||||
# tops out at 0.398 while the cohort sits at 0.43-0.59; gate 0.42.
|
||||
assert LibLibEngine().config.detect_ncc_threshold >= 0.42
|
||||
|
||||
def test_small_image_size_floor(self):
|
||||
# The one full-corpus false fire with the final template was a 200x200
|
||||
# icon on a 20px template; the engine refuses small images outright.
|
||||
eng = LibLibEngine()
|
||||
assert not eng.detect(np.full((200, 200, 3), 100, np.uint8)).detected
|
||||
wm, _ = _compose(200, 200)
|
||||
assert not eng.detect(wm).detected # even a composed mark under the floor
|
||||
|
||||
def test_registry_row(self):
|
||||
mark = registry.get_mark("liblib")
|
||||
assert mark.location == "bottom-center"
|
||||
assert mark.in_auto
|
||||
|
||||
|
||||
class TestDetectAndMask:
|
||||
def test_detects_composed_mark(self):
|
||||
eng = LibLibEngine()
|
||||
wm, _ = _compose(1792, 2400)
|
||||
det = eng.detect(wm)
|
||||
assert det.detected, f"composed mark missed (conf={det.confidence:.3f})"
|
||||
|
||||
def test_clean_frame_stays_quiet(self):
|
||||
eng = LibLibEngine()
|
||||
img = np.full((2400, 1792, 3), 100, np.uint8)
|
||||
assert not eng.detect(img).detected
|
||||
|
||||
def test_mask_covers_logo_and_wordmark(self):
|
||||
"""The footprint must cover the triangle logo LEFT of the wordmark while
|
||||
staying bounded by the match box vertically (the blob bbox bled into
|
||||
background structure and ate real content, 2026-07-22)."""
|
||||
eng = LibLibEngine()
|
||||
wm, (ax, ay, gw, gh, lx0) = _compose(1792, 2400)
|
||||
mask = eng.footprint_mask(wm)
|
||||
assert mask is not None
|
||||
ys, xs = np.where(mask > 0)
|
||||
assert xs.min() <= lx0 + gh // 2 # covers the logo
|
||||
assert xs.max() >= ax + gw - int(0.05 * gw) # covers the wordmark's right edge
|
||||
assert ys.min() >= ay - gh # does not bleed far above the mark
|
||||
|
||||
def test_no_mask_on_clean_frame(self):
|
||||
eng = LibLibEngine()
|
||||
img = np.full((2400, 1792, 3), 100, np.uint8)
|
||||
assert eng.footprint_mask(img) is None
|
||||
Reference in New Issue
Block a user