可以从静态场景中检测出移动的物体,并对目标进行标记和计数。本文的主要工作包括:在图像预处理阶段,本文采用HSV色彩空间减轻了目标阴影对目标提取的影响,采用中值滤波器去掉了椒盐噪声,采用图像二值化使图像变的简单,采用图像学去噪中的腐蚀和膨胀分别提取消除图像噪声和填充图像空洞。在动态目标识别的阶段,采用三帧差分法提取出动态的目标,并用更新运动历史图像的方法来减轻重影现象。最后通过在原图像帧中画矩形框的方式来标记动态目标的位置,并显示当前运动目标的个数,以及当前帧率、系统运行时间、当前帧数等系统信息。
gra.h
#ifndef GRA_H_INCLUDED #define GRA_H_INCLUDED #endif // GRA2_H_INCLUDED #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <math.h> #include <float.h> #include <limits.h> #include <time.h> #include <ctype.h> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; //声明函数 void bgrToGray(IplImage *src,IplImage *dst); IplImage* doPyrDown( IplImage* src ); IplImage* doPyrUp( IplImage* src ); void graAbsDiff(IplImage* src1,IplImage* src2,IplImage* dst); void graZero(IplImage* src); void graAdd(IplImage* src1,IplImage* src2,IplImage* dst); //转化为灰度图,传入三通道的图像,返回单通道的图像 void bgrToGray(IplImage *src,IplImage *dst) { if(src==NULL) { printf("bgrToGray传入的图像为空"); return; } int i,j,k,avg; int height,width,step,channels; uchar *data,*dstdata; height=src->height; width=src->width; step=src->widthStep; channels=src->nChannels; data=(uchar*)src->imageData; dstdata=(uchar*)dst->imageData; for(i=0; i<height; i++) { for(j=0; j<width; j++) { //for(k=0; k<lena->nChannels; k++) // { avg=0.072169*data[ i * step+ j * channels + 1]+0.715160*data[ i *step+ j * channels + 2]+0.212671*data[ i *step+ j *channels + 3]; //data[i*lena->widthStep+j*lena->nChannels+k]=255-data[ i * lena->widthStep+ j * lena->nChannels + k]; // } dstdata[i*dst->widthStep+j]=avg; } } } IplImage* doPyrDown( IplImage* src ) { IplImage* result = cvCreateImage( cvSize( src -> width/2, src -> height/2 ), src -> depth, src -> nChannels ); //库函数调用cvPyrDown cvPyrDown( src, result, CV_GAUSSIAN_5x5 ); //高斯变换 return result; } IplImage* doPyrUp( IplImage* src ) { IplImage* result = cvCreateImage( cvSize( src -> width*2, src -> height*2 ), src -> depth, src -> nChannels ); cvPyrUp( src, result, CV_GAUSSIAN_5x5 ); return result; } //两个图像矩阵之差的绝对值 void graAbsDiff(IplImage* src1,IplImage* src2,IplImage* dst) { if(src1==NULL||src2==NULL) { printf("graAbsDiff传入的图像为空"); return; } int i,j,k; int height,width,step,channels; uchar *dataSrc1,*dataSrc2,*dataDst; height=src1->height; width=src1->width; step=src1->widthStep; channels=src1->nChannels; dataSrc1=(uchar*)src1->imageData; dataSrc2=(uchar*)src2->imageData; dataDst=(uchar*)dst->imageData; // dstdata=(uchar*)dst->imageData; //printf("\ngraZero(IplImage* src): 将要清零的图像通道为%d",channels); for(i=0; i<height; i++) { for(j=0; j<width; j++) { for(k=0; k<channels; k++) { dataDst[i*step+j*channels+k]=abs(dataSrc1[i*step+j*channels+k]-dataSrc2[i*step+j*channels+k]); } } } } //清零矩阵值 void graZero(IplImage* src) { if(src==NULL) { printf("graZero传入的图像为空"); return; } int i,j,k; int height,width,step,channels; uchar *data,*dstdata; height=src->height; width=src->width; step=src->widthStep; channels=src->nChannels; data=(uchar*)src->imageData; // dstdata=(uchar*)dst->imageData; //printf("\ngraZero(IplImage* src): 将要清零的图像通道为%d",channels); for(i=0; i<height; i++) { for(j=0; j<width; j++) { for(k=0; k<channels; k++) { data[i*step+j*channels+k]=0; } } } } //计算两个数组的元素级的和 void graAdd(IplImage* src1,IplImage* src2,IplImage* dst) { if(src1==NULL||src2==NULL) { printf("graAbsDiff传入的图像为空"); return; } int i,j,k; int height,width,step,channels; uchar *dataSrc1,*dataSrc2,*dataDst; height=src1->height; width=src1->width; step=src1->widthStep; channels=src1->nChannels; dataSrc1=(uchar*)src1->imageData; dataSrc2=(uchar*)src2->imageData; dataDst=(uchar*)dst->imageData; // dstdata=(uchar*)dst->imageData; //printf("\ngraZero(IplImage* src): 将要清零的图像通道为%d",channels); for(i=0; i<height; i++) { for(j=0; j<width; j++) { for(k=0; k<channels; k++) { dataDst[i*step+j*channels+k]=abs(dataSrc1[i*step+j*channels+k]+dataSrc2[i*step+j*channels+k]); } } } } //二值化处理 void graThreshold(IplImage* src1,IplImage* dst,double threshold,double max_value) { if(src1==NULL) { printf("\ngraThreshold没有传入图像"); return ; } int i,j,k; int height,width,step,channels; uchar *dataSrc1,*dataDst; height=src1->height; width=src1->width; step=src1->widthStep; channels=src1->nChannels; if(channels!=1) { printf("将要把彩色图像二值化"); bgrToGray(src1,dst); } dataSrc1=(uchar*)src1->imageData; dataDst=(uchar*)dst->imageData; // dstdata=(uchar*)dst->imageData; //printf("\ngraZero(IplImage* src): 将要清零的图像通道为%d",channels); for(i=0; i<height; i++) { for(j=0; j<width; j++) { if(dataSrc1[i*step+j]>threshold) { dataDst[i*step+j]=max_value; } else { dataDst[i*step+j]=0; } } } } //中值滤波 bool graFilterMid(IplImage* &image,int k) { if(image==NULL) { printf("\nFilterMid没有传入图像"); return false; } uchar *ImagePix=(uchar *)image->imageData; int m=(k-1)/2; for(int x=m; x<image->height-m; ++x) { for(int y=m; y<image->width-m; ++y) { uchar PixArray[100]; int t=0; for(int i=-m; i<m+1; ++i) { for(int j=-m; j<m+1; j++) { PixArray[t++]=((uchar *)image->imageData)[(x+i)*image->widthStep+y+j]; } } //冒泡排序 for(int i=0; i<k*k-1; ++i) for(int j=0; j<k*k-i-1; ++j) { if(PixArray[j]>PixArray[j+1]) { uchar k=PixArray[j]; PixArray[j]=PixArray[j+1]; PixArray[j+1]=k; } } ImagePix[x*image->widthStep+y]=PixArray[(k*k-1)/2]; } } return true; } void saturate_SV(IplImage* image) { for(int y = 0; y < image -> height; ++y) { //ptr指针指向第y行的起始位置 uchar* ptr=(uchar*)( image -> imageData + y * image->widthStep);//(字节类型指针:uchar*) //改变S和V在x维的值 for(int x = 0; x < image -> width; ++x) { //ptr[3 * x + 0] =180; //ptr[3 * x + 1] =0; ptr[3 * x + 2] =255; } } }
main.cpp
#include "gra.h" using namespace cv; using namespace std; // various tracking parameters (in seconds) const double MHI_DURATION = 0.5; const double MAX_TIME_DELTA = 0.5; const double MIN_TIME_DELTA = 0.05; const int N = 3; int num=0; //设置当目标区域的面积大于某个值时才画矩形框 const int CONTOUR_MAX_AERA = 1600; // ring pFrame buffer IplImage **buf = 0; int last = 0; // 临时图像 IplImage *mhi = 0; // MHI: motion history image int filter = CV_GAUSSIAN_5x5; CvPoint pt[4]; int main(int argc, char** argv) { printf("程序开始,按ESC可以退出程序"); IplImage *pFrame = NULL; IplImage *pFrame1 = NULL; int inMonitor=0; int num=0; float fps; char str[5]; double t = 0; int countsInMonitor=0; double runT=clock(); char runTime[10]; double runT2; char* str3; int seconds=0; IplImage* motion = 0; CvCapture* pCapture = 0; int width,height; if(argc>2) { fprintf(stderr, "Usage: bkgrd [video_file_name]\n"); return -1; } //如果没有传入参数,则读取摄像头 if (argc ==1) { if( !(pCapture = cvCaptureFromCAM(-1))) { fprintf(stderr, "Can not open camera.\n"); return -2; } } //打开视频文件 if(argc == 2) if( !(pCapture = cvCaptureFromFile(argv[1]))) { fprintf(stderr, "Can not open video file %s\n", argv[1]); return -2; } if( pCapture ) { pFrame=cvQueryFrame( pCapture ); IplImage *silh1,*silh2,*silh; pFrame1 = cvCreateImage( cvSize(pFrame->width,pFrame->height), 8, 3 ); width=pFrame->width; height=pFrame->height; //标记监控区域 CvPoint pt1_Rect; CvPoint pt2_Rect; CvPoint pt3_Rect; CvPoint pt4_Rect; pt1_Rect.x=(1.0/5)*width; pt1_Rect.y=0; pt2_Rect.x=(1.0/5)*width; pt2_Rect.y=pFrame->height; pt3_Rect.x=(4.0/5)*width; pt3_Rect.y=0; pt4_Rect.x=(4.0/5)*width; pt4_Rect.y=pFrame->height; int thickness=1; int line_type=8; CvScalar color=CV_RGB(100,100,100); //设定监控区域 CvRect ROI_rect; ROI_rect.x=pt1_Rect.x; ROI_rect.y=pt1_Rect.y; ROI_rect.width=pt3_Rect.x-pt1_Rect.x; ROI_rect.height=pt2_Rect.y-pt1_Rect.y; //有东西进入监控区域是输入的信息 char warnings[20]; silh1 = cvCreateImage( cvSize(pFrame->width,pFrame->height), 8, 1 ); silh2 = cvCreateImage( cvSize(pFrame->width,pFrame->height), 8, 1 ); silh = cvCreateImage( cvSize(pFrame->width,pFrame->height), 8, 1 ); cvNamedWindow("Motion",CV_WINDOW_AUTOSIZE); cvMoveWindow("Motion",100,100); for(;;) { t = (double)getTickCount(); num++; if( !cvGrabFrame( pCapture )) break; pFrame = cvRetrieveFrame( pCapture ); if( pFrame ) { if( !motion ) { motion = cvCreateImage( cvSize(pFrame->width,pFrame->height), 8, 1 ); cvZero( motion ); motion->origin = pFrame->origin; } } //update_mhi( image, motion); //clock()返回处理器调用某个进程或函数所花费的时间。 double timestamp = clock()/100.; // get current time in seconds CvSize size = cvSize(pFrame->width,pFrame->height); // get current frame size int i, j, idx1, idx2,idx3; uchar val; float temp; IplImage* pyr = cvCreateImage(cvSize((size.width & -2)/2, (size.height & -2)/2), 8, 1 ); CvMemStorage *stor; CvSeq *cont, *result, *squares; CvSeqReader reader; //如果mhi的值为空。创建mhi,buf。第一次进入循环时运行 if( !mhi || mhi->width != size.width || mhi->height != size.height ) { 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 ); printf("sd"); cvZero( buf[i] ); } cvReleaseImage( &mhi ); mhi = cvCreateImage( size, IPL_DEPTH_32F, 1 ); cvZero( mhi ); // clear MHI at the beginning } // end of if(mhi) cvCvtColor(pFrame,pFrame1,CV_BGR2HSV); saturate_SV(pFrame1); cvCvtColor(pFrame1,pFrame1,CV_HSV2BGR); //cvCvtColor( pFrame, buf[last], CV_BGR2GRAY ); // convert frame to grayscale bgrToGray( pFrame1, buf[last]); idx1 = last; idx2 = (last + 1) % N; // index of (last - (N-1))th frame idx3=(idx2+1)%N; last = idx3; cvShowImage("asda",pFrame1); // 做帧差 //cvShowImage("asd",buf[idx1]); graAbsDiff( buf[idx1], buf[idx2], silh1 ); // get difference between frames graAbsDiff( buf[idx2], buf[idx3], silh2 ); // get difference between frames graAdd(silh1,silh2,silh); cvShowImage("aaaa",silh); // 对差图像做二值化 graThreshold( silh, silh, 50, 255); // and threshold it //去掉影像(silhouette) 以更新运动历史图像 cvUpdateMotionHistory( silh, mhi, timestamp, MHI_DURATION ); // update MHI // convert MHI to blue 8u image //线性变换数组,把MHI变为8位的图像 cvCvtScale( mhi, motion, 255./MHI_DURATION, (MHI_DURATION - timestamp)*255./MHI_DURATION ); // 中值滤波,消除小的噪声 graFilterMid(motion,3); // cvShowImage("中值滤波之后",motion); // 向下采样,去掉噪声 //cvPyrDown( motion, pyr, 7 ); pyr=doPyrDown(motion); cvDilate( pyr, pyr, 0, 1 ); // 做膨胀操作,消除目标的不连续空洞 //cvPyrUp( pyr, motion, 7 ); motion=doPyrUp(pyr); cvSetImageROI(motion,ROI_rect); // // 下面的程序段用来找到轮廓 // // Create dynamic structure and sequence. stor = cvCreateMemStorage(0); cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint) , stor); // 找到所有轮廓 cvFindContours( motion, stor, &cont, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0)); /* for(;cont;cont = cont->h_next) { // Number point must be more than or equal to 6 (for cvFitEllipse_32f). if( cont->total < 6 ) continue; // Draw current contour. cvDrawContours(image,cont,CV_RGB(255,0,0),CV_RGB(255,0,0),0,1, 8, cvPoint(0,0)); } // end of for-loop: "cont" */ cvResetImageROI(motion); cvSetImageROI(pFrame,ROI_rect); // 直接使用CONTOUR中的矩形来画轮廓 countsInMonitor=0; for(; cont; cont = cont->h_next) { CvRect r = ((CvContour*)cont)->rect; if(r.height * r.width > CONTOUR_MAX_AERA) // 面积小的方形抛弃掉 { cvRectangle( pFrame, cvPoint(r.x,r.y), cvPoint(r.x + r.width, r.y + r.height), CV_RGB(255,0,0), 1, CV_AA,0); inMonitor=1; countsInMonitor++; } } // free memory cvReleaseMemStorage(&stor); cvReleaseImage( &pyr ); cvResetImageROI(pFrame); //用来显示当前第几帧 CvFont font; cvInitFont( &font, CV_FONT_HERSHEY_SIMPLEX,0.5, 0.5, 0, 1, 1); //显示fps t = ((double)cvGetTickCount() - t) / getTickFrequency(); fps = 1.00 / t; //sprintf(str, "%5f", fps); sprintf(str,"fps: %.1f",fps); //addTail(str," f/s "); //putText(pFrame, str, Point(0,30), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0,0,255)); cvPutText(pFrame, str , cvPoint(20,pFrame->height-20), &font, CV_RGB(255,0,0)); //显示运行时间 runT2=clock(); seconds=(int)runT2/1000; sprintf(runTime,"time: %ds",seconds); cvPutText(pFrame, runTime, cvPoint(100,pFrame->height-20), &font, CV_RGB(255,0,0)); char texta[10]; //把int型num转化为字符串 sprintf(texta,"%dth frame",num); //addTail(texta," frame"); cvPutText(pFrame, texta , cvPoint(200,pFrame->height-20), &font, CV_RGB(255,0,0)); //如果有人进入则提醒 if(inMonitor==1) { sprintf(warnings,"%d in monitor area",countsInMonitor); cvPutText(pFrame, warnings , cvPoint(0.25*width,height-60), &font, CV_RGB(255,0,0)); } //标记监控区域 cvLine( pFrame, pt1_Rect, pt2_Rect,color ,thickness, line_type, 0 ); cvLine( pFrame, pt3_Rect, pt4_Rect,color ,thickness, line_type, 0 ); cvShowImage( "Motion", pFrame ); inMonitor=0; if( cvWaitKey(10) >= 0 ) break; } cvReleaseCapture( &pCapture ); cvDestroyWindow( "Motion" ); printf("\n程序结束,一共运行了%d s",seconds); } return 0; }