opencv学习之基于背景提取等目标跟踪算法#20190704

/* ***********************************************************************************************************************
任务目标:
基于背景提取的目标跟踪算法实践及代码分析。
*********************************************************************************************************************** */

include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int labelTargets(Mat &src, Mat &mask, int thresh = 100);
// 在自定义函数模块声明中对thresh的值进行了赋值

int main()
{
char fn = "D:CommonSoftwareOpenCVWorkplaceExample2_trackvtest.avi";
// 指针
fn指向视频地址,此处使用的是OpenCV库中自带的视频
VideoCapture cap; // 定义VideoCapture类用以打开指定视频
Mat source, image, foreGround, backGround, fgMask;
// Mat类:原始视频、缩放后视频、前景、背景、掩膜
Ptr pBgModel =
createBackgroundSubtractorMOG2().dynamicCast();
/* ********************************************************************************************************************
1. 此处采用了模板类的概念,变量"pBgModel"是一个指向"BackgroundSubtractor"对象的指针。
这里的Mat.Ptr(i)[j]并未指定相应"i"和"j"的值,所以默认值都为0,即指向Mat第一行的第一个元素。
2. OpenCV中不止有一种背景提取方法,但是所有的背景提取方法都归结为同一个基类"BackgroundSubtractor",
具体所采用的方法为"creatBackgroundSubtractorMOG2"。
3. "creatBackgroundSubtractorMOG2()"对"pBgModel"指针进行初始化,将其初始化成混合高斯模型。(注
意:在OpenCV 3.0以后的版本中不再含有"BackgroundSubtractorMOG"模型。)
4. 利用C++中的"dynamicCast"函数将pBgModel指针所指的内容动态的转换成"BackgroundSubtractor"型,
并检测是否成功。这里"BackgroundSubtractor"是一个类的引用,所以"()"中也需要放入一个类的引用,否则
抛出一个异常。
******************************************************************************************************************** */

cap.open(fn);

/* ********************************************************************************************************************
1. 使用cap类的open函数打开指针*fn所指的视频
2. cap.open()中()内也可以放入数字,而放入数字代表的是接入几号摄像机的视频,该摄像机必须支持windows
的VFW。而且摄像机的编号是从0开始的。
******************************************************************************************************************** */
if (!cap.isOpened()) // cap类的isOpened函数查看是否成功打开
cout << "Cannot open the file: " << fn << endl;

for (; ; )  // 强制循环
{
	cap >> source;  // 将cap提取到的当前帧传给source(原始图像)
	if (source.empty())  // 如果原始图像为空,即视频已结束
	{
		break;  // 直接跳出循环
	}
	
	resize(source, image, Size(source.cols / 2, source.rows / 2), INTER_LINEAR);
	// 如果原始图像不为空,调用resize函数改变原始图像尺寸,方式为"线性差值法"

	if (foreGround.empty())
	{   // 通过前景是否为空,判断当前帧是否为视频开始
		foreGround.create(image.size(), image.type());
	}   // 如果是视频开始,则为"前景图像"创建矩阵空间,此处是按照原始图像的缩放图像的尺寸和类型进行初始化

	pBgModel->apply(image, fgMask);

/* *********************************************************************************************************************
在这里pBgModel这个指针在定义时已经设定好了处理模式,而"apply"函数会实时的将"pBgModel"中的当前帧
缩放图像"image"采用pBgModel的方式进行处理,并提取fgMask,即前景的掩膜。
********************************************************************************************************************* */

	GaussianBlur(fgMask, fgMask, Size(5, 5), 0);  // 利用高斯滤波器对前景的掩膜做平滑降噪
	threshold(fgMask, fgMask, 30, 255, THRESH_BINARY);  // 通过阈值化去除掩膜中灰度小于30的像素

	foreGround = Scalar::all(0);  // 利用"Scalar"函数将所创建的前景矩阵空间中所有元素赋初值0
	image.copyTo(foreGround, fgMask);  // 标记运动目标

/* **********************************************************************************************************************
调用Mat类的copyTo()函数,仅将原始图像的缩放图像(image)中的掩膜部分(fgMask)复制到前景图像中。
********************************************************************************************************************** */

	int nTargets = labelTargets(image, fgMask);
	cout << "There are " << nTargets << " targets." << endl;

	pBgModel->getBackgroundImage(backGround);

/* ***********************************************************************************************************************
这里使用了getBackgroundImage()函数,其会从pBgModel设定的模型中提取出背景。
*********************************************************************************************************************** */

	imshow("Sized Image", image);  // 显示原始图像的缩放图像。
	imshow("Background", backGround);  // 显示背景图像。
	imshow("Foreground", foreGround);  // 显示前景图像。
	imshow("Foreground Mask", fgMask);  // 显示前景图像的掩膜。

	char key = waitKey(100);  // 每一帧等待100毫秒
	if (key == 27)  // 如果想中途终止程序,按"Esc"退出
	{
		break;
	}
}

waitKey(0);  // 程序执行结束后防止闪退

}

