OpenCV|OpenCV基础(14)OpenCV在视频中的简单背景估计

在许多计算机视觉应用程序中,处理能力很低。在这种情况下,我们必须使用简单而有效的技术。
在这篇文章中,我们将介绍一种这样的技术,用于估计场景背景时,相机是静态的,场景中有一些移动的物体。这种情况并不少见。例如,许多交通和监控摄像头都是固定的。
1.时域中值滤波 为了理解我们将在这篇文章中描述的想法,让我们考虑一个一维的更简单的问题。
假设我们每10毫秒估计一个量(比如房间的温度)。
假设房间的温度是70华氏度。
OpenCV|OpenCV基础(14)OpenCV在视频中的简单背景估计
文章图片

在上图中,我们展示了两个温度计的测量结果——一个好的温度计和一个坏的温度计。
好的温度计显示在左边,报告70度与一些噪声。为了得到更准确的温度估计值,我们可以简单地在几秒钟内将数值平均。由于噪声是正的和负的高斯值,平均值将抵消噪声。实际上,在这种特定情况下的平均值是70.01。
另一方面,坏温度计在大多数时候表现得和好的温度计一样,但偶尔,数字是完全错误的。
事实上,如果我们把不好的温度计所记录的数字平均一下,我们得到71.07度。这显然是高估了。
我们还能很好地估计温度吗?
答案是肯定的。当数据包含异常值时,中值是我们试图估计的值的一个更稳健的估计值。
【OpenCV|OpenCV基础(14)OpenCV在视频中的简单背景估计】中值是按升序或降序排序时数据的中值。
上面显示的曲线的中位数是70.05度,比71.07度的估计值要好得多。
唯一的缺点是,与平均值/平均值相比,中位数的计算成本更高。
2.使用中值进行背景估计 现在,让我们回到相机静态时估计背景的问题。
我们可以假设大多数时候,每个像素看到的是相同的背景,因为相机没有移动。偶尔,一辆汽车或其他移动的物体出现在前面,模糊了背景。
对于一个视频序列,我们可以随机采样一些帧(比如25帧)。
换句话说,对于每个像素,我们现在有25个背景的估计。只要一个像素超过50%的时间没有被汽车或其他移动物体覆盖,那么在这25帧中像素的中值就可以很好地估计出该像素上的背景。
我们可以对每个像素重复这个操作,恢复整个背景。
3.背景估计代码 (1)Python

import numpy as np import cv2 from skimage import data, filters# 打开视频 cap = cv2.VideoCapture('video.mp4')# 随机选择25帧 frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)# 将选定的帧存储在数组中 frames = [] for fid in frameIds: cap.set(cv2.CAP_PROP_POS_FRAMES, fid) ret, frame = cap.read() frames.append(frame)# 计算沿时间轴的中值 medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)# 显示中值帧 cv2.imshow('frame', medianFrame) cv2.waitKey(0)

(2)C++
#include #include #include using namespace std; using namespace cv; int computeMedian(vector 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 vec) {// 注意:期望图片是CV_8UC3 cv::Mat medianImg(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0)); for(int row=0; row elements_B; std::vector elements_G; std::vector elements_R; for(int imgNumber=0; imgNumber(row, col)[0]; int G = vec[imgNumber].at(row, col)[1]; int R = vec[imgNumber].at(row, col)[2]; elements_B.push_back(B); elements_G.push_back(G); elements_R.push_back(R); }medianImg.at(row, col)[0]= computeMedian(elements_B); medianImg.at(row, col)[1]= computeMedian(elements_G); medianImg.at(row, col)[2]= computeMedian(elements_R); } } return medianImg; }int main(int argc, char const *argv[]) {std::string 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"; // 随机选择25帧 default_random_engine generator; uniform_int_distributiondistribution(0, cap.get(CAP_PROP_FRAME_COUNT)); vector 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); } // 计算沿时间轴的中值 Mat medianFrame = compute_median(frames); // 显示 imshow("frame", medianFrame); waitKey(0); }

