目录

1 时间中值滤波

2 使用中值进行背景估计

3 帧差分

4 总结和代码

5 参考


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

1 时间中值滤波

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

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

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

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

2 使用中值进行背景估计

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

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

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

C++版本

  1. #include <opencv2/opencv.hpp>
  2. #include <iostream>
  3. #include <random>
  4. using namespace std;
  5. using namespace cv;
  6. int computeMedian(vector<int> elements)
  7. {
  8. nth_element(elements.begin(), elements.begin()+elements.size()/2, elements.end());
  9. //sort(elements.begin(),elements.end());
  10. return elements[elements.size()/2];
  11. }
  12. cv::Mat compute_median(std::vector<cv::Mat> vec)
  13. {
  14. // Note: Expects the image to be CV_8UC3
  15. cv::Mat medianImg(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0));
  16. for(int row=0; row<vec[0].rows; row++)
  17. {
  18. for(int col=0; col<vec[0].cols; col++)
  19. {
  20. std::vector<int> elements_B;
  21. std::vector<int> elements_G;
  22. std::vector<int> elements_R;
  23. for(int imgNumber=0; imgNumber<vec.size(); imgNumber++)
  24. {
  25. int B = vec[imgNumber].at<cv::Vec3b>(row, col)[0];
  26. int G = vec[imgNumber].at<cv::Vec3b>(row, col)[1];
  27. int R = vec[imgNumber].at<cv::Vec3b>(row, col)[2];
  28. elements_B.push_back(B);
  29. elements_G.push_back(G);
  30. elements_R.push_back(R);
  31. }
  32. medianImg.at<cv::Vec3b>(row, col)[0]= computeMedian(elements_B);
  33. medianImg.at<cv::Vec3b>(row, col)[1]= computeMedian(elements_G);
  34. medianImg.at<cv::Vec3b>(row, col)[2]= computeMedian(elements_R);
  35. }
  36. }
  37. return medianImg;
  38. }
  39. int main(int argc, char const *argv[])
  40. {
  41. std::string video_file;
  42. // Read video file
  43. if(argc > 1)
  44. {
  45. video_file = argv[1];
  46. } else
  47. {
  48. video_file = "video.mp4";
  49. }
  50. VideoCapture cap(video_file);
  51. if(!cap.isOpened())
  52. cerr << "Error opening video file\n";
  53. // Randomly select 25 frames
  54. default_random_engine generator;
  55. uniform_int_distribution<int>distribution(0,
  56. cap.get(CAP_PROP_FRAME_COUNT));
  57. vector<Mat> frames;
  58. Mat frame;
  59. for(int i=0; i<25; i++)
  60. {
  61. int fid = distribution(generator);
  62. cap.set(CAP_PROP_POS_FRAMES, fid);
  63. Mat frame;
  64. cap >> frame;
  65. if(frame.empty())
  66. continue;
  67. frames.push_back(frame);
  68. }
  69. // Calculate the median along the time axis
  70. Mat medianFrame = compute_median(frames);
  71. // Display median frame
  72. imshow("frame", medianFrame);
  73. waitKey(0);
  74. }

python版本

  1. import numpy as np
  2. import cv2
  3. from skimage import data, filters
  4. # Open Video
  5. cap = cv2.VideoCapture('video.mp4')
  6. # Randomly select 25 frames
  7. frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)
  8. # Store selected frames in an array
  9. frames = []
  10. for fid in frameIds:
  11. cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
  12. ret, frame = cap.read()
  13. frames.append(frame)
  14. # Calculate the median along the time axis
  15. medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)
  16. # Display median frame
  17. cv2.imshow('frame', medianFrame)
  18. cv2.waitKey(0)

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

结果如下所示:

3 帧差分

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

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

我们来看看代码吧。

C++版本

  1. // Reset frame number to 0
  2. cap.set(CAP_PROP_POS_FRAMES, 0);
  3. // Convert background to grayscale
  4. Mat grayMedianFrame;
  5. cvtColor(medianFrame, grayMedianFrame, COLOR_BGR2GRAY);
  6. // Loop over all frames
  7. while(1)
  8. {
  9. // Read frame
  10. cap >> frame;
  11. if (frame.empty())
  12. break;
  13. // Convert current frame to grayscale
  14. cvtColor(frame, frame, COLOR_BGR2GRAY);
  15. // Calculate absolute difference of current frame and the median frame
  16. Mat dframe;
  17. absdiff(frame, grayMedianFrame, dframe);
  18. // Threshold to binarize
  19. threshold(dframe, dframe, 30, 255, THRESH_BINARY);
  20. // Display Image
  21. imshow("frame", dframe);
  22. waitKey(20);
  23. }
  24. cap.release();
  25. return 0;
  26. }

