【opencv】车辆分车道计数

代码是CSDN上扒的,发现里面有个关键bug,修改完重新上传一下

【opencv】车辆分车道计数_第1张图片【opencv】车辆分车道计数_第2张图片【opencv】车辆分车道计数_第3张图片

#include "cv.h"
#include "highgui.h"
#include 
#include 
#include 
#include 
#include 

const double MHI_DURATION = 0.5;
const int N = 3;
const int CONTOUR_MAX_AERA = 800;//最小区域
int last = 0;
int nFrmNum = 0;
bool FindCar=false;
int No[5]={0,0,0,0,0};
int CarNum[4]={0,0,0,0};
int CarSum = 0;

IplImage **buf = 0;// ring image buffer
IplImage *mhi = 0; // MHI: motion history image

struct AvTrackBlock{
		int Direction;      //1表示进入区域,0表示离开区域
		int FramesTracked;  //已经跟踪到车辆的帧数。
		int avgX;           //车辆x方向中心
		int avgY;           //车辆y方向中心
	} TrackBlock[4][5];

void DrawText(IplImage* pImg,char* text,CvPoint Point);
void DrawCount(IplImage* pImg,int count,CvPoint Point);
void AddBlobTrack(IplImage* img,int i,CvPoint pt_Rect);
void update_mhi( IplImage* img, IplImage* dst, int diff_threshold );

int main()
{
	IplImage* motion = NULL; 
	IplImage* image;
	CvCapture* capture = NULL;

	capture = cvCaptureFromFile("01.avi");//打开文件
	if( !cvGrabFrame( capture ))	
		printf( "Can not open video file %s\n");
	cvNamedWindow( "Motion", 1 );		//窗口命名
	cvNamedWindow( "temp", 2 );		//窗口命名
	cvNamedWindow( "temp2", 3 );		//窗口命名
	cvNamedWindow( "temp3", 4 );		//窗口命名
//	cvNamedWindow( "temp4", 4 );		//窗口命名
	cvMoveWindow("Motion", 0, 100 );
	cvMoveWindow("temp", 300, 100 );
	cvMoveWindow("temp2", 650, 100 );
	cvMoveWindow("temp3", 950, 100 );
//	cvMoveWindow("temp4", 1200, 100 );
	for(int i=0;i<4;i++)		//初始化跟踪结构体
	for(int j=0;j<5;j++)
	{
		TrackBlock[i][j].Direction=0;
		TrackBlock[i][j].FramesTracked=0;
		TrackBlock[i][j].avgX=0;
		TrackBlock[i][j].avgY=0;
	}

					 
	if( capture )
	{
		while(image = cvQueryFrame(capture))//获取视频
		{       
			nFrmNum++; 
			image = cvRetrieveFrame(capture,0 );
			if(nFrmNum == 1)
			{     
				motion = cvCreateImage( cvSize(image->width,image->height), 8, 1 );//创建图像
				cvZero( motion );												//置0
				motion->origin = image->origin;									//对其原点      			
			}
			update_mhi( image, motion, 60 );									//更新
			cvShowImage( "Motion", image );										//显示图像
			
			if( cvWaitKey(10) >= 0 )											//如果等待错过10  则跳出循环
				break;
		}
		cvReleaseCapture( &capture );											//释放捕获
		cvDestroyWindow( "Motion" );											//销毁窗口
	}
	return 0;
}

