目录

1 时间中值滤波

2 使用中值进行背景估计

3 帧差分

4 总结和代码

5 参考


许多计算机视觉应用中,硬件配置往往较低。在这种情况下,我们必须使用简单而有效的技术。在这篇文章中,我们将介绍一种这样的技术,用于在摄像机静态并且场景中有一些移动物体时估计场景的背景。这种情况并不罕见。例如,许多交通和监控摄像机都是严格固定的。

1 时间中值滤波

为了理解我们将在本文中描述的想法,让我们考虑一维中的一个更简单的问题。假设我们每10毫秒估算一个数量(比如房间的温度)。比方说,房间的温度是华氏70度。

在上图中,我们显示了两个温度计的测量结果,一个好的温度计和一个坏的温度计。左边显示的好温度计报告70度,有一定程度的高斯噪声。为了更准确地估算温度,我们可以简单地在几秒钟内对这些值进行平均。由于噪声是具有正值和负值的高斯噪声,因此平均值将抵消噪声。实际上,这个特定情况下的平均值是70.01。

另一方面,坏温度计在大多数情况下表现得像好温度计,但有时,数字完全错误。事实上,如果我们采用坏温度计报告的平均值,我们得到71.07度。这显然是高估了。

我们还能得到很好的温度估算吗?答案是肯定的。当数据包含异常值时,中位数是我们试图估计的值的更稳健估计。当按升序或降序排序时,中位数是数据的中间值。上面右边显示的曲线的中值是70.05度,这是一个比71.07度更好的估计值。唯一的缺点是,与平均值相比,中位数的计算成本更高。

2 使用中值进行背景估计

现在,让我们回到相机静止时估计背景的问题。

我们可以假设大多数时候,每个像素都看到同一块背景,因为相机没有移动。偶尔,汽车或其他移动物体会出现在前方并遮挡背景。对于视频序列,我们可以随机采样几帧(比如25帧)。

换句话说,对于每个像素,我们现在有25个背景估计值。只要一个像素没有被汽车或其他移动物体覆盖超过50%的时间,这些像素的中值将给出该像素背景的良好估计。我们可以为每个像素重复此操作并恢复整个背景。代码如下:

C++版本

#include <opencv2/opencv.hpp>
#include <iostream>
#include <random> using namespace std;
using namespace cv; int computeMedian(vector<int> elements)
{
nth_element(elements.begin(), elements.begin()+elements.size()/2, elements.end()); //sort(elements.begin(),elements.end());
return elements[elements.size()/2];
} cv::Mat compute_median(std::vector<cv::Mat> vec)
{
// Note: Expects the image to be CV_8UC3
cv::Mat medianImg(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0)); for(int row=0; row<vec[0].rows; row++)
{
for(int col=0; col<vec[0].cols; col++)
{
std::vector<int> elements_B;
std::vector<int> elements_G;
std::vector<int> elements_R; for(int imgNumber=0; imgNumber<vec.size(); imgNumber++)
{
int B = vec[imgNumber].at<cv::Vec3b>(row, col)[0];
int G = vec[imgNumber].at<cv::Vec3b>(row, col)[1];
int R = vec[imgNumber].at<cv::Vec3b>(row, col)[2]; elements_B.push_back(B);
elements_G.push_back(G);
elements_R.push_back(R);
} medianImg.at<cv::Vec3b>(row, col)[0]= computeMedian(elements_B);
medianImg.at<cv::Vec3b>(row, col)[1]= computeMedian(elements_G);
medianImg.at<cv::Vec3b>(row, col)[2]= computeMedian(elements_R);
}
}
return medianImg;
} int main(int argc, char const *argv[])
{
std::string video_file;
// Read video file
if(argc > 1)
{
video_file = argv[1];
} else
{
video_file = "video.mp4";
} VideoCapture cap(video_file);
if(!cap.isOpened())
cerr << "Error opening video file\n"; // Randomly select 25 frames
default_random_engine generator;
uniform_int_distribution<int>distribution(0,
cap.get(CAP_PROP_FRAME_COUNT)); vector<Mat> frames;
Mat frame; for(int i=0; i<25; i++)
{
int fid = distribution(generator);
cap.set(CAP_PROP_POS_FRAMES, fid);
Mat frame;
cap >> frame;
if(frame.empty())
continue;
frames.push_back(frame);
}
// Calculate the median along the time axis
Mat medianFrame = compute_median(frames); // Display median frame
imshow("frame", medianFrame);
waitKey(0);
}

