• 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

  • 相关阅读:
    redis单机安装以及简单redis集群搭建
    Linux中JDK安装教程
    微信公众号开发(一)
    easyui多图片上传+预览切换+支持IE8
    mybatis动态sql之foreach标签
    java List递归排序,传统方式和java8 Stream优化递归,无序的列表按照父级关系进行排序(两种排序类型)
    java钉钉通讯录同步
    java使用poi生成导出Excel(新)
    java 图片转base64字符串、base64字符串转图片
    Spring事务mysql不回滚:mysql引擎修改
  • 原文地址:https://www.cnblogs.com/ax204/p/11133555.html
Copyright © 2020-2023  润新知