gate: judge the DEFECT SHAPE, and ignore frames where SSIM means nothing

The gate failed a perfectly good encode (min 0.2552, mean 0.9841) because it demanded a per-frame
floor of 0.985 for EVERY frame. Two measured facts say that is the wrong test:

* SSIM collapses on near-flat frames. Two almost-black frames of a fade score ~0.25 while their
  neighbours score 0.998, so a minimum over all frames condemns good encodes. The gate now measures
  each source frame's luma std and drops the flat ones from the verdict (reported, never hidden;
  if more than half the window is flat the exclusion is skipped so the gate still means something).
* A defect has a SHAPE. Measured distributions on this library: good encodes 0 of 1500 frames below
  0.95; the file that triggered this 5 of 1500 below 0.85 (0.33%, isolated) at a 0.984 mean; the
  genuine decoder-defect files 44 of 1499 (2.9%) with the mean falling. So the verdict is now
  'mean < 0.98 OR more than 1% of frames below 0.85' -> fail; a handful of scattered low frames at a
  healthy mean is logged as fades, not a defect - and the log line carries the counts, so the
  decision is auditable.
This commit is contained in:
2026-10-07 11:16:17 +08:00
parent d6bab29077
commit aad65c56d4
+64 -8
View File
@@ -57,6 +57,33 @@ X265_POOLS = 10 # E-cores only (affinity 0x3FF0000)
E_CORE_MASK = 0x3FF0000
SSIM_TARGET = 0.985 # per-frame floor vs the source window (480x270 comparison)
SSIM_WINDOW = 60 # seconds sampled for the SSIM gate
BAD_FRAME_SSIM = 0.85 # a frame this far below the source is 'bad' for the share test
MAX_BAD_SHARE = 0.01 # ... and more than this share of them means a systematic defect, not fades
FLAT_LUMA_STD = 3.0 # a source frame flatter than this: SSIM on it means nothing
def _flat_frames(raw_path, n, w=480, h=270, thresh=FLAT_LUMA_STD):
"""Indices of near-flat source frames (fades, black, plain titles) in a raw YUV window.
SSIM collapses on such frames - two nearly black frames score ~0.25 while their neighbours
score 0.998 - so a minimum taken over every frame condemns perfectly good encodes (measured on
this library: the file that failed had 5 of 1500 frames below 0.85, all isolated, at a 0.98
mean, while the genuine decoder-defect files had 44 of 1499).
"""
import numpy as np
fsz = w * h * 3 // 2
flat = set()
with open(raw_path, "rb") as f:
for i in range(n):
luma = f.read(w * h)
if len(luma) < w * h:
break
if float(np.frombuffer(luma, dtype=np.uint8).std()) < thresh:
flat.add(i)
f.seek(fsz - w * h, 1)
return flat
SIZE_CEIL = 1.0 # output video bitrate must not exceed the source's
QUALITY_STEPS = 2 # on a quality-gate failure, retry with the quality raised this many
# steps (cq -2 / crf -2) before giving up; the retry only fires on a
@@ -306,7 +333,7 @@ def ssim_vs_source(src_local, out_local, info, workdir, tag, dur=SSIM_WINDOW):
"-map", "0:v:0", "-an", "-vf", vf,
"-fps_mode", "passthrough", "-f", "rawvideo", dstp], timeout=7200)
if r.returncode != 0 or not os.path.exists(dstp) or not os.path.getsize(dstp):
return None, None, 0, None
return None, None, 0, None, {}
counts.append(os.path.getsize(dstp) // (480 * 270 * 3 // 2))
n_src, n_out = counts
if n_src and abs(n_out - n_src) / float(n_src) > 0.01:
@@ -318,7 +345,7 @@ def ssim_vs_source(src_local, out_local, info, workdir, tag, dur=SSIM_WINDOW):
return None, None, 0, ("source timeline irregular: %d frames vs %d in the sampled window "
"(%.1f%% difference) - the CFR encode fills the source's holes, so a "
"frame-by-frame comparison is not applicable" % (n_src, n_out,
100.0 * (n_out - n_src) / n_src))
100.0 * (n_out - n_src) / n_src)), {}
subprocess.run([FFMPEG, "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", cmp_scale,
"-i", ref, "-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", cmp_scale, "-i", out,
"-lavfi", "ssim=stats_file=ssim_%s.log" % tag, "-f", "null", "-"],
@@ -332,8 +359,23 @@ def ssim_vs_source(src_local, out_local, info, workdir, tag, dur=SSIM_WINDOW):
if m:
vals.append(float(m.group(1)))
n = len(vals)
# SSIM is meaningless on a nearly flat frame: two almost-black frames of a fade scored 0.25
# while their neighbours scored 0.998, so a MINIMUM over every frame condemns good encodes.
# Measure the source frames' flatness and drop them from the verdict (reported, not hidden).
flat = set()
try:
flat = _flat_frames(ref, n)
except Exception as e: # numpy missing / unreadable raw: fall back to using every frame
log(" ! flat-frame scan skipped: %s" % e)
if flat and len(flat) < n * 0.5:
vals = [v for i, v in enumerate(vals) if i not in flat]
log(" %d near-flat source frame(s) excluded from the picture verdict (SSIM is "
"meaningless on them)" % len(flat))
n = len(vals)
lo = min(vals) if vals else None
mean = (sum(vals) / len(vals)) if vals else None
bad = sum(1 for v in vals if v < BAD_FRAME_SSIM)
stats = {"below": bad, "flat": len(flat), "total": n}
note = None
# If the direct pairing looks bad, try to EXPLAIN the mismatch as a timeline offset before
# condemning the file: a source whose timeline drifts (or had a hole earlier) makes the encode
@@ -357,7 +399,7 @@ def ssim_vs_source(src_local, out_local, info, workdir, tag, dur=SSIM_WINDOW):
os.remove(f)
except OSError:
pass
return lo, mean, n, note
return lo, mean, n, note, stats
def _paired_ssim(ref_raw, out_raw, off, nf, workdir, tag):
@@ -535,7 +577,7 @@ def process(src, args, dest=None, origin=None):
# that the output video bitrate did not exceed the source's.
ssim_note, growth = "copy (video bit-identical)", 100.0
if tier != "copy":
lo, mean, nfr, irreg = ssim_vs_source(loc_src, loc_out, info, STAGE, tag)
lo, mean, nfr, irreg, sstat = ssim_vs_source(loc_src, loc_out, info, STAGE, tag)
obps = out_video_bps(loc_out, info["dur"])
growth = 100.0 * obps / vbps if vbps else 0.0
if irreg:
@@ -552,13 +594,27 @@ def process(src, args, dest=None, origin=None):
return "FAIL ssim: no frames compared", None
ssim_note = "ssim min %.4f mean %.4f (%d frames)%s" % (
lo, mean, nfr, "" if not q_off else " at quality+%d" % q_off)
if lo < SSIM_TARGET:
# The verdict is a DEFECT SHAPE, not a single frame. A systematic mismatch drops the
# mean and puts a real share of frames below the floor; a handful of scattered low
# frames at a healthy mean is what fades, black frames and hard cuts do to SSIM.
# Measured on this library: good encodes 0 of 1500 frames below 0.95; the file that
# triggered this rule 5 of 1500 below 0.85 (0.33%) at 0.98 mean; the genuine
# decoder-defect files 44 of 1499 (2.9%) with the mean falling too.
bad_share = (sstat.get("below", 0) / float(nfr)) if nfr else 0.0
systematic = (mean is not None and mean < 0.98) or bad_share > MAX_BAD_SHARE
if systematic:
if q_off < QUALITY_STEPS and (attempt == 1):
q_off = QUALITY_STEPS
log(" quality gate %.4f < %.3f - retrying at quality+%d" %
(lo, SSIM_TARGET, q_off))
log(" quality gate: mean %.4f, %d of %d frames below %.2f (%.2f%%) - "
"retrying at quality+%d" % (mean, sstat.get("below", 0), nfr,
BAD_FRAME_SSIM, 100.0 * bad_share, q_off))
continue
return "FAIL quality: %s < target %.3f" % (ssim_note, SSIM_TARGET), None
return ("FAIL quality: %s - systematic: mean %.4f, %.2f%% of frames below %.2f"
% (ssim_note, mean, 100.0 * bad_share, BAD_FRAME_SSIM)), None
if lo is not None and lo < SSIM_TARGET:
log(" %d scattered frame(s) below %.2f (%.2f%% of the window) with a healthy "
"mean - treated as fades/black, not a defect"
% (sstat.get("below", 0), BAD_FRAME_SSIM, 100.0 * bad_share))
if vbps and obps > vbps * SIZE_CEIL:
return "FAIL size: output %.0f kbps > source %.0f kbps (%.1f%%)" % (
obps / 1000, vbps / 1000, growth), None