示例成品 · 平台演示,按左边这组点选真跑出来的
先说结论:**当前对话里我拿不到 `seq_045.exr` 的实际像素数据**,所以不能直接给出“第几帧有问题”的真实结果。下面是直接可跑的脚本,跑完会生成你要的 3 个交付物:
- Excel 明细表:`seq_045_noise_audit.xlsx`
- 异常帧号 CSV:`seq_045_abnormal_frames.csv`
- 给 VFX 主管看的报告:`seq_045_noise_report.md`
如果 `seq_045.exr` 不是序列而只是单个文件,脚本只能做单帧噪点评估,判断不了“忽明忽暗”。序列目录里应是一组命名类似 `seq_045.0001.exr`、`seq_045.0002.exr` 的文件。
---
## 直接可跑的脚本
把下面脚本保存为 `audit_seq045.py`,改好 `SEQ_DIR` 和 `SEQ_PATTERN`,运行即可。
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
seq_045 噪点帧审计脚本
输出:
- seq_045_noise_audit.xlsx 全帧明细 Excel
- seq_045_abnormal_frames.csv 异常帧号 CSV
- seq_045_noise_report.md 给 VFX 主管看的报告
用法:
1) 安装依赖:pip install numpy pandas openpyxl opencv-python imageio
2) 修改 SEQ_DIR 和 SEQ_PATTERN 为实际序列路径/命名
3) 运行:python audit_seq045.py
"""
import os
import re
import glob
import numpy as np
import pandas as pd
import cv2
# ====== 按实际路径改这里 ======
SEQ_DIR = r"./seq_045"
SEQ_PATTERN = "seq_045*.exr"
# ===============================
EXCEL_PATH = "seq_045_noise_audit.xlsx"
CSV_PATH = "seq_045_abnormal_frames.csv"
REPORT_PATH = "seq_045_noise_report.md"
def natural_key(name):
nums = re.findall(r"\d+", name)
return [int(n) for n in nums]
def read_linear_rgb(path):
"""读取 EXR 为线性 RGB float32。优先 OpenCV,失败则 imageio/OpenEXR。"""
# 方式 1:OpenCV
try:
arr = cv2.imread(path, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_COLOR)
if arr is not None:
if arr.dtype != np.float32:
arr = arr.astype(np.float32) / 255.0
return cv2.cvtColor(arr, cv2.COLOR_BGR2RGB).astype(np.float32)
except Exception:
pass
# 方式 2:imageio
try:
import imageio.v3 as iio
arr = iio.imread(path)
if arr.ndim == 2:
arr = np.repeat(arr[..., None], 3, axis=2)
elif arr.shape[-1] > 3:
arr = arr[..., :3]
return arr.astype(np.float32)
except Exception:
pass
# 方式 3:OpenEXR
try:
import OpenEXR
import Imath
exr = OpenEXR.InputFile(path)
hdr = exr.header()
dw = hdr["dataWindow"]
w = dw.max.x - dw.min.x + 1
h = dw.max.y - dw.min.y + 1
pt = Imath.PixelType(Imath.PixelType.FLOAT)
r = np.frombuffer(exr.channel("R", pt), dtype=np.float32).reshape(h, w)
g = np.frombuffer(exr.channel("G", pt), dtype=np.float32).reshape(h, w)
b = np.frombuffer(exr.channel("B", pt), dtype=np.float32).reshape(h, w)
return np.stack([r, g, b], axis=-1).astype(np.float32)
except Exception:
pass
raise IOError(f"无法读取 EXR: {path}")
def brightness(rgb):
return 0.2126 * rgb[..., 0] + 0.7152 * rgb[..., 1] + 0.0722 * rgb[..., 2]
def robust_noise_sigma(residual):
med = np.median(residual)
mad = np.median(np.abs(residual - med))
return float(1.4826 * mad)
def analyze_frame(path, prev_res):
rgb = read_linear_rgb(path)
y = brightness(rgb).astype(np.float32)
# 高频残差:去掉低频结构,剩下的主要就是噪点/颗粒
y_blur = cv2.GaussianBlur(y, (0, 0), sigmaX=1.0)
res = y - y_blur
noise_sigma = robust_noise_sigma(res)
# 暗部:取最暗 5% 像素,评估暗部颗粒
dark_cut = np.quantile(y, 0.05)
dark_mask = y < dark_cut
if dark_mask.sum() < 200:
dark_noise = np.nan
else:
dark_res = res[dark_mask]
dark_noise = 1.4826 * np.median(np.abs(dark_res - np.median(dark_res)))
# 帧间残差差异:用于抓“忽明忽暗”
if prev_res is not None and prev_res.shape == res.shape:
diff = res - prev_res
flicker_diff = flo
点左边「开工 · 直接出成品」,出一份你自己的版本(文字免费)