机器视觉---ViBe算法

1. ViBe 算法简介

ViBe 是一种基于像素级背景建模的前景检测算法,由 Olivier Barnich 和 Marc Van Droogenbroeck 在 2009 年提出。它的特点是:

  • 初始化速度快:只需要第一帧(或少数几帧)就能建立背景模型。
  • 在线更新:检测的同时不断更新背景模型,适应缓慢变化。
  • 内存占用小:每个像素只存储有限数量的样本值。
  • 实时性好:算法简单,计算量低,可以在普通 CPU 上实时运行。

ViBe 广泛应用于视频监控、运动检测、行人跟踪等场景。


2. 算法核心思想

ViBe 的核心是每个像素用一个随机样本库(background model)表示其背景颜色分布,对于新到来的像素值,通过比较它与样本库的相似程度来判断是前景还是背景。

2.1 背景模型表示

  • 对每个像素 ((x, y)),维护一个样本集合 (B(x, y)),包含 (N) 个历史像素值(灰度或彩色)。
  • 这些样本是从过去的帧中随机选取的,代表该像素可能的背景颜色。

2.2 分类规则

  • 对于当前像素值 (p(x, y)),计算它与样本库 (B(x, y)) 中每个样本的距离(如欧氏距离)。
  • 如果样本库中存在 至少 #min 个样本 与当前像素的距离小于阈值 (R),则认为该像素是背景 ,否则是前景

2.3 模型初始化

  • 传统方法需要多帧来构建背景模型,而 ViBe 只需要第一帧:
    • 对第一帧的每个像素,从它自身和邻域像素中随机选取 (N) 个值作为初始样本库。
    • 这样可以快速建立背景模型,无需等待多帧累积。

2.4 模型更新

  • 检测到是背景的像素,有一定概率(如 1/16)将其值加入样本库,并随机替换掉一个旧样本。
  • 同时,为了保持空间一致性,还会随机更新邻域像素的样本库(空间扩散)。
  • 这种更新策略可以有效抑制"鬼影"(ghost)现象。

3. 关键参数

  • NNN:每个像素的样本数(通常取 20~30)。
  • RRR:距离阈值(判断相似的阈值,灰度图可设 20~40)。
  • #min⁡\#_{\min}#min:匹配阈值(样本库中满足距离条件的最小数量,通常取 2~3)。
  • 更新概率 PupdateP_{\text{update}}Pupdate:背景点更新自身样本库的概率(如 1/16)。
  • 邻域大小:初始化和更新时考虑的邻域范围(如 3×3)。

4. 算法流程

  1. 初始化
    • 对第一帧的每个像素,从自身和 8 邻域中随机选择 (N) 个像素值作为初始样本库。
  2. 前景/背景分类
    • 对新帧每个像素,计算与样本库中样本的距离。
    • 统计距离小于 (R) 的样本个数,如果 ≥ (#_{\min}) 则为背景,否则为前景。
  3. 模型更新
    • 如果像素被分类为背景,以概率 PupdateP_{\text{update}}Pupdate 将其值加入样本库,并随机替换一个旧样本。
    • 同时随机选择一个邻域像素,同样以概率 PupdateP_{\text{update}}Pupdate 用当前像素值替换其样本库中的一个样本。

5. 优缺点

优点

  • 初始化快,无需长时间积累背景。
  • 计算量小,实时性好。
  • 内存占用低。
  • 能较好地适应缓慢光照变化。

缺点

  • 对快速光照变化和动态背景(如树叶摇动)鲁棒性一般。
  • 可能产生短暂的"鬼影",但通过空间扩散可以缓解。
  • 对彩色图需要考虑三通道距离,计算量稍大。

6. C++ 实现示例

下面给出一个简化的灰度图版本 ViBe 实现(OpenCV 支持)。

cpp 复制代码
#include <opencv2/opencv.hpp>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace cv;
using namespace std;

class ViBe {
public:
    ViBe(int numSamples = 20, int matchThreshold = 2, int distanceThreshold = 20, int updateProbability = 16) {
        N = numSamples;
        minMatches = matchThreshold;
        R = distanceThreshold;
        updateProb = updateProbability;
        srand(time(0));
    }

    void init(const Mat& firstFrame) {
        height = firstFrame.rows;
        width = firstFrame.cols;

        // 初始化样本库
        samples.resize(height, vector<vector<uchar>>(width, vector<uchar>(N)));

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                for (int k = 0; k < N; k++) {
                    int dx = rand() % 3 - 1;
                    int dy = rand() % 3 - 1;
                    int ni = i + dy;
                    int nj = j + dx;
                    if (ni < 0) ni = 0;
                    if (nj < 0) nj = 0;
                    if (ni >= height) ni = height - 1;
                    if (nj >= width) nj = width - 1;
                    samples[i][j][k] = firstFrame.at<uchar>(ni, nj);
                }
            }
        }
    }

    void processFrame(const Mat& frame, Mat& foreground) {
        foreground.create(height, width, CV_8U);

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                uchar pixel = frame.at<uchar>(i, j);
                int matches = 0;
                for (int k = 0; k < N; k++) {
                    if (abs(pixel - samples[i][j][k]) < R) {
                        matches++;
                        if (matches >= minMatches) break;
                    }
                }

                if (matches >= minMatches) {
                    foreground.at<uchar>(i, j) = 0; // 背景
                    // 随机更新样本库
                    if (rand() % updateProb == 0) {
                        int idx = rand() % N;
                        samples[i][j][idx] = pixel;
                    }
                    // 随机更新邻域
                    if (rand() % updateProb == 0) {
                        int dx = rand() % 3 - 1;
                        int dy = rand() % 3 - 1;
                        int ni = i + dy;
                        int nj = j + dx;
                        if (ni >= 0 && ni < height && nj >= 0 && nj < width) {
                            int idx = rand() % N;
                            samples[ni][nj][idx] = pixel;
                        }
                    }
                } else {
                    foreground.at<uchar>(i, j) = 255; // 前景
                }
            }
        }
    }

