ubuntu22.04@laptop opencv get started: 014_simple_background_estimation_in_videos
1. 源由
在许多计算机视觉应用中,可用的处理能力有限。在这种情况下,必须使用简单但有效的技术。
在本文中,将介绍一种针对场景背景估计的技术,当摄像头静止对场景中的移动对象进行检测。这种场景并不罕见,例如:许多交通和监控摄像头都是固定不动的。
2. 应用demo
014_simple_background_estimation_in_videos是opencv背景分析技术,在这种特定场景中,对移动物体进行检测的技术。
2.1 c++应用demo
c++应用demo工程结构:
014_simple_background_estimation_in_videos/cpp$ tree .
.
├── cmakelists.txt
└── remove_video_bg.cpp
0 directories, 2 files
确认opencv安装路径:
$ find /home/daniel/ -name "opencvconfig.cmake"
/home/daniel/opencv/installation/opencv-4.9.0/lib/cmake/opencv4/
/home/daniel/opencv/opencv/build/opencvconfig.cmake
/home/daniel/opencv/opencv/build/unix-install/opencvconfig.cmake
$ export opencv_dir=/home/daniel/opencv/installation/opencv-4.9.0/lib/cmake/opencv4/
c++应用demo工程编译执行:
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build . --config release
$ cd ..
$ ./build/remove_video_bg
2.2 python应用demo
python应用demo工程结构:
014_simple_background_estimation_in_videos/python$ tree .
.
├── remove_video_bg_save.py
└── remove_video_bg.py
0 directories, 2 files
python应用demo工程执行:
$ workoncv-4.9.0
$ python remove_video_bg.py
$ python remove_video_bg_save.py
3. 时间中值滤波
让我们考虑一个在一维空间中的简单问题。假设我们每 10 毫秒估计一个数量(比如房间的温度)。
假设房间的温度是华氏 70 度。
在上图中,我们展示了来自两个温度计的测量结果 — 一个好的温度计和一个坏的温度计。
左侧显示的好的温度计报告的是 70 度,带有一定程度的高斯噪声。为了获得更准确的温度估计,我们可以简单地在几秒钟内对数值进行平均。由于噪声是高斯噪声,具有正负值,因此平均值将消除噪声。实际上,在这种特定情况下,平均值为 70.01 度。
另一方面,坏的温度计大部分时间的行为类似于好的温度计,但偶尔,数字完全错误。
事实上,如果我们取坏温度计报告的数字的平均值,我们得到 71.07 度。这显然是一个过高的估计。
我们是否仍然可以获得一个良好的温度估计?
答案是肯定的。当数据中包含异常值时,中值是我们试图估计的值的更稳健的估计。
中值是将数据按升序或降序排序后的中间值。
上述曲线的中值是 70.05 度,这比 71.07 度要好得多的估计。
唯一的缺点是相对于平均值/平均值而言,中值的计算成本更高。
4. 使用中值进行背景估计
在计算机视觉中,背景估计是许多应用的关键步骤之一。而使用中值进行背景估计是一种常见且有效的技术。
当摄像头处于静止状态时,我们可以假设大多数时间每个像素看到的是相同的背景,因为摄像头没有移动。只是偶尔会有汽车或其他移动物体进入前景,遮挡了背景。
对于一个视频序列,我们可以随机采样几帧(比如 25 帧)。这意味着对于每个像素,我们现在有 25 个背景的估计值。只要一个像素在这 25 帧中被汽车或其他移动物体遮挡的时间不超过 50%,那么在这 25 帧中该像素的中值将给出该像素的背景的良好估计。
我们可以对视频中的每个像素重复这个过程,并恢复整个背景。这种方法相对简单且计算效率高,因此在许多计算机视觉应用中被广泛采用。
4.1 背景评估
通过背景评估图像,可以给出路上无车时,整个画面情况。
c++:
std::string video_file;
// read video file
if(argc > 1) {
video_file = argv[1];
} else {
video_file = "../input/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:
# open video
cap = cv2.videocapture('../input/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)
4.2 帧差法计算
显然的下一个问题是我们是否可以为每一帧创建一个遮罩,显示出运动中的图像部分。
这可以通过以下步骤来实现:
- 将中值帧转换为灰度图像。
- 循环遍历视频中的所有帧。提取当前帧并将其转换为灰度图像。
- 计算当前帧与中值帧之间的绝对差异。
- 对上述图像进行阈值化,以去除噪声并将输出进行二值化。
4.2.1 中值帧转换为灰度
c++:
// convert background to grayscale
mat graymedianframe;
cvtcolor(medianframe, graymedianframe, color_bgr2gray);
python:
# convert background to grayscale
graymedianframe = cv2.cvtcolor(medianframe, cv2.color_bgr2gray)
4.2.2 遍历所有帧,并转换为灰度
c++:
// read frame
cap >> frame;
if (frame.empty())
break;
// convert current frame to grayscale
cvtcolor(frame, frame, color_bgr2gray);
python:
# read frame
ret, frame = cap.read()
# convert current frame to grayscale
frame = cv2.cvtcolor(frame, cv2.color_bgr2gray)
4.2.3 计算当前帧与中值帧差异
c++:
// calculate absolute difference of current frame and the median frame
mat dframe;
absdiff(frame, graymedianframe, dframe);
python:
# calculate absolute difference of current frame and
# the median frame
dframe = cv2.absdiff(frame, graymedianframe)
4.2.4 进行阈值化去除噪声
c++:
// threshold to binarize
threshold(dframe, dframe, 30, 255, thresh_binary);
// display image
imshow("frame", dframe);
python:
# treshold to binarize
th, dframe = cv2.threshold(dframe, 30, 255, cv2.thresh_binary)
# display image
cv2.imshow('frame', dframe)
4.3 效果
基于中值背景估计运动检测
5. 总结
这里的中值法可以理解为取中间值。
hm…可能确实说的有点“拗口”,可以理解为去掉最大值,去掉最小值,取中间那个值。
python采用了numpy库中的median方法:
numpy.median(a, axis=none, out=none, overwrite_input=false, keepdims=false)
c++没有这个库,所以代码层面就需要自己实现:
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;
}
关于中值c++的api函数,可以参考下面具体链接:
void nth_element( randomit first, randomit nth, randomit last, compare comp )
实际情况可能更加复杂,比如:如下路口场景(中值方法获取的)
- 户外摄像头自动曝光调整亮度
- 摄像头收到外力影响而导致振动
- 全天候户外,日光会影响周边环境亮度(上述算法代码只是一次性去了25帧)
以上种种因素都会导致实际中值背景侦测移动物体受到干扰,详细情况下面效果。
注:后续,我们会再对这种实际场景进行分析和验证。
4k road traffic video for object detection and tracking
因此,面对实际问题,可能需要多种算法复合应用,甚至自研的算法来得到更加的效果。这里仅提供了熟悉入门的一个示例代码,希望能够让大家更好的理解和熟悉opencv。
6. 参考资料
【1】ubuntu22.04@laptop opencv get started
【2】ubuntu22.04@laptop opencv安装
【3】ubuntu22.04@laptop opencv定制化安装
7. 补充
学习是一种过程,对于前面章节学习讨论过的,就不在文中重复了。
有兴趣了解更多的朋友,请从《ubuntu22.04@laptop opencv get started》开始,一个章节一个章节的了解,循序渐进。
发表评论