OpenCV3学习(10.4)基于KNN的背景/前景分割算法BackgroundSubtractorKNN算法

算法流程
源码的算法流程可以总结如下,对于图像某个位置的新像素值:

(1)与该像素值历史信息(包括前几帧的像素值和像素点是前景还是背景的判断)比较,如果像素值之间的差别在指定阈值内,则认为新像素值与该历史信息是匹配的,是“潜在的”一类;所有历史信息比较完毕后,如果与历史信息匹配的次数超过了设定阈值,那么:(1)新像素点被归为“潜在背景点”(2)如果被匹配的历史信息中属于背景的点个数超过设定阈值,那么新的像素点就被归为背景点

(2)将新像素点根据一定规则保存到历史信息中(这个保存规则还挺复杂的)

看源码的过程中算法的第一步流程清晰明了,很容易理解,但是第二步过程的保存规则着实搞晕了,主要的疑问是为什么搞这么复杂的保存规则?好吧,为了验证其作用和目的,我先把算法流程简化为:实时保存每个像素点的历史7帧信息,之后新的像素点与历史7帧像素点比较,然后按照上面的流程进行前景/背景的判断。

#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
#include <iostream>
#include <sstream>
using namespace cv;
using namespace std;
 
const int HISTORY_NUM = 7;// 14;// 历史信息帧数
const int nKNN = 3;// KNN聚类后判断为背景的阈值
const float defaultDist2Threshold = 20.0f;// 灰度聚类阈值
 
struct PixelHistory
{
	unsigned char *gray;// 历史灰度值
	unsigned char *IsBG;// 对应灰度值的前景/背景判断,1代表判断为背景,0代表判断为前景
};
 
 
int main()
{
	PixelHistory* framePixelHistory = NULL;// 记录一帧图像中每个像素点的历史信息
	cv::Mat frame, FGMask, FGMask_KNN;
	int keyboard = 0;
	int rows, cols;
	rows = cols = 0;
	bool InitFlag = false;
	int frameCnt = 0;
	int gray = 0;
	VideoCapture capture("768X576.avi");
	Ptr<BackgroundSubtractorKNN> pBackgroundKnn =
		createBackgroundSubtractorKNN();
	pBackgroundKnn->setHistory(200);
	pBackgroundKnn->setDist2Threshold(600);
	pBackgroundKnn->setShadowThreshold(0.5);
 
	while ((char)keyboard != 'q' && (char)keyboard != 27)
	{
		// 读取当前帧
		if (!capture.read(frame))
			exit(EXIT_FAILURE);
 
		cvtColor(frame, frame, CV_BGR2GRAY);
		if (!InitFlag)
		{
			// 初始化一些变量
			rows = frame.rows;
			cols = frame.cols;
			FGMask.create(rows, cols, CV_8UC1);// 输出图像初始化
 
			// framePixelHistory分配空间
			framePixelHistory = (PixelHistory*)malloc(rows*cols * sizeof(PixelHistory));
			for (int i = 0; i < rows*cols; i++)
			{
				framePixelHistory[i].gray = (unsigned char*)malloc(HISTORY_NUM * sizeof(unsigned char));
				framePixelHistory[i].IsBG = (unsigned char*)malloc(HISTORY_NUM * sizeof(unsigned char));
				memset(framePixelHistory[i].gray, 0, HISTORY_NUM * sizeof(unsigned char));
				memset(framePixelHistory[i].IsBG, 0, HISTORY_NUM * sizeof(unsigned char));
			}
 
			InitFlag = true;
		}
		if (InitFlag)
		{
			FGMask.setTo(Scalar(255));
			for (int i = 0; i < rows; i++)
			{
				for (int j = 0; j < cols; j++)
				{
					gray = frame.at<unsigned char>(i, j);
					int fit = 0;
					int fit_bg = 0;
					// 比较确定前景/背景
					for (int n = 0; n < HISTORY_NUM; n++)
					{
						if (fabs(gray - framePixelHistory[i*cols + j].gray[n]) < defaultDist2Threshold)// 灰度差别是否位于设定阈值内
						{
							fit++;
							if (framePixelHistory[i*cols + j].IsBG[n])// 历史信息对应点之前被判断为背景
							{
								fit_bg++;
							}
						}
					}
					if (fit_bg >= nKNN)// 当前点判断为背景
					{
						FGMask.at<unsigned char>(i, j) = 0;
					}
					// 更新历史值
					int index = frameCnt % HISTORY_NUM;
					framePixelHistory[i*cols + j].gray[index] = gray;
					framePixelHistory[i*cols + j].IsBG[index] = fit >=nKNN ? 1:0;// 当前点作为背景点存入历史信息
					
				}
			}
		}
 
		pBackgroundKnn->apply(frame, FGMask_KNN);
		imshow("Frame", frame);
		imshow("FGMask", FGMask);
		imshow("FGMask_KNN", FGMask_KNN);
 
		keyboard = waitKey(30);
		frameCnt++;
	}
	capture.release();
 
	return 0;
 
}

 

算法效果如上图所示:左图是检测的背景(黑色)/前景(白色)效果,右图是原图。可以看到行人能够有效的检测得到,但是同时行人背后出现了局部认为是前景的区域。这是为啥?因为我们的历史信息就是前7帧像素值,由于行人具有一定宽高,存在身后区域的之前几帧都被行人身体覆盖的情况。所以即便行人已经走过,被行人覆盖的背景像素得以露出,但是它对比的像素点大多是行人的历史像素点,所以又被划分为前景。

那如何解决这个问题?一种思路就是历史信息不仅包含一部分“最近的历史信息”,譬如前7帧,还要包含一部分“遥远的历史信息”,譬如前面的第10帧,第15帧之类的。这样即使行人经过的区域“最近的历史信息”都是行人区域的像素值,但是“遥远的历史信息”中就是行人未经过时的背景点像素值。行人已经走过,被行人覆盖的背景像素得以露出,它与该点“遥远的历史信息”比较相似,那也会被判断为背景点。
想法很美好,但是实际操作中还是遇到很多问题的,譬如历史帧数和一些阈值如何做到相互匹配且鲁棒?如何定义“最近的历史信息”和“遥远的历史信息”,它们的更新规则是什么?自己虽然做了一些尝试,但是短时间内无法对这个给出比较普适的结果。但是BackgroundSubtractorKNN给出了,且其背后有一定的数学理论支撑(尴尬,就是这部分数学理论楼主没看懂。。)

转载自:https://blog.csdn.net/lwx309025167/article/details/78573152(有源码解读)

Ptr<BackgroundSubtractorKNN>cv::createBackgroundSubtractorKNN(int history = 500, double dist2Threshold = 400.0, bool detectShadows = true)

history -- 用于建模的历史帧数

dist2Threshold -- 像素和样本之间平方距离的阈值,以确定像素是否接近该样本。此参数不影响后台更新。

dectectShadow -- 如果为true,算法将检测阴影并标记它们。 它会降低速度,因此如果您不需要此功能,请将参数设置为false。

  • 4
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值