19f21d56ca757.png

1. 项目概览:一套会“看深度”的智能抠图小助手 🧑‍💻

本项目来自华南理工小课题“抠图算法小组”,目标是构建一个面向图片与视频的人物图像抠取系统。它并不满足于普通 2D 图像里的“凭颜色猜边界”,而是尝试引入 Astra+ 深度相机采集到的深度信息,让系统在复杂背景、人物边缘、背景替换等场景中获得更稳定的判断依据。

换句话说,它想做的事情很直接:用户上传视频或图片,系统识别人物前景,生成透明度遮罩,再把人物自然地合成到新背景里。背景负责换装,人物负责出片,算法负责认真干活。😎

项目条目 内容
主题方向 人物图像抠取、视频抠像、背景替换
团队成员 王实依、方润泽、张辰旭
相机型号 Astra+
支持设备 Zora P1 开发板
核心能力 图片/视频上传、相机采集、深度辅助抠像、背景替换、结果下载
源码链接 Portrait-extraction-algorithm
教程链接 人物图像扣取教程

2. 为什么要用深度相机做人物抠取?📷

传统人物抠图通常只依赖 RGB 图像。这个思路在背景干净、光照稳定、人物衣物与背景颜色差异明显时效果很好;但一旦遇到头发丝、透明边缘、复杂室内背景、低对比服装,算法就容易开始“犹豫”:这块到底是人,还是背景?边界要不要保留?阴影算不算前景?

Astra+ 这类深度相机可以额外提供距离信息。对于人物抠取而言,深度图像相当于给系统补了一条线索:如果某些像素明显远离人物所在深度层,它们就更可能属于背景。项目中的预处理脚本正是利用这一点,将 RGB 视频与深度视频结合起来,为后续 matting 模型提供更干净的输入。

对比维度 仅 RGB 抠图 深度辅助抠图
输入信息 颜色、纹理、边缘 颜色、纹理、边缘、距离
背景复杂度适应 容易受颜色相似影响 可利用深度层次排除部分背景
视频稳定性 易出现边缘闪烁 可通过深度预处理减少干扰
设备要求 普通摄像头即可 需要 Astra+ 等深度相机
适用场景 轻量图像处理 视频会议、虚拟拍摄、AR 展示、人机交互

3. 系统总体架构:从采集到换背景,一条龙服务 🧩

19f21da87af68.png

从项目文件结构看,系统可以拆成五个层次:

层级 主要文件 作用
数据采集层 01fixingvideo.py 调用 Orbbec SDK,同步采集 RGB 与深度视频
深度预处理层 01preprocess.py 根据深度阈值削弱远处背景干扰
视频抠像层 model/、inference.py 基于 Robust Video Matting 思路输出前景、透明度与合成视频
合成调用层 invoking3.py、evaluation/ 调用模型并完成前景、alpha、背景的融合
Web 交互层 app.py、templates/、static/ 提供登录、上传、展示、下载等页面功能

简化后的处理链路如下:

阶段 输入 输出
1. 相机采集 Astra+ RGB 流、深度流 rgb.mp4、depth.mp4
2. 深度预处理 RGB 视频、深度视频 rgb_processed.mp4
3. 抠像推理 预处理 RGB 视频、模型权重 前景 foreground.mp4、透明度 alpha.mp4
4. 背景合成 前景、alpha、新背景 合成结果 composition.mp4 或 output.mp4
5. 网页展示 处理后视频 在线预览、下载、清理缓存

4. RGB-D 视频采集:先把“看见”和“测距”同步起来 🛰️

19f21dcafeaec.png

项目的 01fixingvideo.py 使用 pyorbbecsdk 连接深度相机,启用彩色流与深度流,并通过帧同步机制保证两个视频序列在时间上尽量对齐。对于视频抠像来说,这一步非常重要:RGB 帧和深度帧如果错位,人物边界就会像没对齐的贴纸一样,边缘处理会变得很“迷惑”。

下面是采集逻辑的核心片段,整理自项目代码:

pipeline = Pipeline()
config = Config()

color_profile = pipeline.get_stream_profile_list(OBSensorType.COLOR_SENSOR) 
                        .get_default_video_stream_profile()
depth_profile = pipeline.get_stream_profile_list(OBSensorType.DEPTH_SENSOR) 
                        .get_default_video_stream_profile()

