内容运营自动化 封面选帧除了亮度还要看什么

0 阅读8分钟

一条流水线跑到第 50 天,发布这件事本身已经不新鲜了。真正每天都在变的是:今天这条片子,封面该用哪一帧?

我的第一版封面选帧逻辑简单得可笑——在成片里按固定间隔抽 8 帧,算平均亮度,挑最亮的那一张。理由也站得住:AI 短片常见问题是画面压暗、发灰,挑亮的能保底。跑了两个星期,问题一个接一个冒出来:选中的是白墙、是过曝的天空、是人物转身时的后脑勺、是一帧糊掉的运动模糊。亮度这个指标太便宜了,便宜到它对「这张图能不能当封面」几乎没有信息量。

这篇记录的是我后来加上的几个判据,以及它们各自拦住了什么。

亮度为何是最差的单一判据

亮度(luma)是一个全局均值,它对画面里到底有什么一无所知。一张纯白背景的过曝帧亮度能到 240,一张构图完整、主体清晰的中景可能只有 95。用亮度排序,等于系统性地偏好「大面积浅色块」——墙面、天花板、雪地、天空。

更麻烦的是,AI 生成视频的亮度分布本身就不稳定。同一段提示词生成的 5 个镜头,模型在不同时间步上的曝光倾向会漂,导致成片里明暗分布比实拍更离散。用单一阈值卡亮度,实际是在和模型的随机性对赌。

所以第一个改动不是换阈值,而是换思路:先筛掉不可用的帧,再在可用帧里排序。这两件事的判据完全不同,混在一个分数里必然打架。

先做硬淘汰 把不可用帧剔掉

封面选帧四步法:抽帧、硬淘汰、分布排序与兜底

硬淘汰负责回答「这一帧有没有致命缺陷」,只有二值判断,不打分。我用了四条:

一、过曝与死黑。统计亮度直方图两端:>250 的像素占比超过 12%,或 <8 的像素占比超过 25%,直接淘汰。这两个数是试出来的,不是理论值——我的片子里有大量夜景和室内低光镜头,卡太紧会把正常的暗调镜头全杀掉。

二、模糊。用拉普拉斯方差(variance of Laplacian)衡量高频能量,低于阈值判为糊帧。注意它和分辨率强相关:576p 的成片方差天然低于 1080p,所以阈值必须按素材分辨率分档,不能一套走天下。我的 576p 生成稿用的是比成片宽一档的阈值。

三、主体缺失。封面最忌讳「人不在画面中心」。用一个人像/显著性检测拿到主体框,要求主体框面积占比落在 18%~70% 之间,且框中心距画面中心不超过画面短边的 30%。低于 18% 说明人太远或没有主体,高于 70% 是贴脸特写——做封面会显得压抑。

四、重复帧。抽帧间隔密的时候,相邻两帧几乎是同一张图,会让候选池虚高,也让后面的排序被同一镜头的多帧占据。用感知哈希(pHash)做汉明距离去重,距离小于 6 的视为同一帧,只保留方差最高的那张。

def hard_filter(frames, prev_hashes, ref_h=576):
    keep = []
    for f in frames:
        luma = f.hist_luma()
        if luma.bright_ratio(250) > 0.12:   continue   # 过曝
        if luma.dark_ratio(8) > 0.25:       continue   # 死黑
        # 拉普拉斯方差阈值按分辨率缩放,576p 基线 90,1080p 约 220
        if f.lap_var < 90 * (ref_h / 576) ** 2: continue
        r = f.subject_ratio()
        if not (0.18 <= r <= 0.70):         continue
        if abs(f.subject_cx - 0.5) > 0.30:  continue
        h = f.phash()
        if any(hamming(h, p) < 6 for p in prev_hashes[-3:]):
            continue
        keep.append(f)
        prev_hashes.append(h)
    return keep

这四条跑完,候选池通常只剩抽帧总数的三到四成。剩下这些,才是值得排序的。

用分布挑帧 而不是用均值

通过硬淘汰之后,排序才登场。这里的核心转变是:不再比均值,比分布。

一张好封面的直方图通常有三个特征:不是单峰、不是极端偏侧、两端都有余量。单峰意味着画面平淡(一片墙、一片天),两端都有余量意味着有明暗层次。我用直方图的偏度(skewness)和峰度(kurtosis)来刻画:

  • 偏度绝对值小于 0.8,说明明暗相对均衡,没有严重一侧倾倒;
  • 峰度小于 3.2,说明分布足够分散,没有挤在同一亮度区间;
  • 直方图在 10%~90% 区间内的非空 bin 数不少于 40(共 64 个 bin),避免「看起来有分布、实际是两块色块」。
