学习OpenCV快一年了,最近做了一个简单的人流量统计的项目,分享给大家。
       本次人流量统计用的是纯OpenCV的技术,没有涉及深度学习的知识,如果大家深度学习做得好的话,效果会更好。
       首先介绍我的环境Windows10+OpenCV3.4.3+VS2017 
OpenCV代码实现
       首先是头文件,相关函数以及全局变量的设定。

//头文件
#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>
 
using namespace cv;
using namespace std;
 
//鼠标操作,画一条红线,撞线计数
void on_MouseHandle(int event, int x, int y, int flags, void* param);
void DrawLine(Mat &img, Rect box);
 
Rect g_box;
bool g_DrawingBox = false;
Point p1, p2;
int Thecount = 0;
int precolor = 0;
int all = 0;

然后我们将进入主程序部分:

int main(int argc, char**)
{
	//全局变量的初始化
	g_box = Rect(-1, -1, 0, 0);
	p1 = Point(0, 0);
	p2 = Point(0, 0);
	//打开视频文件
	VideoCapture capture;
	capture.open("E:/The Program/OpenCV3.3.4/Video/manflow.mp4");
	if (!capture.isOpened()) {
		printf("could not find the video file...\n");
		return -1;
	}
	// create windows
	Mat frame;
	Mat bsmaskMOG2;
	Mat threshimg;
    //读取人形掩码
	Mat man1, man2, man3;
	man1 = imread("C:/Users/dlgker/Desktop/man1.png");
	man2 = imread("C:/Users/dlgker/Desktop/man2.png");
	man3 = imread("C:/Users/dlgker/Desktop/man3.png");
	cvtColor(man1, man1, COLOR_BGR2GRAY);//转化为灰度图像,便于后面的图形矩匹配
	cvtColor(man2, man2, COLOR_BGR2GRAY);
	cvtColor(man3, man3, COLOR_BGR2GRAY);
	vector<vector<Point>> contours;//存取轮廓的点
	vector<Vec4i> hierarchy;//存取轮廓的层级
	//Mat	bsmaskKNN;
	namedWindow("input video", CV_WINDOW_AUTOSIZE);
	//namedWindow("MOG2", CV_WINDOW_AUTOSIZE);
	//namedWindow("KNN Model", CV_WINDOW_AUTOSIZE);
	capture.read(frame);
	//因手机像素太高,未来便于观察,把图像缩小为以前的0.6倍
	resize(frame, frame, Size(round(frame.cols*0.6), round(frame.rows*0.6)));
	//鼠标回调函数
	setMouseCallback("input video", on_MouseHandle, (void*)&frame);
	//3×3的矩形核
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
 
	// intialization BS
	//基于MIG2的背景去除法
	Ptr<BackgroundSubtractor> pMOG2 = createBackgroundSubtractorMOG2();
	//基于KNN的背景去除法
	//Ptr<BackgroundSubtractor> pKNN = createBackgroundSubtractorKNN();
	bool drawline = false;

 进过一段时间的操作,我们的代码初始化完成了。代码中man1,man2和man3读取的是相关彩色的人形掩码。关于掩码的提取相信大家不陌生,利用一些简单的OpenCV处理技术就可以实现,比如说人站在白底的墙前,拍一张照片,然后使用一次OpenCV里的阈值函数就可以提取出人形掩码,当然,也可以使用背景去除法,直接用QQ截图去截取掩码图像就可以,我当时也是图方便,也是采用的这种方法,总之,方法有很多种,大家去尝试。例如这是其中一张人形掩码,做这个人形掩码的目的是为了我们后面的轮廓Hu据匹配,以识别出人。

 此处,我们可以用两种背景剔除的方法,一种是基于BackgroundSubtractorMIG2的背景去除法,BackgroundSubtractorMOG2用于动态目标检测,用到的是基于自适应混合高斯背景建模的背景减除法,相对于BackgroundSubtractorMOG,其具有更好的抗干扰能力,特别是光照变化。还有一种是基于KNN的背景去除法,这是一种基于深度学习的方法。

    while (capture.read(frame)) {
    //因手机像素太高,未来便于观察,把图像缩小为以前的0.6倍
    resize(frame, frame, Size(round(frame.cols*0.6), round(frame.rows*0.6)));

    // MOG BS
    //用MOG方法实现背景去除
    pMOG2->apply(frame, bsmaskMOG2);
    //判断用户是否划线
    if (g_DrawingBox)
    {
        drawline = true;
    }
    if (drawline)
    {
        DrawLine(frame, g_box);
    }
    //开运算,除去相关噪点
    morphologyEx(bsmaskMOG2, bsmaskMOG2, MORPH_OPEN, kernel, Point(-1, -1));
    //设置阈值,把图像变为2值图像0->0,1->255
    threshold(bsmaskMOG2, threshimg, 200, 255, THRESH_BINARY);
    //提取轮廓
    findContours(threshimg, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    for (size_t i = 0; i < contours.size(); i++)
    {
        double length,alllength;
        //获取轮廓的长度
        length = arcLength(contours[i],1);
        alllength = frame.cols + frame.rows;
        //把轮廓长度小于阈值的轮廓去除掉,相当于再一次去除噪点
        if (length > alllength) {
            double match1, match2, match3;
            //进行Hu矩匹配,识别出视频中的人
            match1 = matchShapes(threshimg, man1, CONTOURS_MATCH_I1, 0);
            match2 = matchShapes(threshimg, man2, CONTOURS_MATCH_I1, 0);
            match3 = matchShapes(threshimg, man3, CONTOURS_MATCH_I1, 0);
            //当匹配系数足够高的时候,开始画出人的识别框,矩形框的中心点,便于撞线计数
            if (match1 < 0.1 || match2 < 0.1 || match3 < 0.1)
            {
                RotatedRect box;
                //找出最小的外接矩形
                box = minAreaRect(contours[i]);
                Point2f vertex[4];
                Point center;
                box.points(vertex);
                center.x = round((vertex[0].x + vertex[2].x + vertex[3].x + vertex[1].x) / 4);
                center.y = round((vertex[1].y + vertex[2].y + vertex[3].y + vertex[0].y) / 4);
                //通过画四条线,画出矩形框
                for (int p = 0; p < 4; p++)
                {
                    line(frame, vertex[p], vertex[(p + 1) % 4], Scalar(0, 0, 255), 2, 8, 0);
                }
                //画出矩形的中心点
                circle(frame, center, 5, Scalar(0, 255, 0), 5, 8, 0);
            }
        }
    }
    //imshow("MOG2", threshimg);
    //创建一个直线迭代器对象,
    //用于检测用户所画的红线上绿色通道上是否有上跳沿的信号
    //如果有上跳沿信号,全局变量Thecount开始计数
    LineIterator it(frame, p1, p2, 8);
    all = 0;
    for (int j = 0; j < it.count; ++j, ++it)
    {
        if (precolor == 0 && (int)(*it)[1] == 255)
        {
            Thecount++;
            //precolor = 0;
        }
        all = all + (int)(*it)[1];
        if ((int)(*it)[1] == 255)
        {
            precolor = 255;
            break;
        }
    }
    if (all == 0)
    {
        precolor = 0;
    }
    stringstream ss;
    string str;
    string str1="The Count: ";
    ss << Thecount;
    ss >> str;
    str = str1 + str;
    //把记好的数打印到图像的(20,20)坐标点上
    putText(frame, str, Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255));
    imshow("input video", frame);
    //cout << "Count:" << Thecount << endl;
    // KNN BS mask
    //pKNN->apply(frame, bsmaskKNN);
    //imshow("KNN Model", bsmaskKNN);
    char c = waitKey(3);

    if (c == 27) {
        break;
    }
}

capture.release();
return 0;
}