python版本

  1. # Reset frame number to 0
  2. cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
  3. # Convert background to grayscale
  4. grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)
  5. # Loop over all frames
  6. ret = True
  7. while(ret):
  8. # Read frame
  9. ret, frame = cap.read()
  10. # Convert current frame to grayscale
  11. frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  12. # Calculate absolute difference of current frame and
  13. # the median frame
  14. dframe = cv2.absdiff(frame, grayMedianFrame)
  15. # Treshold to binarize
  16. th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY)
  17. # Display image
  18. cv2.imshow('frame', dframe)
  19. cv2.waitKey(20)
  20. # Release video object
  21. cap.release()
  22. # Destroy all windows
  23. cv2.destroyAllWindows()

4 总结和代码

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

本文所有代码见:

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

具体代码如下:

C++:

  1. #include "pch.h"
  2. #include <opencv2/opencv.hpp>
  3. #include <iostream>
  4. #include <random>
  5. using namespace std;
  6. using namespace cv;
  7. // 计算中值
  8. int computeMedian(vector<int> elements)
  9. {
  10. // 对图像进行排序,并返回中间值
  11. nth_element(elements.begin(), elements.begin() + elements.size() / 2, elements.end());
  12. //sort(elements.begin(),elements.end());
  13. return elements[elements.size() / 2];
  14. }
  15. // 获得中值图像
  16. cv::Mat compute_median(std::vector<cv::Mat> vec)
  17. {
  18. // Note: Expects the image to be CV_8UC3
  19. // 中值图像
  20. cv::Mat medianImg(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0));
  21. // 循环遍历每一个像素点
  22. for (int row = 0; row < vec[0].rows; row++)
  23. {
  24. for (int col = 0; col < vec[0].cols; col++)
  25. {
  26. std::vector<int> elements_B;
  27. std::vector<int> elements_G;
  28. std::vector<int> elements_R;
  29. // 遍历所有图像
  30. for (int imgNumber = 0; imgNumber < vec.size(); imgNumber++)
  31. {
  32. // 提取当前点BGR值
  33. int B = vec[imgNumber].at<cv::Vec3b>(row, col)[0];
  34. int G = vec[imgNumber].at<cv::Vec3b>(row, col)[1];
  35. int R = vec[imgNumber].at<cv::Vec3b>(row, col)[2];
  36. elements_B.push_back(B);
  37. elements_G.push_back(G);
  38. elements_R.push_back(R);
  39. }
  40. // 计算中值
  41. medianImg.at<cv::Vec3b>(row, col)[0] = computeMedian(elements_B);
  42. medianImg.at<cv::Vec3b>(row, col)[1] = computeMedian(elements_G);
  43. medianImg.at<cv::Vec3b>(row, col)[2] = computeMedian(elements_R);
  44. }
  45. }
  46. return medianImg;
  47. }
  48. int main()
  49. {
  50. // 视频地址
  51. std::string video_file = "./video/video.mp4";
  52. // 打开视频文件
  53. VideoCapture cap(video_file);
  54. if (!cap.isOpened())
  55. {
  56. cerr << "Error opening video file\n";
  57. }
  58. // Randomly select 25 frames
  59. // 随机选取25帧图像
  60. default_random_engine generator;
  61. // cap.get(CAP_PROP_FRAME_COUNT)视频帧数
  62. uniform_int_distribution<int> distribution(0, cap.get(CAP_PROP_FRAME_COUNT));
  63. // 25张图像集合
  64. vector<Mat> frames;
  65. Mat frame;
  66. // 随机从视频片段中挑选25张图像
  67. for (int i = 0; i < 25; i++)
  68. {
  69. // 获取序号
  70. int fid = distribution(generator);
  71. cap.set(CAP_PROP_POS_FRAMES, fid);
  72. Mat frame;
  73. cap >> frame;
  74. if (frame.empty())
  75. {
  76. continue;
  77. }
  78. frames.push_back(frame);
  79. }
  80. // Calculate the median along the time axis
  81. Mat medianFrame = compute_median(frames);
  82. // Display median frame
  83. // 显示中值图像帧
  84. imshow("frame", medianFrame);
  85. waitKey(0);
  86. // Reset frame number to 0
  87. // 重新从第0帧开始
  88. cap.set(CAP_PROP_POS_FRAMES, 0);
  89. // Convert background to grayscale
  90. // 将背景转换为灰度图
  91. Mat grayMedianFrame;
  92. cvtColor(medianFrame, grayMedianFrame, COLOR_BGR2GRAY);
  93. // Loop over all frames
  94. while (1)
  95. {
  96. // Read frame
  97. // 读取帧
  98. cap >> frame;
  99. if (frame.empty())
  100. {
  101. break;
  102. }
  103. // Convert current frame to grayscale
  104. // 将图转换为灰度图
  105. cvtColor(frame, frame, COLOR_BGR2GRAY);
  106. // Calculate absolute difference of current frame and the median frame
  107. Mat dframe;
  108. // 差分
  109. absdiff(frame, grayMedianFrame, dframe);
  110. // Threshold to binarize
  111. // 二值化
  112. threshold(dframe, dframe, 30, 255, THRESH_BINARY);
  113. // Display Image
  114. imshow("frame", dframe);
  115. waitKey(20);
  116. }
  117. cap.release();
  118. return 0;
  119. }