//  参数:
//  img – 输入视频帧
//  dst – 检测结果
void  update_mhi( IplImage* img, IplImage* dst, int diff_threshold )
{
    double timestamp = clock()/100.; // get current time in seconds  
    int i, idx1, idx2;
	int avgX1 = 0;
	int avgY1 = 0; 

	CvSize size = cvSize(img->width,img->height); // get current frame size
	IplImage* silh;
    /*********************************/IplImage* pyr = cvCreateImage( cvSize((size.width & -2)/2, (size.height & -2)/2), 8, 1 );
	/*-2补码 11111111 11111111 11111111 11111110   那么size.width & -2就是把最后一位width的最后一位置0,
	  下面分析为什么要把最后一位置为0
	  如果源图像的width为奇数,则通过和-2相与,最后一位设为0,相当于图像width减小了1,然后再除以2,刚好为源图像分配了一半的width*/
    CvMemStorage *stor;
    CvSeq *cont;
	CvPoint pt1_Rect,pt2_Rect,pt3_Rect,pt4_Rect,pt5_Rect,pt6_Rect,pt7_Rect,pt8_Rect,pt9_Rect,pt10_Rect,pt11_Rect;

	pt1_Rect.x=0;						
    pt1_Rect.y=200;
	pt2_Rect.x=img->width;
    pt2_Rect.y=200;
	cvLine( img, pt1_Rect, pt2_Rect,CV_RGB(255,0,0) ,1,CV_AA, 0 );//画横边界线
	pt3_Rect.x=110;
    pt3_Rect.y=0;
	pt4_Rect.x=58;
    pt4_Rect.y=img->height;
	cvLine( img, pt3_Rect, pt4_Rect,CV_RGB(255,0,0) ,1,CV_AA, 0 );//第一条竖线
	pt5_Rect.x=135;
    pt5_Rect.y=0;
	pt6_Rect.x=140;
    pt6_Rect.y=img->height;
	cvLine( img, pt5_Rect, pt6_Rect,CV_RGB(255,0,0) ,1,CV_AA, 0 );//第二条竖线
	pt7_Rect.x=155;
    pt7_Rect.y=0;
	pt8_Rect.x=215;
    pt8_Rect.y=img->height;
	cvLine( img, pt7_Rect, pt8_Rect,CV_RGB(255,0,0) ,1,CV_AA, 0 );//第三条竖线
	//初始化
	for(int i=0;i<5;i++)
		No[i]=0;
    if( !mhi || mhi->width != size.width || mhi->height != size.height )//初始化 清空buf、mhi
    {
        if( buf == 0 ) 
        {
            buf = (IplImage**)malloc(N*sizeof(buf[0]));
            memset( buf, 0, N*sizeof(buf[0]));
        }        
        for( i = 0; i < N; i++ )									
        {
            cvReleaseImage( &buf[i] );
            buf[i] = cvCreateImage( size, IPL_DEPTH_8U, 1 );
            cvZero( buf[i] );
        }
        cvReleaseImage( &mhi );
        mhi = cvCreateImage( size, IPL_DEPTH_32F, 1 );
        cvZero( mhi ); // clear MHI at the beginning
    } // end of if(mhi)
	
    cvCvtColor( img, buf[last], CV_BGR2GRAY ); // 图像灰度化
	
    idx1 = last;
    idx2 = (last + 1) % N; //调整 index of (last - (N-1))th frame 
    last = idx2;
    // 做帧差
    silh = buf[idx2];
	
    cvAbsDiff( buf[idx1], buf[idx2], silh ); // 得到两图的不同

    // 对差图像做二值化
    cvThreshold( silh, silh, 45, 255, CV_THRESH_BINARY ); // and threshold it
    cvShowImage( "temp",   silh);

    cvUpdateMotionHistory( silh, mhi, timestamp, MHI_DURATION ); //去掉影像(silhouette) 以更新运动历史图像 把MHI变为8位的图像
																									//MHI(motion history image) 中在运动发生的象素点被设置为当前时间戳,而运动发生较久的象素点被清除。
    cvCvtScale( mhi, dst, 255./MHI_DURATION, (MHI_DURATION - timestamp)*255./MHI_DURATION ); //dst = mhi*255./MHI_DURATION+(MHI_DURATION - timestamp)*255./MHI_DURATION
    cvCvtScale( mhi, dst, 255./MHI_DURATION, 0 );    //dst = mhi*255./MHI_DURATION+0  图像格式的的选择依赖于 filename 的 只有 8 位单通道 用 cvCvtScale 和 cvCvtColor 转换
     // 中值滤波,简单滤波、高斯滤波,消除小的噪声
	/*cvSmooth(dst,dst,CV_GAUSSIAN,3,3,0);
	cvSmooth(dst,dst,CV_GAUSSIAN,3,3,0);
    cvSmooth( dst, dst, CV_MEDIAN, 3, 0, 0, 0 );
	cvSmooth(dst,dst,CV_BLUR,3,3,0);*/

	//DrawCount(dst,No[5],cvPoint(100,100));	//写计数值
	//DrawCount(dst,sum[0],cvPoint(10,200));	//写计数值
	//DrawCount(dst,sum[1],cvPoint(80,200));
	//DrawCount(dst,sum[2],cvPoint(140,200));
	//DrawCount(dst,sum[3],cvPoint(220,200));
	/******************************************/	


    // 向下采样,去掉噪声
    cvPyrDown( dst, pyr, 7 );
	cvErode( pyr,pyr, NULL,1); //腐蚀

	
    cvDilate( pyr, pyr, 0, 1 );  // 做膨胀操作,消除目标的不连续空洞
    cvPyrUp( pyr, dst, 7 );

     // 中值滤波,滤波、高斯滤波,消除小的噪声
	//cvSmooth(dst,dst,CV_BLUR,3,3,0);
    cvSmooth( dst, dst, CV_MEDIAN, 3, 0, 0, 0 );	
   	cvShowImage( "temp2",silh);
	cvSmooth(dst,dst,CV_GAUSSIAN,3,3,0);
	cvShowImage( "temp3",dst );
//	cvShowImage( "temp3",dst );
    // 下面的程序段用来找到轮廓
    //
    // Create dynamic structure and sequence.
	//用opencv提取轮廓的时候,就要申请一块内存来存储找到的轮廓序列,所以你可以看到用轮廓函数的时候前面都有这么一条语句。
	//调试程序的时候可以设个断点到你可以看到这块内存的大小和你找到的轮廓数量以及顶点个数有关。
    stor = cvCreateMemStorage(0);
    cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint) , stor);
   
    // 找到所有轮廓
    cvFindContours( dst, stor, &cont, sizeof(CvContour), 
                    CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));	
    // 直接使用CONTOUR中的矩形来画轮廓
    for(;cont;cont = cont->h_next)	//遍历所有轮廓
    {

		CvRect r = ((CvContour*)cont)->rect;	//去除轮廓边界
		avgX1 = (r.x + r.x + r.width) / 2; 
		avgY1 = (r.y + r.y + r.height) / 2;			
		pt11_Rect.x=(r.x + r.x + r.width) / 2;
		pt11_Rect.y=(r.y + r.y + r.height) / 2;				
		if(r.height * r.width > CONTOUR_MAX_AERA) //当轮廓满足一定条件(过滤噪声)
		{
			pt9_Rect.x = r.x;
			pt9_Rect.y = r.y;
			pt10_Rect.x = r.x + r.width;
			pt10_Rect.y = r.y +r.height;//得到左下角和右上角两个坐标
			if((r.y+r.height)<200)					//未过界画绿色
				  {
					  cvRectangle( img, cvPoint(r.x,r.y), cvPoint(r.x + r.width, r.y + r.height),CV_RGB(0,0,255), 1, CV_AA,0);
					 // No[5]++;
				  }
			else 
			{
				cvRectangle( img, pt9_Rect, pt10_Rect,CV_RGB(255,0,0), 1, CV_AA,0);
				if(0<=avgX1&&avgX1<60&& avgY1 > 200/* && avgY1 < 220*/)		//如果车辆进入到了感兴趣区域  第一车道										
					AddBlobTrack(img,0,pt11_Rect);					
				else if(60<=avgX1&&avgX1<138&& avgY1 > 200 /*&& avgY1 < 220*/)	//如果车辆进入到了感兴趣区域  第二车道	
				{					
					AddBlobTrack(img,1,pt11_Rect);
				}
				else if(138<=avgX1&&avgX1<215&& avgY1 > 200/* && avgY1 < 220*/)	//如果车辆进入到了感兴趣区域  第三车道
				{			
					
					AddBlobTrack(img,2,pt11_Rect);
				}
				else if(215<=avgX1&&avgX1width&& avgY1 > 200 /*&& avgY1 < 220*/)//如果车辆进入到了感兴趣区域  第四车道
				{			
				
					AddBlobTrack(img,3,pt11_Rect);
				}
			}
		}
	}
    for(int i=0;i<4;i++)	//出了感兴趣区域。
    {
        for(int j=0;j<5;j++)
        {
            if(TrackBlock[i][j].FramesTracked != nFrmNum&&TrackBlock[i][j].FramesTracked!=0)
            {

                TrackBlock[i][j].Direction=0;
                TrackBlock[i][j].FramesTracked=0;
                TrackBlock[i][j].avgX=0;
                TrackBlock[i][j].avgY=0;
                CarNum[i]++;
            }
        }
    }
    CarSum=CarNum[0]+CarNum[1]+CarNum[2]+CarNum[3];
    DrawCount(img,CarSum,cvPoint(100,100));	//写计数值
    DrawCount(img,CarNum[0],cvPoint(10,200));	//写计数值
    DrawCount(img,CarNum[1],cvPoint(80,200));
    DrawCount(img,CarNum[2],cvPoint(140,200));
    DrawCount(img,CarNum[3],cvPoint(220,200));

	
    // 释放内存
    cvReleaseMemStorage(&stor);
    cvReleaseImage( &pyr );
}