然后我们进入读取视频的环节,首先我们需要利用背景去除法来做背景去除。完成后的图像如下所示:

 可见,仅仅通过背景去除的图形还是不能识别的,当人经过时,人的周围会产生很大的噪声造成算法的误判。但是相对人来说,周围像素值明显偏低。因为背景去除后的图像噪声有很多,我们需要进行一次开运算才能去除部分细小的噪声。开运算后的结果:

图片中的细小噪点得到充分的减少,经过一次阈值后,结果可见如下:

可见,经过一番的操作,人形提取已基本完成。接下来就是我们的识别环节了,不慌,这个时候,我们还要最后对我们处理的图像做除去噪点的操作。这个时候,我们需要用arcLength()函数获取每个轮廓的长度,对于长度比较小的轮廓,我们将其剔除,不让其参与识别。把符合要求的轮廓与预先读取的掩码进行轮廓Hu矩匹配,因为图像的hu矩是一种具有平移、旋转和尺度不变性的图像特征。所以在图形识别方面具有非常广泛的应用。当然,现在也有非常多的识别方法,最多的就是基于深度学习的SSD和Yolov2和Yolov3模型。这些深度学习最大的优点就是识别稳定,识别效果好,准确性高。但是这些深度学习方法也有一些缺点,对硬件的要求高,特别是显卡,不然视频处理速度就很慢,对于我这种电脑没有N卡的人,也算是一种伤害。二是深度学习需要大量的训练集,而对于一些特殊的应用,劣势就明显了。好了,我们再往下说,识别出来后,我们可以把人给框起来,然后用绿色的点把人中心画出来。而对于人流量的计数,就是要利用这个绿色的点。当用户划一条线后,我们把线上的值设置为BGR为(0,0,255),我们再在这条直线上设置一个直线的迭代器,就可以把直线上的点的信息统计下来,当人穿过直线时,绿色的点会穿过直线,这时,直线上部分点的绿色通道将由0跳变到255,通过收集这种上跳沿的信号。我们就可以实现人流量的计数了。
       最后,还有两个函数的代码,也分享给大家。