config.enable_stream(color_profile)
config.enable_stream(depth_profile)
pipeline.enable_frame_sync()
pipeline.start(config)

frames = pipeline.wait_for_frames(100)
color_frame = frames.get_color_frame()
depth_frame = frames.get_depth_frame()

采集后,系统将彩色视频写入 RGB 目录,将深度数据归一化为可视化视频写入 D 目录。这样做有两个好处:

  • 便于后续算法读取统一格式的视频文件。
  • 便于人工检查 RGB 与深度图是否匹配,调试时不用盲猜。

5. 深度预处理:给背景先做一次“体检”🩺

项目中的 01preprocess.py 使用了一个简洁但有效的思路:读取 RGB 帧和深度帧,如果某个像素的深度值超过阈值,就把该 RGB 像素置黑。它的作用不是直接完成精细抠图,而是提前压制一部分明显属于背景的区域,让后续模型更专注于人物边缘。

depth_threshold = 100

ret_rgb, frame_rgb = cap_rgb.read()
ret_depth, frame_depth = cap_depth.read()

scalar_depth = frame_depth[:, :, 0]
depth_mask = scalar_depth >= depth_threshold
frame_rgb[depth_mask] = [0, 0, 0]

out.write(frame_rgb)

可以把它理解成一个深度引导的粗筛选:

若 D(u, v) < threshold:
    该像素更可能处于人物所在深度范围,保留 RGB 信息
否则:
    该像素更可能属于远处背景,削弱或置黑

这种方法的学术意义在于,它把单纯依赖颜色分布的抠像问题,转化为“颜色 + 空间距离”的联合判断问题。深度信息不一定能完全替代语义分割或 matting 网络,但它能在复杂背景中提供额外约束,尤其适合人物与背景颜色相近、纹理混杂的场景。

处理对象 技术动作 对抠图的帮助
RGB 帧 保留人物邻近区域颜色 给模型提供纹理与边缘信息
深度帧 按阈值生成背景掩码 快速排除远距离干扰
预处理视频 背景区域置黑 降低模型误判概率

6. 核心抠像模型:RVM 思路下的时序人物 Matting 🧠

19f21de8be777.png

项目模型主要位于 model/ 目录,整体思路借鉴 Robust Video Matting:网络不只看单帧图像,还通过循环记忆结构利用前后帧信息,从而减少视频抠像中常见的边缘闪烁问题。

模型可以概括为:

输入 RGB 视频帧
    ↓
Backbone 提取多尺度特征
    ↓
LR-ASPP 聚合上下文
    ↓
Recurrent Decoder 结合历史帧记忆
    ↓
Projection 输出前景残差与透明度 alpha
    ↓
Guided Filter 恢复高分辨率细节

项目中的 MattingNetwork 支持 mobilenetv3 与 resnet50 两类骨干网络。前者更轻量,适合边缘端部署;后者表达能力更强,但计算负担也更高。

class MattingNetwork(nn.Module):
    def __init__(self, variant='mobilenetv3', refiner='deep_guided_filter'):
        super().__init__()
        assert variant in ['mobilenetv3', 'resnet50']

        if variant == 'mobilenetv3':
            self.backbone = MobileNetV3LargeEncoder()
            self.aspp = LRASPP(960, 128)
            self.decoder = RecurrentDecoder([16, 24, 40, 128], [80, 40, 32, 16])
        else:
            self.backbone = ResNet50Encoder()
            self.aspp = LRASPP(2048, 256)
            self.decoder = RecurrentDecoder([64, 256, 512, 256], [128, 64, 32, 16])

        self.project_mat = Projection(16, 4)

在前向推理阶段,模型会输出前景 fgr 与透明度 pha。其中 pha 是抠图任务的关键:它不是简单的 0/1 二值掩码,而是一个连续透明度图。头发丝、衣服边缘、半透明区域都可以通过 0 到 1 之间的 alpha 值表达出来。

fgr_residual, pha = self.project_mat(hid).split([3, 1], dim=-3)
fgr = fgr_residual + src_sm

if downsample_ratio != 1:
    fgr, pha = self.refiner(src, src_sm, fgr, pha, hid)

fgr = fgr.clamp(0., 1.)
pha = pha.clamp(0., 1.)
return [fgr, pha, r1, r2, r3, r4]

7. 时序记忆:视频抠像不能只看一帧 🎞️

单帧抠图像拍证件照,视频抠图像连拍一百张证件照还要求每张边缘都稳定。为了让输出不闪,项目使用了 ConvGRU 结构,让模型保留上一帧的记忆状态。

class ConvGRU(nn.Module):
    def forward_single_frame(self, x, h):
        if h is None:
            h = torch.zeros((x.shape[0], self.out_channels, x.shape[2], x.shape[3]),
                            device=x.device, dtype=x.dtype)

        xh = torch.cat([x, h], dim=1)
        r, z = self.ih(xh).split([self.out_channels, self.out_channels], dim=1)
        r = torch.sigmoid(r)
        z = torch.sigmoid(z)

        xrh = torch.cat([x, r * h], dim=1)
        n = torch.tanh(self.hh(xrh))
        h = (1 - z) * h + z * n
        return h

这段逻辑的直观解释如下:

门控变量 含义 对视频抠像的作用
r 重置门 控制上一帧记忆是否参与当前判断
z 更新门 控制新信息写入多少
h 隐状态 保存人物边界、姿态、运动等时序线索

有了这些记忆,模型处理连续视频时不会每一帧都“重新认识一个人”,而是会把前后帧联系起来。这对手臂晃动、发丝边缘、衣服褶皱等细节尤其重要。

8. Guided Filter:把边缘细节再精修一下 🔍

模型中的 fast_guided_filter.py 与 deep_guided_filter.py 用于把低分辨率预测结果细化回高分辨率图像。其核心思想是:先在低分辨率上快速估计前景和透明度,再借助原始高分辨率图像的边缘结构进行引导恢复。

mean_x = self.boxfilter(lr_x)
mean_y = self.boxfilter(lr_y)
cov_xy = self.boxfilter(lr_x * lr_y) - mean_x * mean_y
var_x = self.boxfilter(lr_x * lr_x) - mean_x * mean_x

A = cov_xy / (var_x + self.eps)
b = mean_y - A * mean_x
A = F.interpolate(A, hr_x.shape[2:], mode='bilinear', align_corners=False)
b = F.interpolate(b, hr_x.shape[2:], mode='bilinear', align_corners=False)
return A * hr_x + b

如果说深度预处理是在前面帮算法“扫清背景杂物”,那么 guided filter 就是在后面给边缘“补妆”:让轮廓更贴近真实图像结构,避免人物边缘糊成一团。

9. 背景替换:alpha 负责温柔地“混合”前景与背景 🌈

19f21e34a207a.png

背景替换的数学形式很清楚:

Composite = Foreground * Alpha + Background * (1 - Alpha)

项目的 inference.py 和 invoking3.py 都体现了这一合成逻辑。模型输出前景 fgr 和透明度 pha 后,只需要准备一张背景图或背景视频,就能得到最终合成结果。

fgr, pha, *rec = model(src, *rec, downsample_ratio)

if writer_com is not None:
    writer_com.write(fgr * pha + bgr * (1 - pha))

evaluation/ 目录中的脚本还进一步支持把前景、alpha、背景和合成结果分别保存,便于定量评估和可视化对比。

输出类型 文件含义 可视化价值
foreground 抠出的人物前景 检查人物纹理是否完整
alpha 透明度遮罩 检查边缘、头发、半透明区域
background 替换背景 检查合成场景是否匹配
composition 最终合成视频 直接展示系统效果

10. Web 端:让算法从代码走到可操作界面 🖥️

19f21e50b7942.png

项目使用 Flask 构建了一个原型 Web 端。templates/login.html 与 register.html 提供账号入口,charts.html 提供视频上传、深度视频展示、背景图片展示、进度条与结果视频预览区域。整体上,这使项目不再只是命令行脚本,而是具备“用户上传、系统处理、网页查看结果”的应用雏形。

后端核心路由位于 app.py:

@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['file']
    if file and allowed_file(file.filename):
        filename = str(uuid.uuid4()) + '.' + file.filename.rsplit('.', 1)[1].lower()
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        return filename

@app.route('/download/<filename>')
def download(filename):
    path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    return send_file(path, as_attachment=True)

从工程化角度看,当前 Web 端更接近“可演示原型”。例如,上传、处理、进度查询、清理缓存等接口已经有基本结构,但个别依赖导入、模型调用封装和异常处理仍需要补齐。这个状态很正常:科研课题原型往往先验证链路,再逐步把接口打磨成稳定产品。

