导读:本期聚焦于苏沐橙创作的《视频中的阴影如何去除?光照估计与阴影补偿方法解析》,敬请观看详情。视频画面里移动的阴影常让目标检测和跟踪算法误判,直接对单帧做二值化几乎无法稳定分离阴影与真实物体。解决思路通常分两步:先估计场景光照分布,再对阴影区域做亮度补偿,使被遮挡背景恢复原有色彩。光照估计常用颜色空间变换、梯度域或Retinex模型,阴影补偿则结合形态学、边缘保持滤波和帧间信息。本文围绕视频阴影去除流程展开,对比基于HSV色彩空间、梯度方向场和深度学习三类方法,给出可运行的Python与OpenCV实现,分析参数选择与失效场景。相比静态图像,视频可以利用时间连续性缓解抖动与漏检,但需要注意动态背景和移动阴影的耦合问题。文中示例代码可直接用于监控、自动驾驶和增强现实等场景的阴影抑制预处理。

视频阴影在动态场景里是一个容易被忽略却影响很大的干扰源。无论是交通监控中的车辆检测、安防场景下的人员跟踪,还是自动驾驶对路面目标的识别,移动阴影经常被背景建模或分割算法误判为前景,导致计数错误、跟踪框漂移和场景理解偏差。阴影去除不能简单等同于图像增强,因为如果只是整体提亮,原本正常的亮部会过曝;正确思路是先估计光照分布,判断哪些像素处于阴影覆盖之下,再只对阴影区域进行亮度与颜色补偿。视频相比单帧图像多了一个重要优势,就是可以利用连续帧的时间一致性,减少单帧检测中的抖动和误检。整体流程可以概括为背景建模、阴影候选检测、掩码后处理、光影补偿四个步骤。

视频中的阴影如何去除?光照估计与阴影补偿方法解析

一、阴影去除的核心难点与光照估计思路

阴影并不是单纯把某个区域变暗。实际场景中至少存在硬阴影、软阴影和自阴影三类。硬阴影边缘锐利、明度下降剧烈,软阴影边界模糊且明度变化较缓,自阴影则是物体自身遮挡光线形成的暗部。不同类型的阴影对检测方法的要求差异很大,单一阈值很难同时处理。

光照估计在这类任务里并不是要精确重建光源位置或强度,而是要获得阴影区域与相邻非阴影区域之间的光照比例关系。常见的一种思路是Retinex模型,它假设图像可以分解为反射率与照度两个分量,阴影主要改变照度分量,反射率保持不变。基于这个假设,如果能从视频帧中估计背景反射率,再比较当前帧与背景帧的亮度差异,就能得到阴影候选区。

另一类更实用的方法直接利用颜色空间和梯度特征。阴影覆盖时,像素明度通常下降,饱和度也会出现变化,而色相相对稳定。梯度方向则对光照变化具有更好的鲁棒性,因为阴影主要削弱梯度幅值,纹理方向不会轻易改变。光照补偿的目标则是在阴影候选掩码基础上,把阴影区域的亮度恢复到无阴影状态,同时避免改变纹理和色相。最简单的补偿是线性增益,但线性增益容易放大噪声;更稳的做法会引入边缘保持滤波或局部直方图匹配。

二、基于HSV与形态学的经典补偿实现

在固定摄像头视频中,背景建模是最自然的入口。将当前帧与背景帧转换到HSV空间后,阴影覆盖区域通常表现为明度V下降、饱和度S略微下降、色相H基本不变。利用这三个特征可以设计一个轻量级掩码检测器。它的优点是计算量小,容易部署在实时监控系统上。

以下代码演示了基于HSV的阴影掩码检测与补偿流程。函数接收当前帧和背景帧,返回补偿后的图像以及阴影掩码。

import cv2
import numpy as np

def remove_shadow_hsv(frame, background, alpha=0.4, beta=0.9, sat_thresh=60, hue_thresh=15):
    frame_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    bg_hsv = cv2.cvtColor(background, cv2.COLOR_BGR2HSV)
    h, s, v = cv2.split(frame_hsv)
    bh, bs, bv = cv2.split(bg_hsv)

    # 阴影候选:V 分量下降明显,饱和度下降,色相接近
    v_ratio = v / (bv + 1e-6)
    s_diff = s.astype(np.int16) - bs.astype(np.int16)
    hue_diff = np.abs(h.astype(np.int16) - bh.astype(np.int16))
    hue_diff = np.minimum(hue_diff, 180 - hue_diff)

    mask = (v_ratio >= alpha) & (v_ratio <= beta) & \
           (s_diff <= 0) & (np.abs(s_diff) <= sat_thresh) & \
           (hue_diff <= hue_thresh)

    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    mask = cv2.morphologyEx(mask.astype(np.uint8), cv2.MORPH_CLOSE, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)

    # 补偿:非阴影区域保持原值,阴影区域按亮度增益恢复
    gain = (bv + 1e-6) / (v + 1e-6)
    gain = np.clip(gain, 1.0, 3.0)
    result = frame.copy()
    for c in range(3):
        result[:, :, c] = np.where(mask > 0, np.clip(frame[:, :, c] * gain, 0, 255), frame[:, :, c])
    return result.astype(np.uint8), mask

函数里alpha和beta控制亮度下降比例的接受范围,sat_thresh和hue_thresh用来排除彩色物体。形态学闭运算先把细碎阴影连接起来,开运算再去掉误检噪点。补偿部分使用bv与v的比值作为增益,并限制在1.0到3.0之间,避免阴影边界出现过度提亮。