python版本

import numpy as np
import cv2
from skimage import data, filters # Open Video
cap = cv2.VideoCapture('video.mp4') # Randomly select 25 frames
frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25) # Store selected frames in an array
frames = []
for fid in frameIds:
cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
ret, frame = cap.read()
frames.append(frame) # Calculate the median along the time axis
medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8) # Display median frame
cv2.imshow('frame', medianFrame)
cv2.waitKey(0)

如您所见,我们随机选择25帧并计算25帧内每个像素的中位数。只要每个像素至少有50%的时间看到背景,这个中间帧就是对背景的良好估计。

结果如下所示:

3 帧差分

显而易见的下一个问题是,我们是否可以为每个帧创建一个掩码,该掩码显示运动中的图像部分。这可以通过以下步骤完成:

  1. 将中间帧转换为灰度。
  2. 循环播放视频中的所有帧。提取当前帧并将其转换为灰度。
  3. 计算当前帧和中间帧之间的绝对差值。
  4. 对上面的图像进行阈值化以消除噪音并将输出二值化。

我们来看看代码吧。

C++版本

//  Reset frame number to 0
cap.set(CAP_PROP_POS_FRAMES, 0); // Convert background to grayscale
Mat grayMedianFrame;
cvtColor(medianFrame, grayMedianFrame, COLOR_BGR2GRAY); // Loop over all frames
while(1)
{
// Read frame
cap >> frame; if (frame.empty())
break; // Convert current frame to grayscale
cvtColor(frame, frame, COLOR_BGR2GRAY); // Calculate absolute difference of current frame and the median frame
Mat dframe;
absdiff(frame, grayMedianFrame, dframe); // Threshold to binarize
threshold(dframe, dframe, 30, 255, THRESH_BINARY); // Display Image
imshow("frame", dframe);
waitKey(20);
} cap.release();
return 0;
}

python版本

# Reset frame number to 0
cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # Convert background to grayscale
grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY) # Loop over all frames
ret = True
while(ret): # Read frame
ret, frame = cap.read()
# Convert current frame to grayscale
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Calculate absolute difference of current frame and
# the median frame
dframe = cv2.absdiff(frame, grayMedianFrame)
# Treshold to binarize
th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY)
# Display image
cv2.imshow('frame', dframe)
cv2.waitKey(20) # Release video object
cap.release() # Destroy all windows
cv2.destroyAllWindows()

4 总结和代码

对于本文描述的方法,如文章题目一样简单背景估计,用于做运动检测非常不准确,所选择的中间帧不一定能够完整代表实际环境(如果大部分都是运动的物体就完蛋了)。此外中间帧选取时计算量过大,速度太慢,不推荐使用,运动检测还是老老实实用混合高斯或者vibe。

本文所有代码见:

https://github.com/luohenyueji/OpenCV-Practical-Exercise

具体代码如下:

C++:

