Spaces:
Paused
Paused
File size: 4,838 Bytes
b2c1b6b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | """
Watermark auto-detection module.
Two strategies:
1. Text detection via EasyOCR (optional install).
2. Contrast-anomaly detection for semi-transparent logos/patterns (always available).
"""
import cv2
import numpy as np
from typing import List, Dict
try:
import easyocr
_reader_instance = None
EASYOCR_AVAILABLE = True
except ImportError:
EASYOCR_AVAILABLE = False
def _get_reader():
global _reader_instance
if _reader_instance is None:
import easyocr
_reader_instance = easyocr.Reader(["en"], gpu=False)
return _reader_instance
def detect_watermarks(image_path: str) -> List[Dict]:
"""
Detect watermarks in an image using available methods.
Returns a list of region dicts:
{x, y, w, h, confidence, type}
All coordinates are in original image pixel space.
"""
img = cv2.imread(image_path)
if img is None:
return []
regions: List[Dict] = []
if EASYOCR_AVAILABLE:
regions.extend(_detect_text(img))
regions.extend(_detect_transparent(img))
return _merge_overlapping(regions)
# ---------------------------------------------------------------------------
# Text detection
# ---------------------------------------------------------------------------
def _detect_text(img: np.ndarray) -> List[Dict]:
reader = _get_reader()
h, w = img.shape[:2]
results = reader.readtext(img, paragraph=False, min_size=10)
regions = []
for bbox, text, confidence in results:
if confidence < 0.3:
continue
xs = [pt[0] for pt in bbox]
ys = [pt[1] for pt in bbox]
x = max(0, int(min(xs)) - 5)
y = max(0, int(min(ys)) - 5)
x2 = min(w, int(max(xs)) + 5)
y2 = min(h, int(max(ys)) + 5)
regions.append({
"x": x, "y": y,
"w": x2 - x, "h": y2 - y,
"confidence": float(confidence),
"type": "text",
"label": text[:20],
})
return regions
# ---------------------------------------------------------------------------
# Semi-transparent / logo detection
# ---------------------------------------------------------------------------
def _detect_transparent(img: np.ndarray) -> List[Dict]:
"""
Detect logo/pattern watermarks by finding structured residuals
that deviate from the smoothed background.
"""
h, w = img.shape[:2]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).astype(np.float32)
# Background estimate via heavy blur
bg = cv2.GaussianBlur(gray, (51, 51), 0)
residual = np.abs(gray - bg).astype(np.uint8)
_, thresh = cv2.threshold(residual, 15, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
n_labels, _, stats, _ = cv2.connectedComponentsWithStats(closed)
min_area = h * w * 0.005
max_area = h * w * 0.40
regions = []
for i in range(1, n_labels):
area = stats[i, cv2.CC_STAT_AREA]
if not (min_area <= area <= max_area):
continue
bx = stats[i, cv2.CC_STAT_LEFT]
by = stats[i, cv2.CC_STAT_TOP]
bw = stats[i, cv2.CC_STAT_WIDTH]
bh = stats[i, cv2.CC_STAT_HEIGHT]
aspect = bw / bh if bh > 0 else 0
if aspect > 10 or aspect < 0.1:
continue
regions.append({
"x": int(bx), "y": int(by),
"w": int(bw), "h": int(bh),
"confidence": 0.55,
"type": "logo",
})
return regions
# ---------------------------------------------------------------------------
# Merge overlapping boxes
# ---------------------------------------------------------------------------
def _merge_overlapping(regions: List[Dict]) -> List[Dict]:
if len(regions) <= 1:
return regions
boxes = [(r["x"], r["y"], r["x"] + r["w"], r["y"] + r["h"]) for r in regions]
changed = True
while changed:
changed = False
out: List = []
used = [False] * len(boxes)
for i in range(len(boxes)):
if used[i]:
continue
x1, y1, x2, y2 = boxes[i]
for j in range(i + 1, len(boxes)):
if used[j]:
continue
bx1, by1, bx2, by2 = boxes[j]
if x1 < bx2 and x2 > bx1 and y1 < by2 and y2 > by1:
x1, y1 = min(x1, bx1), min(y1, by1)
x2, y2 = max(x2, bx2), max(y2, by2)
used[j] = True
changed = True
out.append((x1, y1, x2, y2))
used[i] = True
boxes = out
return [
{"x": x1, "y": y1, "w": x2 - x1, "h": y2 - y1, "confidence": 0.7, "type": "merged"}
for x1, y1, x2, y2 in boxes
]
|