private:
    int height, width;
    int N; // 样本数
    int minMatches; // 匹配阈值
    int R; // 距离阈值
    int updateProb; // 更新概率分母
    vector<vector<vector<uchar>>> samples; // 样本库
};

int main() {
    VideoCapture cap(0); // 打开摄像头
    if (!cap.isOpened()) return -1;

    Mat frame, gray, fg;
    cap >> frame;
    cvtColor(frame, gray, COLOR_BGR2GRAY);

    ViBe vibe;
    vibe.init(gray);

    while (true) {
        cap >> frame;
        cvtColor(frame, gray, COLOR_BGR2GRAY);

        vibe.processFrame(gray, fg);

        imshow("Frame", frame);
        imshow("Foreground", fg);

        if (waitKey(30) == 27) break;
    }

    return 0;
}

7. 运行说明

  • 编译:g++ vibe.cpp -o vibe pkg-config --cflags --libs opencv4``
  • 运行:./vibe
  • 程序会调用摄像头,实时显示前景检测结果。
  • 白色区域为前景(运动物体),黑色为背景。

8. 扩展

  • 彩色图支持 :将样本库改为 Vec3b 存储,距离计算改为三通道欧氏距离。
  • 后处理:对前景图进行形态学操作(腐蚀、膨胀)去除噪声。
  • 自适应阈值 :根据局部亮度调整 RRR。
  • 多尺度样本:考虑不同时间间隔的样本,提高模型鲁棒性。

ViBe 是一种高效的背景建模算法,适合实时视频分析。它的核心是随机样本库 + 空间扩散更新,这种设计使其初始化快、计算量低、鲁棒性较好。在实际应用中,可以根据场景需求调整参数,或结合其他算法进行优化。

相关推荐
老纪的技术唠嗑局3 小时前
AI 改变数据库产品实践探索
人工智能
数在表哥3 小时前
从数据孤岛到智能决策:数据驱动+AI如何重构企业运营的技术路径与方法论(一)
人工智能·重构·数据驱动·企业运营
CoovallyAIHub3 小时前
如何在 2025 年构建强大的实时视频检测?
深度学习·算法·计算机视觉
金井PRATHAMA3 小时前
语义与认知中的循环解释悖论及其对人工智能自然语言处理的深层语义分析的影响和启示
人工智能·自然语言处理·知识图谱
CoovallyAIHub3 小时前
2025 年度 AI 行业百科《State of AI 2025》来了!推理元年、算力焦虑与价值回归
深度学习·算法·计算机视觉
moonsims3 小时前
MR(混合现实)与AI(人工智能)结合的自主飞行技术
人工智能
Bar_artist4 小时前
AI 颠覆室内设计:SpatialGen 实现 “一句话生成 3D 房间”
人工智能·3d
寒冬没有雪4 小时前
矩阵的翻转与旋转
c++·算法·矩阵
ʚ希希ɞ ྀ4 小时前
二叉树的层序遍历
数据结构·算法