怎么在调试的时候查看如何调试opencv源代码码详情

新浪广告共享计划>
广告共享计划
OpenCV&不能跟进进入函数源代码中调试的解决方案
这个问题其实遇到很多次了,某个参数又问题导致异常,然后却显示没得关联的源代码,只能看反汇编代码···表示汇编一塌糊涂,无意间搜索的时候看到这篇文章,找到了解决方案。截下里面关键的楼层对话就OK了。
原帖地址:
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
如题,为什么不能跟踪进入opencv的函数,查看该函数的源代码?
我的vs2008系统配置为:
1. 头文件:E:\OpenCV2.1\vc2008\include\opencv
2. 库文件:E:\OpenCV2.1\vc2008\lib\Debug
E:\OpenCV2.1\vc2008\lib\Release
注:本来应该只有E:\OpenCV2.1\vc2008\lib一个目录,但是我用vs2008打开opencv2.1的工程,对工程进行了包括debug和release的批生成,从新编译了整个opencv2.1工程,得到了最新的库,被vs自动放到了E:\OpenCV2.1\vc2008\lib\Debug和E:\OpenCV2.1\vc2008\lib\Release两个目录下。
3. 源文件:E:\OpenCV2.1\src\cv
E:\OpenCV2.1\src\cvaux
E:\OpenCV2.1\src\cvaux\vs
E:\OpenCV2.1\src\cxcore
E:\OpenCV2.1\src\highgui
E:\OpenCV2.1\src\ml
做好上述配置后,我运行自己的程序,但是无论如何都不能单句调试进入任何一个opencv函数的内部源代码进行调试,请高手指点!
//---------------------------------------------------------------------------
我成功了,根据dependcywalker的显示,我发现我的链接的dll不正确
这来源我配置的环境变量不对
如果你也是跟着这个Guide过来的,那你的环境变量也很可能不对:
那里面说到:
配置Windows环境变量Path
将D:\Program
Files\OpenCV2.0\vc2008\bin加入Windows系统环境变量Path中。加入后可能需要注销当前Windows用户(或重启)后重新登陆才生效。
就是这句话的问题,你不要把这个加入环境变量,
应该加你自己编译的那个目录
我的OpenCV的路径是C:\OpenCV2.1\
所以我把环境变量配置成:C:\OpenCV2.1\vc2008\bin\DC:\OpenCV2.1\vc2008\bin\Release
把起先配的C:\OpenCV2.1\vc2008\bin删掉
希望能对后来人有所帮助。
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
按照上面的修改 Path
的对应目录就OK了。当然你要注销或者重启一次。
这里我是OpenCV 2.2
的版本,在VS2010添加配置src目录的时候有点不一样,最方便的方法是在OpenCV
2.2中搜索src关键字,然后复制所有找到的src目录,再新建一个AllSrc的目录丢OpenCV 2.2
根目录中,把刚才复制的哪一些src文件夹& 粘贴 过去,在添加 AllSrc 这个文件夹到
VS2010的源文件目录就OK了。
编译OenCV 2.2
,OPENCV中文论坛有个是VS2010专用的这个是不用编译的版本,但是就不出有debug等调试文件,也就是能编译,想要调试还是很麻烦。建议下个需要编译的版本,OPENCV中文网站有下载的,然后可以参照:windows7&VS2010&OpenCV2.2.0&TBB&CMake&2.8.4编译配置成功
来编译,我是成功了的,偶然遇到有一个错误什么的,再编译一次我是全部通过了。good luck
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。Windows下利用CMake和VS2013编译OpenCV | 柠檬树博客
当前位置&:& / /Windows下利用CMake和VS2013编译OpenCV
获取OpenCV有两种途径,一是预编译好的库,二是下载源代码自己编译。OpenCV官网提供
下载的OpenCV既包含编译好的库,也包含源代码。通过OpenCV官网右上角的彩带,可以获取托管在GitHub上的OpenCV最新源代码。本篇博文就是利用CMake和VS2013编译GitHub上获取的最新源代码。
先来说一下自己编译的好处。
由于获取的是最新源代码,所以可以在OpenCV发布某个版本之前利用集成进来的高级功能;
自己编译可以在程序调试时跟踪源码;
预编译的库不包含一些功能,比如TBB,如果想使用这些功能只能自己动手了。
总之,应了那句俗话:自己动手,丰衣足食。
编译源代码用到的工具包括:VS2013、、Git工具(),(如果需要包含进来的话)。
一、使用CMake生成VS2013的OpenCV解决方案
利用TortoiseGit获取最新的OpenCV源代码,放在C:\OpenCVLatest。
在OpenCVLatest目录下新建目录build\vs2013x64,用于放置生成的解决方案和编译好的库。
打开CMake,在Where is the source code后面的文本框选择OpenCV的源代码,Where to build the binaries选择第2步新建的文件夹vs2013x64,如下图所示:
点击“Configure”按钮,在弹出的对话框中,选择“Visual Studio 12 Win64”,使用默认的本地编译器,如下图所示:
点击“Finish”之后,等待配置完成,如下图所示:
找到WITH_TBB,勾选:
再次点击“Configure”按钮,配置完成出现红色标记,修改TBB_INCLUDE_DIRS的Value为:C:\OpenCVLatest\tbb42_oss_win\tbb42_oss\include
再次点击“Configure”按钮,会出现如下所示:
这个地方需要注意一下,因为生成的Value值到intel64,这里需要修改为intel64/vc12,对应vs2013,如下图所示:
再次点击“Configure”按钮,这时就没有红色标记出现了,点击“Generate”按钮,生成OpenCV的解决方案。
二、VS2013编译OpenCV解决方案生成库
打开OpenCV的解决方案,右击CMakeTargets下的INSTALL,选择“生成”,生成Debug版的dll和lib,如下图所示:
修改配置为“Release”,重复步骤1,生成Release版的dll和lib,如下图所示:
生成完毕,在OpenCV解决方案下的install文件夹里就有了dll,lib和h头文件了。
虽然生成了库文件了,如果可以Debug跟踪源码,还得保留OpenCV解决方案。
三、生成过程中遇到的错误
通过CMake生成解决方案的过程中由于不仔细,导致后面生成时遇到了一些error LNK1104错误,比如说无法打开opencv_core300d.lib,无法打开tbb_debug.lib之类。原因就是在指定的目录找不到这些文件,其实出现这些问题的根本原因是在配置TBB的时候配置错了,必须在intel64后面加上那个vc12,否则找不到。通过查看附加库目录就可以知道配置的是否正确,如下图所示:
四、结束语
本篇博文,到此就结束了。对于想学习OpenCV,对计算机视觉有兴趣的朋友,动手编译自己的OpenCV库,应该是一个不错的开始。对文章中出现的不足和错误,欢迎指正。
本文链接地址:
- 21,382 浏览数
- 12,006 浏览数
- 8,868 浏览数
- 8,499 浏览数
- 8,416 浏览数
- 8,254 浏览数
- 5,291 浏览数
- 5,016 浏览数
- 3,472 浏览数
- 3,417 浏览数10213人阅读
当我们有时想查看opencv自带的函数的源代码,比如函数cvCreateImage, 此时我们选中cvCreateImage, 点击鼠标右键-&转到定义,我们会很惊讶的发现为什么只看到了cvCreateImage的一个简单声明,而没有源代码呢?这是因为openCV将很多函数被加入了函数库,并被编译成了dll,所以只能看到函数申明,没法看到源代码。下面我们详细解释并讲解如何利用cmake查看opencv的源代码。
1:解释原因
&&&&&& 要想解释这个,我们必须得熟悉opencv的安装目录&opencv的安装见我这篇blog:&。
我的安装目录在D:\Program Files\OpenCV2.4.3\opencv。在这个目录下面还有很多子目录:3rdparty、android、build,data、doc、include、modules、samples和很多cmake文件。build目录是编译生成的目录,就是用openCV源代码编译生成的2进制库文件集(dll、lib和入口头文件include)。对于编程来说,仅仅需要build这一个文件夹就可以了。那么其他文件夹是用来干什么的呢?其实源代码就包含在这些文件夹下面,因为build文件夹就是在其他文件夹的基础上CMake编译生成的。大部分源代码放在modules文件夹下例如,core文件夹下就包含了基本数据类型的定义,imgproc文件夹下包含了常用的数字图像处理函数源代码:如cvCanny()、cvSobel()。
2:如何利用cmake查看opencv的源代码
&1&cmake可以到官网去下载,安装一切按默认设置,一路点击“下一步”即可;
&2&打开Cmake工具,如下图所示:
(<span style="color:#)点击“Where&is&the&source&code”后面的那个“Browse&Source...”按钮,选择OpenCV的安装路径;例如,笔者的Opencv2.0安装路径为C:\opencv2.0src\OpenCV2.0;
(<span style="color:#)点击“Where&to&build&the&binaries”后面的那个“Browse&Build...”按钮,选择CMake生成的工程文件所在的路径;例如,笔者选择的路径为:C:\opencv2.0src\OpenCV2.0VS2008;
如下图所示:
(<span style="color:#)点击左下方的“Configure”按钮,选择编译平台,如笔者的机器上安装的是VS2008,因此,就选择Visual&Studio&9&2008。如下图所示:
(<span style="color:#)点击“Finsh”按钮,出现如下画面:
(<span style="color:#)再次点击“Config”按钮,结果如下所示:
(<span style="color:#)&点击“Generate”按钮,结果如下:
(<span style="color:#)至此,就生成了你所需要的Visual&Studio工程文件了;例如,笔者选择的工程文件所在的路径C:\opencv2.0src\OpenCV2.0VS2008就是如下的样子:
看到了opencv.sln工程了,用vs打开它,需要相应的函数的源码,在里面搜索查看就可以了。
总结为: 用CMake导出opencv 源码,生成VC&#43;&#43;项目,然后用vs打开工程,去里面搜索整个工程。
作者:小村长 &出处:&欢迎转载或分享,但请务必声明文章出处。
(新浪微博:小村长zack, 欢迎交流!)
版权声明:本文为博主原创文章,未经博主允许不得转载。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:291057次
积分:4796
积分:4796
排名:第2981名
原创:158篇
转载:30篇
评论:182条
文章:15篇
阅读:32857
文章:16篇
阅读:80261
(1)(4)(14)(13)(34)(13)(5)(8)(4)(3)(4)(6)(7)(6)(1)(1)(4)(9)(19)(5)(8)(1)(6)(7)(6)下次自动登录
现在的位置:
& 综合 & 正文
移植OpenCV的AdaBoost人脸检测算法到DM6467
http://blog.csdn.net/hyanglu1573/article/details/
上移植的步骤
要将人脸检测移植到DM6467,我们使用OpenCV现有的源码作为基础。首先,需要在PC上用C语言实现人脸检测的编写,然后移植OpenCV到DM6467,接下来再修改直至程序运行无误。
上用C语言实现人脸检测
在OpenCV安装包中已经提供了使用Haar特征、AdaBoost算法和级联分类器来检测人脸的算法源码,并且提供了经过精心训练好的级联分类器。分类器数据使用xml格式文件存储,位于$(OPENCV1.0)\ data\haarcascades\,该目录下有用于人脸、上半身和全身等特征的级联分类器,我们使用的是人脸特征haarcascade_frontalface_alt.xml。对于人脸特征也还可以使用该目录下的haarcascade_frontalface_alt2.xml,效果也不错。
由于在嵌入式系统中一般使用C语言,所以我们需要使用C语言编写实现人脸检测的程序。鉴于OpenCV中提供了易用的API,程序编写比较简单,代码量也不大,具体源码如下所示。
#include "cv.h"
#include "highgui.h"
#include &stdio.h&
void main() {
IplImage *img = 0, *img_small = 0;
//原始图像和缩放后的图像
int i, scale = 2;
//scale控制缩放比例
CvPoint point1, point2;
//检测到的人脸矩形的两个点
//检测到的人脸的矩形
CvSize scale_
//缩放后的图像大小
CvScalar color = {0, 255, 0};
//矩形框颜色
CvMemStorage* storage = cvCreateMemStorage(0);
//分配存储空间
CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( "haarcascade_frontalface_alt.xml", 0, 0, 0 );
//加载分类器
img = cvLoadImage( "lena.jpg", 0);
//加载图像
scale_size = cvSize(img-&width / scale, img-&height / scale);
//计算放缩后的图像比例
img_small = cvCreateImage(scale_size, IPL_DEPTH_8U, 1);
//创建放缩后的图像
cvResize(img, img_small, 1);
//放缩图像
cvClearMemStorage( storage );
//清零存储空间
if( cascade ) {
CvSeq* faces = cvHaarDetectObjects( img_small, cascade, storage, 1.1, 2, 0 , cvSize(30, 30) );//检测人脸
for( i = 0; i & (faces ? faces-&total : 0); i++ )
//画出人脸矩形框
rect = (CvRect*)cvGetSeqElem( faces, i );
point1 = cvPoint(rect-&x, rect-&y);
point2 = cvPoint(rect-&x + rect-&width, rect-&y + rect-&height);
cvRectangle(img_small, point1, point2, color, 2, 8, 0);
cvResize(img_small, img, 1);
//缩放图像回原来的大小
cvNamedWindow( "result", CV_WINDOW_AUTOSIZE );
//显示执行过人脸检测算法后的图像
cvShowImage( "result", img );
cvWaitKey(0);
cvDestroyWindow( "result");
cvReleaseImage( &img_small );
cvReleaseImage(&img);
程序运行后的效果见图 8。另外,经测试得到PC机上运行人脸检测算法时,对于一张512X512的只有一张人脸的图像,算法执行时间大约700ms,放缩图像至1/4即256X256时,检测时间大约200ms,如果放缩至128X128,检测时间大约为40ms。可以看出,图像大小对人脸检测算法的运行时间有非常大的影响,几乎成正比关系。所以,当我们移植人脸检测算法到资源有限的嵌入式平台时,可以将图像缩小以降低计算量,保证实时性。
8 PC机上实现人脸检测的效果图1
OpenCV的人脸检测算法不止能检测一幅图像中的单张人脸,还能检测多人脸,测试效果如图 9所示。
9 PC机上实现人脸检测的效果图2
基础架构在DM6467上的移植
要移植人脸检测算法到DM6467,需要先将OpenCV的基本数据结构、关键的一些宏定义、重要数据结构的初始化及相关操作、一些最基本的图像处理操作(如画矩形框、圆形)等移植到DM6467上,然后再逐步移植各个高层算法,包括人脸检测算法。
由于嵌入式平台中一般使用C语言进行编程,而OpenCV中包含部分C++格式的代码,在移植的时候这部分代码在编译器中无法识别,所以需要删除掉C++格式的代码或者编写对应的C语言实现。鉴于网上已有的EMCV是一个不错的OpenCV嵌入式版本,我们可以在其基础上进行修改和添加。
在移植OpenCV到DM6467上时,需要考虑一些细节问题,其中很关键的一个问题是如何实现ARM端的无缝调用各个OpenCV算法。如果将每种不同的算法分别封装成一个codec,那么工作量很大并且有许多重复性工作。对于这个问题,TI提供了C6Accel这个codec,集成了DSPLIB、IMGLIB和VLIB等库,功能强大,效率极高,使用方便,并且很关键的是扩展性良好。所以,我们是在C6Accel的基础上移植OpenCV的。对于C6Accel的介绍请参考原来的文档《使用C6Accel进行Sobel处理》。
C6Accel包含两个版本,1.x版本对应C64x和C64x+的DSP,2.x版本对应C674x的DSP,这两类DSP的区别主要就是是否具有硬件浮点计算单元,C64x系列是不带硬件浮点单元的。我们的移植是在1.x版本的基础上进行的,参考了2.x版本的源码。在C6Accel 2.x版本中,DSP端的OpenCV算法是封装成了库文件的,无法修改,使用不方便,所以我们需要自己修改EMCV源码以适应DSP平台。对于具体的移植过程请参考原来的文档《移植OpenCV到ARM并集成到C6Accel》。
人脸检测算法相关代码的移植
在完成了OpenCV基本数据结构的移植之后,还需要移植一些高层图像处理函数,包括cvResize、cvIntegral和cvCanny,这几个函数是cvHaarDetectObjects函数中所需要调用的。其中cvCanny函数可以通过参数设置来选择是否执行(默认是不执行)。由于cvCanny算法比较复杂,并且大量使用了C++语法,移植难度较大,所以我们在移植人脸检测算法时默认不使用canny滤波。另外,为了提高人脸检测算法的识别率,可以使用cvEqualizeHist函数增强图像对比度,然后再进行人脸检测。不过为了简单起见,暂时也还没有移植该函数。经测试发现,AdaBoost人脸检测算法具有很高的检测率,即使不使用cvEqualizeHist也可以在绝大部分情况下成功检测到人脸。并且,不使用cvEqualizeHist也可以降低算法复杂度,减轻DSP的负担。
OpenCV的源码模块清晰,函数之间耦合程度低,移植比较方便。在移植人脸检测算法时,对于算法部分的代码修改很少,主要包括两部分:一是删除其中一些使用IPP的代码,如果不删除编译会出问题。至于如何正确地删除IPP相关代码请参考之前的文档《移植OpenCV到嵌入式平台的注意事项》。二是如何处理其中的CvType haar_type这个结构体,下面详细讲解这个问题。
CvType haar_type( CV_TYPE_NAME_HAAR, icvIsHaarClassifier,
(CvReleaseFunc)cvReleaseHaarClassifierCascade,
icvReadHaarClassifier, icvWriteHaarClassifier,
icvCloneHaarClassifier );
haar_type该结构体是与级联分类器文件相关的,用于判断某xml文件是否是haar级联分类器、如何读取xml文件中的数据到cascade结构体以及其他一些复制、写和释放操作。该结构体相当于是一个注册函数,将cvhaar.c文件中的一些函数注册到系统,经系统统一调用。也就是说由于xml文件可能包含各种OpenCV中所使用的数据,每种数据格式需要对应的函数来读取其中的数据。当使用cvLoad检测到xml文件是haar级联分类器文件时,OpenCV就调用cvhaar.c文件中通过CvType注册的对应的读取函数来读xml文件中的数据到cascade结构体。
由于使用cvLoad函数加载分类器xml文件的代码中包含了部分C++代码和语法,并且很难修改成C语言版,所以我干脆放弃使用cvLoad来加载级联分类器,而是在PC上将级联分类器存储为自定义格式的文件,然后再自己编写函数来读取文件并将数据传递给cascade结构体。这样做既减轻了移植的工作量,也降低了算法复杂度。至于级联分类器具体的处理办法见下一节内容。
另外,为了进一步降低DSP负担,我们还可以通过仔细阅读代码然后删掉一些无用的片段。暂时只删掉了一段对cascade数据进行有效性检查的代码,如下所示(红色部分为最后删掉了的)。这部分代码包括四层循环,相当复杂,如果我们保证了传入的cascade数据无误,那么删除掉这部分代码可以减少很多计算量。
for( i = 0; i & cascade-& i++ )
CvHaarStageClassifier* stage_classifier = cascade-&stage_classifier +
if( !stage_classifier-&classifier || stage_classifier-&count &= 0 )
sprintf( errorstr, "header of the stage classifier #%d is invalid " "(has null pointers or non-positive classfier count)", i );
CV_ERROR( CV_StsError, errorstr );
max_count = MAX( max_count, stage_classifier-&count );
total_classifiers += stage_classifier-&
for( j = 0; j & stage_classifier-& j++ )
CvHaarClassifier* classifier = stage_classifier-&classifier +
total_nodes += classifier-&
for( l = 0; l & classifier-& l++ )
for( k = 0; k & CV_HAAR_FEATURE_MAX; k++ )
if( classifier-&haar_feature[l].rect[k].r.width )
CvRect r = classifier-&haar_feature[l].rect[k].r;
int tilted = classifier-&haar_feature[l].
has_tilted_features |= tilted != 0;
if( r.width & 0 || r.height & 0 || r.y & 0 || r.x + r.width & orig_window_size.width
|| (!tilted && (r.x & 0 || r.y + r.height & orig_window_size.height))
|| tilted && (r.x - r.height & 0 ||
r.y + r.width + r.height & orig_window_size.height)))
sprintf( errorstr, "rectangle #%d of the classifier #%d of "
"the stage classifier #%d is not inside "
"the reference (original) cascade window", k, j, i );
CV_ERROR( CV_StsNullPtr, errorstr );
除了以上部分的修改,已经完成的另外一个大修改是将hidcascade的创建放在了初始化阶段、加载分类器数据之后。icvCreateHidHaarClassifierCascade函数用于创建hidcascade结构体并初始化,原来的代码中这部分是放在cvHaarDetectObjects函数中的,我现在把它提出来放到读取分类器文件之后,也即交给ARM处理,DSP专注于运算。
if( !cascade-&hid_cascade )
CV_CALL( icvCreateHidHaarClassifierCascade(cascade) );
OpenCV中将级联分类器数据存储为xml文件,读取时非常复杂,函数层层嵌套,并且还需要很多递归操作。为了降低复杂度,我将haar分类器数据按最简单的格式存储,只包含纯的数据,不含任何其他冗余信息。存储的顺序就是按照cascade结构体中个成员的定义顺序来存储的,具体的存储代码如下所示。
int SaveCascade(CvHaarClassifierCascade *cascade)
int i, j, k, m,
if((haar = fopen("haar_feature.bin", "wb")) == NULL) return -1;
tempi = cascade-&fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&orig_window_size.fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&orig_window_size.fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&real_window_size.fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&real_window_size.fwrite(&tempi, sizeof(int), 1, haar);
tempd = cascade-&fwrite(&tempd, sizeof(double), 1, haar);
for(i = 0; i & cascade-& i ++) {
tempi = cascade-&stage_classifier[i].fwrite(&tempi, sizeof(int), 1, haar);
tempf = cascade-&stage_classifier[i].fwrite(&tempf, sizeof(float), 1, haar);
for (j = 0; j & cascade-&stage_classifier[i]. j ++)
tempi = cascade-&stage_classifier[i].classifier[j].fwrite(&tempi, sizeof(int), 1, haar);
printf("cascade-&stage_classifier[%d].classifier[%d].count=%d\n", i, j, cascade-&stage_classifier[i].classifier[j].count);
for(k = 0; k & cascade-&stage_classifier[i].classifier[j]. k ++)
tempi = cascade-&stage_classifier[i].classifier[j].haar_feature[k].fwrite(&tempi, sizeof(int), 1, haar);
for(m = 0; m & 3; m ++)
tempi = cascade-&stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.x;fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.y;fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.fwrite(&tempi, sizeof(int), 1, haar);
tempf = cascade-&stage_classifier[i].classifier[j].haar_feature[k].rect[m].fwrite(&tempf, sizeof(float), 1, haar);
tempf = *(cascade-&stage_classifier[i].classifier[j].threshold);fwrite(&tempf, sizeof(float), 1, haar);
tempf = *(cascade-&stage_classifier[i].classifier[j].threshold + 1);fwrite(&tempf, sizeof(float), 1, haar);
tempi = *(cascade-&stage_classifier[i].classifier[j].left);fwrite(&tempi, sizeof(int), 1, haar);
tempi = *(cascade-&stage_classifier[i].classifier[j].left + 1);fwrite(&tempi, sizeof(int), 1, haar);
tempi = *(cascade-&stage_classifier[i].classifier[j].right);fwrite(&tempi, sizeof(int), 1, haar);
tempi = *(cascade-&stage_classifier[i].classifier[j].right + 1);fwrite(&tempi, sizeof(int), 1, haar);
tempf = *(cascade-&stage_classifier[i].classifier[j].alpha);fwrite(&tempf, sizeof(float), 1, haar);
tempf = *(cascade-&stage_classifier[i].classifier[j].alpha + 1);fwrite(&tempf, sizeof(float), 1, haar);
tempi = cascade-&stage_classifier[i].fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&stage_classifier[i].fwrite(&tempi, sizeof(int), 1, haar);
tempi = cascade-&stage_classifier[i].fwrite(&tempi, sizeof(int), 1, haar);
fclose(haar);
文件的读取与存储是对应操作,只需要按照以上顺序读取到cascade结构体中即可。重点需要考虑cascade结构体中包含的许多指针,这些指针在读取数据时需要根据实际情况来分配存储空间,例如:
cascade-&stage_classifier = (CvHaarStageClassifier *)malloc(cascade-&count * sizeof(CvHaarStageClassifier));
cascade-&stage_classifier[i].classifier = (CvHaarClassifier *)malloc((cascade-&stage_classifier[i].count) * sizeof(CvHaarClassifier));
cascade-&stage_classifier[i].classifier[j].threshold = (float *)malloc(2 * sizeof(float));
在上一节中加载cascade时使用的内存分配方法经验证在DM6467上会出错,编译没有问题,但运行程序时出现如图 10所示的错误。
可以看出,由于cascade结构体要占用大量琐碎的内存空间,CMEM在分配内存时超过一定量就会出现错误,提示内存不足。这是因为CMEM模块主要是用来分配大块的物理连续内存,对于这种大量的琐碎内存分配能力不足,结果导致内存碎片过多,进而内存分配失败。
10 内存分配错误
为了解决这个问题,我们需要将cascade结构体所占用的内存空间大小计算出来,然后分配一整块连续内存空间给cascade。在查看icvCreateHidHaarClassifierCascade函数源码时意外地发现该函数中对于hidcascade的内存就是连续分配的,所以我们可以参考其方案来为cascade分配连续内存。最终的内存分配代码如下所示:
#define STAGE_CLASSIFIER_COUNT 22
#define CLASSIFIER_COUNT 2135
int classifier_count[22] = {3,16,21,39,33,44,50,51,56,71,80,103,111,102,135,137,140,160,177,182,211,213};
Memory_AllocParams cvMemParams = {Memory_CONTIGHEAP, Memory_NONCACHED, Memory_DEFAULTALIGNMENT, 0};
CvHaarClassifierCascade * LoadCascade()
FILE *haar = NULL;
int i, j, k, m, tempi, total_
CvHaarClassifierCascade *cascade = NULL;
total_size = sizeof(CvHaarClassifierCascade) + STAGE_CLASSIFIER_COUNT * sizeof(CvHaarStageClassifier)
+ CLASSIFIER_COUNT * (sizeof(CvHaarClassifier) + sizeof(CvHaarFeature) + 4 * sizeof(int) + 4 * sizeof(float));
cascade = (CvHaarClassifierCascade *)Memory_alloc(total_size, &cvMemParams);//malloc(total_size);
cascade-&stage_classifier = (CvHaarStageClassifier *)(cascade + 1);
其中,STAGE_CLASSIFIER_COUNT、CLASSIFIER_COUNT和classifier_count[22]的值都是通过在存储cascade数据到文件时计算出来的。由于我们现在只使用一个固定的人脸分类器文件,所以可以将这个参数设置为固定值,如果要使代码能够读取其他文件,那么可以在存储cascade时将这几个参数的值存储在文件的最开始部分,在读取时就可以快速确定这些参数的值了。
DM6467是ARM+DSP双核架构,ARM端的Linux使用虚拟地址,DSP端的DSP/BIOS使用物理地址,ARM端的指针不能直接传递给DSP使用,因此,在将数据从ARM端传递到DSP端时需要进行虚拟地址和物理地址之间的转换。
对于虚拟地址和物理地址之间的转换,TI提供了CMEM模块可以很方便的进行地址转换。如果在分配内存时我们直接使用malloc分配,那么是无法获取物理地址的。所以,如果某段数据需要传递到DSP端,那么必须使用CMEM模块进行内存分配。
C6Accel使用了CodecEngine中的iUniversal,对于提供的一帧视频数据是提供了地址转换操作的,但是在进行人脸检测时,需要传递级联分类器cascade结构体,该结构体中包含一些指针变量,这些指针就需要自己在ARM端动手进行地址转换。CMEM模块已经提供了易用的API进行地址转换,具体的转换代码如下所示:
void traverse_and_translate_cascade(CvHaarClassifierCascade *cascade )
CvHaarStageClassifier *
CvHaarClassifier *
CvHaarFeature *
int stage_count, classifier_count, feature_
unsigned int new_thresh, new_left, new_right, new_
stage_count = cascade-&
for (i = 0; i & stage_ i++)
stage = cascade-&stage_classifier +
classifier_count = stage-&
for (j = 0; j & classifier_ j++)
classifier = stage-&classifier +
feature_count = classifier-&
for (k = 0; k & feature_ k++)
feature = classifier-&haar_feature +
classifier-&haar_feature = classifier-&haar_feature ? (void *)Memory_getBufferPhysicalAddress(classifier-&haar_feature, sizeof(CvHaarFeature),NULL) : NULL;
classifier-&threshold = classifier-&threshold ? (void *)Memory_getBufferPhysicalAddress(classifier-&threshold,sizeof(float),NULL) : NULL;
classifier-&left = classifier-&left ? (void *)Memory_getBufferPhysicalAddress(classifier-&left,sizeof(int),NULL) : NULL;
classifier-&right = classifier-&right ? (void *)Memory_getBufferPhysicalAddress(classifier-&right,sizeof(int),NULL) : NULL;
classifier-&alpha = classifier-&alpha ? (void *)Memory_getBufferPhysicalAddress(classifier-&alpha,sizeof(float),NULL) : NULL;
Memory_cacheWbInv( (void *)classifier, sizeof(CvHaarClassifier));
stage-&classifier = stage-&classifier ? (void *)Memory_getBufferPhysicalAddress(stage-&classifier,sizeof(CvHaarClassifier),NULL) : NULL;
Memory_cacheWbInv( (void *)stage,sizeof(CvHaarStageClassifier));
traverse_and_translate_hid_cascade(cascade-&hid_cascade, fp);
cascade-&stage_classifier = cascade-&stage_classifier ? (void *)Memory_getBufferPhysicalAddress(cascade-&stage_classifier,sizeof(CvHaarStageClassifier),NULL) : NULL;
cascade-&hid_cascade = cascade-&hid_cascade ? (void *)Memory_getBufferPhysicalAddress(cascade-&hid_cascade,sizeof(CvHidHaarClassifierCascade),NULL) : NULL;
(unsigned int)cascade-&hid_cascade);
Memory_cacheWbInvAll();
fclose(fp);
中使用malloc
为了规范DSP上算法的编写,降低集成和移植的难度,TI提出了xDAIS算法标准,并在其基础上针对多媒体应用而对xDAIS进行扩展,成为xDM标准。符合xDM标准的算法可以无缝集成到Codec Engine框架中,很方便地供ARM端调用。xDAIS算法标准规定算法不能自己分配内存,必须是向系统申请,所有的内存都由系统统一分配、管理和释放。
在OpenCV中,大量的图像处理算法都有各种各样的内存需求,如果对所有的内存需求都需要向系统申请,那么移植OpenCV到DSP就几乎是不可能的事情了,要修改的代码太多甚至可能根本无法修改。所以,我们需要考虑如何使得一个DSP端的codec能够自己分配内存。
经过多方查找,终于在一个博客中发现了相关信息。C语言中分配内存一般我们使用malloc函数,malloc函数申请的内存默认是定位到动态存储区的堆区(Heap)中,在TI的code generation tool编译器中需要使用伪代码将用malloc分配的内存重定位到堆区。如果没有指定malloc分配内存所放的位置,那么就会内存分配失败。其实,在codec中不能分配内存就是因为默认时没有指定malloc分配内存所放置的位置。这么来看,要使得DSP端的codec使用malloc就是很容易的了。
在Codec Server中,mmemap.tci文件用于分配各个段的具体起始物理地址和段大小,server.tcf文件用于配置与内存相关的一些参数,也包括代码重定位的配置。
修改在$(C6ACCEL_INSTALL_DIR)/c6accel/soc/packages/ti/c6accel_unitserver/dm6467/中的server.tcf文件,定为malloc到heap区(添加以下红色代码)。
===========================================================================
MEM : Global
===========================================================================
prog.module("MEM").BIOSOBJSEG = bios.DDRALGHEAP;
prog.module("MEM").MALLOCSEG
= bios.DDRALGHEAP;
在移植人脸算法到DM6467上的过程中,可以使用一些小技巧,以减轻工作量、加快移植速度,提高算法效率等。具体来说,我在移植过程中收获了以下一些经验:
人脸检测算法比较复杂,要移植到嵌入式平台,必须对算法的执行过程有清晰的了解。要达到该要求,大家直接想到的肯定是单步调试。但是由于一般在PC上编写OpenCV程序时都是使用了库,无法单步查看源码,所以,我们可以把OpenCV中cv和cxcore部分的源码全部拿出来和应用程序一起组成一个VS2010工程,然后再单步运行程序,就可以查看算法每一步是如何执行。了解了算法的细节之后移植才会更容易。
由于移植OpenCV源码移植到嵌入式平台时需要做很多修改,修改过程中出现很多错误是难免的事,那么代码的调试工作就是一件很烦人的事了。为了降低调试的复杂度,强烈建议将算法在PC上编写好之后再移植。VS2010拥有强大的调试能力,可以很容易发现代码中的错误和bug。
在调试程序时一定要多做备份。很可能原来好的代码经过多次修改之后出现错误再改回去就很困难了,所以,备份程序时非常重要的。每次程序做了大修改或者每次程序调试成功之后都需要保存一份备份文件。同时,还可以使用虚拟机的snapshot功能很方便地备份整个虚拟机。在使用snapshot时注意它可以使用分支备份,这是很好一个功能。另外,为了减小备份的文件大小,建议每次都讲虚拟机关闭或者中止之后再备份。
在编程序之前一定要想好该怎么编,不要没有思路就开始瞎编,这很可能导致在没有弄清楚原理时编写的错误代码在后期很难发现。要知道其实编写程序是很快的,但要知道怎么编这才更重要。
在将级联分类器数据存储到文件时,如果fwrite中使用”wt”参数(文本文件)会导致最后的文件大小比实际数据大一点点,进而导致读取数据时出错。我没有发现这是什么原因,初步估计是与文件分页相关。后来将”wt”参数修改为”wb”就正确了。
最终程序运行效果如图 11和图 12所示。
11 人脸检测效果图1
12 人脸检测效果图2
可以看出,程序对于单张人脸和多人脸都能成功识别,并且经测试发现,当人脸倾斜角度不是很大时也完全可以识别,这说明AdaBoost人脸检测算法鲁棒性极好。
在显示器输出人脸检测结果的同时,串口终端输出的一些程序运行信息如图 13所示。
13 串口终端输出信息
从图 13中可以看出,AdaBoost人脸检测算法计算量很大,DSP一直接近满负荷,ARM端CPU占有率较低,因为在等待DSP完成计算返回结果。由于这人脸检测算法移植到DM6467的工作是初步完成,还没有对代码进行任何优化,现在已经能够达到2帧每秒的速度也还算是不错了。
从上一节可以看出,当前人脸检测在DM6467上运行的效果还很一般,速度只有2帧每秒,完全无法达到实时性的要求,所以待解决的问题主要就是如何提高算法效率,提高实时性,具体来说还有以下一些工作可以做:
仔细阅读人脸检测算法源码,删除无用部分,减小代码量以及降低算法复杂度。
对输入图像设置人脸检测的感兴趣区域,减小计算窗口,这可以大大降低人脸检测算法的运算时间。
由于视频中连续两帧之间的关联性很大,所以可以根据上一帧检测到人脸的位置来大致判断下一帧中人脸所在的位置。
图像积分算法可以交由VLIB来处理,由于VLIB是使用C和汇编混合编程,算法经过优化,效率极高,所以可以先用VLIB进行积分再由OpenCV检测人脸。
可以在人脸检测之前将图像放缩至1/4或者更小,缩短人脸检测的时间。
可以平衡ARM和DSP端的负载分配,当前情况下ARM端比较闲而DSP端一直满载运行,所以可以再将一部分计算交给ARM来处理,例如resize。
可以动态调整cvHaarDetectObjects函数的参数。
增加图像直方图均衡化的功能(cvEqualizeHist),增强图像对比度,提高人脸检测算法的识别率。
在人脸检测算法中将一些复杂代码进行优化,包括线性汇编、循环展开、内联函数和宏定义等,也还可以使用DSPLIB、IMGLIB等库提供的函数。
考虑编译器优化,通过设置编译参数提高流水线效率。
使用cache,将图像分块放置到cache中计算,可以大大缩短计算时间。
使用EDMA进行内存搬运,大大降低CPU负担,使CPU专注于运算。
考虑如何优化指针对齐,提高从内存中读取数据的速度。
考虑如何减少内存碎片,尽量使用连续内存。
在完成人脸检测之后,如果时间充足,那么可以再拿一段时间来进行代码优化,做到每秒10帧以上,基本达到实时性要求。
既然我们现在已经能够检测人脸,根据Haar-AdaBoost算法,我们也可以检测其他刚性物体,这只需要一个训练过程,虽然训练过程比较麻烦。
&&&&推荐文章:
【上篇】【下篇】

我要回帖

更多关于 opencv人脸检测源代码 的文章

 

随机推荐