python:

  1. import numpy as np
  2. import cv2
  3. # Open Video
  4. cap = cv2.VideoCapture('video/video.mp4')
  5. # Randomly select 25 frames
  6. frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)
  7. # Store selected frames in an array
  8. frames = []
  9. for fid in frameIds:
  10. cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
  11. ret, frame = cap.read()
  12. frames.append(frame)
  13. # Calculate the median along the time axis
  14. medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)
  15. # Display median frame
  16. cv2.imshow('frame', medianFrame)
  17. cv2.waitKey(0)
  18. # Reset frame number to 0
  19. cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
  20. # Convert background to grayscale
  21. grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)
  22. # Loop over all frames
  23. ret = True
  24. while(ret):
  25. # Read frame
  26. ret, frame = cap.read()
  27. if frame is None:
  28. break
  29. # Convert current frame to grayscale
  30. frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  31. # Calculate absolute difference of current frame and
  32. # the median frame
  33. dframe = cv2.absdiff(frame, grayMedianFrame)
  34. # Treshold to binarize
  35. th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY)
  36. # Display image
  37. cv2.imshow('frame', dframe)
  38. cv2.waitKey(20)
  39. # Release video object
  40. cap.release()
  41. # Destroy all windows
  42. 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. 有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。

    第一题 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数 ...

  2. 前端框架Vue------>第一天学习(3)

    文章目录 8 .使用Axios实现异步通信 9 .表单输入绑定 9.1 . 什么是双向数据绑定 9.2 .为什么要实现数据的双向绑定 9.3 .在表单中使用双向数据绑定 8 .使用Axios实现异步通 ...

  3. Java函数式编程:二、高阶函数,闭包,函数组合以及柯里化

    承接上文:Java函数式编程:一.函数式接口,lambda表达式和方法引用 这次来聊聊函数式编程中其他的几个比较重要的概念和技术,从而使得我们能更深刻的掌握Java中的函数式编程. 本篇博客主要聊聊以 ...

  4. 2022NISACTF--WEB

    easyssrf 打开题目,显示的是 尝试输入, 发现输入flag有东西 读取文件 访问下一个网站 读取文件 不能以file开头 直接伪协议,base64解码 checkIn 奇怪的unicode编码 ...

  5. C#字典出错“集合已经修改,可能无法执行枚举操作”

    出现这个现象的原因是由于线程安全考虑,如果你边对字典循环,又同时移除字典中的某个键值对, 那么将会出现这种错误,解决这种问题的方法是你没次remove某个键值对后需要break结束对字典的循环.

  6. JAVA缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

    大家好,又见面了. 本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面.如果感兴趣,欢迎关注以获取后续更新. 有诗云"纸上得来终觉浅,绝知 ...

  7. mindxdl--common--validators.go

    // Copyright (c) 2021. Huawei Technologies Co., Ltd. All rights reserved.// Package common this file ...

  8. wiki搭建详细过程及步骤

    wiki搭建详细过程及步骤 1.查看yum库中jdk的版本 2.选择java-1.8.0安装 3.配置环境变量 4.环境变量生效 5.查看jdk是否安装成功 6.启动mysql服务 7.下载confl ...

  9. ROS应用层通信协议解析

    参考:http://wiki.ros.org/ROS/Master_API http://wiki.ros.org/ROS/Connection Header 说明 ROS本质上就是一个松耦合的通信框 ...

  10. mysql不需要密码,乱输入密码就能进去。。。。解决

    为什么MySQL 不用输入用户名和密码也能访问 今天后天连接数据库时密码写错了,却发现后台能够拿到数据库中的数据,又故意把用户名和密码都写错,结果还是可以.这就意味着任何一个人只要登入服务器,就可以轻 ...