//鼠标事件的回调函数
void on_MouseHandle(int event, int x, int y, int flags, void* param)
{
	Mat &image = *(Mat*)param;
	switch (event)
	{
	case EVENT_MOUSEMOVE:
	{
		if (g_DrawingBox)
		{
			g_box.width = x - g_box.x;
			g_box.height = y - g_box.y;
		}
	}
	break;
	case EVENT_LBUTTONDOWN:
	{
		g_DrawingBox = true;
		g_box = Rect(x, y, 0, 0);
	}
	break;
	case EVENT_LBUTTONUP:
	{
		g_DrawingBox = false;
		if (g_box.width < 0)
		{
			g_box.x += g_box.width;
			g_box.width *= -1;
		}
		if (g_box.height < 0)
		{
			g_box.y += g_box.height;
			g_box.height *= -1;
		}
		DrawLine(image, g_box);
	}
	break;
	default:
		break;
	}
}
//划线函数
void DrawLine(Mat &img, Rect box)
{
	
	p1 = Point(box.x,box.y);
	p2 = Point(box.x + box.width, box.y + box.height);
	line(img, p1, p2, Scalar(0, 0, 255), 3, 8);
	//rectangle(img, box.tl(), box.br(), Scalar(0, 0, 255));
}

 这样,我们基于OpenCV的简单人流量统计就完成了。下面是我们做出的最终效果:

  可见,效果还不错。可能大家会说当多人重叠,一起走过镜头的时候,可能会识别不出来。其实大家不要那么紧张,我们可以把摄像头悬挂起来,从上往下拍,利用同样的原理可以进行,基于人头的人流量统计,车流量统计……这样就不会有重叠了。

来源: https://blog.csdn.net/weixin_42532354/article/details/85373393

发表评论