VC++在数字图像处理中的几点技巧与经验.docx
- 文档编号:14191326
- 上传时间:2023-06-21
- 格式:DOCX
- 页数:15
- 大小:23.58KB
VC++在数字图像处理中的几点技巧与经验.docx
《VC++在数字图像处理中的几点技巧与经验.docx》由会员分享,可在线阅读,更多相关《VC++在数字图像处理中的几点技巧与经验.docx(15页珍藏版)》请在冰点文库上搜索。
VC++在数字图像处理中的几点技巧与经验
VC++在数字图像处理中的几点技巧与经验
VisualC++作为一个功能非常强大的可视化应用程序开发工具,是计算机界公认的最优秀的应用开发工具之一。
Microsoft的基本类库MFC使得开发Windows应用程序比以往任何时候都要容易。
C++提供的各种函数、指针操作和直接对硬件操作使得图像处理速度较快,专门为VC++设计的OpenGL和DirectX技术可以使开发人员在Windows环境下比较容易地完成图像图形的高性能处理和显示。
用VC++编制的应用软件无论在处理速度和图像的显示等方面都表现出较高的效率,因此在编制基于Windows的各类数字图像处理程序时,VC++无疑是程序员进行图像处理的最佳选择。
本文主要介绍了本人在用VC++进行图像基本处理时获得一些技巧与经验,希望与广大爱好图像处理的编程者分享。
一、模板(template)的使用
对于图像处理应用软件来说,图像数据的管理是至关重要的,在"电脑编程与技巧"杂志99年11期,本人介绍了一个封装的通用图像基类(CImage),可完成BMP格式图像数据的管理和一些基本的图像处理功能。
但是该CImage类只是对位图进行处理,即操作的数据类型为无符号8位字符型(BYTE)。
但是在进行边缘检测、相关跟踪、快速傅立叶变换等图像处理中经常需要定义一些int或float类型的二维数组来保存处理的中间结果或最终结果。
另外如果用OpenGL编程来进行图像处理和显示,为了保证跨平台特性,OpenGL定义了一套数据类型:
有无符号8位字符型(BYTE)、16位短整型(short)、无符号16位短整型(unsignedshort)、32位字符型(int)、无符号32位字符型(unsignedint)、单精度浮点数(float)等类型。
如果对应于每一种数据类型都定义一个类,这样
虽然能解决问题,但并不是一个明智的办法。
例如要修改一个变量或函数,那对应于每一种数据类型定义的类多要做同样的修改,毫无疑问,这种方法操作繁琐,容易发生错误,需要做大量的拷贝修改工作,因此是一种低效的方法。
C++引入的模板(template)概念,这一个关键字会告诉编译器下面的定义将操作一个或更多的非特定的类型。
只有当对象被定义时,这些类型才必须被指定以使编译器能够替代它们。
因此可以用一个非特定的参数来作类型名称,用该非特定的参数来定义一个通用的数据类或一个函数,诸如于MFC类库中的CArrary、CTypedPtrList等类。
下面给出了支持不同数据类型的一个图像二维数据模板类和一个快速排序类.
1.定义不同数据类型的数据模板类
数据模板类的定义如下:
//--------不同数据类型图像二维数组模板的定义----------
template
classImageTemplate
{
public:
T**lp_AddRow;//二维数组的数据指针
unsignedintWidth;//数组的宽度(列数)
unsignedintHeight;//数组的高度(行数)
unsignedintImageSize;//数组的大小
public:
ImageTemplate(void);//构造函数
~ImageTemplate(void);//析构函数
//带参数的析构函数
ImageTemplate(unsignedintw,unsignedinth);
//分配数据内存的函数
voidConstruct(unsignedintw,unsignedinth);
//释放数据内存的函数
voidDeleteData();
};
//构造函数
template
ImageTemplate
:
ImageTemplate(void)
{
lp_AddRow=NULL;
Width=Height=0;
}
//析构函数
template
ImageTemplate
:
~ImageTemplate(void)
{
DeleteData();
}
//带参数的析构函数
template
ImageTemplate
:
ImageTemplate(unsignedintw,unsignedinth)
{
lp_AddRow=NULL;
Width=w;
Height=h;
ImageSize=w*h;
Construct(w,h);
}
//分配数据内存的函数
template
voidImageTemplate
:
Construct(unsignedintw,unsignedinth)
{
DeleteData();
if(Height>0)lp_AddRow=newT*[Height];
if(ImageSize>0)
{
lp_AddRow[0]=newT[ImageSize];
memset(lp_AddRow[0],0,ImageSize*sizeof(T));
for(unsignedinti=1;i lp_AddRow[i]=lp_AddRow[i-1]+Width; } } //释放数据内存的函数 template voidImageTemplate : DeleteData() { if(lp_AddRow! =NULL) { if(lp_AddRow[0]! =NULL)delete[]lp_AddRow[0]; delete[]lp_AddRow; lp_AddRow=NULL; } } //定义不同数据的类 typedefImageTemplate typedefImageTemplate typedefImageTemplate typedefImageTemplate 在程序中使用方法如下: FloatImagefImage(640,480); SintImagesImage(640,480); 修改或获取数据可以通过公共的数据指针变量lp_AddRow,如 for(intj=0;j<480;j++) for(inti=0;i<640;i++) { fImage.lp_AddRow[j][i]=i*j/100.0f; iImage.lp_AddRow[j][i]=20*(i+j); } 由于图像处理有着快速的要求,因此一般将数据指针作为公共变量直接使用,如果考虑到程序的安全性和稳定性,可以将该数据指针定义为私有变量,通过重载的操作符号[]来进行数据的修改和获取。 2.支持不同数据类型的快速排序的类 在图像处理中,对于提取出来的各种特征量要进行分析和识别,快速数据排序是其中常用的一种算法。 虽然可以用函数重载的方法对不同的类型编制该类型数据的排序程序,但是引入的代码较多,因此函数重载的方法是一种低效的方法。 本快速排序的类的排序功能由c标准库 qsort(void*base,size_tnum,size_twidth, int(__cdecl*compare)(constvoid*elem1,constvoid*elem2)); 函数来完成,其中参数含义如下: void*base数据指针 size_tnum数据个数 size_twidth单个数据的字节宽度 int(__cdecl*compare)比较函数 对应于不同类型的数据使用该函数,按一般的方法需要编制对应不同数据类型的比较函数,而且还要进行数据类型强制转换,因此比较麻烦。 利用下面定义的类就可以很方便的实现数据的快速排序。 //基于模板的快速排序类 template classLQuickSort { public: //升序排列的比较函数 staticintcmpT1(constvoid*ap,constvoid*bp); //降序排列的比较函数 staticintcmpT2(constvoid*ap,constvoid*bp); //排序的函数 staticvoidrank(T*lp,//数据的指针 intlength,//数据的个数 BOOLIsAscend=true);//升序 }; //升序排列的比较函数 template intLQuickSort : cmpT1(constvoid*ap,constvoid*bp) { Ta=*(T*)ap; Tb=*(T*)bp; if(a>b) return1; if(a return-1; return0; } //降序排列的比较函数 template intLQuickSort : cmpT2(constvoid*ap,constvoid*bp) { Ta=*(T*)ap; Tb=*(T*)bp; if(a>b) return-1; if(a return1; return0; } //------排序的函数---------------------------- //IsAscend=true //T*lp,数据的指针 //intlength,数据的个数 //BOOLIsAscendtrue升序排列,false降序排列 template voidLQuickSort : rank(T*lp,intlength,BOOLIsAscend) { if(IsAscend) qsort((void*)lp,length,sizeof(T),cmpT1); else qsort((void*)lp,length,sizeof(T),cmpT2); } 由于定义的类的成员函数为静态(static)类型,因此也可以不用声明该类的对象而调用排序函数。 待排序的数据格式如下: intix[10]={10,-10,2,...}; floatfx[10]={10.0,-10.4,2.3,...}; 具体的排序用法有下面的两种。 用法一: 定义类的对象 LQuickSort LQuickSort iSort.rank(ix,10,true);//升序排列 iSort.rank(ix,10,false);//降序排列 fSort.rank(fx,10,true);//升序排列 fSort.rank(fx,10,false);//降序排列 用法二: 不定义类的对象 LQuickSort : rank(ix,10,true);//升序排列 LQuickSort : rank(ix,10,false);//降序排列 LQuickSort : rank(fx,10,true);//升序排列 LQuickSort : rank(fx,10,false);//降序排列 由于排序函数的最后一个参数缺省为true,因此升序排列也可以写为: LQuickSort : rank(ix,10);//升序排列 用上面定义的类就可以很方便地实现对不同类型的数据快速排序。 使用模板来定义类时,可以看出除了template 这里的T时替换参数,它表示一个类型名称。 如果使用非内联成员函数定义,则要在函数定义前加入template 在成员函数的定义中,类名称被限制为模板参数类型,如LQuickSort 而且更为特殊的是,即使定义非内联成员函数时,函数的实现代码也要放在类的头文件中,否则会发生连接错误。 这似乎违背了通常的头文件定义准则: “不要在分配存储空间前放置任何东西”,这条准则是为了防止连接时的多重定义错误。 但模板定义比较特殊,由template<...>处理的任何东西都以为意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。 在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。 所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。 可以看出使用了模板后,大大简化了代码,提高了图像处理编程的效率。 二、高精度获得处理所用的时间 对于一种算法,其处理占用的时间越少则表明该算法效率越高,因此获得算法占用的时间是衡量算法的一个指标。 普通的获得处理占用时间的方法有: 1.用time()函数或CTime类获得秒量级的时间;2.用WindowsAPI函数GetTickCount()可以获得毫秒量级的时间。 下面给出一个可以达到微秒量级的时间测量类,主要通过WindowsAPI函数QueryPerformanceFrequency()和QueryPerformanceCounter()两个函数来实现。 用QueryPerformanceFrequency()函数获得计算机高精度的性能计数的频率,该频率值在多台机器上测量均为f=1193180,这样其分辨率为1/f<1微秒,只要获得处理前后 QueryPerformanceCounter()返回当前高精度的性能计数值的差值,就可以获得处理占用的时间。 具体程序如下: //精确获得算法处理时间的类(毫秒量级) classLTimeCount { private: //算法处理时间(单位: 秒) doubleUseTime; //计数值 LARGE_INTEGERTime,Frequency,old; public: //计时开始 voidStart() { QueryPerformanceFrequency(&Frequency); QueryPerformanceCounter(&old); UseTime=0.0; } //计时结束 voidEnd() { QueryPerformanceCounter(&Time); UseTime=(double)(Time.QuadPart-old.QuadPart)/(double)Frequency.QuadPart; } //获得算法处理时间(单位: 秒) doubleGetUseTime() { returnUseTime; } }; 使用的方法为: //定义一个时间测量类的对象 LTimeCountm_CountUseTime; //开始计时 m_CountUseTime.Start(); //下面进行相应的处理 //--图像处理算法-- //计时结束 m_CountUseTime.End(); //获得并显示处理占用的时间 CStringmsg; msg.Format("处理的时间是: %12.8f秒\n",m_CountUseTime.GetUseTime()); AfxMessageBox(msg); 通过实际的测试,该方法获得的时间可以达到微妙量级,同时发现用GetTickCount()函数获得的计数很不准确。 以时间间隔为40毫秒,用GetTickCount()函数测量的时间间隔误差在3-5毫秒。 三、图像处理算法程序优化的方法 1.减少乘除运算,将乘除运算变为加减运算。 如将a=2*b,写为a=b+b。 将多项式计算a=c[0]+c[1]*x+c[2]*x*x+… +c[n]*pow(x,n),写为for(a=c[n],i=n;i>=0;i--)a=a*x+c[i]。 2.使用指针加快速度 由于指针直接指向操作的内存地址,因此用指针可以加快程序执行的速度。 一般在进行图像的卷积运算时,摸板的数据是连续的内存,则可以用*lpTemp++语句对数据进行操作,该语句的意思是在进行操作后,指针pTemp加1从而指向下一个数据。 由于C++语言对"++"进行了优化,因此可以较大幅度的提高速度。 3.建立查找表或变量 如果在一个循环中要多次用到sin()、cos()、exp()等函数时,这些函数的参数是不变的,则可以通过在外定义查找表或变量的方法来减少计算量。 如普通的获得图像数据的方法如下: Sin1=sin(Theta); Cos1=cos(Theta); Exp1=exp(-Theta*Theta); for(RowAddress=0,j=0;j { for(i=0;i image[RowAddress+i]=i*Sin1+j*Cos1+i*j*Exp1; RowAddress+=imgW; } 用上面的处理方法可以减少ImgW*(ImgH-1)次乘法运算和3*ImgW*ImgH-3次调用sin()、cos()、exp() 等函数运算。 如果进行高斯滤波等运算,则可以建立数据查找表,事先将摸板的数据算好存在查找表中,使用不须计算可以直接使用,可以节省大量的计算时间。 如果一次运算需要涉及多行的数据,则可以通过建立指向图像数据行首地址的指针查找表来加快速度,如下: BYTE**RowAddress=newBYTE*[ImgH]; for(RowAddress[0]=InputImage,i=1;i { RowAddress[i]=RowAddress[i-1]+ImgW; } 这样用RowAddress[y][x]便可以对坐标(x,y)的灰度值进行操作。 4.内存的快速初始化和拷贝 用memset()函数可以对一块连续内存进行初始化,memcpy()函数可以将一块连续内存的数据拷贝到另一块连续内存中。 如memset(Sigma,0,ImgW*ImgH*sizeof(int))语句将Sigma指针指向的一块连续int类型的内存初始化为0。 而memcpy(tmpImage,InputImage,Imagesize)语句可以将InputImage指针指向的一块连续内存的数据快速拷贝到tmpImage指针指向的一块连续内存中。 如果拷贝目标或源内存是不连续,由于在一行上面是连续的,因此可以变成数据高度次数行拷贝也可完成。 5.数据指针转换 由于计算机的数据总线的位数一般是32,因此数据类型的占用字节位数为32或32的整数倍,则操作速度将会加快,以用按位取反操作符(~)来对整数型数据取反为例,将数据指针强制由BYTE*类型转换为DWORD*类型,速度比用BYTE*类型指针速度提高4倍。 6.寄存器优化 传统的编译器对寄存器的利用率往往不高,因为传统的编译器对寄存器的分配是以表达式为单位,对于不同的表达式寄存器被重新分配。 通常这种方法有利于克服寄存器的溢出,但这种方法在一个循环中会造成一个数据反复从内存中装入寄存器,如下面的语句。 for(i=0;i 变量a和b公用了一个寄存器,因此每次循环时变量a和b都要重新装入。 但是这样的方法受到寄存器数量的限制,而且要求程序员对寄存器非常熟悉,因此一般并不常用。 实际上许多图像的处理形式都类似于一个模板与图像进行卷积运算。 因此针对摸板的大小进行了不同的优化。 对小模板处理以Sobel算子为例,一般的程序如下 for(j=1;j { for(i=1;i { tempx=tempy=0; for(y=-1;y<=1;y++) { for(x=-1;x<=1;x++) { tempx+=Image[j+y][I+x]*xMask[y+1][x+1] tempy+=Image[j+y][I+x]*yMask[y+1][x+1] } } nG=int(sqrt(dx*dx+dy*dy)+0.5); NewImage[j][i]=(nG>255)? 255: nG; } } 将内部循环展开,这样首先循环开销如循环控制变量的修改、循环条件的判断被删除;其次,内部循环展开使得原先指令数较少的循环体指令序列变成指令数众多的非循环指令序列。 这样许多指令调度和经典优化可以被使用。 将摸板的元素都各自分配一个寄存器,如mx0=xMask[0][0],在循环体中用寄存器变量mx0代替xMask[0][0]可以减少大量的寄存器装载时间。 另外如果模板的系数已知则直接用常数计算,如下面的代码,速度比未优化前提高了许多。 for(j=1;j { for(i=1;i { dx=RowAddress[j-1][i+1]-RowAddress[j-1][i-1]; dx+=(RowAddress[j][i+1]+RowAddress[j][i+1]- RowAddress[j][i-1]-RowAddress[j][i-1]); dx+=RowAddress[j+1][i+1]-RowAddress[j+1][i-1]; dy=RowAddress[j+1][i-1]-RowAddress[j-1][i-1]; dy+=(RowAddress[j+1][i]+RowAddress[j+1][i]- RowAddress[j-1][i]-RowAddress[j-1][i]); dy+=RowAddress[j+1][i+1]
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- VC 数字图像 处理 中的 技巧 经验