ubuntu22.04@laptop OpenCV Get Started: 014_simple_background_estimation_in_videos

ubuntu22.04@laptop OpenCV Get Started: 014_simple_background_estimation_in_videos

  • 1. 源由
  • 2. 应用Demo
    • 2.1 C++应用Demo
    • 2.2 Python应用Demo
  • 3. 时间中值滤波
  • 4. 使用中值进行背景估计
    • 4.1 背景评估
    • 4.2 帧差法计算
      • 4.2.1 中值帧转换为灰度
      • 4.2.2 遍历所有帧,并转换为灰度
      • 4.2.3 计算当前帧与中值帧差异
      • 4.2.4 进行阈值化去除噪声
    • 4.3 效果
  • 5. 总结
  • 6. 参考资料
  • 7. 补充

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 度。

ubuntu22.04@laptop OpenCV Get Started: 014_simple_background_estimation_in_videos_第1张图片在上图中,我们展示了来自两个温度计的测量结果 — 一个好的温度计和一个坏的温度计。

左侧显示的好的温度计报告的是 70 度,带有一定程度的高斯噪声。为了获得更准确的温度估计,我们可以简单地在几秒钟内对数值进行平均。由于噪声是高斯噪声,具有正负值,因此平均值将消除噪声。实际上,在这种特定情况下,平均值为 70.01 度。

另一方面,坏的温度计大部分时间的行为类似于好的温度计,但偶尔,数字完全错误。

事实上,如果我们取坏温度计报告的数字的平均值,我们得到 71.07 度。这显然是一个过高的估计。

我们是否仍然可以获得一个良好的温度估计?

答案是肯定的。当数据中包含异常值时,中值是我们试图估计的值的更稳健的估计。

中值是将数据按升序或降序排序后的中间值。

上述曲线的中值是 70.05 度,这比 71.07 度要好得多的估计。

唯一的缺点是相对于平均值/平均值而言,中值的计算成本更高。

4. 使用中值进行背景估计

在计算机视觉中,背景估计是许多应用的关键步骤之一。而使用中值进行背景估计是一种常见且有效的技术。

当摄像头处于静止状态时,我们可以假设大多数时间每个像素看到的是相同的背景,因为摄像头没有移动。只是偶尔会有汽车或其他移动物体进入前景,遮挡了背景。

对于一个视频序列,我们可以随机采样几帧(比如 25 帧)。这意味着对于每个像素,我们现在有 25 个背景的估计值。只要一个像素在这 25 帧中被汽车或其他移动物体遮挡的时间不超过 50%,那么在这 25 帧中该像素的中值将给出该像素的背景的良好估计。

我们可以对视频中的每个像素重复这个过程,并恢复整个背景。这种方法相对简单且计算效率高,因此在许多计算机视觉应用中被广泛采用。

4.1 背景评估

通过背景评估图像,可以给出路上无车时,整个画面情况。

ubuntu22.04@laptop OpenCV Get Started: 014_simple_background_estimation_in_videos_第2张图片

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)

4.2 帧差法计算

显然的下一个问题是我们是否可以为每一帧创建一个遮罩,显示出运动中的图像部分。

这可以通过以下步骤来实现:

  1. 将中值帧转换为灰度图像。
  2. 循环遍历视频中的所有帧。提取当前帧并将其转换为灰度图像。
  3. 计算当前帧与中值帧之间的绝对差异。
  4. 对上述图像进行阈值化,以去除噪声并将输出进行二值化。

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 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 )

实际情况可能更加复杂,比如:如下路口场景(中值方法获取的)

  1. 户外摄像头自动曝光调整亮度
  2. 摄像头收到外力影响而导致振动
  3. 全天候户外,日光会影响周边环境亮度(上述算法代码只是一次性去了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》开始,一个章节一个章节的了解,循序渐进。

你可能感兴趣的:(Linux,opencv,人工智能,计算机视觉)