目录
- 前言
- 1 Temporal Median Filtering
- 2 使用中值进行背景估计
- 3 背景估计代码(C ++ / Python)
- 3.1 Python代码
- 3.2 C++代码
- 4 帧差分(C++/Python)
- 4.1 Python代码
- 4.2 C++代码
- 4.3 结果
前言
首先,奉上原文链接:https://www.learnopencv.com/simple-background-estimation-in-videos-using-opencv-c-python/ 。
在我的计算机视觉应用中,达到你可以使用的需求,你需要进一步处理的东西很少。在这种情况下,我们必须使用简单而有效的技术。
在这篇文章中,我们将介绍一种这样的技术,用于在摄像机静态并且场景中有一些移动物体时估计场景的背景。这种情况并不罕见。例如,许多交通和监控摄像机都是严格固定的。
1 Temporal Median Filtering
为了理解我们将在本文中描述的想法,让我们考虑一维中的一个更简单的问题。
假设我们每10毫秒估算一个数量(比如房间的温度)。
比方说,房间的温度是华氏70度。
在上图中,我们显示了两个温度计的测量结果——一个好的温度计和一个坏的温度计。
左边显示的好温度计报告70度,有一定程度的高斯噪声。为了更准确地估算温度,我们可以简单地在几秒钟内对这些值进行平均。由于噪声是具有正值和负值的高斯噪声,因此平均值将抵消噪声。实际上,这个特定情况下的平均值是70.01。
另一方面,坏温度计在大多数情况下表现得像好温度计,但有时,数字完全错误。
事实上,如果我们采用坏温度计报告的平均值,我们得到71.07度。这显然是高估了。
我们还能得到很好的温度估算吗?
答案是肯定的。当数据包含异常值时,中位数是我们试图估计的值的更鲁棒的估计。
当按升序或降序排序时,中位数是数据的中间值。
上面显示的曲线的中值是70.05度,这是一个比71.07度更好的估计值。
唯一的缺点是,与平均值相比,中位数的计算成本更高。
2 使用中值进行背景估计
现在,让我们回到相机静止时估计背景的问题。
我们可以假设大多数时候,每个像素都看到同一块背景,因为相机没有移动。偶尔,汽车或其他移动物体会出现在前方并遮挡背景。
对于视频序列,我们可以随机采样几帧(比如25帧)。
换句话说,对于每个像素,我们现在有25个背景估计值。只要一个像素没有被汽车或其他移动物体覆盖超过50%的时间,在这25个帧上的像素的中值将给出该像素处的背景的良好估计。
我们可以为每个像素重复此操作并恢复整个背景。
3 背景估计代码(C ++ / Python)
3.1 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)
3.2 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);
}
如您所见,我们随机选择25帧并计算25帧内每个像素的中位数。只要每个像素至少有50%的时间看到背景,这个中值帧就是对背景的良好估计。
结果如下所示:
4 帧差分(C++/Python)
显而易见的下一个问题是,我们是否可以为每个帧创建一个掩码,该掩码显示运动中的图像部分。
这可以通过以下步骤完成:
- 将中值帧转换为灰度
- 循环播放视频中的所有帧,提取当前帧并将其转换为灰度
- 计算当前帧和中值帧之间的绝对差值
- 阈值上面的图像以消除噪音并将输出二值化
我们来看看代码吧。
4.1 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.2 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;
}
4.3 结果
下面的视频显示了背景估计和帧差分的输出。
网址:https://youtu.be/UTd40wOXUmY