def distribution_score(frame):
    h = frame.hist_luma(bins=64, normalize=True)
    xs = np.arange(64)
    mu = (h * xs).sum()
    var = (h * (xs - mu) ** 2).sum()
    sd = math.sqrt(var) or 1e-6
    skew = (h * (xs - mu) ** 3).sum() / sd ** 3
    kurt = (h * (xs - mu) ** 4).sum() / sd ** 4
    occupied = int(((h > 0.004) & (xs >= 6) & (xs <= 58)).sum())
    s = 1.0
    s -= min(abs(skew) / 2.0, 0.6)          # 偏度惩罚,封顶 0.6
    s -= min(max(kurt - 3.0, 0) / 2.0, 0.5) # 峰度惩罚,封顶 0.5
    s += min(occupied / 40.0, 1.0) * 0.5    # 占用度加分
    return s

这个分数不追求绝对值有意义,只用来在同一批候选里排个序。我刻意把所有惩罚项都设了封顶,避免某一项极端值把排序完全支配——这一点在实测里很关键,早期没封顶时,一个几乎纯色的镜头因为峰度极高被压到垫底,连带着同一镜头的其他可用帧也排不上来。

抽帧策略 均匀抽不如关键点抽

抽帧方式也影响最终结果。固定间隔抽帧(比如每秒 2 帧)在小概率事件上采样效率很低:一个 3 秒的好镜头,按 2fps 抽只有 6 帧,而一个 10 秒的空镜能抽 20 帧。候选池天然偏向长镜头。

我改成三层抽:

  1. 镜头切分优先:先用直方图差分做镜头边界检测,每个镜头内至少抽 3 帧(首、中、尾),保证每个镜头都有代表;
  2. 运动补偿:镜内按帧间差异挑变化最大的若干帧,避开「静止空镜」占坑;
  3. 全局补抽:最后再按 1fps 均匀补一轮,防止镜头切分漏检(切点密集的快速剪辑容易漏)。

这样抽出来的候选池,镜头覆盖度比均匀抽高得多,也不再被长镜头绑架。

一组可复现的权重与阈值

把上面几层合成一个总排序分,我给的是一个经验权重,而不是最优解。列出来是为了说明量级关系:

W = {
    "dist":      0.42,   # 直方图分布(偏度/峰度/占用度)
    "subject":   0.28,   # 主体清晰度与居中程度
    "sharp":     0.18,   # 拉普拉斯方差归一化到同批最大值
    "headroom":  0.12,   # 亮部余量,避免压边
}

四点值得说:

  • 分布权重最高,因为它和「这张图有没有层次」最相关,而这正是封面第一眼给人的信息;
  • 主体权重次之,它决定了「能不能一眼认出是谁」;
  • 清晰度排第三,因为硬淘汰已经把糊帧全剔了,剩下的差异没有想象中大,权重给太高会让排序被噪点噪声带偏;
  • 亮部余量放最后,它只是防压边(避免标题文字压上去糊成一片),不该主导选择。

另外所有分量我都先做了同批归一化(除以本批最大值),不做归一化的话,方差这一项的绝对数值范围比分布分大一个数量级,权重就形同虚设。

选不中时的兜底路径

工程上必须承认:有些片子在硬淘汰之后一帧都不剩。极端暗调、全片运动模糊、主体全程贴脸——这些情况真实存在。这时候不能死等,也不能让流程卡住。

我的兜底是三段式:

  1. 放宽硬淘汰:把过曝/死黑阈值各松 30%,模糊阈值降 20%,重跑一次;
  2. 仍未命中,转文字封面:用标题主色块 + 大字排版合成一张纯文字封面,风格上与成片保持一致(同一套配色和字体),人工看图时一眼能认出是哪一期;
  3. 记录事件:把「封面降级」写进当天的工作记录,连续三天出现降级就回头查是不是生成参数漂了。

这套兜底跑了一个多月,真正触发文字封面的只有两次,都是夜景戏。但它的价值不在使用频率,在于流程永远不会因为一张图停住。

积累样本比调参数更划算

最后一点是方法论层面的。上面那些阈值——12%、25%、18%~70%、skew 0.8、kurt 3.2——都是我自己片子的经验值,换一个内容风格大概率要重调。所以更有价值的动作不是继续抠参数,而是把每次人工换封面的决定记下来。

我的做法很简单:每次人工覆盖自动选择,就记一条「自动选中 A 帧,人工改成 B 帧,原因:过曝/脸糊/构图偏/时间点不对」。攒到三十来条,就能看出哪个判据在实际决策中最常被推翻,调整方向自然浮出来。这比对着单张图微调阈值靠谱得多。

自动化真正省下的不是「选图」这个动作,而是让「选图」这件事变得可以被复盘。