这套实现适合固定相机、软阴影占主导的场景,例如室内监控和阴天交通视频。缺点是强烈阳光下的硬阴影边界清晰、内部明度下降剧烈,容易和其他暗色运动物体混淆;同时当背景更新不及时时,静止物体移动后留下的伪阴影也会被误检。实际使用中通常会把背景建模和阴影掩码更新放在同一个循环里,让背景缓慢适应光照变化。

三、结合梯度与帧间信息的改进方法

HSV空间对颜色偏移比较敏感,当阴影带有较强环境光染色时,色相条件容易失效。梯度方向特征则更鲁棒,因为阴影主要削弱梯度幅值,而纹理方向在阴影覆盖前后变化不大。通过比较当前帧与背景帧的梯度方向余弦相似度,再结合能量比值,可以把阴影与真正移动物体区分开。

以下代码利用Sobel算子计算两个帧的梯度方向,并输出阴影掩码。相比单纯基于亮度比值的方法,它在纹理较弱的区域也能给出更稳定的判断。

import cv2
import numpy as np

def gradient_shadow_mask(frame, background, grad_thresh=0.6):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    bg_gray = cv2.cvtColor(background, cv2.COLOR_BGR2GRAY)

    gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3)
    gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3)
    bgx = cv2.Sobel(bg_gray, cv2.CV_32F, 1, 0, ksize=3)
    bgy = cv2.Sobel(bg_gray, cv2.CV_32F, 0, 1, ksize=3)

    mag = np.sqrt(gx ** 2 + gy ** 2)
    bg_mag = np.sqrt(bgx ** 2 + bgy ** 2)
    # 用点积计算方向一致性,避免除零
    dot = gx * bgx + gy * bgy
    denom = mag * bg_mag + 1e-6
    cos_sim = dot / denom

    # 阴影区域:梯度方向一致,但当前梯度能量低于背景
    energy_ratio = (mag + 1e-6) / (bg_mag + 1e-6)
    mask = (cos_sim > 0.8) & (energy_ratio < 0.7)
    mask = cv2.morphologyEx(mask.astype(np.uint8), cv2.MORPH_CLOSE, np.ones((7, 7), np.uint8))
    return mask

这段代码先分别计算两帧的水平、垂直梯度,直接用点积得到方向一致性,避免除零。当方向相似度高于0.8且当前梯度能量不足背景的70%时,该像素被认为是阴影。形态学闭运算使用7乘7的矩形核,有助于连接阴影内部的小孔洞。需要注意,快速移动的物体会在梯度上表现为方向不一致,因此不会被误判为阴影,这是它优于纯HSV方法的地方。

对于视频序列,单帧掩码还会出现闪烁和边界抖动。常见做法是对连续帧的掩码做指数移动平均,或者对每个连通域跟踪并用卡尔曼滤波平滑面积与中心位置。补偿阶段如果直接使用线性增益,阴影边界可能出现明显光晕,换成边缘保持滤波或泊松重建能在保持纹理的同时平滑亮度过渡。帧间信息还可以帮助处理软阴影:软阴影边界在单帧里很难确定,但结合前后几帧的亮度变化趋势,可以更稳定地估计阴影扩散范围。

四、深度学习方案与工程落地注意事项

深度学习通常把视频阴影去除建模成图像到图像的翻译任务。输入至少包含当前帧和背景帧,部分模型还会显式输入阴影掩码或光照先验;输出是与当前帧对齐的无阴影图像。UNet结构用于保留空间细节,生成对抗网络用来增强视觉自然度,Transformer模块则能建模更大范围的上下文关系。训练数据常用ISTD、SRD等静态阴影数据集,视频场景还会引入时间维度。

对于视频处理,纯单帧推理会忽略时间一致性,出现相邻帧亮度跳变。可以在网络中插入ConvLSTM或光流对齐模块,让模型利用多帧信息稳定输出。以下是一个简化的推理函数示例,假设模型已经训练完成并接收当前帧和背景帧两个输入。

import torch
import numpy as np

def infer_shadow_removal(model, frame, background, device):
    model.eval()
    x = torch.from_numpy(frame).permute(2, 0, 1).float().unsqueeze(0) / 255.0
    bg = torch.from_numpy(background).permute(2, 0, 1).float().unsqueeze(0) / 255.0
    with torch.no_grad():
        y = model(x.to(device), bg.to(device))
    out = y.squeeze(0).permute(1, 2, 0).cpu().numpy()
    return (out * 255).astype('uint8')

工程落地时需要考虑推理延迟、内存占用和模型泛化能力。如果场景光照相对稳定、摄像头固定,先用HSV或梯度方法产生候选掩码,再用轻量学习模型进行补偿,往往比端到端大模型更可控。对于强阴影、复杂纹理和动态背景并存的室外场景,学习方法的召回率更高,但需要大量标注数据,并且在边缘设备上要配合量化与TensorRT或ONNX Runtime加速。

还有一点容易忽略:阴影去除后的结果不能直接替代原始视频用于所有任务。若下游是目标检测,保留阴影信息有时反而有助于判断物体高度和接触面。是否需要去除阴影,应当根据后续算法对光照变化的敏感程度来决策。实际项目中更推荐把阴影掩码作为辅助通道送给下游模型,而不是在预处理阶段完全抹掉阴影。

视频阴影去除光照估计阴影补偿修改时间:2026-09-30 17:15:43

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0930/63905.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。