在许多计算机视觉应用中,可用的处理能力有限。在这种情况下,必须使用简单但有效的技术。
在本文中,将介绍一种针对场景背景估计的技术,当摄像头静止对场景中的移动对象进行检测。这种场景并不罕见,例如:许多交通和监控摄像头都是固定不动的。
014_simple_background_estimation_in_videos是OpenCV背景分析技术,在这种特定场景中,对移动物体进行检测的技术。
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
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
让我们考虑一个在一维空间中的简单问题。假设我们每 10 毫秒估计一个数量(比如房间的温度)。
假设房间的温度是华氏 70 度。
在上图中,我们展示了来自两个温度计的测量结果 — 一个好的温度计和一个坏的温度计。
左侧显示的好的温度计报告的是 70 度,带有一定程度的高斯噪声。为了获得更准确的温度估计,我们可以简单地在几秒钟内对数值进行平均。由于噪声是高斯噪声,具有正负值,因此平均值将消除噪声。实际上,在这种特定情况下,平均值为 70.01 度。
另一方面,坏的温度计大部分时间的行为类似于好的温度计,但偶尔,数字完全错误。
事实上,如果我们取坏温度计报告的数字的平均值,我们得到 71.07 度。这显然是一个过高的估计。
我们是否仍然可以获得一个良好的温度估计?
答案是肯定的。当数据中包含异常值时,中值是我们试图估计的值的更稳健的估计。
中值是将数据按升序或降序排序后的中间值。
上述曲线的中值是 70.05 度,这比 71.07 度要好得多的估计。
唯一的缺点是相对于平均值/平均值而言,中值的计算成本更高。
在计算机视觉中,背景估计是许多应用的关键步骤之一。而使用中值进行背景估计是一种常见且有效的技术。
当摄像头处于静止状态时,我们可以假设大多数时间每个像素看到的是相同的背景,因为摄像头没有移动。只是偶尔会有汽车或其他移动物体进入前景,遮挡了背景。
对于一个视频序列,我们可以随机采样几帧(比如 25 帧)。这意味着对于每个像素,我们现在有 25 个背景的估计值。只要一个像素在这 25 帧中被汽车或其他移动物体遮挡的时间不超过 50%,那么在这 25 帧中该像素的中值将给出该像素的背景的良好估计。
我们可以对视频中的每个像素重复这个过程,并恢复整个背景。这种方法相对简单且计算效率高,因此在许多计算机视觉应用中被广泛采用。
通过背景评估图像,可以给出路上无车时,整个画面情况。
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_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);
}
// 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)
显然的下一个问题是我们是否可以为每一帧创建一个遮罩,显示出运动中的图像部分。
这可以通过以下步骤来实现:
C++:
// Convert background to grayscale
Mat grayMedianFrame;
cvtColor(medianFrame, grayMedianFrame, COLOR_BGR2GRAY);
Python:
# Convert background to grayscale
grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)
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)
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)
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)
基于中值背景估计运动检测
这里的中值法可以理解为取中间值。
Hm…可能确实说的有点“拗口”,可以理解为去掉最大值,去掉最小值,取中间那个值。
Python采用了numpy库中的median方法:
numpy.median(a, axis=None, out=None, overwrite_input=False, keepdims=False)
C++没有这个库,所以代码层面就需要自己实现:
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) {
// 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 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;
}
关于中值C++的API函数,可以参考下面具体链接:
void nth_element( RandomIt first, RandomIt nth, RandomIt last, Compare comp )
实际情况可能更加复杂,比如:如下路口场景(中值方法获取的)
以上种种因素都会导致实际中值背景侦测移动物体受到干扰,详细情况下面效果。
注:后续,我们会再对这种实际场景进行分析和验证。
4K Road traffic video for object detection and tracking
因此,面对实际问题,可能需要多种算法复合应用,甚至自研的算法来得到更加的效果。这里仅提供了熟悉入门的一个示例代码,希望能够让大家更好的理解和熟悉OpenCV。
【1】ubuntu22.04@laptop OpenCV Get Started
【2】ubuntu22.04@laptop OpenCV安装
【3】ubuntu22.04@laptop OpenCV定制化安装
学习是一种过程,对于前面章节学习讨论过的,就不在文中重复了。
有兴趣了解更多的朋友,请从《ubuntu22.04@laptop OpenCV Get Started》开始,一个章节一个章节的了解,循序渐进。