实时人流量检测

Zhang X, Yan J, Feng S, et al. Water Filling: Unsupervised People Counting via Vertical Kinect Sensor[C]// IEEE Ninth International Conference on Advanced Video and Signal-Based Surveillance. IEEE, 2012:215-220.

将 Kinect 放在天花板上,朝向地面,由 Kinect 传感器生成RGB和深度图,深度信息是用来达到外观不变的目的。由于人头部到 Kinect 的距离总是比身体其他部位离得更近,人数计算的问题也就等价于寻找本地数据中 局部深度值最小的区域。根据深度图的特点,water-filling算法能鲁棒地标度不变地找到这些局部区域。

整体思路:

int createImgPath(char *path, int imgType, int seqNo);
//read a frame
int getAnRGBImgFrame(IplImage *img, int seqNo);
//读取文本型深度图信息
int txtDepthDataInput(int **f_depth_map, char path[]);
//黑区补偿处理
int txtDepthDataInputWithMask(int **f_depth_map, char path[]);
//将water filling 后的检测的结果转为图像
int saveBinaryDepthImage(int **depth_map, char path[]);
//计算联通区域的中心
int calcLabelCenter(int** labeled_depth_map, vector<Man> &manSet);
//绘制人头标记框图
int drawBoundingBoxes( IplImage *depth_image,char path[], const vector<Man> &manSet);
//绘制人头标记框图,并将图片存入本地path 同时在图片上,绘制进、出人数信息
int drawBoxesWithIO( IplImage *depth_image,char path[], const vector<Man> &manSet, double *people );
//输出txt图像文件
int saveTxtImage(int **txtImage, char path[]);
//两遍扫方法连通区域求解
int twoPassTxt(int **water_filled) ;
//基于上一帧,计算当前帧的人头数
int calcDirByNearestNeighbor(vector<Man> &curSet, int curNum, vector<Man> &lastSet, int lastNum);
//模拟降雨填充效果,得到相应的g_function
int waterFilling(int **f_depth_map,int **g_depth_map,int **result_depth_map);
… … … … … …

具体实现:
Kinect本身的红外发射器功率较小,场景中的光照会产生影响,得到的深度数据不稳定的。要想获得更准确的结果,需要对深度图进行去噪处理。
去噪的方法有很多,中值滤波、高斯滤波、双边滤波等,
试了一下均值滤波(八邻域)和双边滤波(delta_r=0.5,delta_s=0,5)

知道滤波方法后,代码很很容易实现。八邻域滤波的最终实验效果会好很多,也更能满足实时性。

预处理时,要从Mat图中获取txt深度信息,转换的同时将非法距离数值过滤,然后用卷积模板进行补偿。

预处理完后就可以模拟降雨过程了
int waterFilling(int **f_depth_map, int **g_depth_map, int **result_depth_map)主要代码:

/*************************XuMengqian*********************************/
            for (int j=0;j<8;j++)
            {
                tmp_y = y+direction[j].y;
                tmp_x = x+direction[j].x;

                d[j]=f_depth_map[tmp_y][tmp_x]+g_depth_map[tmp_y][tmp_x]
                    -f_depth_map[y][x]-g_depth_map[y][x];//(tmp_x,tmp_y)是(x,y)的邻域
                if (d[j]<min_d)
                {
                    min_d=d[j];
                    min_d_x=tmp_x;
                    min_d_y=tmp_y;
                }
            }
            if ( (min_d+Rain_droppedonetime)<=0 )
            {
                x=min_d_x;
                y=min_d_y;
            }
            else
            {
                g_depth_map[y][x]+=Rain_droppedonetime;
                sum_rain+=Rain_droppedonetime;
                w=w-Rain_droppedonetime;
            }

降雨之后要找连通区域,对区域进行计数才是总人数。
连通区域标记大多数都用的是两遍扫描法,具体原理和流程网上能搜到很多解释。

其他:
openNI是Kinect的官方API,用来采集视频数据。实验中用到的一些基本操作:

/*************************XuMengqian*********************************/
    //初始化OpenNI2运行环境,看是否有错误 
    result = OpenNI::initialize();
    if(checkOpenNIError( result, "initialize content" )!=0) return -1;  //OpenNI运行环境有误    
    Device device;  
    result = device.open( openni::ANY_DEVICE );  //打开设备
    if(checkOpenNIError( result, "initialize content" )!=0) return -2;  //不存在可用的Kinect设备

    //深度视频流,创建与设置  
    VideoStream oniDepthStream; //创建深度图像流 
    result = oniDepthStream.create( device, openni::SENSOR_DEPTH );  
    VideoMode modeDepth;        //设置深度视频模式
    modeDepth.setResolution( WIDTH, HEIGHT );  
    modeDepth.setFps(fps );  
    modeDepth.setPixelFormat( PIXEL_FORMAT_DEPTH_1_MM );  
    oniDepthStream.setVideoMode(modeDepth);
    result = oniDepthStream.start();    //开启深度数据流

    //深度视频流,创建与设置
    VideoStream oniColorStream; //创建彩色视频流
    result = oniColorStream.create( device, openni::SENSOR_COLOR );       
    VideoMode modeColor;    //设置彩色视频流模式
    modeColor.setResolution( WIDTH*2, HEIGHT*2 );  
    modeColor.setFps( fps);  
    modeColor.setPixelFormat( PIXEL_FORMAT_RGB888 );  
    oniColorStream.setVideoMode( modeColor);
    result = oniColorStream.start();    //开启彩色视频流 

    //读取深度视频帧
    isDepthOk = false;
    if( oniDepthStream.readFrame( &oniDepthImg ) == STATUS_OK )  
    {
        isDepthOk = true;
        //创建16位1通道,深度值图像
        cv::Mat cvRawImg16U( oniDepthImg.getHeight(), oniDepthImg.getWidth(), CV_16UC1, (void*)oniDepthImg.getData() ); 
        //提取深度值,并存入二维数组中
        cvtMatToTxtDepth(cvRawImg16U, f_depth_map);
        //cvtMatToTxtDepthWithMask(cvRawImg16U, f_depth_map);
        //将深度距离转化为灰度图
        cvRawImg16U.convertTo( cvDepthImg, CV_8U, 255.0/(oniDepthStream.getMaxPixelValue()));
    } 

    //读取彩色视频帧
    isRGBOk = false;
    if( oniColorStream.readFrame( &oniColorImg ) == STATUS_OK )  
    {  
        isRGBOk = true;
        //将数据转换至OpenCV类型,8位3通道
        cv::Mat cvRGBImg( oniColorImg.getHeight(), oniColorImg.getWidth(), CV_8UC3, (void*)oniColorImg.getData() );  
        cv::cvtColor( cvRGBImg, cvBGRImg, CV_RGB2BGR );
    }

跟踪直接帧差,选距离小于阈值的认为是同一人。

结果:

  • 7
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值