数字图像处理合集终章——车流量统计(后附源码)

题目要求:
包括1)基于高斯混合背景建模的运动目标提取;2)基于矩形度/圆形度/面积的车辆目标判别;3)区域生长法获取完整的车辆目标;4)统计不同方向的车流量(单位是辆/分钟),对于白天场景下车流量能够有效的统计。

题目分析:
首先明白混合高斯背景建模,其基本思想为:定义每个像素点的分布模型为由多个单高斯模型组成的集合,根据每一个新的像素值更新模型参数,按照一定的准则判断哪些像素点为背景点,哪些为前景点,从而实现对运动目标的检测。
使用混合高斯背景建模提取出前景之后,对前景进行图像预处理,进行滤波、膨胀、腐蚀等操作得到较为完整的前景运动车辆二值图。在这一操作时,使用While大循环对每一帧进行处理。辨识二值图像中的车辆目标时,可以使用opencv内的Rect类,将前景车辆目标圈出。在检测时,将图像中的一部分划分出来作为检测区域,当车辆进入这一区域时对其进行计数。
综上,重点在于辨识车辆目标和进行计数。

算法分析
1.系统模块划分
根据第二部分的分析,将本车流量统计系统分为如下几个模块。
1.1混合高斯背景建模
使用opencv自带的高斯建模函数createBackgroundSubtractorMOG2()对视频进行背景建模。视频中还可能存在影子等内容,故使用setVarThreshold减少影子对之后测试的影响。使用BackgroundSubtractorMOG2中的操作apply对背景进行更新。使用Scalar对获取到的前景保持像素不变,背景换成零像素。对前景二值化处理即可进行图像预处理模块。本模块主要实现代码如下所示。

Ptr<BackgroundSubtractorMOG2> pBgmodel = createBackgroundSubtractorMOG2();
pBgmodel->setVarThreshold(500);
pBgmodel->apply(image, fgMask);
foreGround = Scalar::all(0);
image.copyTo(foreGround, fgMask);
	pBgmodel->getBackgroundImage(backGround);

1.2图像预处理
对获取到的每一帧二值图进行预处理,本系统中对获取到的二值图进行了高斯滤波、膨胀、腐蚀等操作,使用的结构元素为(13*13)大小的。主要实现代码如下所示。

Mat element = getStructuringElement(MORPH_RECT, Size(13, 13));
GaussianBlur(fgMask, fgMask, Size(5, 5), 0);
			dilate(fgMask, fgMask, element);
	erode(fgMask, fgMask, element);

1.3分析辨识车辆目标
本系统中使用Rect类标记每一辆车,通过轮廓检测将车辆标识出来,并画出矩形类的轮廓。对得到的每一个矩形阵,设定最小宽度及最大宽度排除一些伪目标,再对当前帧中的所有轮廓进行排序,当其中某个轮廓(连通分量)的面积小于当前帧中最大轮廓(连通分量)面积的1/6时认为其是伪目标。将真实车辆目标放入一个动态数组并将其画出。通过两层检测基本能排除所有不是车辆的伪目标并画出车辆目标。主要实现代码如下所示。

vector<vector<Point>> contours;
vector<vector<Point>>Realcontours;
vector<Vec4i> hierarchy;
findContours(fgMask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, cvPoint(0, 0));
	getSizeContours(contours);
sort(contours.begin(), contours.end(), descSort);
for (int i = 0; i < contours.size(); i++){
			//当第i个连通分量的外接矩阵面积小于最大面积的1/6,则认为是伪目标
			if (contourArea(contours[i]) < contourArea(contours[0]) / 5)
				break;
			//包含轮廓的最小矩阵
			rct = boundingRect(contours[i]);
			Realcontours.push_back(contours[i]);}
	rectangle(image, rct, SCALAR_GREEN, 2);
使用的自定义函数:
void getSizeContours(vector<vector<Point>> &contours){
		int cmin = 100;   // 最小轮廓长度
		int cmax = 1000;   // 最大轮廓长度
		vector<vector<Point>>::const_iterator itc = contours.begin();
		while (itc != contours.end()){
		if ((itc->size()) < cmin || (itc->size()) > cmax){
			itc = contours.erase(itc);}
		else ++itc;
		}
		}
