一、实验原理

       背景差分法是一种对静止场景进行运动分割的通用方法,它将当前获取的图像帧与背景图像做差分运算,得到目标运动区域的灰度图,对灰度图进行阈值化提取运动区域,而且为避免环境光照变化影响,背景图像根据当前获取图像帧进行更新。在稳定的监控场景中,背景一般保持不变或变化缓慢,而前景目标(如行人、车辆等)则表现为明显的移动或变化。通过设置一个背景模型,实时更新并存储当前场景的静态部分,然后将新进帧与背景模型进行逐像素比较,从而计算出差分图像。该差分图像清晰地显示了前景目标的位置和形状,这就是背景差分法的理论基础。

二、实验代码

import numpy as np
import cv2
import os


def initialize_background_model(video_path, num_frames=50):
    """
    初始化背景模型(使用中值法)

    参数:
    video_path: 视频文件路径
    num_frames: 用于初始化的帧数

    返回:
    background_model: 背景模型
    """
    # 打开视频
    cap = cv2.VideoCapture(video_path)
    # 读取前N帧
    frames = []
    frame_count = 0
    while frame_count < num_frames:
        ret, frame = cap.read()
        if not ret:
            break

        frames.append(frame)
        frame_count += 1

        if frame_count % 10 == 0:
            print(f"已采集 {frame_count}/{num_frames} 帧")

    cap.release()
    print(f"成功采集 {len(frames)} 帧用于背景建模")

    # 转换为numpy数组
    frames_array = np.array(frames)

    # 使用中值法计算背景模型
    print("使用中值法建立背景模型...")
    background_model = np.median(frames_array, axis=0).astype(np.uint8)
    return background_model


def background_subtraction(current_frame, background_model, threshold=30):
    """
    背景差分法检测前景

    参数:
    current_frame: 当前帧
    background_model: 背景模型
    threshold: 二值化阈值

    返回:
    foreground_mask: 前景掩码
    result_frame: 标记结果的帧
    """
    # 计算绝对差
    diff = cv2.absdiff(current_frame, background_model)
    # 转换为灰度图
    gray_diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)

    # 二值化
    _, foreground_mask = cv2.threshold(gray_diff, threshold, 255, cv2.THRESH_BINARY)

    # 在原图上标记前景(红色)
    result_frame = current_frame.copy()
    result_frame[foreground_mask == 255] = [0, 0, 255]  # BGR格式,红色

    return foreground_mask, result_frame


def main():
    # 参数设置
    video_path = 'video1.mp4'  # 视频文件路径
    num_frames = 50  # 用于初始化的帧数
    threshold = 30  # 背景差分阈值

    # 初始化背景模型(只使用中值法)
    print("正在使用中值法初始化背景模型...")
    background_model = initialize_background_model(video_path, num_frames)

    # 保存背景模型(中值法)
    cv2.imwrite('background_model.jpg', background_model)

    # 创建显示窗口
    cv2.namedWindow('frame1', cv2.WINDOW_NORMAL)
    cv2.resizeWindow('frame1', 1200, 400)

    print("开始实时显示背景差分结果...")
    print("按 'q' 键退出")
    print("按 'r' 键重新开始播放")
    print("按 '+' 键增加阈值")
    print("按 '-' 键减少阈值")

    # 循环播放视频
    while True:
        # 打开视频
        cap = cv2.VideoCapture(video_path)
        if not cap.isOpened():
            print("错误:无法打开视频文件!")
            break

        frame_count = 0

        while True:
            ret, frame = cap.read()
            if not ret:
                break  # 视频结束,重新开始

            # 背景差分
            foreground_mask, result_frame = background_subtraction(frame, background_model, threshold)

            # 调整掩码为彩色用于显示
            mask_display = cv2.cvtColor(foreground_mask, cv2.COLOR_GRAY2BGR)

            # 水平拼接三个画面
            combined_frame = np.hstack([frame, result_frame, mask_display])

            # 添加文字说明
            cv2.putText(combined_frame, f"original (threshold: {threshold})", (10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
            cv2.putText(combined_frame, "back", (frame.shape[1] + 10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
            cv2.putText(combined_frame, "yanma", (2 * frame.shape[1] + 10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)

            # 显示帧编号
            cv2.putText(combined_frame, f"帧: {frame_count}", (10, combined_frame.shape[0] - 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)

            # 显示结果
            cv2.imshow('frame1', combined_frame)

            # 键盘控制
            key = cv2.waitKey(25) & 0xFF
            if key == ord('q'):  # 退出
                cap.release()
                cv2.destroyAllWindows()
                return
            elif key == ord('r'):  # 重新开始
                break
            elif key == ord('+'):  # 增加阈值
                threshold = min(255, threshold + 5)
                print(f"阈值增加到: {threshold}")
            elif key == ord('-'):  # 减少阈值
                threshold = max(0, threshold - 5)
                print(f"阈值减少到: {threshold}")
            elif key == ord('p'):  # 暂停/继续
                print("暂停,按任意键继续...")
                cv2.waitKey(0)

            frame_count += 1

        cap.release()


if __name__ == "__main__":
    main()

三、实验结果

       本实验中采用中值法建立背景模型实现背景差分,可观察到背景模型比较干净,二值化图像有轻微误差。阈值是用来决定像素差异是否足够大以被认为是前景的关键参数,本实验中可根据视频情况改变阈值,更好的分离背景和前景,检测出运动目标。

四、问题解答

如何得到背景模型?

      使用算法训练得到背景模型,本实验中采用中值法,通过计算视频序列中每个像素位置的中值来建立模型。读取多帧视频帧作为训练样本,对于图像中的每个像素位置 (x, y),从所有训练帧中收集该位置的像素值,然后计算中值得到背景模型。

参考博客

原文链接:https://blog.csdn.net/ajianyingxiaoqinghan/article/details/72628402

原文链接:https://blog.csdn.net/weixin_42607969/article/details/147806313

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