/* ***********************************************************************************************************************
任务目标:
基于背景提取的目标跟踪算法实践及代码分析。
*********************************************************************************************************************** */
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
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
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的使用:
- 定义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)。
- 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