阈值法:
对每一帧进行阈值处理,取较低的一个阈值进行二值化处理。假设以下为视频流中的任意一帧
代表任意一点处的亮度值(灰度空间),代表一个固定的阈值,对当前帧做以下二值化处理:
该算法比较适合运动物体的亮度大于周围环境的情况,如夜晚的汽车前灯、尾灯等。
下面基于阈值法的前景检测,完成夜晚视频中车辆的检测、跟踪和计数:
【算法的步骤】
1、首先画出感兴趣区域,步骤再此博文已详细描述:视频中画出感兴趣区域
2、对进入感兴趣区域的车辆进行前灯的检测,跟踪和计数
代码如下:
#include
using namespace std;
#include
#include
#include
#include
using namespace cv;
//Trackbar控制的变量及该值的最大值
int thresh = 200;
const int MAX_THRESH = 255;
//Trackbar控制的函数
void thresh_callback(int,void*);
int w_h = 25;
const int MAX_W_H = 100;
int vehicleFrequency = 2;//这个值不能过于大,否则拍到的车辆里,没有车牌
const int MAX_VEHICLEFREQUENCY = 15;
//全局标量
Mat frame;//原视频流的帧
Mat grayFrame;//颜色空间转化
Mat binaryFrame;//二值化
Mat kernel;//进行形态学处理的核函数
Rect box_vehicle;
bool vehicleBool;//判断是否有车辆
//存储边缘
vector> contours;
vector hierarchy;
Rect vehicleRect;//通过前灯检测,定位出来的车辆
/*----定义鼠标事件--画矩形区域:作用当两个车灯----*/
//第一步:全局变量
bool drawing_box = false;
bool gotBox = false;
Rect box;
Point downPoint;
void mouseRectHandler(int event, int x, int y, int flags, void *param)
{
switch (event)
{
case CV_EVENT_MOUSEMOVE:
if (drawing_box)
{
//鼠标的移动到downPoint的右下角
if( x >=downPoint.x && y >= downPoint.y)
{
box.x = downPoint.x;
box.y = downPoint.y;
box.width = x - downPoint.x;
box.height = y - downPoint.y;
}
//鼠标的移动到downPoint的右上角
if( x >= downPoint.x && y <= downPoint.y)
{
box.x = downPoint.x;
box.y = y;
box.width = x - downPoint.x;
box.height = downPoint.y - y;
}
//鼠标的移动到downPoint的左上角
if( x <= downPoint.x && y <= downPoint.y)
{
box.x = x;
box.y = y;
box.width = downPoint.x - x;
box.height = downPoint.y - y;
}
//鼠标的移动到downPoint的左下角
if( x <= downPoint.x && y >= downPoint.y)
{
box.x = x;
box.y = downPoint.y;
box.width = downPoint.x -x;
box.height = y - downPoint.y;
}
}
break;
case CV_EVENT_LBUTTONDOWN:
//按下鼠标,代表可以可以开始画矩形
drawing_box = true;
//记录起点
downPoint = Point(x,y);
break;
case CV_EVENT_LBUTTONUP:
//松开鼠标,代表结束画矩形
drawing_box = false;
gotBox = true;
break;
default:
break;
}
}
int main(int argc,char*argv[])
{
//视频流的输入
VideoCapture video(argv[1]);
if( !video.isOpened())
return -1;
//得到形态学处理的kernel
kernel = getStructuringElement(MORPH_CROSS,Size(3,3),Point(-1,-1));
//register,注册鼠标事件
namedWindow("video",CV_WINDOW_AUTOSIZE);
setMouseCallback("video",mouseRectHandler,NULL);
//输出视频流,并同时画出感兴趣区域
for(;;)
{
video>>frame;
if(!frame.data)
break;
//控制阈值
createTrackbar("前灯检测:","video",&thresh,MAX_THRESH,thresh_callback);
thresh_callback( 0, 0 );//这一行很重要
//去除面积过小的
createTrackbar("面积:","video",&w_h,MAX_W_H,thresh_callback);
thresh_callback( 0, 0 );
//代表一辆车,如果出现的频数大于
createTrackbar("频数:","video",&vehicleFrequency,MAX_VEHICLEFREQUENCY,NULL);
//当得到box时,去除setMouseCallback
if(gotBox)
{
setMouseCallback("video",NULL,NULL);
break;
}
rectangle(frame,box,Scalar(255,0,0),2);//画出感兴趣区域
imshow("video",frame);
if(waitKey(33) == 'q')
break;
}
//用于跟踪算法和计数
bool currentVehicleBool = false;
bool previousVehicleBool = false;
int numberVehicle = 0;
//用于记录每一辆车的的帧数,避免是一闪而过的,可能不是车辆
int numberVehicleFrame = 1;
int currentVehicleNumber = 0;
int previousVehicleNumber = 0;
//以上画出感兴趣区域,以下可以判断是否为车辆
for(;;)
{
vehicleRect.width = 0;
vehicleRect.height = 0;
video >> frame;
if(!frame.data)
break;
//颜色空间转换
CV_Assert(frame.channels() == 3);
cvtColor(frame,grayFrame,COLOR_BGR2GRAY);
//控制阈值
createTrackbar("前灯检测:","video",&thresh,MAX_THRESH,thresh_callback);
thresh_callback( 0, 0 );//这一行很重要
//去除面积过小的
createTrackbar("面积:","video",&w_h,MAX_W_H,thresh_callback);
thresh_callback( 0, 0 );
createTrackbar("频数:","video",&vehicleFrequency,MAX_VEHICLEFREQUENCY,NULL);
//画出感兴趣区域
rectangle(frame,box,Scalar(255,255,0),2);
//画出检测到的前灯
rectangle(frame,vehicleRect,Scalar(0,255,0),2);
//判断vehicleRect和box是否相交,来判断是否有车辆
box_vehicle = box & vehicleRect;
//显示二值化
imshow("二值化",binaryFrame);
if(box_vehicle.width >0 && vehicleRect.height > 0)
{
currentVehicleBool = true;
if( previousVehicleBool ==false )
{
//代表这是第几辆车
numberVehicle++;
//记录当前帧的车辆的标号
currentVehicleNumber = numberVehicle;
}
//用于计数,该辆车出现的帧数
if( currentVehicleNumber == previousVehicleNumber )
{
numberVehicleFrame ++;
if( numberVehicleFrame == vehicleFrequency+1)
{
cout << "抓拍" << endl;
//imshow("抓拍",frame);//保存图片
}
}
}
else
{
currentVehicleBool = false;
//将归为1,重新计数
numberVehicleFrame = 1;
}
//记录上一帧的是否有车辆
previousVehicleBool = currentVehicleBool;
//记录上一帧,车辆的标号(number)
previousVehicleNumber = currentVehicleNumber;
//显示图片
imshow("video",frame);
if(waitKey(33) == 'q')
break;
}
return 0;
}
void thresh_callback(int,void*)//得到新的vehicleRect
{
if(!grayFrame.data)
return;
//阈值处理
threshold(grayFrame,binaryFrame,double(thresh),255.0,THRESH_BINARY);
//均值滤波
medianBlur(binaryFrame,binaryFrame,5);
//形态学处理
morphologyEx(binaryFrame,binaryFrame,MORPH_OPEN,kernel,Point(-1,-1),7,BORDER_REPLICATE);
//存储边缘
vector> contours;
vector hierarchy;
//找到边缘,注意为什么先把binaryFrame克隆一遍
Mat tempBinaryFrame = binaryFrame.clone();
findContours( tempBinaryFrame,contours,hierarchy, CV_RETR_TREE , CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );//注意这个findContours中binaryImage既是输入又是输出
vector> contours_poly( contours.size() );
//存储
vector boundRect;
boundRect.clear();
for(int index = 0;index < contours.size(); index++)
{
approxPolyDP( Mat(contours[index]), contours_poly[index], 3, true );
Rect rect = boundingRect( Mat(contours_poly[index]) );
//做筛选(去除较小的w_h)
if( rect.width < w_h || rect.height < w_h)
continue;
boundRect.push_back(rect);
}
//得到整个汽车前灯轮廓
for(int index = 0;index < boundRect.size() ;index++)
{
if(index ==0)
vehicleRect = boundRect[0];
else
vehicleRect = vehicleRect | boundRect[index];//得到最大的矩形
}
}
【运行结果】: