正在走向自律 2025-04-08 03:26 采纳率: 100%
浏览 13
已结题

数字人视频绿幕背景替换背景图

(1)遇到问题的现象描述
一段mp4绿幕背景视频,我想替换为其他背景图,第一步抠图的时候发现结果边缘还有绿色虚线,两手空隙直接也有少量绿幕背景图没扣掉,导致替换的背景图视频效果不佳。

img

img

(2)问题相关代码,运行结果,没有报错

import sys
import cv2
import numpy as np
import os
from pathlib import Path
from typing import Tuple, Optional

# ================== 配置常量区 ==================
DEBUG_MODE = True            # 开启调试窗口和实时参数调节
GREEN_RANGE: Tuple[np.ndarray, np.ndarray] = (
    np.array([25, 40, 40]),   # 最低HSV阈值 (H, S, V)
    np.array([100, 255, 255]) # 最高HSV阈值
)
MORPH_KERNEL_SIZE: Tuple[int, int] = (9, 9)  # 形态学核尺寸
MORPH_CLOSE_ITERS: int = 3    # 闭运算迭代次数
MORPH_OPEN_ITERS: int = 2     # 开运算迭代次数
EDGE_BLUR_SIZE: int = 15      # 边缘羽化强度(必须奇数)
# ================================================

def create_debug_windows():
    """创建调试窗口和滑动条(仅在DEBUG_MODE开启时生效)"""
    if DEBUG_MODE:
        cv2.namedWindow('Debug Controls')
        cv2.createTrackbar('H Min', 'Debug Controls', GREEN_RANGE[0][0], 180, lambda x: None)
        cv2.createTrackbar('H Max', 'Debug Controls', GREEN_RANGE[1][0], 180, lambda x: None)
        cv2.createTrackbar('S Min', 'Debug Controls', GREEN_RANGE[0][1], 255, lambda x: None)
        cv2.createTrackbar('V Min', 'Debug Controls', GREEN_RANGE[0][2], 255, lambda x: None)

def get_dynamic_hsv_range():
    """实时获取调试参数(调试模式专用)"""
    if DEBUG_MODE:
        return (
            np.array([
                cv2.getTrackbarPos('H Min', 'Debug Controls'),
                cv2.getTrackbarPos('S Min', 'Debug Controls'),
                cv2.getTrackbarPos('V Min', 'Debug Controls')
            ]),
            np.array([GREEN_RANGE[1][0], 255, 255])
        )
    return GREEN_RANGE

def dynamic_hsv_adjust(hsv_frame: np.ndarray) -> np.ndarray:
    """动态亮度补偿(解决阴影/高光问题)"""
    v_channel = hsv_frame[:, :, 2].astype(np.float32)
    v_mean = np.mean(v_channel)
    contrast = 1.2 if v_mean < 100 else 0.8  # 自动对比度调整
    hsv_frame[:, :, 2] = np.clip(v_channel * contrast, 0, 255).astype(np.uint8)
    return hsv_frame

def apply_morphology(mask: np.ndarray) -> np.ndarray:
    """增强型形态学处理"""
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, MORPH_KERNEL_SIZE)
    mask = cv2.GaussianBlur(mask, (5, 5), 0)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=MORPH_CLOSE_ITERS)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=MORPH_OPEN_ITERS)
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=1)

def process_frame(frame: np.ndarray) -> Tuple[np.ndarray, Optional[np.ndarray], Optional[np.ndarray]]:
    """核心处理流程(返回处理帧 + 调试数据)"""
    # HSV颜色空间处理
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    hsv = dynamic_hsv_adjust(hsv)

    # 获取动态阈值
    lower, upper = get_dynamic_hsv_range()
    mask = cv2.inRange(hsv, lower, upper)

    # 形态学优化
    mask = apply_morphology(mask)

    # 边缘羽化处理
    alpha = cv2.merge([mask] * 3).astype(float) / 255.0
    alpha = cv2.GaussianBlur(alpha, (EDGE_BLUR_SIZE, EDGE_BLUR_SIZE), 0)

    # 前景合成(替换背景在此修改)
    background = np.zeros_like(frame)  # 替换为实际背景
    foreground = cv2.multiply(frame.astype(float), 1 - alpha)
    processed = cv2.add(foreground, cv2.multiply(background.astype(float), alpha)).astype(np.uint8)

    # 返回处理结果和调试数据
    if DEBUG_MODE:
        return processed, hsv, mask
    return processed, None, None

def main():
    # 初始化调试窗口
    if DEBUG_MODE:
        create_debug_windows()

    # 路径配置(根据实际路径修改)
    current_dir = Path(__file__).resolve().parent
    input_video = current_dir / "../../video_ppt/assets/input_videos/20250407/3283dcc22420686162e32102af8304bb.mp4"
    output_video = current_dir / "../../video_ppt/output/temp/20250407/v1_6-10.mp4"

    # 路径验证
    if not input_video.exists():
        raise FileNotFoundError(f"输入视频不存在:{input_video}")
    output_video.parent.mkdir(parents=True, exist_ok=True)

    # 视频流初始化
    cap = cv2.VideoCapture(str(input_video))
    if not cap.isOpened():
        raise RuntimeError("视频打开失败")

    # 获取视频参数(添加总帧数获取)
    frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = cap.get(cv2.CAP_PROP_FPS)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    current_frame = 0

    # 进度条显示函数
    def print_progress(current: int, total: int):
        bar_length = 30
        progress = current / total
        block = int(round(bar_length * progress))
        text = "\rProgress: [{}{}] {:.1f}%  Frame: {}/{}".format(
            "=" * block, " " * (bar_length - block),
            progress * 100, current, total
        )
        sys.stdout.write(text)
        sys.stdout.flush()

    # 视频写入器配置(兼容OpenCV 4.5.x)
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(str(output_video), fourcc, fps, (frame_width, frame_height), isColor=True)

    try:
        while cap.isOpened():
            ret, frame = cap.read()
            if not ret:
                break

            current_frame += 1  # 更新当前帧计数

            # 显示进度条(每帧更新,但控制台每5%更新一次)
            if total_frames > 0 and (current_frame % max(1, total_frames // 200) == 0 or current_frame == total_frames):
                print_progress(current_frame, total_frames)

            # 处理帧并获取调试数据
            processed_frame, hsv_debug, mask_debug = process_frame(frame)

            # 调试显示
            if DEBUG_MODE:
                cv2.imshow('Final Output', processed_frame)
                if hsv_debug is not None:
                    cv2.imshow('HSV Debug', cv2.cvtColor(hsv_debug, cv2.COLOR_HSV2BGR))
                if mask_debug is not None:
                    cv2.imshow('Mask Debug', mask_debug)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break

            # 写入处理后的帧
            out.write(processed_frame)
            # 处理完成后换行
        if total_frames > 0:
            print("\n处理完成!")
    finally:
        cap.release()
        out.release()
        if DEBUG_MODE:
            cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

(3)我的初步解决思路是
开启Debug调整HSV各通道分布值,观察效果,但是无论如何调整都没能解决问题

# ================== 配置常量区 ==================
DEBUG_MODE = True            # 开启调试窗口和实时参数调节
GREEN_RANGE: Tuple[np.ndarray, np.ndarray] = (
    np.array([25, 40, 40]),   # 最低HSV阈值 (H, S, V)
    np.array([100, 255, 255]) # 最高HSV阈值
)
MORPH_KERNEL_SIZE: Tuple[int, int] = (9, 9)  # 形态学核尺寸
MORPH_CLOSE_ITERS: int = 3    # 闭运算迭代次数
MORPH_OPEN_ITERS: int = 2     # 开运算迭代次数
EDGE_BLUR_SIZE: int = 15      # 边缘羽化强度(必须奇数)
# ================================================

img


img

(4)操作系统、软件版本
操作系统:windows10
python3.9 conda环境 requirements.txt如下:


# three_nine_env
# 核心依赖
numpy==1.21.5              # 确保与 OpenCV 和 MoviePy 兼容
opencv-python==4.5.5.64    # 视频处理核心库
moviepy==1.0.3             # 视频合成库(适配低版本依赖)
python-pptx==0.6.20        # PPT文件处理库

# MoviePy 依赖链(精确版本锁定)
imageio[ffmpeg]==2.31.6    # 视频编解码支持(与 moviepy 1.0.3 兼容)
decorator==4.4.2           # 必须匹配 moviepy 1.0.3 的依赖范围(<5.0tqdm==4.65.0               # 进度条工具
proglog==0.1.9             # moviepy 1.x 的隐藏依赖(不指定会触发版本冲突)

# 视频识别语音转文本内容
zhconv==1.4.3
openai-whisper==20240930

# 高质量的视频输出插件
opencv-contrib-python==4.5.5.64  # 包含ximgproc模块

展开全部

  • 写回答

4条回答 默认 最新

  • 观熵 2025-04-08 10:32
    关注

    你这个项目做得非常扎实,描述也非常清晰!问题核心在于:

    绿幕抠图效果不干净,即:边缘残绿、人体空隙(如手指缝)绿幕残留,导致背景替换效果不自然。

    下面我给你快速定位原因,并提供可立即改进的解决方案 + 代码调整建议👇


    ✅ 一、问题现象总结

    现象本质原因
    人体边缘有绿色虚边HSV 色域不准确 / 羽化不足 / 蒙版抗锯齿差
    手指缝等细节没抠掉掩膜过粗 / 形态学运算过度模糊了细节
    动态绿幕强度不一光照不均 or 背景绿色分布范围太广

    ✅ 二、解决方案总览(建议逐条尝试)


    ✅ 方案 1:更准确的绿色取值范围(HSV 再缩紧)

    你当前的阈值是:

    GREEN_RANGE = (np.array([25, 40, 40]), np.array([100, 255, 255]))
    

    问题是这个范围太“宽”,覆盖了非绿但含绿量区域(如手边光反射)。建议缩窄:

    GREEN_RANGE = (np.array([35, 60, 60]), np.array([85, 255, 255]))
    

    或开启 debug 滑动条,实时调试时将 V 值适当调高,S 值不要太低(避免低饱和颜色误抠)。


    ✅ 方案 2:手动替换背景部分逻辑存在问题(背景图未注入)

    你这段代码中:

    background = np.zeros_like(frame)  # 替换为实际背景
    

    👆这里应该用真实背景图替换,例如:

    background_img = cv2.imread("your_background.jpg")
    background = cv2.resize(background_img, (frame.shape[1], frame.shape[0]))
    

    然后再替换:

    composited = cv2.add(
        cv2.multiply(frame.astype(float), 1 - alpha),
        cv2.multiply(background.astype(float), alpha)
    ).astype(np.uint8)
    

    ✅ 方案 3:增强形态学 + 二次优化掩膜处理

    你当前形态学处理是:

    cv2.morphologyEx(... iterations=2)
    

    ✅ 增强建议:

    • 调整内核为 (3,3),提高精度;
    • 引入 cv2.erode + cv2.dilate 分阶段处理;
    • 加入额外的空洞修复(cv2.inpaint)或区域填补。

    例如:

    def refine_mask(mask: np.ndarray) -> np.ndarray:
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
        mask = cv2.erode(mask, kernel, iterations=1)
        mask = cv2.dilate(mask, kernel, iterations=2)
        return mask
    

    在主流程中插入:

    mask = apply_morphology(mask)
    mask = refine_mask(mask)
    

    ✅ 方案 4:边缘羽化优化(使用 mask 生成 soft edge)

    你的 alpha 是基于 mask,但实际上直接拉高 feather 效果:

    EDGE_BLUR_SIZE = 21  # 设置为更大值,如 21 或 31
    alpha = cv2.GaussianBlur(alpha, (EDGE_BLUR_SIZE, EDGE_BLUR_SIZE), 0)
    

    也可以用距离变换(cv2.distanceTransform)生成更自然的羽化:

    inv_mask = 255 - mask
    dist = cv2.distanceTransform(inv_mask, cv2.DIST_L2, 5)
    dist = np.clip(dist / np.max(dist), 0, 1)
    alpha = np.expand_dims(1 - dist, axis=2)
    

    ✅ 方案 5:引入 AI 抠图库(适用于关键帧增强)

    如果绿幕复杂度很高,建议关键帧用 AI 抠图(如 rembg):

    pip install rembg
    
    from rembg import remove
    output = remove(cv2.imencode('.png', frame)[1].tobytes())
    bg_removed = cv2.imdecode(np.frombuffer(output, np.uint8), cv2.IMREAD_UNCHANGED)
    

    ✅ 三、推荐调参顺序(从轻量 → 重型)

    1. ✅ 调整 GREEN_RANGE 范围(先手动调好再进代码)
    2. ✅ 替换 background 为真实背景图
    3. ✅ 优化形态学处理 + 二次 refine mask
    4. ✅ 加强羽化范围(羽化不够就会有绿边)
    5. ✅ 关键帧用 rembg 抠图精修(可选)

    ✅ 一句话总结:

    你出现的绿边、残影问题核心在于 HSV 范围过宽 + mask 边缘不干净 + 没正确换背景图,建议精调 GREEN_RANGE、替换背景图真实图像、优化 mask 羽化与形态学处理,即可得到自然干净的视频合成效果。


    展开全部

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(3条)
编辑
预览

报告相同问题?

问题事件

  • 系统已结题 4月18日
  • 已采纳回答 4月11日
  • 创建了问题 4月8日
手机看
程序员都在用的中文IT技术交流社区

程序员都在用的中文IT技术交流社区

专业的中文 IT 技术社区,与千万技术人共成长

专业的中文 IT 技术社区,与千万技术人共成长

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

客服 返回
顶部