动态背景下的动态目标算法简介

从开始做毕设到现在已经有3个多月了,课题主要包括以下几个方面:动态环境中动态目标检测、动态目标跟踪、MFC设计、六足机器人设计等。这篇主要是简单介绍一下检测算法,具体源码在上一篇博客有,感兴趣的可以看一看。

检测算法使用传统的背景补偿,即通过计算相邻两帧图像的仿射变换矩阵,把前一帧映射到当前帧,再帧差就可以得出运动目标。

1、特征提取。相邻两帧提取特征点,使用SURF算法,主要还是稳准快。

2、特征匹配。将两帧提取的特征点匹配,主要有以下3步。

(1)、KNN粗略匹配,opencv有函数实现

(2)、最近邻匹配,对于前一帧里每一个点,在当前帧里找到匹配度最高和次高的两个点的汉明距离,记做d1.d2,d1/d2 < 阈值,则选匹配度最高的点为匹配点;否则去掉这一对匹配点

(3)、对称约束。先正向匹配,再反向匹配,若正反匹配的点对应,认为是匹配较好的点。

匹配到此结束

3、外点滤除。目的是去掉待检测目标上的点,提高仿射矩阵的精准度。内容太多,我就直接上图了。

动态背景下的动态目标算法简介动态背景下的动态目标算法简介

4、背景补偿。通过上一步提取的背景点(内点)计算前一帧到当前帧的仿射矩阵H

5、前景提取。

(1)、由仿射矩阵H把前一帧映射到当前帧上,帧差(阈值大约40-60)

(2)、形态学操作、滤波等等

(3)、重点来了  

      离散区域归并。这一步比较重要是因为,前面帧差的结果经常是分散的,比如前景是个走动的人,经过上一步只会是一个人的分散的轮廓提取出来了,再绘制轮廓的话,一个人就被分成了好几块。(懒得配图了)方法如图:

动态背景下的动态目标算法简介动态背景下的动态目标算法简介

图中说的可能不太详细,具体可以参考我的代码。