bool descSort(vector<Point> p1, vector<Point> p2) {
			return contourArea(p1) > contourArea(p2);
			}

1.4建立检测区域对其中的车辆进行计数
本模块中需要建立一个检测区域,市面上大多数车流量检测使用的都是直线检测,使用车辆过线对车辆进行计数,本系统没有采用这样的方法,而是使用一个检测矩形框进行检测。使用opencv中矩形类的特殊操作辅助。
在屏幕下方画出一个矩形框作为检测区域,每一个进入此区域的车辆对其进行计数,但这里涉及到一个轮廓跟踪的问题。即一个已经进入框内但并未出框的车辆多次计数的问题。本模块使用一个标志carFlag对该车辆是否已经计数(即该车辆是否为新进入检测区的车辆)。轮廓跟踪部分,本系统使用中心点进行跟踪,建立一个存放中心点的动态数组 centerPointArray,计算每一帧中出现的车辆的中心点,将该中心点与数组中已存在的中心点使用欧几里得距离计算公式算出其距离,若该距离大于设定的一个距离值则认为该车辆为一辆新的车辆并将该中心点加入中心点数组。若小于设定的距离值则认为其是该帧中的某一个车辆的下一个位置,并对所有计算出的距离进行排序,距离差值最小的则认为该中心点是数组中这一中心点车辆的下一个位置并将该中心值替换到数组中。本模块的不足之处是设定的距离值需要通过多次统计检测才能得出,且为人工计算出。
设定一个全局变量CarCount存储车辆的数量,当中心点距离小于距离设定值且carFlag值为True时才使CarCount自增一次。为了增加用户体验感,车辆在计数范围内并被计数时使用红色进行画框,其余时间使用绿色画框。本模块主要实现代码如下所示。

vector<Point> centerPointArray;
Rect Testrct(0, imageheigh,image.cols,100);
		rectangle(image, Testrct, SCALAR_GREEN, 2);
		for (int i = 0; i < Realcontours.size(); i++) {
			Rect Realrct = boundingRect(Realcontours[i]);
			//获取中心点坐标
			Point center;
			center = getCenterPoint(boundingRect(Realcontours[i]));
			if(center.y <= imageheigh+100 && center.y >= imageheigh ){
				double distance;
				//用于判断是否进行了中心点坐标替换,即是否是同一辆车false时不是同一辆车要计数
				bool carFlag = false;
				//判断中心点数组中是否有值,如果有则遍历匹配距离
				if (centerPointArray.size() > 0) {
					for (auto i = centerPointArray.begin(); i < centerPointArray.end(); i++){
						distance = sqrt(pow((center.x- i->x), 2) + pow((center.y - i->y), 2));
						if (distance <= 30){
							*i = center;
							carFlag = true;}}}
				if (carFlag == false){
					centerPointArray.push_back(center);
					CarCount++;}
				rectangle(image, Realrct, SCALAR_RED, 2);
			} else{
				rectangle(image, Realrct, SCALAR_GREEN, 2);	
				}
				}

使用的自定义函数:

Point getCenterPoint(Rect rect){
	Point cpt;
	cpt.x = rect.x + cvRound(rect.width / 2.0);
	cpt.y = rect.y + cvRound(rect.height / 2.0);
	return cpt;
	}

1.5尾处理
车流量计数完毕需要对用户展示一些处理过程的处理结果如原视频、视频背景、视频运动目标、运动目标二值图、视频处理结果并将计数展示在视频处理结果的右上角。右上角显示计数结果部分应能根据视频的大小进行字体大小的变换,在力所能及的部分增加用户体验感。本模块主要实现代码如下所示。