#include "pch.h"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <random> using namespace std;
using namespace cv; // 计算中值
int computeMedian(vector<int> elements)
{
// 对图像进行排序,并返回中间值
nth_element(elements.begin(), elements.begin() + elements.size() / 2, elements.end());
//sort(elements.begin(),elements.end()); return elements[elements.size() / 2];
} // 获得中值图像
cv::Mat compute_median(std::vector<cv::Mat> vec)
{
// Note: Expects the image to be CV_8UC3
// 中值图像
cv::Mat medianImg(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0)); // 循环遍历每一个像素点
for (int row = 0; row < vec[0].rows; row++)
{
for (int col = 0; col < vec[0].cols; col++)
{
std::vector<int> elements_B;
std::vector<int> elements_G;
std::vector<int> elements_R; // 遍历所有图像
for (int imgNumber = 0; imgNumber < vec.size(); imgNumber++)
{
// 提取当前点BGR值
int B = vec[imgNumber].at<cv::Vec3b>(row, col)[0];
int G = vec[imgNumber].at<cv::Vec3b>(row, col)[1];
int R = vec[imgNumber].at<cv::Vec3b>(row, col)[2]; elements_B.push_back(B);
elements_G.push_back(G);
elements_R.push_back(R);
} // 计算中值
medianImg.at<cv::Vec3b>(row, col)[0] = computeMedian(elements_B);
medianImg.at<cv::Vec3b>(row, col)[1] = computeMedian(elements_G);
medianImg.at<cv::Vec3b>(row, col)[2] = computeMedian(elements_R);
}
}
return medianImg;
} int main()
{
// 视频地址
std::string video_file = "./video/video.mp4"; // 打开视频文件
VideoCapture cap(video_file); if (!cap.isOpened())
{
cerr << "Error opening video file\n";
} // Randomly select 25 frames
// 随机选取25帧图像
default_random_engine generator;
// cap.get(CAP_PROP_FRAME_COUNT)视频帧数
uniform_int_distribution<int> distribution(0, cap.get(CAP_PROP_FRAME_COUNT)); // 25张图像集合
vector<Mat> frames;
Mat frame; // 随机从视频片段中挑选25张图像
for (int i = 0; i < 25; i++)
{
// 获取序号
int fid = distribution(generator);
cap.set(CAP_PROP_POS_FRAMES, fid);
Mat frame;
cap >> frame;
if (frame.empty())
{
continue;
}
frames.push_back(frame);
} // Calculate the median along the time axis
Mat medianFrame = compute_median(frames); // Display median frame
// 显示中值图像帧
imshow("frame", medianFrame);
waitKey(0); // Reset frame number to 0
// 重新从第0帧开始
cap.set(CAP_PROP_POS_FRAMES, 0); // Convert background to grayscale
// 将背景转换为灰度图
Mat grayMedianFrame;
cvtColor(medianFrame, grayMedianFrame, COLOR_BGR2GRAY); // Loop over all frames
while (1)
{
// Read frame
// 读取帧
cap >> frame; if (frame.empty())
{
break;
} // Convert current frame to grayscale
// 将图转换为灰度图
cvtColor(frame, frame, COLOR_BGR2GRAY); // Calculate absolute difference of current frame and the median frame
Mat dframe;
// 差分
absdiff(frame, grayMedianFrame, dframe); // Threshold to binarize
// 二值化
threshold(dframe, dframe, 30, 255, THRESH_BINARY); // Display Image
imshow("frame", dframe);
waitKey(20);
} cap.release();
return 0;
}

python:

import numpy as np
import cv2 # Open Video
cap = cv2.VideoCapture('video/video.mp4') # Randomly select 25 frames
frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25) # Store selected frames in an array
frames = []
for fid in frameIds:
cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
ret, frame = cap.read()
frames.append(frame) # Calculate the median along the time axis
medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8) # Display median frame
cv2.imshow('frame', medianFrame)
cv2.waitKey(0) # Reset frame number to 0
cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # Convert background to grayscale
grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY) # Loop over all frames
ret = True
while(ret): # Read frame
ret, frame = cap.read()
if frame is None:
break
# Convert current frame to grayscale
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Calculate absolute difference of current frame and
# the median frame
dframe = cv2.absdiff(frame, grayMedianFrame)
# Treshold to binarize
th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY)
# Display image
cv2.imshow('frame', dframe)
cv2.waitKey(20) # Release video object
cap.release() # Destroy all windows
cv2.destroyAllWindows()

5 参考

https://www.learnopencv.com/simple-background-estimation-in-videos-using-opencv-c-python/

[OpenCV实战]36 使用OpenCV在视频中实现简单背景估计的更多相关文章

  1. [OpenCV实战]50 用OpenCV制作低成本立体相机

    本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...

  2. [OpenCV实战]48 基于OpenCV实现图像质量评价

    本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...

  3. [OpenCV实战]45 基于OpenCV实现图像哈希算法

    目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...

  4. [OpenCV实战]14 使用OpenCV实现单目标跟踪

    目录 1 背景 1.1 什么是目标跟踪 1.2 跟踪与检测 2 OpenCV的目标跟踪函数 2.1 函数调用 2.2 函数详解 2.3 综合评价 3 参考 在本教程中,我们将了解OpenCV 3中引入 ...

  5. [OpenCV实战]47 基于OpenCV实现视觉显著性检测

    人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...

  6. [OpenCV实战]44 使用OpenCV进行图像超分放大

    图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...

  7. [OpenCV实战]43 使用OpenCV进行背景分割

    运动背景分割法Background Segment主要是指通过不同方法拟合模型建立背景图像,将当前帧与背景图像进行相减比较获得运动区域.下图所示为检测图像: 通过前面的检测帧建立背景模型,获得背景图像 ...

  8. [OpenCV实战]33 使用OpenCV进行Hough变换

    目录 1 什么是霍夫变换 1.1 应用霍夫变换以检测图像中的线条 1.2 累加器 1.3 线条检测 1.4 圆环的检测 2 代码 3 参考 1 什么是霍夫变换 霍夫变换是用于检测图像中的简单形状(诸如 ...

  9. [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡

    本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...

随机推荐

  1. Windows docker环境安装

    前期准备 1.hyper-v功能 win10家庭版没有提供hyper-v的问题可通过如下脚本解决,保存为bat并运行重启电脑即可. pushd "%~dp0" dir /b %Sy ...

  2. python求列表均值,方差,标准差

    import numpy as np a = [1,2,3,4,5,6] #求均值 a_mean = np.mean(a) #求方差 a_var = np.var(a) #求标准差 a_std = n ...

  3. 利用xtrabackup8完全,增量备份及还原MySQL8

    利用xtrabackup8完全,增量备份及还原MySQL8 1.环境准备 服务器 作用 数据库版本 xtrabackup版本 10.0.0.8 数据备份 mysql8.0.26 8.0.28 10.0 ...

  4. JS学习笔记 (四) 数组进阶

    1.基本知识 1.数组是值的有序集合.每个值叫做一个元素,而每个元素在数组中的位置称为索引,以数字表示,以0开始. 2.数组是无类型的.数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的 ...

  5. Sql Server性能排查和优化懒人攻略

    转载自作者zhang502219048的微信公众号[SQL数据库编程]:Sql Server性能排查和优化懒人攻略 很多年前,笔者那时刚从广东技术师范学院(现为广东技术师范大学,以前为广东民族学院)的 ...

  6. 洛谷P4135 Ynoi2016 掉进兔子洞 (带权bitset?/bitset优化莫队 模板) 题解

    题面. 看到这道题,我第一反应就是莫队. 我甚至也猜出了把所有询问的三个区间压到一起处理然后分别计算对应询问答案. 但是,这么复杂的贡献用什么东西存?难道要开一个数组 query_appear_tim ...

  7. Go语言基础-从菜鸟到火鸡

    课程介绍: 1.概述和环境搭建 2.程序开发 3.数据类型 4. 指针 5. 标志符 6. 运算符 7. 进制介绍 8.流程控制 9.循环控制 10.break与continue 11.函数 12.g ...

  8. Go语言核心36讲43-----io包中接口的好处与优势

    上一篇文章中,我主要讲到了io.Reader的扩展接口和实现类型.当然,io代码包中的核心接口不止io.Reader一个. 我们基于它引出的一条主线,只是io包类型体系中的一部分.我们很有必要再从另一 ...

  9. Go语言核心36讲22

    你好,我是郝林,今天我们继续来分享错误处理. 在上一篇文章中,我们主要讨论的是从使用者的角度看"怎样处理好错误值".那么,接下来我们需要关注的,就是站在建造者的角度,去关心&quo ...

  10. 使用vite + vue3 + ant-design-vue + vue-router + vuex 创建一个管理应用

    使用vite + vue3 + ant-design-vue + vue-router + vuex 创建一个管理应用的记录 使用vite 创建项目 我创建的node 版本是 v16.17.1 使用N ...