//目标离散区域归并
/*
img_rgb:	RGB图
img_abs:	提取目标的二值化图
*/
void Func::combinTarget(Mat &img_rgb, Mat &img_abs)
{
	Mat img_rgb1, img_abs1;
	img_rgb.copyTo(img_rgb1);
	img_abs.copyTo(img_abs1);	//保存区域连接后的二值化图
	cvtColor(img_rgb1, img_rgb1, CV_BGR2HSV);	//RGB-》hsv
	vector<vector<Point>> contours;
	findContours(img_abs, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
	if (contours.size() > 1) {
		//contour_center[0].clear();
		contour_center[1].clear();	//初始化contour_center
		//hsiMsg[0].clear();
		hsiMsg[1].clear();
		for (size_t i = 0; i < contours.size(); i++)	//初始化hsiMsg
		{
			vector<double> msg(6);
			msg[0] = 0.0;	//H 均值
			msg[0] = 0.0;	//S 均值
			msg[0] = 0.0;	//I 均值
			msg[0] = 0.0;	//H 方差
			msg[0] = 0.0;	//S 方差
			msg[0] = 0.0;	//I 方差
			hsiMsg[1].push_back(msg);
		}
		
		Rect rect;	//缓存每个区域的外接矩形
		//遍历每个轮廓
		for (size_t i = 0; i < contours.size(); i++)
		{
			//获取轮廓质心
			Moments mu = moments(contours[i]);
			contour_center[1].push_back(Point2f(mu.m10 / mu.m00, mu.m01 / mu.m00));
			//printf("质心%d: (%.1f, %.1f) \n", i, contour_center[i].x, contour_center[i].y);
			//获取轮廓内部像素点个数
			int pixelNum = 0;	//缓存轮廓内部像素点个数
			//获取轮廓内部颜色信息
			Mat mat = Mat::zeros(img_rgb.size(), CV_8UC1);
			cv::drawContours(mat, contours, i, Scalar::all(255), -1);
			rect = boundingRect(contours[i]);	//获取区域外接矩形
			vector<double> hsi(3);	// HSI
			//在区域矩形内遍历目标点
			//计算区域内HSI的均值
			for (int row = rect.y; row < (rect.y+rect.height); row++)
			{
				const uchar* imgRow = img_rgb1.ptr<uchar>(row);
				for (int col = rect.x; col < (rect.x+rect.width); col++)
				{
					if ((int)mat.at<uchar>(row, col) == 255) {
						//获取HSV分量
						hsi[0] = img_rgb1.at<Vec3b>(row, col)[0] * 2;
						hsi[1] = img_rgb1.at<Vec3b>(row, col)[1] / 255;
						hsi[2] = img_rgb1.at<Vec3b>(row, col)[2] / 255;

						//计算区域内HSI的均值
						hsiMsg[1][i][0] += hsi[0];	//H均值
						hsiMsg[1][i][1] += hsi[1];	//S均值
						hsiMsg[1][i][2] += hsi[2];	//I均值

						pixelNum++;	//记录轮廓内像素点个数
					}
				}
			}
			hsiMsg[1][i][0] /= pixelNum;	//H均值
			hsiMsg[1][i][1] /= pixelNum;	//S均值
			hsiMsg[1][i][2] /= pixelNum;	//I均值
			//计算区域内HSI的方差
			for (size_t row = rect.y; row < (rect.y + rect.height); row++)
			{
				for (size_t col = rect.x; col < (rect.x + rect.width); col++)
				{
					if (mat.at<uchar>(row, col) == 255) {
						//获取HSV分量
						hsi[0] = img_rgb1.at<Vec3b>(row, col)[0] * 2;
						hsi[1] = img_rgb1.at<Vec3b>(row, col)[1] / 255;
						hsi[2] = img_rgb1.at<Vec3b>(row, col)[2] / 255;

						//计算区域内HSI的均值
						hsiMsg[1][i][3] += pow(hsi[0] - hsiMsg[1][i][0], 2) / pixelNum;	//H方差
						hsiMsg[1][i][4] += pow(hsi[1] - hsiMsg[1][i][1], 2) / pixelNum;	//S方差
						hsiMsg[1][i][5] += pow(hsi[2] - hsiMsg[1][i][2], 2) / pixelNum;	//I方差
					}
				}
			}
			printf("轮廓%d: HSI均值:%.2f, %.2f, %.2f   HSI方差:%.2f, %.2f, %.2f\n", 
				i, hsiMsg[1][i][0], hsiMsg[1][i][1], hsiMsg[1][i][2], hsiMsg[1][i][3], hsiMsg[1][i][4], hsiMsg[1][i][5]);
		}
		/*********** 到这里区域内的HSI颜色信息已统计完毕 **********/
		//根据颜色信息归并
		for (size_t i = 0; i < contours.size()-1; i++)
		{
			for (size_t j = i+1; j < contours.size(); j++)
			{
				//1.质心相距50以内视为临近
				if (pts2fDist(contour_center[1][i], contour_center[1][j]) < 50.0) {
					//比较颜色特征距离
					double d = colorDist(hsiMsg[1][i], hsiMsg[1][j]);	//计算颜色特征距离
					printf("颜色特征距离:%.2f\n", d);
					//putText(img_rgb, to_string(int(d)), Point((int)((contour_center[1][i].x + contour_center[1][j].x) / 2), ((int)(contour_center[1][i].y + contour_center[1][j].y) / 2)),
						 //FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1, 8);
					//2.颜色特征相差3000以内视为同一目标
					if (d < 3000) {
						line(img_abs1, Point2f(contour_center[1][i].x, contour_center[1][i].y),	//连接归并区域
							Point2f(contour_center[1][j].x, contour_center[1][j].y), Scalar::all(255), 3, 8);
						printf("画线");
					}
				}
			}
		}
		/******************* 根据位置和目标位移信息归并 *****************/
		if (contour_center[0].empty()) {
			contour_center[0].assign(contour_center[1].begin(), contour_center[1].end());
		}
		if (hsiMsg[0].empty()) {
			hsiMsg[0].assign(hsiMsg[1].begin(), hsiMsg[1].end());
		}
		//下面是归并方法
		//1.提取当前帧与前一帧位置接近的区域对
		//2.计算区域对的颜色特征距离确定前后帧对应目标区域
		vector<vector<double>> targetsMovMsg;	//缓存相邻两帧对应目标区域的移动方向、距离和当前帧质心(x y)
		vector<double> targetMovMsg(4);	//缓存相邻两帧某一目标区域的移动方向、距离和当前帧质心(x y)
		for (size_t i = 0; i < contour_center[1].size(); i++)
		{
			for (size_t j = 0; j < contour_center[0].size(); j++)
			{
				//1.距离小于20认为临近
				if (pts2fDist(contour_center[1][i], contour_center[0][j]) < 30.0) {
					//2.颜色特征距离小于10000认为是同一目标
					if (colorDist(hsiMsg[1][i], hsiMsg[0][j]) < 8000.0) {
						//3.计算目标移动方向、距离和当前帧质心
						targetMovMsg[0] = tgtMovDirec(contour_center[0][j], contour_center[1][i]);//移动方向
						targetMovMsg[1] = pts2fDist(contour_center[1][i], contour_center[0][j]);  //移动距离
						targetMovMsg[2] = (double)contour_center[1][i].x;	//当前帧位置 X
						targetMovMsg[3] = (double)contour_center[1][i].y;	//当前帧位置 Y
						targetsMovMsg.push_back(targetMovMsg);	//记录

						//将移动轨迹画在图上
						line(img_rgb, Point(int(contour_center[0][j].x), int(contour_center[0][j].y)),
							Point(int(contour_center[1][i].x), int(contour_center[1][i].y)), Scalar(0, 0, 255), 2, 8);
						printf("由(%d, %d)->(%d, %d)移动了%.2f \n", int(contour_center[0][j].x), int(contour_center[0][j].y), 
							int(contour_center[1][i].x), int(contour_center[1][i].y), targetMovMsg[1]);
					}
				}
			}
		}
		//比较相邻两帧目标区域的位置和移动信息
		if (targetsMovMsg.size() > 1) {
			for (size_t i = 0; i < targetsMovMsg.size() - 1; i++)
			{
				for (size_t j = i +1; j < targetsMovMsg.size(); j++)
				{
					//1.区域质心在当前帧相距小于50
					Point2f p1 = Point2f((float)targetsMovMsg[i][2], (float)targetsMovMsg[i][3]);
					Point2f p2 = Point2f((float)targetsMovMsg[j][2], (float)targetsMovMsg[j][3]);
					printf("区域质心在当前帧距离:%.2f\n", pts2fDist(p1, p2));
					if (pts2fDist(p1, p2) < 55.0) {
						//2.评价不同物体的移动一致性
						double movOffset = getmovOffset(targetsMovMsg[i], targetsMovMsg[j]);
						printf("移动一致性: %.2f \n", movOffset);
						if (movOffset <= 30) {
							//移动一致性小于20,认为是同一目标
							line(img_abs1, Point(int(targetsMovMsg[i][2]), int(targetsMovMsg[i][3])),	//连接归并区域
								Point(int(targetsMovMsg[j][2]), int(targetsMovMsg[j][3])), Scalar::all(255), 3, 8);
						}			
					}
				}
			}
		}

		//更新前一帧区域位置和颜色信息
		contour_center[0].assign(contour_center[1].begin(), contour_center[1].end());	//更新位置信息
		hsiMsg[0].assign(hsiMsg[1].begin(), hsiMsg[1].end());	//更新HSI颜色信息
	}
	cv::imshow("目标归并", img_abs1);
	cv::imshow("区域移动轨迹", img_rgb);

	if(prev_con.empty())
		img_abs.copyTo(prev_con);
	cv::imshow("前一帧二值图", prev_con);

	img_abs.copyTo(prev_con);

	img_abs1.copyTo(img_abs);
}

6、绘制轮廓(目标识别)

7、迭代循环。到此算法结束

上个图吧(背景是不断变化的,我后面看看能不能传视频上来。红线是标记了区域归并前各分散区域的移动轨迹)。

平台是VS2017+opencv3.3.0

动态背景下的动态目标算法简介