void DrawText(IplImage* pImg,char* text,CvPoint Point)
{
	CvFont font;//初始化字体格式;
	cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 0.4, 0.4, 0, 1, 8);
	cvPutText( pImg, text, Point, &font, cvScalar(0,0,255));
}

void DrawCount(IplImage* pImg,int count,CvPoint Point)
{
	char data[64];
	memset(data,0,64);
	sprintf(data,"Count:%d",count);
	DrawText(pImg,data,Point);
}
void AddBlobTrack(IplImage* img,int i,CvPoint pt_Rect)
{
	for(int j=0;j<5;j++)
		if(TrackBlock[i][j].avgX !=0 && abs(pt_Rect.x-TrackBlock[i][j].avgX)<20 && 
			abs(pt_Rect.y-TrackBlock[i][j].avgY)<20)//避免车辆轮廓重复检测
		{
			TrackBlock[i][j].FramesTracked=nFrmNum;
			TrackBlock[i][j].avgX=pt_Rect.x;
			TrackBlock[i][j].avgY=pt_Rect.y;
			j=5;
			FindCar=true;
		}						
	if(FindCar!=true && pt_Rect.y>200)
	{
		TrackBlock[i][No[i]].Direction=1;
		TrackBlock[i][No[i]].FramesTracked=nFrmNum;
		TrackBlock[i][No[i]].avgX=pt_Rect.x;
		TrackBlock[i][No[i]].avgY=pt_Rect.y;
		if(No[i]==5)
			No[i]=0;							
		else
			No[i]++;		                                                     
	}						
	FindCar=false;				
 }

下载

http://download.csdn.net/detail/qq_15947787/9879238


2017/6/28更

因为这个计数方式依然存在问题,重复计数严重,因此在原有代码上做了修改,同一车道计数一辆后可设置N帧图后检测到的才算第二辆,并且检测差分图区域的灰度作为综合判断标准,最后生成行程图。


界面因为后期还要再加功能,空出了点地方

【opencv】车辆分车道计数_第4张图片


结果图

这个图在读取指定帧的时候根据视频总帧数与某帧图(可设定)国界线上的像素堆叠出来,最后把车辆的检测结果以X,FRAME坐标copy上去。可针对不同视频设置不同的参数

【opencv】车辆分车道计数_第5张图片

你可能感兴趣的:(c++,opencv,opencv)