Register the Kling 可灵AI 3.0 visible text mark; park Yuanbao and cat-logo (measured)

Kling (USCC cohort 91110108335469089C, n=30): kling_engine.py, gate 0.35
(clean p99 0.304 / max 0.320), strict-only, unimodal 0.12/short on the
shared ladder, fitted locate box, no rival margin (crossfire 1/400 doubao
below gate, 0 jimeng, 0 clean), parity 9/9 detect->fill->re-detect.
Suppresses the jimeng pill like doubao/qwen. identify gains visible_kling.

Yuanbao: measured negative -- the two-line italic block does not separate
from clean corners on either front-end at any render/box/font setting;
the fitted recipe stays in render_vendor_silhouettes.py MARK_OPTS.

cat-logo: cohort has only 2 unique carriers, parked on evidence; the
draw_catlogo silhouette already separates (0.50 vs clean max 0.333), so
registration is a gate pick once more uniques arrive.

vendor_mark_calibrate: --fit-geometry takes locate-box overrides (two-line
marks were clipped by the inherited box) and the aspect sweep reaches 0.62.
This commit is contained in:
Victor Kuznetsov
2026-07-22 08:49:06 -07:00
parent 3a659ededc
commit 5d63b9161f
12 changed files with 494 additions and 21 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+1
View File
@@ -447,6 +447,7 @@ _VISIBLE_MARK_PLATFORM = {
"doubao": "ByteDance Doubao (visible 豆包AI生成 mark detected)",
"jimeng": "ByteDance Jimeng / Dreamina (visible 即梦AI mark detected)",
"qwen": "Alibaba Tongyi Qianwen (visible 千问AI生成 mark detected)",
"kling": "Kuaishou Kling (visible 可灵AI 3.0 mark detected)",
"samsung": "Samsung Galaxy AI (visible 'Contenuti generati dall'AI' mark detected)",
}
+149
View File
@@ -0,0 +1,149 @@
"""Kling (可灵, Kuaishou) visible watermark detector/localizer.
Kling stamps its generations with a thin, light-gray "可灵AI 3.0" text strip in the
bottom-right corner, preceded by the vendor's spiral logo (not part of the detection
silhouette -- logos vary between releases, the text run is what discriminates).
Known variants: an "Omni" suffix release, a latin "KlingAI 3.0" release, and a
version-less "可灵AI" -- the silhouette targets the common "可灵AI 3.0" core, so the
suffix variants are only caught when the core run is bold enough (measured below).
Detection matches the bundled glyph silhouette against the corner; removal is the
shared **localize -> fill** (the glyph-bbox :meth:`footprint_mask` feeds
``region_eraser``), NOT reverse-alpha. This module supplies only Kling's tuned
:class:`TextMarkConfig` (``assets/kling_alpha.png`` -- a font-rendered synthetic
silhouette from ``scripts/render_vendor_silhouettes.py``, never cut from an
upload). It also feeds ``identify`` as the medium-confidence ``visible_kling``
signal via the registry.
EVERY tuned number below was measured on the vendor cohort (30 TC260 carriers whose
producer USCC 91110108335469089C names the entity, 2026-07-21; harness
``scripts/vendor_mark_calibrate.py``), NOT inherited from Doubao:
* The mark scales with the SHORT side at ~0.12 of it (mark_w/short measured
0.118-0.122 across portrait AND landscape carriers -- unimodal, so the shipped
3-rung ladder covers it) and sits ~0.03 off the right/bottom edges; the locate
box fractions below are fitted from the measured absolute mark rects.
* ``alpha_height_frac`` comes from the silhouette aspect (0.239) at the fitted
width, matching the aspect the fit converged on (0.25).
* Gate 0.35, one step above the clean arm's max: on the cohort-vs-clean run
(cohort-contamination-guarded, 286 hand-labelled clean frames) the clean arm
scored p99 0.304 / max 0.320, and every cohort frame >= 0.35 carries a visible
可灵AI 3.0 mark (9 of ~19 eyeballed visible marks fire = ~47% recall of visible
marks; the misses are the faint "Omni"-suffix release, the latin "KlingAI"
release and the version-less "可灵AI", which score 0.17-0.25 and cannot be
reached without engulfing the clean arm).
* STRICT ONLY (``provenance_ncc_factor`` 1.0): the sub-gate band holds real Kling
variants AND the clean arm's top (clean p90 0.220 vs variant marks at 0.17-0.25
-- they overlap), so a provenance-relaxed arm cannot separate them. No
provenance relaxation exists for this mark.
* No rival margin: at the shipped gate the template fires on 1 of 400
Doubao-marked frames (0.2%, a 豆包 frame sitting INSIDE the Kling cohort, still
below the gate), 0 of 298 Jimeng-marked frames and 0 of 286 hand-labelled clean
frames, and a 0.10 rival margin costs zero genuine Kling detections -- so it is
simply unnecessary (same conclusion shape as Qwen).
"""
# The module-level _alpha_template / _glyph_silhouette / _template_match_score below
# are thin test-facing shims (imported by tests/), so pyright's src-only pass sees them
# as unused; the use is cross-module.
# pyright: reportUnusedFunction=false
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from remove_ai_watermarks import _text_mark_engine
from remove_ai_watermarks._text_mark_engine import TextMarkConfig, TextMarkDetection, TextMarkEngine
if TYPE_CHECKING:
from pathlib import Path
from numpy.typing import NDArray
# Locate geometry as a fraction of the image SHORT side (measured basis -- see
# scale_base). The box is fitted to the measured mark rects: the mark's right
# margin is ~0.034 of the short side and its bottom margin ~0.027; width/height
# cover the mark plus NCC slack.
WM_WIDTH_FRAC = 0.19
WM_HEIGHT_FRAC = 0.05
MARGIN_RIGHT_FRAC = 0.03
MARGIN_BOTTOM_FRAC = 0.023
# Glyph appearance: a light, low-saturation gray rendered brighter than the local
# background (white top-hat), same overlay class as Doubao -- inherited, and
# harmless because the tophat front-end turns these gates into weights.
MAX_SATURATION = 55
LOGO_MIN_LUMA = 150
TOPHAT_DELTA = 12
DETECT_MIN_COVERAGE = 0.04 # unused by the tophat front-end (kept for config parity)
# Calibrated 2026-07-21 on the vendor cohort vs 286 hand-labelled clean frames
# (cohort-contamination-guarded): clean p99 0.304 / max 0.320, and every cohort
# frame scoring >= 0.35 carries a visible 可灵AI 3.0 mark. 0.35 was picked over
# 0.33 (also zero clean fires) for margin against unseen clean content at a cost
# of zero measured cohort detections.
DETECT_NCC_THRESHOLD = 0.35
# Detection-silhouette geometry (fraction of the short side), fitted on the
# cohort: the mark's width (0.12, unimodal) and the silhouette aspect (0.239).
_ALPHA_WIDTH_FRAC = 0.12
_ALPHA_HEIGHT_FRAC = 0.0287
_CONFIG = TextMarkConfig(
name="Kling",
asset_name="kling_alpha.png",
corner="br",
margin_floor=4,
width_frac=WM_WIDTH_FRAC,
height_frac=WM_HEIGHT_FRAC,
margin_x_frac=MARGIN_RIGHT_FRAC,
margin_bottom_frac=MARGIN_BOTTOM_FRAC,
max_saturation=MAX_SATURATION,
logo_min_luma=LOGO_MIN_LUMA,
tophat_delta=TOPHAT_DELTA,
morph_open_size=5,
detect_min_coverage=DETECT_MIN_COVERAGE,
detect_ncc_threshold=DETECT_NCC_THRESHOLD,
detect_frontend="tophat",
scale_basis="short", # measured: mark_w/short 0.118-0.122 across orientations
alpha_width_frac=_ALPHA_WIDTH_FRAC,
alpha_height_frac=_ALPHA_HEIGHT_FRAC,
min_gw=8,
# STRICT ONLY: the sub-gate band (real Kling variants at 0.17-0.25) overlaps
# the clean arm's top (p90 0.220), so provenance relaxation is disabled
# outright (factor 1.0 = never relaxed).
provenance_ncc_factor=1.0,
)
KlingDetection = TextMarkDetection
def _alpha_template() -> NDArray[Any] | None:
"""The bundled Kling alpha template (float [0,1]), or None."""
return _text_mark_engine.load_alpha_template(_CONFIG.asset_name)
def _glyph_silhouette() -> NDArray[Any] | None:
"""Binary "可灵AI 3.0" silhouette (255 = glyph) from the alpha map, or None."""
return _text_mark_engine.glyph_silhouette(_CONFIG.asset_name)
def _template_match_score(box_mask: NDArray[Any], scale_base: int) -> float:
"""TM_CCOEFF_NORMED of the Kling glyph silhouette against ``box_mask``."""
return _text_mark_engine.template_match_score(box_mask, scale_base, _CONFIG)
class KlingEngine(TextMarkEngine):
"""Detect/localize the visible Kling "可灵AI 3.0" watermark (locate -> mask; mask feeds the fill)."""
def __init__(self) -> None:
super().__init__(_CONFIG)
def load_image_bgr(path: str | Path) -> NDArray[Any]:
"""Read an image as BGR ndarray (helper for scripts/tests)."""
from remove_ai_watermarks import image_io
img = image_io.imread(path)
if img is None:
raise FileNotFoundError(f"Failed to read image: {path}")
return img
@@ -21,6 +21,7 @@ Entries:
- ``doubao`` -- ByteDance Doubao "豆包AI生成" text strip, bottom-right.
- ``jimeng`` -- ByteDance Jimeng / Dreamina "★ 即梦AI" wordmark, bottom-right.
- ``qwen`` -- Alibaba Tongyi Qianwen "千问AI生成" text strip, bottom-right.
- ``kling`` -- Kuaishou Kling "可灵AI 3.0" text strip, bottom-right.
- ``samsung`` -- Samsung Galaxy AI "Contenuti generati dall'AI" strip, bottom-left.
- ``jimeng_pill`` -- Jimeng-basic "AI生成" pill, top-left (capture-less).
"""
@@ -85,6 +86,7 @@ _PRODUCT_OF: dict[str, str] = {
"jimeng": "jimeng",
"jimeng_pill": "jimeng", # same product as the Jimeng wordmark
"qwen": "qwen",
"kling": "kling",
"samsung": "samsung",
}
@@ -359,6 +361,10 @@ def _engine(key: str) -> Any:
from remove_ai_watermarks.qwen_engine import QwenEngine
_engines[key] = QwenEngine()
elif key == "kling":
from remove_ai_watermarks.kling_engine import KlingEngine
_engines[key] = KlingEngine()
elif key == "samsung":
from remove_ai_watermarks.samsung_engine import SamsungEngine
@@ -509,6 +515,7 @@ _REGISTRY: tuple[KnownMark, ...] = (
_text_mark("doubao", "Doubao 豆包AI生成 text", "bottom-right"),
_text_mark("jimeng", "Jimeng 即梦AI wordmark", "bottom-right"),
_text_mark("qwen", "Qwen 千问AI生成 text", "bottom-right"),
_text_mark("kling", "Kling 可灵AI 3.0 text", "bottom-right"),
_text_mark("samsung", "Samsung Galaxy AI text", "bottom-left"),
KnownMark("jimeng_pill", "Jimeng AI生成 pill", "top-left", True, _pill_detect, _pill_mask, _pill_features),
)
@@ -590,7 +597,7 @@ def _keep_pill(keys: set[str], *, provenance: frozenset[str], footprint_flat: bo
Doubao detection; a Qwen image likewise (another vendor's bottom-right mark naming
its own product), so a confident Qwen detection suppresses the pill the same way.
No confirmation at all -> never remove (blocks false fires on non-Jimeng content)."""
if "doubao" in keys or "qwen" in keys:
if "doubao" in keys or "qwen" in keys or "kling" in keys:
return False
if "jimeng" in keys:
return True