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;
相关推荐
NULL 博文链接:https://lps-683.iteye.com/blog/2281273
qt旅游-旅游网站-旅游网站源码-旅游网站java代码-旅游网站设计与实现-基于springboot的旅游网站-基于Web的旅游网站设计与实现-旅游项目-旅游项目代码旅游系统-旅游系统源码-旅游管理系统-旅游管理系统java代码-旅游...
CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht CPU历史之旅--回望过去的脚步.mht
旅游-旅游系统-旅游系统源码-旅游管理系统-旅游管理系统java代码-旅游系统设计与实现-基于springboot的旅游系统-基于Web的旅游系统设计与实现-旅游网站-旅游网站代码-旅游平台-旅游平台代码-旅游项目-旅游项目代码-...
触屏版自适应手机wap软件网站模板 触屏版自适应手机wap软件网站模板
触屏版自适应手机wap软件网站模板 触屏版自适应手机wap软件网站模板
机械设计多层储物架sw18可编辑非常好的设计图纸100%好用.zip
基于普中51开发板的超声波测距+蜂鸣器报警 (附开发板原理图) 基于普中51开发板的超声波测距+蜂鸣器报警 (附开发板原理图)
【原创整理,严禁任何团队和个人转载获利,转载必究!】 环境保护 税的实施效应是近年来研究的热点方向之一,将上市公司环境保护税与企业创新、企业治理 等领域问题的实证研究更是如日中天。附件内为1990-2022上市公司环境保护税( 排污费)数据大合集,包括上市公司应缴环境保护税、实缴环境保护税及分项数据,样本期 长达33年!累计涵盖近15w+观测值数量,3500+样本企业,数据涵盖年度与月度 变量,可根据需要自行筛选选用!本数据集包括参考来自权威文献做法构建的衡量上市公司 环境保护税的详细数据,包括测算出的最终指标以及所有原始数据! 附 件内所有文件均包括xls、dta格式面板数据,无偿赠送您权威参考文献原文、参考代 码、原始数据(指标构建所需的各个变量都可直接查到,充分保证数据真实性、准确性!! )以及2022最新版本上市公司年度信息,充分节约您宝贵的时间,提升科研效率!本数 据集可直接用Stata、Eviews等软件进行计量检验!本数据集适用于所有涉及上 市公司环境保护税效应方向的实证研究与理论分析,更是非常适合在此基础上进行拓展研究 ,能够帮助大家高
自动编码器autoencoder-python源码.zip
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
省级2008-2020信息传输、软件和信息技术服务业城镇单位就业人员+软件业务收 入等 指标包括:信息传输、软件和信息技术服务业城镇单位就业人员(万人)、软件业务 收入(万元)、高技术产品出口额占商品出口额比重(%)
本文档是课题研究的研究报告内含调研以及源码设计以及结果分析
Unity3D版本游戏源码2-97草莓大作战游戏开发模板Berry Match-Three 4.2提取方式是百度网盘分享地址
触屏版自适应手机wap软件网站模板 触屏版自适应手机wap软件网站模板
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提高了应用性能,简化了开发流程,并且能更快地响应市场需求。
本文档是课题研究的研究报告内含调研以及源码设计以及结果分析
unity开发
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。