如你所见,我们随机选择25帧并计算25帧中每个像素的中位数。只要每个像素至少有50%的时间看到背景,这个中值帧就是对背景的一个很好的估计。
OpenCV|OpenCV基础(14)OpenCV在视频中的简单背景估计
文章图片

4.帧间差分 下一个显而易见的问题是,我们是否可以为每一帧创建一个mask,以显示在运动中的图像部分。
这可以通过以下步骤完成:
  • 将中值帧转换为灰度。
  • 循环遍历视频中的所有帧。提取当前帧并将其转换为灰度。
  • 计算当前帧和中值帧之间的绝对差值。
  • 对上面的图像设置阈值以去除噪声并对输出进行二值化。
    (1)Python
import numpy as np import cv2 from skimage import data, filters# 读取视频 cap = cv2.VideoCapture('video.mp4')# 随之选择25帧 frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)# 将选定的帧存储在数组中 frames = [] for fid in frameIds: cap.set(cv2.CAP_PROP_POS_FRAMES, fid) ret, frame = cap.read() frames.append(frame)# 计算沿时间轴的中值 medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)# 显示中值帧 cv2.imshow('frame', medianFrame) cv2.waitKey(0)# 重置帧号为0 cap.set(cv2.CAP_PROP_POS_FRAMES, 0)# 转换背景到灰度 grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)# 循环所有帧 ret = True while(ret):# 读取帧 ret, frame = cap.read() # 将当前帧转换为灰度 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 计算当前帧和中间帧的绝对差值 dframe = cv2.absdiff(frame, grayMedianFrame) # 二值化 th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY) # 显示 cv2.imshow('frame', dframe) cv2.waitKey(20)# 释放视频对象 cap.release()# 关闭所有窗口 cv2.destroyAllWindows()

(2)C++
#include #include #include using namespace std; using namespace cv; int computeMedian(vector 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 vec) { // 图像为CV_8UC3 cv::Mat medianImg(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0)); for(int row=0; row elements_B; std::vector elements_G; std::vector elements_R; for(int imgNumber=0; imgNumber(row, col)[0]; int G = vec[imgNumber].at(row, col)[1]; int R = vec[imgNumber].at(row, col)[2]; elements_B.push_back(B); elements_G.push_back(G); elements_R.push_back(R); }medianImg.at(row, col)[0] = computeMedian(elements_B); medianImg.at(row, col)[1] = computeMedian(elements_G); medianImg.at(row, col)[2] = computeMedian(elements_R); } } return medianImg; }int main(int argc, char const *argv[]) { std::string 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"; // 随机选择25帧 default_random_engine generator; uniform_int_distributiondistribution(0, cap.get(CAP_PROP_FRAME_COUNT)); vector 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); } // 计算沿时间轴的中值 Mat medianFrame = compute_median(frames); // 显示帧中值 imshow("frame", medianFrame); waitKey(0); //重置帧号为0 cap.set(CAP_PROP_POS_FRAMES, 0); // 转换背景到灰度 Mat grayMedianFrame; cvtColor(medianFrame, grayMedianFrame, COLOR_BGR2GRAY); // 循环所有帧 while(1) {// 读取帧 cap >> frame; if (frame.empty()) break; // 当前帧转换为灰度 cvtColor(frame, frame, COLOR_BGR2GRAY); // 计算当前帧和中值帧的绝对差值 Mat dframe; absdiff(frame, grayMedianFrame, dframe); // 二值化 threshold(dframe, dframe, 30, 255, THRESH_BINARY); // 显示 imshow("frame", dframe); waitKey(20); } cap.release(); return 0; }

Demo视频地址链接:https://pan.baidu.com/s/1Es80KMhz3Cg95XPMZHjgnQ 提取码:123a
参考目录 https://learnopencv.com/simple-background-estimation-in-videos-using-opencv-c-python/

    推荐阅读