int labelTargets(Mat &src, Mat &mask, int thresh) // 自定义的目标标记函数
{
Mat seg = mask.clone(); // 将掩膜复制到seg当中
vector<vector > cnts; // 定义了二维点类型变量cuts
findContours(seg, cnts, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 调用findContours()函数对掩膜复制图像seg中的Blob进行检测,检测方式是忽略内部边缘仅识别外部边缘

// 以下进行筛选
float area;  // 定义浮点型area变量用以存放单一Blob外围边缘所围面积
Rect rect;
int count = 0;  // 定义整型变量用以计数
string strCount;  // 定义字符串型变量
for (int i = cnts.size() - 1; i >= 0; i--)
{  // 调用vector的size()函数以检测cnts第一维的个数,即Blob个体数,并对其进行循环处理
	vector<Point> c = cnts[i];  // 定义一维点类型变量c用以存放Blob个体,即一维cnts集合
	area = contourArea(c);  // 调用contourArea()函数计算该单一Blob外围边缘所围面积
	if (area < thresh) // 滤除面积小于10的分割结果:可能是噪声
	{
		continue;
	}

	count++; // 统计米粒数量
	cout << "blob " << i << " : " << area << endl;
	rect = boundingRect(c);  // 创建包围矩形数据
	// 在原始图像上画出包围矩形,并给每个矩形标号
	rectangle(src, rect, Scalar(0, 0, 0xff), 1);
	// 调用rectangle()函数在src图像中,把rect包围矩形用红色,1个像素粗细的线框包围起来

	stringstream ss;  // 定义字符流变量"ss",作用是将指定字符串生成输入或输出流
	ss << count;  // 将计数数据传递给字符流
	ss >> strCount;  // 将字符流中的数据传送给字符串变量
	putText(src, strCount, Point(rect.x, rect.y), CV_FONT_HERSHEY_PLAIN, 0.5, Scalar(0, 0xff, 0));
	// 调用putText函数,在src图像上将strCount字符串输出在矩形框左上角,并定义了字体和字号和颜色
}

return count;

}

OpenCV中Mat类的指针ptr的使用:

  1. 定义Mat型的类”image”,1)image.ptr(0),指向image第1行第1个元素的指针;2)image.ptr(1),指向image的第2行第1个元素的指针;3)image.ptr(0)[1],指向image第1行第2个元素的指针。
  • 注意:在使用image.ptr()[]指针的时候,要注意防止索引值溢出(cv::Exception)。
  1. dynamic.cast的用法:
  • 将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。

  • 例如:dynamic_cast (expression);

该运算符把expression转换成type-id类型的对象。

Type-id 必须是类的指针、类的引用或者void*。

如果 type-id 是类指针类型,那么expression也必须是一个指针,(对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针)。如果 type-id 是一个引用,那么 expression 也必须是一个引用(对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用)。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
转自 neilkuang

原文地址:https://www.cnblogs.com/ax204/p/11133555.html