int intFontFace = CV_FONT_HERSHEY_SIMPLEX;	//字体类型
		double dblFontScale = (image.rows * image.cols) / 300000.0;//文字大小
		int intFontThickness = (int)std::round(dblFontScale * 3.0);//字体粗细
		Size textSize = cv::getTextSize(std::to_string(CarCount), intFontFace, dblFontScale, intFontThickness, 0);//to_string将数字换为文字
		crossingLine.x = image.cols - 1 - (int)((double)textSize.width * 1.25);
		crossingLine.y = (int)((double)textSize.height * 1.25);
		putText(image, to_string(CarCount), crossingLine, intFontFace, dblFontScale, SCALAR_GREEN, intFontThickness);
		imshow("【视频背景】", backGround);
		imshow("【视频运动目标】", foreGround);
		imshow("【运动目标二值图】", fgMask);
imshow("【视频】", image);

2.系统运行流程图
根据系统模块划分,可得出本系统的流程图如图所示。
系统运行流程图:
系统运行流程图
实现效果:
由于本系统是对视频进行处理,故不能展示所有结果,在此展示本系统处理过程中某一帧的运行结果,并给出最终的统计结果,处理视频来源与网站。
读取原视频如图所示。
原视频读取:
原视频读取
混合高斯背景建模背景分离如图所示,由于运行电脑的配置等原因,背景分离出现一些重影,这也是混合高斯背景建模的不足之处,其对运动缓慢的目标无法很好的对其进行提取。
背景分离:
背景分离

混合高斯背景建模前景提取如图所示。此处将背景像素值置为0,仅留下前景目标。
前景提取:
前景提取
图像预处理结果如图所示。此时已对前景进行二值化、高斯滤波、膨胀、腐蚀等操作。
图像预处理结果:
图像预处理结果
图像计数过程如图所示。进入此区域并被计数的车辆的轮廓最小正外接矩形被画出。
图像计数:
图像计数
图像计数结果如图所示。计数结果为52,针对该视频,此计数结果正确。
图像计数结果:
图像计数结果
最终给出车流量统计的实验源码:

#include "stdafx.h"
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/video/background_segm.hpp>
using namespace cv; 
using namespace std;
void getSizeContours(vector<vector<Point>> &contours);
bool descSort(vector<Point> p1, vector<Point> p2);
Point getCenterPoint(Rect rect);
const Scalar SCALAR_GREEN = Scalar(0.0, 200.0, 0.0);
const Scalar SCALAR_RED = Scalar(0.0, 0.0, 255.0);
int CarCount = 0;
int main()
{
	VideoCapture capture;
	Mat frame, image, foreGround, backGround, fgMask, findcontours;
	Rect rct;
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	//声明一个存储中心点的数组
	vector<Point> centerPointArray;
	Point crossingLine;		//定义2D点
	Ptr<BackgroundSubtractorMOG2> pBgmodel = createBackgroundSubtractorMOG2();
	pBgmodel->setVarThreshold(500);//检测是否有影子
	capture.open("G:\\opencvdemo\\Guass\\CarsDrivingUnderBridge.mp4");
	if (!capture.isOpened())
	{
		cout << "open videp eror!" << endl;
	}

	while (true)
	{
		vector<vector<Point>>Realcontours;
		Mat element = getStructuringElement(MORPH_RECT, Size(13, 13));
		//frame是原始帧
		capture >> frame;
		if (frame.empty())
			break;
		//缩小为原来四分之一,加快处理速度
		resize(frame, image, Size(frame.cols / 2, frame.rows / 2), INTER_LINEAR);
		if (foreGround.empty())
		foreGround.create(image.size(), image.type());
		//得到前景图像,是黑白灰 3种灰度值的图		pBgmodel->apply(image, fgMask);//背景更新
		// 下面是根据前景图的操作,和原图像融合得到有纹理的前景图
		GaussianBlur(fgMask, fgMask, Size(5, 5), 0);
		dilate(fgMask, fgMask, element);
		erode(fgMask, fgMask, element);
		threshold(fgMask, fgMask, 10, 255, THRESH_BINARY);
		
		// 将foreGraound 所有像素置为0
		foreGround = Scalar::all(0);
		image.copyTo(foreGround, fgMask);
		pBgmodel->getBackgroundImage(backGround);
		int imageheigh = (int)std::round((double)image.rows * 0.5);
		findContours(fgMask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, cvPoint(0, 0));
		getSizeContours(contours);
		imshow("【原视频】", image);
		//drawContours(image, contours, -1, SCALAR_RED, 5);
		sort(contours.begin(), contours.end(), descSort);
		for (int i = 0; i < contours.size(); i++)
		{
			//当第i个连通分量的外接矩阵面积小于最大面积的1/6,则认为是伪目标
			if (contourArea(contours[i]) < contourArea(contours[0]) / 5)
				break;
			//包含轮廓的最小矩阵
			rct = boundingRect(contours[i]);
			Realcontours.push_back(contours[i]);
		}
		rectangle(image, rct, SCALAR_GREEN, 2);
		//对车辆进行计数
		Rect Testrct(0, imageheigh,image.cols,100);
		rectangle(image, Testrct, SCALAR_GREEN, 2);
		
		for (int i = 0; i < Realcontours.size(); i++) {
			Rect Realrct = boundingRect(Realcontours[i]);
			Point center;
			center = getCenterPoint(boundingRect(Realcontours[i]));
			
			if(center.y <= imageheigh+100 && center.y >= imageheigh )
			{
				double distance;
				bool carFlag = false;
				if (centerPointArray.size() > 0) {
					for (auto i = centerPointArray.begin(); i < centerPointArray.end(); i++)
					{
						distance = sqrt(pow((center.x- i->x), 2) + pow((center.y - i->y), 2));
						if (distance <= 30)
						{
							*i = center;
							carFlag = true;
						}
					}
				}
				if (carFlag == false)
				{
					centerPointArray.push_back(center);
					CarCount++;
				}
				rectangle(image, Realrct, SCALAR_RED, 2);
			} else{
				rectangle(image, Realrct, SCALAR_GREEN, 2);
			}
		}
		//将计数结果写在右上角
		int intFontFace = CV_FONT_HERSHEY_SIMPLEX;				//字体类型
		double dblFontScale = (image.rows * image.cols) / 300000.0;	//文字大小
		int intFontThickness = (int)std::round(dblFontScale * 3.0);	//字体粗细
		Size textSize = cv::getTextSize(std::to_string(CarCount), intFontFace, dblFontScale, intFontThickness, 0);
		crossingLine.x = image.cols - 1 - (int)((double)textSize.width * 1.25);
		crossingLine.y = (int)((double)textSize.height * 1.25);
		putText(image, to_string(CarCount), crossingLine, intFontFace, dblFontScale, SCALAR_GREEN, intFontThickness);
		imshow("【视频背景】", backGround);
		imshow("【视频运动目标】", foreGround);
		imshow("【运动目标二值图】", fgMask);
		imshow("【视频】", image);
		
		char key = waitKey(100);
		if (key == 27)//27 对应得assic 码是27
			break;
	}
system("pause");
	return 0;
}
void getSizeContours(vector<vector<Point>> &contours)
{
	int cmin = 100;   // 最小轮廓长度
	int cmax = 1000;   // 最大轮廓长度
	vector<vector<Point>>::const_iterator itc = contours.begin();
	while (itc != contours.end())
	{
		if ((itc->size()) < cmin || (itc->size()) > cmax)
		{
			itc = contours.erase(itc);
		}
		else ++itc;
	}
}
bool descSort(vector<Point> p1, vector<Point> p2) {
	return contourArea(p1) > contourArea(p2);
}


Point getCenterPoint(Rect rect)
{
	Point cpt;
	cpt.x = rect.x + cvRound(rect.width / 2.0);
	cpt.y = rect.y + cvRound(rect.height / 2.0);
	return cpt;
}

数字图像处理合集告一段落,希望这一合集对你学习数字图像处理带来帮助。
合集入口

最后附上资源下载链接,均为本合集的资源
资源下载

测试视频观看入口

  • 14
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值