Fix smart_detect: correct pos index, remove O(n²) loop, clean up
Docker Build and Push / build-and-push (cpu, latest) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 11.8) (push) Has been skipped
Docker Build and Push / check-secrets (push) Successful in 5s
Docker Build and Push / build-and-push (cuda, 12.6) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 12.8) (push) Has been skipped
Docker Build and Push / build-and-push (directml, latest) (push) Has been skipped
Build Windows CPU / build (push) Has been cancelled
Build Windows CUDA 11.8 / build (push) Has been cancelled
Build Windows CUDA 12.6 / build (push) Has been cancelled
Build Windows CUDA 12.8 / build (push) Has been cancelled
Build Windows DirectML / build (push) Has been cancelled
Docker Build and Push / build-and-push (cpu, latest) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 11.8) (push) Has been skipped
Docker Build and Push / check-secrets (push) Successful in 5s
Docker Build and Push / build-and-push (cuda, 12.6) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 12.8) (push) Has been skipped
Docker Build and Push / build-and-push (directml, latest) (push) Has been skipped
Build Windows CPU / build (push) Has been cancelled
Build Windows CUDA 11.8 / build (push) Has been cancelled
Build Windows CUDA 12.6 / build (push) Has been cancelled
Build Windows CUDA 12.8 / build (push) Has been cancelled
Build Windows DirectML / build (push) Has been cancelled
- actual_pos_s[] tracks real timestamps per frame (was using positions[i] which breaks when frames fail to read) - Replace O(n²) np.array_equal logo frame matching with simple bool list - Remove unused pos_set variable - Fix docstring still mentioning '8s tail' after removing it - All variables consistent: logo_gray_frames replaces logo_frames_gray Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
5cdc727207
commit
8b583a4b0e
@@ -15,92 +15,68 @@ def detect_static_regions(video_path: str, sample_frames: int = 30, threshold: f
|
||||
max_scan_seconds: float = None):
|
||||
"""
|
||||
饱和度签名 + 跨帧对比法检测水印/Logo。
|
||||
采样策略:每秒采一帧(顺序读取,不随机 seek)+ 超长视频后段每8秒一帧。
|
||||
分析时缩放到 1/4 分辨率提速,结果坐标还原到原始尺寸。
|
||||
采样策略:全程每秒一帧,分析时缩放到 1/4 分辨率提速,坐标还原回原始尺寸。
|
||||
"""
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
|
||||
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
orig_frame_w, orig_frame_h = w, h # 保存原始分辨率,用于最终坐标还原
|
||||
orig_frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
orig_frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
duration_s = total / fps
|
||||
|
||||
# ── 采样策略:全程每秒采一帧 ─────────────────────────────────────────
|
||||
# 全程每秒采一帧
|
||||
step = max(1, int(fps))
|
||||
positions = list(range(0, total, step))
|
||||
|
||||
# ── 顺序读取,缩放到 1/4 分辨率加速分析 ────────────────────────────────
|
||||
SW, SH = max(1, w // 4), max(1, h // 4) # 分析用小尺寸
|
||||
pos_set = set(positions)
|
||||
bgr_frames_small = [] # 缩放后的小帧(用于分析)
|
||||
bgr_frames_orig = [] # 原始尺寸帧(用于 SAM2,只保留少数几帧)
|
||||
# 分析用 1/4 分辨率加速
|
||||
SW, SH = max(1, orig_frame_w // 4), max(1, orig_frame_h // 4)
|
||||
|
||||
bgr_frames = [] # 小尺寸帧,用于分析
|
||||
gray_frames = []
|
||||
keep_orig_interval = max(1, len(positions) // 8) # 每隔若干帧保留一张原始尺寸
|
||||
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
frame_idx = 0
|
||||
pos_iter = iter(sorted(positions))
|
||||
next_pos = next(pos_iter, None)
|
||||
|
||||
while next_pos is not None:
|
||||
if frame_idx < next_pos:
|
||||
# 快速跳帧:只有在需要的位置才真正读
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, next_pos)
|
||||
frame_idx = next_pos
|
||||
actual_pos_s = [] # 每帧对应的时间戳(秒)
|
||||
|
||||
for pos in positions:
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, pos)
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
next_pos = next(pos_iter, None)
|
||||
continue
|
||||
|
||||
small = cv2.resize(frame, (SW, SH), interpolation=cv2.INTER_AREA)
|
||||
bgr_frames_small.append(small)
|
||||
bgr_frames.append(small)
|
||||
gray_frames.append(cv2.cvtColor(small, cv2.COLOR_BGR2GRAY).astype(np.float32))
|
||||
if len(bgr_frames_orig) == 0 or len(bgr_frames_small) % keep_orig_interval == 0:
|
||||
bgr_frames_orig.append(frame)
|
||||
|
||||
frame_idx += 1
|
||||
next_pos = next(pos_iter, None)
|
||||
actual_pos_s.append(pos / fps)
|
||||
|
||||
cap.release()
|
||||
|
||||
# 后续分析全部用小尺寸帧
|
||||
bgr_frames = bgr_frames_small
|
||||
w, h = SW, SH # 分析坐标空间切换到小尺寸
|
||||
|
||||
print(f"[SAM2-detect] 采样 {len(bgr_frames)} 帧(全程每秒一帧,共{duration_s:.1f}s,分析尺寸{SW}x{SH})", flush=True)
|
||||
|
||||
if len(bgr_frames) < 4:
|
||||
print(f"[SAM2-detect] 帧数不足4帧,返回空", flush=True)
|
||||
return []
|
||||
|
||||
# ── 阶段1:通过高饱和度识别含logo帧 ──────────────────────────────────
|
||||
# 水印/logo通常色彩比视频自然内容更鲜艳(高饱和度)
|
||||
# 在帧的上半部分(logo常出现区域)检测高饱和度像素数
|
||||
top_h = h // 5 # 检查帧顶部 20%
|
||||
logo_sat_thresh = 200 # 饱和度 > 200 认为是logo颜色
|
||||
logo_min_pixels = top_h * w * 0.003 # 至少 0.3% 的上方区域为logo颜色
|
||||
# ── 阶段1:高饱和度识别含logo帧 ─────────────────────────────────────
|
||||
top_h = SH // 5
|
||||
logo_sat_thresh = 200
|
||||
logo_min_pixels = top_h * SW * 0.003
|
||||
|
||||
logo_frames_gray = []
|
||||
for i, (bgr, gray) in enumerate(zip(bgr_frames, gray_frames)):
|
||||
is_logo = []
|
||||
for i, bgr in enumerate(bgr_frames):
|
||||
hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)
|
||||
high_sat = int(np.sum(hsv[:top_h, :, 1] > logo_sat_thresh))
|
||||
pos_s = positions[i] / fps
|
||||
if high_sat >= logo_min_pixels:
|
||||
logo_frames_gray.append(gray)
|
||||
print(f"[SAM2-detect] ✓ {pos_s:.1f}s: 高饱和={high_sat}px (含logo)", flush=True)
|
||||
else:
|
||||
print(f"[SAM2-detect] ✗ {pos_s:.1f}s: 高饱和={high_sat}px (无logo)", flush=True)
|
||||
has_logo = high_sat >= logo_min_pixels
|
||||
is_logo.append(has_logo)
|
||||
mark = '✓' if has_logo else '✗'
|
||||
print(f"[SAM2-detect] {mark} {actual_pos_s[i]:.1f}s: 高饱和={high_sat}px", flush=True)
|
||||
|
||||
print(f"[SAM2-detect] logo含帧: {len(logo_frames_gray)}/{len(bgr_frames)}", flush=True)
|
||||
logo_bgr_frames = [bgr_frames[i] for i, v in enumerate(is_logo) if v]
|
||||
nlogo_bgr_frames = [bgr_frames[i] for i, v in enumerate(is_logo) if not v]
|
||||
logo_gray_frames = [gray_frames[i] for i, v in enumerate(is_logo) if v]
|
||||
|
||||
logo_bgr_frames = [bgr_frames[i] for i, g in enumerate(gray_frames)
|
||||
if any(np.array_equal(g, l) for l in logo_frames_gray)]
|
||||
nlogo_bgr_frames = [bgr_frames[i] for i, g in enumerate(gray_frames)
|
||||
if not any(np.array_equal(g, l) for l in logo_frames_gray)]
|
||||
print(f"[SAM2-detect] logo含帧: {len(logo_bgr_frames)}/{len(bgr_frames)}", flush=True)
|
||||
|
||||
if len(logo_frames_gray) < 2:
|
||||
# 分析时使用小尺寸坐标空间
|
||||
w, h = SW, SH
|
||||
|
||||
if len(logo_gray_frames) < 2:
|
||||
print(f"[SAM2-detect] logo帧不足,回退到时序std法", flush=True)
|
||||
stack = np.stack(gray_frames, axis=0)
|
||||
std_map = np.std(stack, axis=0)
|
||||
|
||||
Reference in New Issue
Block a user