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

在许多计算机视觉应用程序中,处理能力很低。在这种情况下,我们必须使用简单而有效的技术。
在这篇文章中,我们将介绍一种这样的技术,用于估计场景背景时,相机是静态的,场景中有一些移动的物体。这种情况并不少见。例如,许多交通和监控摄像头都是固定的。

1.时域中值滤波

为了理解我们将在这篇文章中描述的想法,让我们考虑一个一维的更简单的问题。
假设我们每10毫秒估计一个量(比如房间的温度)。
假设房间的温度是70华氏度。
在这里插入图片描述
在上图中,我们展示了两个温度计的测量结果——一个好的温度计和一个坏的温度计。
好的温度计显示在左边,报告70度与一些噪声。为了得到更准确的温度估计值,我们可以简单地在几秒钟内将数值平均。由于噪声是正的和负的高斯值,平均值将抵消噪声。实际上,在这种特定情况下的平均值是70.01。

另一方面,坏温度计在大多数时候表现得和好的温度计一样,但偶尔,数字是完全错误的。

事实上,如果我们把不好的温度计所记录的数字平均一下,我们得到71.07度。这显然是高估了。

我们还能很好地估计温度吗?
答案是肯定的。当数据包含异常值时,中值是我们试图估计的值的一个更稳健的估计值。

中值是按升序或降序排序时数据的中值。

上面显示的曲线的中位数是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 <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) 
{
  // 注意:期望图片是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;
  // 读取视频文件
  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_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);
  }
  // 计算沿时间轴的中值
  Mat medianFrame = compute_median(frames);

  // 显示
  imshow("frame", medianFrame);
  waitKey(0);
}

如你所见,我们随机选择25帧并计算25帧中每个像素的中位数。只要每个像素至少有50%的时间看到背景,这个中值帧就是对背景的一个很好的估计。
在这里插入图片描述

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 <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) {
	// 图像为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;
	// 读取视频文件
	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_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);
	}

	// 计算沿时间轴的中值
	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/

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值