Web 功能 已有基础 后续建议
用户入口 登录页、注册页 接入真实用户数据库与密码加密
文件上传 AJAX 上传、进度条 区分 RGB、深度、背景文件类型
算法调用 预留 /process 接口 统一调用采集、预处理、推理和合成流程
结果展示 视频预览区域 支持前景、alpha、合成结果多标签查看
结果下载 /download 路由 增加任务编号与历史记录管理

11. 数据集与评估:不是“看起来还行”就完事 📊

项目保留了 dataset/ 与 evaluation/ 两类脚本,这说明系统不仅考虑演示,也考虑训练与评估。

数据集部分支持 ImageMatte、VideoMatte、COCO Panoptic、YouTube-VIS、Supervisely Person 等来源,覆盖静态图像、视频序列、人体分割和前景透明度等不同训练信号。评估脚本则关注 alpha 与前景质量,例如 MAD、MSE、梯度误差、连通性误差、时序差异等指标。

模块 代表文件 说明
图像抠像数据 imagematte.py 读取前景、alpha、背景并构造序列
视频抠像数据 videomatte.py 读取视频前景与透明度序列
人体语义数据 coco.py、youtubevis.py 关注 person 类分割信息
数据增强 augmentation.py 模拟运动、模糊、噪声、颜色变化
低分辨率评估 evaluate_lr.py 适合快速评估
高分辨率评估 evaluate_hr.py 使用 CUDA 加速高分辨率指标计算

其中数据增强尤其值得一提。视频抠像最怕“现实世界突然不按训练集剧本演”:人物移动、背景晃动、画面模糊、光照变化都会影响稳定性。项目通过运动仿射、颜色扰动、噪声、模糊、水平翻转等方式增加训练样本多样性。

if random.random() < self.prob_fgr_affine:
    fgrs, phas = self._motion_affine(fgrs, phas)

if random.random() < self.prob_noise:
    fgrs, bgrs = self._motion_noise(fgrs, bgrs)

if random.random() < self.prob_hflip:
    fgrs = F.hflip(fgrs)
    phas = F.hflip(phas)

12. 技术亮点总结:它不只是“会换背景”✨

技术亮点 具体体现 价值
RGB-D 融合 Astra+ 同步采集 RGB 与深度流 增强复杂背景下的人物定位能力
深度预处理 根据深度阈值削弱远处背景 降低后续模型误分割压力
视频时序建模 ConvGRU 保存多尺度历史状态 减少视频边缘闪烁
轻量骨干网络 支持 MobileNetV3 更适合 Zora P1 等边缘设备
高分辨率细化 Fast/Deep Guided Filter 改善人物轮廓与发丝细节
Web 可视化 Flask + Bootstrap 页面 降低使用门槛,便于展示与测试

13. 典型应用场景:背景换了,生产力也来了 🚀

19f21e9400a57.png

14. 可继续完善的方向:让原型更像产品 🛠️

这个项目已经具备完整链路雏形,但如果要进一步走向稳定应用,可以重点优化以下方面:

方向 当前状态 建议
深度阈值 使用固定阈值 改为自适应阈值或人体深度聚类
相机标定 依赖同步采集 增加 RGB-D 对齐校验与异常提示
后端接口 Flask 原型路由 补齐任务队列、异常处理、日志记录
模型部署 支持 MobileNetV3 针对 Zora P1 做量化、裁剪和推理加速
评估实验 保留指标脚本 增加对比实验、消融实验和用户场景测试
Web 体验 页面可演示 优化文件类型区分、结果多视图、历史任务管理

15. 结语:从一张图里“请出主角”🎯

人物图像抠取看起来像一个简单功能:把人抠出来,把背景换掉。但真正做起来,它涉及相机采集、深度感知、视频时序建模、alpha matting、前后景合成、Web 工程化等多项技术。这个项目的亮点在于,它没有把抠图只当成一个纯 2D 视觉问题,而是借助 Astra+ 深度相机把三维空间信息引入处理流程,让系统更接近真实应用环境。

如果把普通抠图比作“用眼睛看边界”,那么这个系统更像是“既看颜色,也量距离”。主角站在画面里,背景可以优雅退场;算法站在幕后,认真把每一帧安排明白。🎬

19f21ee9b9c9a.png

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注