示例成品 · 平台演示,按左边这组点选真跑出来的
先说清楚:当前对话里我没有实际收到可解码的 EXR/MOV 附件,没法给你编一份“真实逐帧图文报告”。下面给你一套**直接能跑的检测方案 + 报告模板**,按你的要求:连续3镜头、v02、逐5帧、重点查边缘闪变/破洞/亮边、平衡灵敏度。跑完把输出图和表贴回来就是成稿。
---
## 1. 先查什么
这种“静帧看不出来、动起来闪一下”基本优先查三类:
1. **边缘闪变**:相邻帧 matte alpha 在边缘带内变化大。
2. **破洞**:前景 alpha 内部小空洞忽有忽无。
3. **亮边**:合成后边缘亮度明显高于背景边缘,常见于 premultiply 错误、去黑边不好、边缘溢色。
---
## 2. 检测脚本(Python,平衡灵敏度)
你先装依赖:
```bash
pip install OpenEXR opencv-python numpy
```
下面脚本默认按你的文件命名规则抓序列:
`matte_v02_comp_01234.*.exr` / `fg_01234.mov` / `bg_01234.*.exr`
如果实际命名不一样,只改 `MATTE_GLOB`、`BG_GLOB`、`FG_VIDEO` 三行。
```python
import glob, os
import numpy as np
import cv2
import OpenEXR, Imath
FLOAT = Imath.PixelType.FLOAT
SHOTS = [1234, 1235, 1236] # 连续3个镜头
SEG = 5 # 逐5帧
OUT = "reports/v02"
os.makedirs(OUT, exist_ok=True)
# 按实际命名改这里
MATTE_GLOB = "matte_v02_comp_{shot:05d}.*.exr"
BG_GLOB = "bg_{shot:05d}.*.exr"
FG_VIDEO = "fg_{shot:05d}.mov"
def save_map(path, arr):
arr01 = np.clip(arr, 0, 1)
cv2.imwrite(path, (arr01 * 255).astype(np.uint8))
def read_exr_alpha(path):
f = OpenEXR.InputFile(path)
h = f.header()
dw = h['dataWindow']
w = dw.max.x - dw.min.x + 1
hh = dw.max.y - dw.min.y + 1
ch = {c.lower(): c for c in h['channels'].keys()}
if 'a' in ch:
arr = np.frombuffer(f.channel(ch['a'], FLOAT), dtype=np.float32).reshape(hh, w)
else:
arrs = []
for cc in ['r', 'g', 'b']:
arr = np.frombuffer(f.channel(ch[cc], FLOAT), dtype=np.float32).reshape(hh, w)
arrs.append(arr)
arr = 0.2126 * arrs[0] + 0.7152 * arrs[1] + 0.0722 * arrs[2]
return np.clip(arr, 0, 1)
def read_exr_rgb(path):
f = OpenEXR.InputFile(path)
h = f.header()
dw = h['dataWindow']
w = dw.max.x - dw.min.x + 1
hh = dw.max.y - dw.min.y + 1
ch = {c.lower(): c for c in h['channels'].keys()}
rgb = []
for cc in ['r', 'g', 'b']:
arr = np.frombuffer(f.channel(ch[cc], FLOAT), dtype=np.float32).reshape(hh, w)
rgb.append(arr)
return np.stack(rgb, axis=2)
def read_video_rgb(path, frame_idx):
cap = cv2.VideoCapture(path)
if not cap.isOpened():
raise IOError(f"can't open video: {path}")
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
ok, frame = cap.read()
cap.release()
if not ok:
raise IOError(f"can't read video {path} frame {frame_idx}")
return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
def edge_band(a):
gx = cv2.Sobel(a, cv2.CV_32F, 1, 0, ksize=3)
gy = cv2.Sobel(a, cv2.CV_32F, 0, 1, ksize=3)
g = np.hypot(gx, gy)
return (g > 0.02) & (a > 0.01) & (a < 0.99)
def edge_flicker(a0, a1):
m = edge_band(a0) | edge_band(a1)
if not m.any():
return 0.0, np.zeros_like(a0)
diff = np.abs(a1 - a0)
score = float(np.mean(diff[m]))
return score, diff
def hole_score(a):
b = (a > 0.5).astype(np.uint8)
contours, hierarchy = cv2.findContours(b, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
if hierarchy is None:
return 0.0, np.zeros_like(a, dtype=np.uint8)
fg_area = max(int(np.count_nonzero(b)), 1)
hmap = np.zeros_like(a, dtype=np.uint8)
total = 0.0
h = hierarchy[0]
for i, cnt in enumerate(contours):
parent = h[i][3]
if parent != -1:
area = float(cv2.contourArea(cnt))
if 4 <= area <= 0.02 * fg_area:
total += area
cv2.drawContours(hmap, [cnt], -1, 1, -1)
return total / fg_area, hmap
def bright_
点左边「开工 · 直接出成品」,出一份你自己的版本(文字免费)