`

闵大荒之旅(八) ----- 时间都去哪了

 
阅读更多

HOG+SVM做行人检测,是非常经典的做法,但是真正使用过的人可以发现,就OpenCV提供的检测算算法而言,其实时性是非常差的。事实上,OpenCV中还做了一定的优化,比如利用CPU对多尺度行人检测进行一个并行计算,但是,在我笔记本上运行一次完整的检测过程需要1~2秒不等,这种检测速度,若是应用到无人驾驶技术上,检测到人估计那人已经撞飞了。。。为了提高检测速度,利用GPU并行计算是非常合适是解决办法之一。

 

研究了CUDA、HOG,SVM有一阵子了,甚至已经着手开始并行计算的任务,但是,却忽略了一个很重要、很关键的问题-------这些大量的检测时间都耗费在哪里了?

 

感谢老师的指点,让我重新思考这个问题,虽然可能在之前我有简单的考虑过这个问题,但是,工程上是不相信简单的思考的,需要的应该是实实在在的数据!

 

时间到底消耗在哪了?是HOG计算?是多尺度检测?是SVM计算?····还是?

 

只有真正搞清楚了这个问题,才能对症下药!

 

于是,我开始重新温故整个行人检测的过程。很容易,我们发现,去除模板训练的过程(因为这个可以是离线操作,线上不需要做),最主要的操作函数是detectMultiScale函数,这是一个多尺度行人检测的函数!

 

那什么是多尺度行人检测?这里感谢钱师兄给我的解释,简单明了

模板的大小往往是确定的,用得比较多的模板(好吧,我用得比较多)是(128,64)的模板,那么模板中人的大小实际上就有了一个限制。而我们处理的待检测图片的大小是不一定的,可能是640*480,可能是1280*760,这些完全取决于你使用的硬件分辨的支持程度,大小不一带来的问题就是人的大小和模板可能相去甚远,这些大小上的差别很可能就会让算法漏检!为了解决这个问题,其中一个解决办法,也正是detectMultiScale函数中使用的办法就是,将待检测图片缩小成很多个尺寸的图片,那么这些图片中人的大小总会有一个尺寸是适合(其实也不一定,我觉得受到了图片层数的限制)模板的大小的,这样就能够检测出来了。一般默认的是64层,这就是多尺度检测!

 

我们可以看看detectMultiScale函数的源码

void HOGDescriptor::detectMultiScale(
    const Mat& img, vector<Rect>& foundLocations, vector<double>& foundWeights,
    double hitThreshold, Size winStride, Size padding,
    double scale0, double finalThreshold, bool useMeanshiftGrouping) const
{
    double scale = 1.;
    int levels = 0;
    //******************第一部分******************
    vector<double> levelScale;
    for( levels = 0; levels < nlevels; levels++ )
    {
        levelScale.push_back(scale);
        if( cvRound(img.cols/scale) < winSize.width ||
            cvRound(img.rows/scale) < winSize.height ||
            scale0 <= 1 )
            break;
        scale *= scale0;
    }
    levels = std::max(levels, 1);
    levelScale.resize(levels);
   //*********************************************
    std::vector<Rect> allCandidates;
    std::vector<double> tempScales;
    std::vector<double> tempWeights;
    std::vector<double> foundScales;
    Mutex mtx;
   //****************第二部分************************
    parallel_for_(Range(0, (int)levelScale.size()),
                 HOGInvoker(this, img, hitThreshold, winStride, padding, &levelScale[0], &allCandidates, &mtx, &tempWeights, &tempScales));
   //**********************************************
   
    std::copy(tempScales.begin(), tempScales.end(), back_inserter(foundScales));
    foundLocations.clear();
    std::copy(allCandidates.begin(), allCandidates.end(), back_inserter(foundLocations));
    foundWeights.clear();
    std::copy(tempWeights.begin(), tempWeights.end(), back_inserter(foundWeights));
    //********************第三部分***********************
    if ( useMeanshiftGrouping )
    {
        groupRectangles_meanshift(foundLocations, foundWeights, foundScales, finalThreshold, winSize);
    }
    else
    {
        groupRectangles(foundLocations, foundWeights, (int)finalThreshold, 0.2);
    }
}

 经时间测算,第一部分和第三部分时间大概只耗费很短的时间,98%以上的时间都耗费在了第二部分。看到

 parallel_for_函数,自然而然地想到了平行计算!是的CPU也是可以有线程的,但是显然相比于GPU,CPU不是很擅长这个活(就计算而言,CPU很多线程都用来执行各种进程了)。

 parallel_for_这个函数要搞明白我认为尚需时日,但或许不需要先了解那么清楚这个函数的的细节,先搞清楚一下这个函数是对什么进行并行计算的!

 实际上,这是开辟Range(0, (int)levelScale.size())个线程来完成HOGInvoker(···)函数的计算。我们找到
HOGInvoker这个类别,可能乍一看,这个函数不就是个赋值的函数么?

class HOGInvoker : public ParallelLoopBody
{
public:
    HOGInvoker( const HOGDescriptor* _hog, const Mat& _img,
                double _hitThreshold, Size _winStride, Size _padding,
                const double* _levelScale, std::vector<Rect> * _vec, Mutex* _mtx,
                std::vector<double>* _weights=0, std::vector<double>* _scales=0 )
    {
        hog = _hog;
        img = _img;
        hitThreshold = _hitThreshold;
        winStride = _winStride;
        padding = _padding;
        levelScale = _levelScale;
        vec = _vec;
        weights = _weights;
        scales = _scales;
        mtx = _mtx;
    }

    void operator()( const Range& range ) const
    {
        int i, i1 = range.start, i2 = range.end;
        double minScale = i1 > 0 ? levelScale[i1] : i2 > 1 ? levelScale[i1+1] : std::max(img.cols, img.rows);
        Size maxSz(cvCeil(img.cols/minScale), cvCeil(img.rows/minScale));
        Mat smallerImgBuf(maxSz, img.type());
        vector<Point> locations;
        vector<double> hitsWeights;

        for( i = i1; i < i2; i++ )
        {
            double scale = levelScale[i];
            Size sz(cvRound(img.cols/scale), cvRound(img.rows/scale));
            Mat smallerImg(sz, img.type(), smallerImgBuf.data);
            if( sz == img.size() )
                smallerImg = Mat(sz, img.type(), img.data, img.step);
            else
                resize(img, smallerImg, sz);
            hog->detect(smallerImg, locations, hitsWeights, hitThreshold, winStride, padding);
            Size scaledWinSize = Size(cvRound(hog->winSize.width*scale), cvRound(hog->winSize.height*scale));

            mtx->lock();
            for( size_t j = 0; j < locations.size(); j++ )
            {
                vec->push_back(Rect(cvRound(locations[j].x*scale),
                                    cvRound(locations[j].y*scale),
                                    scaledWinSize.width, scaledWinSize.height));
                if (scales)
                {
                    scales->push_back(scale);
                }
            }
            mtx->unlock();

            if (weights && (!hitsWeights.empty()))
            {
                mtx->lock();
                for (size_t j = 0; j < locations.size(); j++)
                {
                    weights->push_back(hitsWeights[j]);
                }
                mtx->unlock();
            }
        }
    }

    const HOGDescriptor* hog;
    Mat img;
    double hitThreshold;
    Size winStride;
    Size padding;
    const double* levelScale;
    std::vector<Rect>* vec;
    std::vector<double>* weights;
    std::vector<double>* scales;
    Mutex* mtx;
};

 其实学过C++的人就明白,operator函数就是一个运算符重载函数,它对“()”进行重载。而其中最主要的,也是最耗时的一步,我想大家一眼就看到了,经过时间测算,也的确是它--------detect!

 

我们再看到detect的源码

void HOGDescriptor::detect(const Mat& img,
    vector<Point>& hits, vector<double>& weights, double hitThreshold,
    Size winStride, Size padding, const vector<Point>& locations) const
{
    hits.clear();
    if( svmDetector.empty() )
        return;

    if( winStride == Size() )
        winStride = cellSize;
    Size cacheStride(gcd(winStride.width, blockStride.width),
                     gcd(winStride.height, blockStride.height));
    size_t nwindows = locations.size();
    padding.width = (int)alignSize(std::max(padding.width, 0), cacheStride.width);
    padding.height = (int)alignSize(std::max(padding.height, 0), cacheStride.height);
    Size paddedImgSize(img.cols + padding.width*2, img.rows + padding.height*2);
    //***************************第一部分**************************
    HOGCache cache(this, img, padding, padding, nwindows == 0, cacheStride);
    //************************************************************	
    if( !nwindows )
        nwindows = cache.windowsInImage(paddedImgSize, winStride).area();

    const HOGCache::BlockData* blockData = &cache.blockData[0];
    int nblocks = cache.nblocks.area();
    int blockHistogramSize = cache.blockHistogramSize;
    size_t dsize = getDescriptorSize();

    double rho = svmDetector.size() > dsize ? svmDetector[dsize] : 0;
    vector<float> blockHist(blockHistogramSize);
    //*******************第二部分***********************
    for( size_t i = 0; i < nwindows; i++ )
    {
        //********************一次for循环**********************
        Point pt0;
        if( !locations.empty() )
        {
            pt0 = locations[i];
            if( pt0.x < -padding.width || pt0.x > img.cols + padding.width - winSize.width ||
                pt0.y < -padding.height || pt0.y > img.rows + padding.height - winSize.height )
                continue;
        }
        else
        {
            pt0 = cache.getWindow(paddedImgSize, winStride, (int)i).tl() - Point(padding);
            CV_Assert(pt0.x % cacheStride.width == 0 && pt0.y % cacheStride.height == 0);
        }
        double s = rho;
        const float* svmVec = &svmDetector[0];
#ifdef HAVE_IPP
        int j;
#else
        int j, k;
#endif
        for( j = 0; j < nblocks; j++, svmVec += blockHistogramSize )
        {
            const HOGCache::BlockData& bj = blockData[j];
            Point pt = pt0 + bj.imgOffset;

            const float* vec = cache.getBlock(pt, &blockHist[0]);
#ifdef HAVE_IPP
            Ipp32f partSum;
            ippsDotProd_32f(vec,svmVec,blockHistogramSize,&partSum);
            s += (double)partSum;
#else
            for( k = 0; k <= blockHistogramSize - 4; k += 4 )
                s += vec[k]*svmVec[k] + vec[k+1]*svmVec[k+1] +
                    vec[k+2]*svmVec[k+2] + vec[k+3]*svmVec[k+3];
            for( ; k < blockHistogramSize; k++ )
                s += vec[k]*svmVec[k];
#endif
        }
        if( s >= hitThreshold )
        {
            hits.push_back(pt0);
            weights.push_back(s);
        }
        //*************************************************
    }
    //************************************************
}

 对上述几个关键的部分进行时间测试:

           第一部分: 20%的时间

           第二部分: 80%的时间

           一次for循环:   0.1ms左右

所以,时间消耗的地方我们终于找到了!20%的时间我们耗费在了HOG算子计算的前部分,80%的时间是HOG算子的计算以及处理。

针对上述结果,我及时调整了并行计算的改写方案:

 

第一阶段:      改写detect函数中的第二部分!也就是nwindows的那个for循环,每一个for循环开一个线程,一共开nwindows个线程。理由很显而易见,for循环整个时间占用很多,但是每次循环的时间却很短,这说明运行次数非常之多!这非常适合并行处理!

 

第二阶段:      改写detect函数的第一部分,正如(七)中的computeGradient函数改写一样,使其也并行计算!

 

其他不变,仍利用CPU的并行计算对64个尺度的图片进行并行处理!

下图为第一方案简单示意图:
                        

 

方案准备:

               改写CUDA最主要的是数据交换,所以这里要搞清楚要输入什么数据,要输出什么数据

               输入数据:                                               改写方案:

                                 Mat& img                                                   ptrstep<uchar>, int img_h, int img_w

                                 double hitThreshold                        

                                 size winstride                                             int winstride_w, int winstride_h

                                 size padding                                              int padding_w, int padding_h

                                 BlockData* blockdata                                ptrstepsz<uchar3>

               输出数据:

                                 vector<point> point                                   ptrstepsz<uchar3>

                                 vector<double> weight                              ptrstep<uchar>

 

               注:1.Mat img我计划输入的灰度图像,所以只能用ptrstep<uchar>类型承接,但是这个类型没有长度成员,所以只能将宽度和长度信息输入进去!

                      2.BlockData结构体包括了一个int和一个point类型,所以用三通道分别承接x,y和int;

                       
 

 

 

 

  • 大小: 186.2 KB
分享到:
评论

相关推荐

    闵大荒之旅(三)---- 抄抄改改opencv之GPUvsCPU

    NULL 博文链接:https://lps-683.iteye.com/blog/2281273

    旅游-旅游网站-旅游网站源码-旅游网站java代码-基于springboot的旅游网站-旅游项目-旅游项目代码-旅游代码-代码

    qt旅游-旅游网站-旅游网站源码-旅游网站java代码-旅游网站设计与实现-基于springboot的旅游网站-基于Web的旅游网站设计与实现-旅游项目-旅游项目代码旅游系统-旅游系统源码-旅游管理系统-旅游管理系统java代码-旅游...

    CPU历史之旅--回望过去的脚步.mht

    CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht

    旅游-旅游系统-旅游系统源码-旅游管理系统-旅游管理系统java代码-基于springboot的旅游系统-旅游项目代码-代码

    旅游-旅游系统-旅游系统源码-旅游管理系统-旅游管理系统java代码-旅游系统设计与实现-基于springboot的旅游系统-基于Web的旅游系统设计与实现-旅游网站-旅游网站代码-旅游平台-旅游平台代码-旅游项目-旅游项目代码-...

    仿Haier 海尔家电家居触屏版html5响应式手机wap企业网站模板.zip

    触屏版自适应手机wap软件网站模板 触屏版自适应手机wap软件网站模板

    手机wap网站 仿腾讯新闻.zip

    触屏版自适应手机wap软件网站模板 触屏版自适应手机wap软件网站模板

    机械设计多层储物架sw18可编辑非常好的设计图纸100%好用.zip

    机械设计多层储物架sw18可编辑非常好的设计图纸100%好用.zip

    基于普中51开发板的超声波测距+蜂鸣器报警 (附开发板原理图)

    基于普中51开发板的超声波测距+蜂鸣器报警 (附开发板原理图) 基于普中51开发板的超声波测距+蜂鸣器报警 (附开发板原理图)

    【重磅、详细、2022更新!】1990-2022上市公司环境保护税(排污费)数据大合集!

    【原创整理,严禁任何团队和个人转载获利,转载必究!】 环境保护 税的实施效应是近年来研究的热点方向之一,将上市公司环境保护税与企业创新、企业治理 等领域问题的实证研究更是如日中天。附件内为1990-2022上市公司环境保护税( 排污费)数据大合集,包括上市公司应缴环境保护税、实缴环境保护税及分项数据,样本期 长达33年!累计涵盖近15w+观测值数量,3500+样本企业,数据涵盖年度与月度 变量,可根据需要自行筛选选用!本数据集包括参考来自权威文献做法构建的衡量上市公司 环境保护税的详细数据,包括测算出的最终指标以及所有原始数据! 附 件内所有文件均包括xls、dta格式面板数据,无偿赠送您权威参考文献原文、参考代 码、原始数据(指标构建所需的各个变量都可直接查到,充分保证数据真实性、准确性!! )以及2022最新版本上市公司年度信息,充分节约您宝贵的时间,提升科研效率!本数 据集可直接用Stata、Eviews等软件进行计量检验!本数据集适用于所有涉及上 市公司环境保护税效应方向的实证研究与理论分析,更是非常适合在此基础上进行拓展研究 ,能够帮助大家高

    自动编码器autoencoder-python源码.zip

    自动编码器autoencoder-python源码.zip

    node-v10.16.2-linux-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    ONNXRuntime部署yolov5-lite目标检测包含C++和Python 源码+模型+说明.zip

    ONNXRuntime部署yolov5-lite目标检测包含C++和Python 源码+模型+说明.zip

    省级2008-2020信息传输、软件和信息技术服务业城镇单位就业人员+软件业务收入等

    省级2008-2020信息传输、软件和信息技术服务业城镇单位就业人员+软件业务收 入等 指标包括:信息传输、软件和信息技术服务业城镇单位就业人员(万人)、软件业务 收入(万元)、高技术产品出口额占商品出口额比重(%)

    基于matlab图像处理的碎纸片的拼接复原内含数据集和源码.docx

    本文档是课题研究的研究报告内含调研以及源码设计以及结果分析

    Unity3D版本游戏源码2-97草莓大作战游戏开发模板BerryMatch-Three4.2

    Unity3D版本游戏源码2-97草莓大作战游戏开发模板Berry Match-Three 4.2提取方式是百度网盘分享地址

    南京政府微门户触屏版手机wap政府网站模板下载.zip

    触屏版自适应手机wap软件网站模板 触屏版自适应手机wap软件网站模板

    node-v12.1.0-linux-ppc64le.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    某城市表层土壤重金属污染分析方案设计以及结果分析.doc

    本文档是课题研究的研究报告内含调研以及源码设计以及结果分析

    unity开发的相关知识什么是unity开发以及学习的意义

    unity开发

    HTML5+CSS+JS精品网页模板100 大学生期末大作业 Web前端网页制作

    HTML5+CSS+JS精品网页模板,设置导航条、轮翻效果,鼠标滑动效果,自动弹窗,点击事件、链接等功能;适用于大学生期末大作业或公司网页制作。响应式网页,可以根据不同的设备屏幕大小自动调整页面布局; 支持如Dreamweaver、HBuilder、Text 、Vscode 等任意html编辑软件进行编辑修改; 支持包括IE、Firefox、Chrome、Safari主流浏览器浏览; 下载文件解压缩,用Dreamweaver、HBuilder、Text 、Vscode 等任意html编辑软件打开,只需更改源代码中的文字和图片可直接使用。图片的命名和格式需要与原图片的名字和格式一致,其他的无需更改。如碰到HTML5+CSS+JS等专业技术问题,以及需要对应行业的模板等相关源码、模板、资料、教程等,随时联系博主咨询。 网页设计和制作、大学生网页课程设计、期末大作业、毕业设计、网页模板,网页成品源代码等,5000+套Web案例源码,主题涵盖各行各业,关注作者联系获取更多源码; 更多优质网页博文、网页模板移步查阅我的CSDN主页:angella.blog.csdn.net。

Global site tag (gtag.js) - Google Analytics