内容来至(http://blog.csdn.net/dadaadao/rss/list)

dadaadao的专栏
[转]Win7x64+VS2012+OpenCV2.4.3+CMake2.8.10+TBB41重编译OpenCV

PS:请参考最新的《Opencv 完美配置攻略 2014 (Win8.1 + Opencv 2.4.8 + VS 2013)》,绝对给力!由于Opencv版本升级,大多人开始用新版本VS,等等,这篇已经过时了,而且当时没有在文中加入更合适的简介的配置方法,所以有一些东西不再适用。重写一篇,,无论是Win7还是Win8,无论是VS2010, VS2012, 还是VS2013,无论是Opencv 2.x.x,方法都是一样的,只是配置思路和操作流程不同而已。

如果想重新编译Opencv,可以参考本文,不过新版本也许不用配置ttb了吧,没试过。如果有需求再玩玩自己编译的。

posted @ 2013-01-11 19:54 from [FreedomShe]

 

重编译的好处:可以调试的时候看OpenCV的源代码。

重编译要得到的东西:Debug版本和Release版本的dll,lib,头文件。(dll添加到环境变量里,运行时用,自己编译的dll调试时可以跟踪到Opencv的源码内;lib和头文件配置到编译器里)

PS:如果只是使用Opencv而不需要跟踪源码,则使用Opencv自带的库文件即可。跳到5配置Opencv开发环境,对应的文件都在..\opencv\build\目录下,其中dll(bin目录),lib目录在平台文件夹下如..\opencv\build\ x86\vc10。

本机Win7 64位系统,装有VS2012,以编译32位的Opencv库为例,要编译64位库需要注意选择64位的配置。

1        下载Opecv,CMake,TBB并安装

下载OpenCV2.4.3:http://opencv.org/downloads.html,解压到D:\Program Files\

下载CMake2.8.10:http://www.cmake.org/cmake/resources/software.html,安装

下载tbb41_20121003oss:http://threadingbuildingblocks.org/download,解压到D:\Program Files\

2        配置TBB环境变量

Path里添加:D:\Program Files\tbb41_20121003oss\bin\ia32\vc11

bin目录内ia32表示要编译32位工程,intel64表示要编译64位工程,vc11表示VS版本为2012

3        用CMake生成VS2012的OpenCV工程

新建文件夹OpenCVProject:D:\Program Files\OpenCVProject(用于存放自己的OpenCV编译工程)。

打开CMake,"Browse Source..."选择Opencv的目录D:/Program Files/opencv(内有CMake的组态档"CMakeLists.txt"),"Browse Build..."选择刚才自己新建的工程存放路径"D:\Program Files\OpenCVProject"。点击Configure按钮,在出现的对话框中选择Visual Studio 11(如果编译64位dll注意选择64位VS11配置),默认Use default native compilers,Finish继续。

image

 

稍等片刻出现该图

第一轮配置完后往下拉,勾选WITH_TBB,点击Configure进入第二轮。

image

 

修改红色部分TBB路径为D:/Program Files/tbb41_20121003oss/include,再次点击Configure;继续点击Configure,直到没有红色标记。

image

 

点击Generate生成Opencv工程,退出CMake。

4        用Opencv VS2012工程编译生成自己的Opencv库

打开生成的Opencv工程,选择CMakeTargets下INSTALL,右键“生成”,生成Debug版dll,lib。

image

 

切换编译模式为Release模式,重复上一步生成Release版dll,lib。

image

 

上面两步后就能看到最终Debug版和Release版的dll,lib,以及文档目录doc,头文件目录include(bin内为两个版本dll,lib内为两个版本lib)。

image

 

目标达成,在D:\Program Files\OpenCVProject\install内有我们所要的dll,lib,include头文件,有了这些就可以进行Opencv开发与源码跟踪了。跟dll关联的源代码在Opencv安装目录D:\Program Files\opencv\modules内。

我习惯将将D:\Program Files\OpenCVProject\install拷贝到D:\Program Files\opencv\下,并将install重命名为vc11x86。而此时D:\Program Files\OpenCVProject没有用了,但是不能删除,否则无法跟踪源码,占用6G多空间,可以通过VS2012的“清理解决方案”来减到3G多。

5        配置Opencv开发环境

在环境变量Path里添加:D:\Program Files\opencv\vc11x86\bin。

6        编写测试工程

6.1    打开VS2012,新建控制台应用程序TestOpencv。

6.2    配置包含目录和库目录

项目->xxx属性->VC++目录->包含目录,添加D:\Program Files\opencv\vc11x86\include

项目->xxx属性->VC++目录->库目录,添加D:\Program Files\opencv\vc11x86\lib

image

 

项目->xxx属性->链接器->输入->附加依赖项,添加lib文件名列表如下图。

image

 

对于配置方案为Debug的配置,添加:

opencv_calib3d243d.libopencv_contrib243d.libopencv_core243d.libopencv_features2d243d.libopencv_flann243d.libopencv_gpu243d.libopencv_highgui243d.libopencv_imgproc243d.libopencv_legacy243d.libopencv_ml243d.libopencv_nonfree243d.libopencv_objdetect243d.libopencv_photo243d.libopencv_stitching243d.libopencv_ts243d.libopencv_video243d.libopencv_videostab243d.lib

对于Release配置,添加

opencv_calib3d243.libopencv_contrib243.libopencv_core243.libopencv_features2d243.libopencv_flann243.libopencv_gpu243.libopencv_highgui243.libopencv_imgproc243.libopencv_legacy243.libopencv_ml243.libopencv_nonfree243.libopencv_objdetect243.libopencv_photo243.libopencv_stitching243.libopencv_ts243.libopencv_video243.libopencv_videostab243.lib

 

Opencv的dll和lib中,末尾带d的就是Debug版本。

6.3    添加测试代码

修改TestOpencv.cpp,代码为:

复制代码
#include "stdafx.h"#include using namespace cv;using namespace std;int main(){	Mat img = imread("c:/pp.jpg");	if(img.empty())	{		cout<<"error";		return -1;	}	imshow("pp的靓照",img);	waitKey();	return 0;}
复制代码

 

 

将要显示的图片保存为c:/pp.jpg编译运行,显示出图片。

image

 

通过设置断点发现,能够跟踪进入Opencv内部函数。

image


作者:dadaadao 发表于2014/11/5 15:52:35  原文链接
阅读:453 评论:1  查看评论
 
[转]Win32 编程入门


Win32    程序开发的流程

 

message based, event driven

Win32程序是message based, event driven。也就是说Win32程序的运行是依靠外部不断发生的事件来驱动的,也就是说,程序不断等待(有一个while循环),等待任何可能的输入,然后做判断,再做适当的处理。因此Win32程序只需要做好如下几件事情就可以了:

1. 定义窗口的外观;

2. 定义当不同的事件发生时,程序做什么样的反应(定义窗口处理函数);

3. 写一个While循环,不断检测新事件的发生,并将其发送给不同的窗口处理函数

 

程序进入点WinMain

main是一般C程序的进入点:int main( int argc, char *argv[], char *envp[])

Win32程序的进入点是

int CALLBACK WinMain(  __in  HINSTANCE hInstance,  __in  HINSTANCE hPrevInstance,  __in  LPSTR lpCmdLine,  __in  int nCmdShow);

当用户要执行一个程序时,首先是Windows 的Shell(Explorer)调用CreateProcess这个系统调用,CreateProcess为这个进程创建虚拟地址,然后将代码和数据载入,然后系统再创建一个主线程开始执行run time startup函数的代码,run time startup 函数会最终调用入口点函数(main, WinMain)。如果用户执行的是一个Win32程序,那么C startup就会调用WinMain并开始执行程序。

窗口类注册与窗口诞生

如果前面所说,Win32的一个重要的责任是定义窗口外观和窗口处理函数。这是通过窗口类注册来完成的。创建窗口可以使用CreateWindow来完成,但在调用CreateWindow时必须先设定窗口的各种属性和行为。设定窗口属性和行为是通过API 函数 RegisterClass 来完成的。RegisterClass需要一个大型数据结构WNDCLASS,窗口的属性和行为就是在这个数据结构中指定的。

ATOM WINAPI RegisterClass(  __in  const WNDCLASS *lpWndClass);

下面是数据结构WNDCLASS的定义:

typedef struct tagWNDCLASS {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
} WNDCLASS, *PWNDCLASS;

 

消息循环是怎么工作的

应用程序可以获得的输入有两种类型:硬件装置产生的消息房子系统队列中、由Windows系统或者其他Windows程序传送过来的消息,放在程序队列中。以应用程序的眼光来看,消息就是消息,来自哪里或放在哪里没有太大区别,程序每次GetMessage就取得一个消息。

每个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的窗口函数(Window Procedure),如果窗口获得一个消息,则这个窗口必须判断消息的类别,决定处理方式。下面是主消息循环的例子:

// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

GetMessage的原型:

BOOL WINAPI GetMessage(  __out     LPMSG lpMsg,  __in_opt  HWND hWnd,  __in      UINT wMsgFilterMin,  __in      UINT wMsgFilterMax);

调用线程的消息队列中获取一个消息,这个函数会一直等到有一个可用的Message时才会返回; 

int WINAPI TranslateAccelerator(  __in  HWND hWnd,  __in  HACCEL hAccTable,  __in  LPMSG lpMsg);

该函数处理菜单命令中的加速键。该函数将一个WM_KEYDOWN或WM_SYSKEYDOWN消息翻译成一个WM_COMMAND或WM_SYSCOMMAND消息(如果在给定的加速键表中有该键的入口),然后将WM_COMMAND或WM_SYSCOMMAND消息直接送到相应的窗口处理过程。

TranslateMessage是用来把虚拟键消息转换为字符消息。由于Windows对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不得字符消息,需要键盘映射转换为字符的消息。
TranslateMessage函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage这个函数就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。
也就是说TranslateMessage会发现消息里是否有字符键的消息,如果有字符键的消息,就会产生WM_CHAR消息,如果没有就会产生什么消息。

 

windows消息处理机制是这样的: 
首先系统(也就是windows)把来自硬件(鼠标,键盘等消息)和来自应用程序的消息放到一个系统消息队列中去。而应用程序需要有自己的消息队列,也就是线程消息队列,每一个线程有自己的消息队列,对于多线程的应用程序就有和线程数目相等的线程消息队列。winsows消息队列把得到的消息发送到线程消息队列,线程消息队列每次取出一条消息发送到指定窗口,不断循环直到程序退出。这个循环就是靠消息环(while(GetMessage()) TranslateMessage();DispatchMessage(); 实现的.GetMessage()只是从线程消息中取出一条消息,而DispatchMessage 
则把取出的消息发送到目的窗口。如果收到WM_CLOSE消息则结束循环,发送postqiutmessage(0),处理WM_DESTROY销毁窗口! 

其实问题的关键在于DispatchMessage到底干了什么 
如果只是去调用相应的窗口,那自己写个switch不就可以了。DispatchMessage与switch不同之处在于DispatchMessage会先调用windows,进入管态(大概是range 0),然后再由windows调用窗口的函数。 为什么这么麻烦? 因为这样windows就可以知道你的程序运行到什么情况了, windows来调用你的窗口,这样你的窗口返回的时候windows就知道你已经处理过一个消息了,如果没有新的消息进入消息队列windows就不再会给你的进程分配时间片如果是你自己写switch的话,windows就不可能这样灵活的分配时间资源利用率就会降低那么还要消息循环干什么,windows直接把消息发给窗口不就可以了吗?因为你要在消息循环里把KEY_DOWN和KEY_UP组合成WM_CHAR,还可以直接屏蔽掉许多对你来说无用的消息,加快速度。

GetMessage: 从线程的消息队列取出一个消息   
TranslateMessage: 将msg结构传给Windows,进行一些转换,比如A键按下,转换成WM_CHAR消息等   
DispatchMessage():再将msg结构传给Windows,Windows将该消息发给窗口过程,由窗口过程处理.

TranslateMessage是对一些键盘事件做预处理。GetMessage是从系统为每个应用程序自动分配的消息对列的头部得到一个消息。

TranslateMessage是翻译需要翻译的消息

DispatchMessage()则会把翻译好的消息发送到系统的消息处理函数中,而这个函数又会把这个消息传    递到注册窗体时用户指定的消息处理函数中翻译消息不是简单的转换,一个消息被翻译后,可能会产生几个消息。

 

作者:dadaadao 发表于2014/2/28 10:50:40  原文链接
阅读:294 评论:0  查看评论
 
[转]Activex、OLE、COM、OCX、DLL之间的区别(转

熟悉面向对象编程和网络编程的人一定对ActiveX、OLE和COM/DCOM这些概念不会陌生,但是它们之间究竟是什么样的关系,对许多们还是比较模糊的。在具体介绍它们的关系之间,我们还是先明确组件(Component)和对象(Object)之间的区别。

组件是一个可重用的模块,它是由一组处理过程、数据封装和用户接口组成的业务对象(Rules Object)。组件看起来像对象,但不符合对象的学术定义。

它们的主要区别是:

 1)组件可以在另一个称为容器(有时也称为承载者或宿主)的应用程序中使用,也可以作为独立过程使用;

 2)组件可以由一个类构成,也可以由多个类组成,或者是一个完整的应用程序;

 3)组件为模块重用,而对象为代码重用。现在,比较流行的组件模型有COM(Component Objiect Module,对象组件模型)/DCOM( Distributed COM,分布式对象组件模型)和CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)。

到这里,已经出现了与本文相关的主题COM,而CORBA与本文无关,就不作介绍。

之所以从组件与对象的区别说起,是想让大家明确COM和 CORBA是处在整个体系结构的最底层,如果暂时对此还不能理解,不妨继续往下看,最后在回过头看一看就自然明白了。

现在开始阐述ActiveX、OLE和COM的关系。首先,让大家有一个总体的概念,从时间的角度讲,OLE是最早出现的,然后是COM和ActiveX;从体系结构角度讲,OLE和ActiveX是建立在 COM之上的,所以COM是基础;单从名称角度讲,OLE、ActiveX是两个商标名称,而COM则是一个纯技术名词,这也是大家更多的听说ActiveX和OLE的原因。

既然OLE是最早出现的,那么就从OLE说起,自从Windows操作系统流行以来,“剪贴板”( Clipboard)首先解决了不同程序间的通信问题(由剪贴板作为数据交换中心,进行复制、粘贴的操作),但是剪贴板传递的都是“死”数据,应用程序开发者得自行编写、解析数据格式的代码,于是动态数据交换(Dynamic Data Exchange,DDE)的通信协定应运而生,它可以让应用程序之间自动获取彼此的最新数据,但是,解决彼此之间的“数据格式”转换仍然是程序员沉重的负担。

对象的链接与嵌入(Object Linking and Embedded,OLE)的诞生把原来应用程序的数据交换提高到“对象交换”,这样程序间不但获得数据也同样获得彼此的应用程序对象,并且可以直接使用彼此的数据内容,其实OLE是Microsoft的复合文档技术,它的最初版本只是瞄准复合文档,但在后续版本OLE2中,导入了COM。

由此可见,COM是应OLE的需求而诞生的,所以虽然COM是OLE的基础,但OLE的产生却在COM之前。 COM的基本出发点是,让某个软件通过一个通用的机构为另一个软件提供服务。COM是应OLE 的需求而诞生,但它的第一个使用者却是OLE2,所以COM与复合文档间并没有多大的关系,实际上,后来COM就作为与复合文档完全无关的技术,开始被广泛应用。

这样一来, Microsoft就开始“染指”通用平台技术。但是COM并不是产品,它需要一个商标名称。而那时Microsoft的市场专家们已经选用了OLE作为商标名称,所以使用COM技术的都开始贴上了 OLE的标签。虽然这些技术中的绝大多数与复合文档没有关系。Microsoft的这一做法让人产生这样一个误解OLE是仅指复合文档呢?还是不单单指复合文档?其实OLE是COM的商标名称,自然不仅仅指复合文档。但Microsoft自己恐怕无法解释清楚,这要花费相当的精力和时间。

于是,随着Internet的发展,在1996年春,Microsoft改变了主意,选择ActiveX作为新的商标名称。ActiveX是指宽松定义的、基于COM的技术集合,而OLE仍然仅指复合文档。当然, ActiveX最核心的技术还是COM。

ActiveX和OLE的最大不同在于,OLE针对的是桌面上应用软件和文件之间的集成,而ActiveX则以提供进一步的网络应用与用户交互为主。到这里,大家应该对ActiveX、OLE和COM三者的关系有了一个比较明确的认识,COM才是最根本的核心技术,所以下面的重点介绍COM。

让对象模型完全独立于编程语言,这是一个非常新奇的思想。这一点从C++和Java的对象概念上,我们就能有所了解。但所谓COM对象究竟是什么呢?为了便于理解,可以把COM看作是某种(软件)打包技术,即把它看作是软件的不同部分,按照一定的面向对象的形式,组合成可以交互的过程和以组支持库。

COM对象可以用C++、Java和VB等任意一种语言编写,并可以用DLL或作为不同过程工作的执行文件的形式来实现。使用COM对象的浏览器,无需关心对象是用什么语言写的,也无须关心它是以DLL还是以另外的过程来执行的。从浏览器端看,无任何区别。这样一个通用的处理技巧非常有用。例如,由用户协调运行的两个应用,可以将它们的共同作业部分作为COM对象间的交互来实现(当然,现在的OLE复合文档也能做到)。为在浏览器中执行从Web服务器下载的代码,浏览器可把它看作是COM对象,也就是说,COM技术也是一种打包可下载代码的标准方法(ActiveX控件就是执行这种功能的)。甚至连应用与本机OS进行交互的方法也可以用COM来指定,例如在Windows和Windows NT中用的是新API,多数是作为COM对象来定义的。可见,COM虽然起源于复合文档,但却可有效地适用于许多软件问题,它毕竟是处在底层的基础技术。用一句话来说,COM是独立于语言的组件体系结构,可以让组件间相互通信。

随着计算机网络的发展,COM进一步发展为分布式组件对象模型,这就是DCOM,它类似于CORBA的ORB,本文对此将不再做进一步的阐述。通过上面的讲述相信大家一定对ActiveX、OLE和COM/DCOM的关系有了一个清楚的了解。

使用Windows的人对于ActiveX控制一定不会陌生,它提供了一种类似于DLL动态链接库的调用,不过它与DLL的唯一区别就是ActiveX不注册不能被系统识别并使用。那么,当我们得到一个ActiveX没有被正确安装且不能使用的消息后,又要安装ActiveX怎么办呢?

1.Regsvr32程序法在Windows的System文件夹下有一个regsvr32.exe的程序,它就是Windows自己带的ActiveX注册和反注册工具。利用它也能够非常方便地注册AcitveX控件,它的用法为:regsvr32/u/s/n/idllname,dllname其中dllname为ActiveX控件文件名,建议在安装前拷贝到System文件夹下参数有如下意义:

    /u- 反注册控件

    /s- 不管注册成功与否,均不显示提示框

    /c -控制台输出

    /i- 跳过控件的选项进行安装 (与注册不同)

    /n- 不注册控件,此选项必须与/i 选项一起使用

例如笔者要注册一amovie.ocx控件,则打入 regsvr32 amovie.ocx即可,要反注册它时只需使用regsvr32 /u amovie.ocx就行了。

2.注册表法所谓注册AcitveX,无非是将一些信息记录在Windows的注册表中,如ShockwaveFlashObject控件,我们可以运行Regedit.exe注册表编辑程序,利用关键字进行搜索,然后把搜索得到后的注册表导出为一REG注册表文件,再将其相应的ActiveX文件拷贝到Windows的System文件夹(一般ActiveX的文件名为OCX,安装在Windows的System文件夹内)下,最后在要安装ActiveX的机器上双击导入刚才导出的注册表文件即可完成安装。

 
总结:Activex,OLE,COM都是微软的一些技术标准。Ole比较老后来发展成Activex,再后来发展成为COM OCX,DLL是扩展名。 Activex有两种扩展名OCX和DLL。实际上你可以把它们的扩暂名字调换。 COM作为ActiveX的更新技术,扩展名也有可能是DLL DLL文件还有可能是动态链接库。主要是装载一些函数,可以动态加载。
作者:dadaadao 发表于2013/11/22 11:48:34  原文链接
阅读:438 评论:0  查看评论
 
[转]classView不显示已存在的类向导

最近在做一个VC++程序,在做的过程中出现一个问题:视图类明明存在可是在ClassView中竟然没有类的相关信息,这是怎么回事呢,网上查资料,原来是vc++ 6.0的一个非常经典的bug.

解决:打开工程所在项目----->找到一个以.ncb结尾的文件,将其删除----->再次打开工程----->看到完整的类信息了----->解决。

注:

NCB是 “No Compile Browser”的缩写,其中存放了供ClassView、WizardBar和Component Gallery使用的信息,由VC开发环境自动生成。无编译浏览文件。当自动完成功能出问题时可以删除此文件。编译工程后会自动生成。

 

作者:dadaadao 发表于2012/7/13 10:41:43  原文链接
阅读:372 评论:0  查看评论
 
[转]mfc的CDialogBar
一、创建DialogBar的派生类
首先,创建对话框资源:在对话框资源编辑器内生成一个Dialog资源,并将其风格(Style)属性必须设置为Child,不能设置为Overlapped或Popup,否则运行肯定出错;至于边界属性则随用户自己喜欢,一般都是选择None。其余属性也随用户选择,一般没有特殊要求还是选择默认的好。
其次,创建基于CDialog的派生类:打开ClassWizard,为以上创建的资源添加一个以CDialog为基类的派生类(因为ClassWizard没有将CDialogBar列在基类目录清单中,所以用户只能先以CDialog类派生)。
再次,修改派生类以CDialogBar为基类:通常需要手工修改几处代码,在本例中派生类以CDataStatus命名。(注:以后讲解中凡是手工改动都是以灰背景显示)
1、   在头文件中修改继承关系
将class CDataStatus : public CDialog   改为class CDataStatus : public CDialogBar
2、   在代码文件中修该构造函数继承关系
将CDataStatus::CDataStatus(CWnd* pParent /*=NULL*/)
: CDialog(CDataStatus::IDD, pParent)
{
        //{{AFX_DATA_INIT(CDataStatus)
               // NOTE: the ClassWizard will add member initialization here
        //}}AFX_DATA_INIT
}
改为
CDataStatus::CDataStatus(CWnd* pParent /*=NULL*/)
{
        //{{AFX_DATA_INIT(CDataStatus)
               // NOTE: the ClassWizard will add member initialization here
        //}}AFX_DATA_INIT
}

3、   将DDX绑定函数中的继承关系去掉
即将void CDataStatus::DoDataExchange(CDataExchange* pDX)
{
        CDialog::DoDataExchange(pDX);
        //{{AFX_DATA_MAP(CCurrentCheckDlg)
        ………..
        //}}AFX_DATA_MAP
}
改为
void CDataStatus::DoDataExchange(CDataExchange* pDX)
{
        //{{AFX_DATA_MAP(CCurrentCheckDlg)
        ………….
        //}}AFX_DATA_MAP
}

4、   重新初始化函数(这个相当重要,如果不这么做的话,DDX函数形同虚设,当然用户的工具条如果没有用到DDX的话当然可以不加这段代码):
首先在ClassWizard的MessageMap中对消息该CDataStatus类的WM_INITDIALOG消息添加处理函数默认名为OnInitDialog。
其次手工修改代码如下:
1、              添加消息映射函数。由于对话框形式的初始化函数消息并未加载到消息映射内,为此我们需要手工添加,要不然代码无法拦截该工具条的初始化消息,形式如下:
将BEGIN_MESSAGE_MAP(CDataStatus, CDialogBar)
    //{{AFX_MSG_MAP(CDataStatus)
    .......
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
改为:
BEGIN_MESSAGE_MAP(CDataStatus, CDialogBar)

    //{{AFX_MSG_MAP(CDataStatus)

    .......

    ON_MESSAGE(WM_INITDIALOG,OnInitDialog)

    //}}AFX_MSG_MAP

END_MESSAGE_MAP()

2、              修改OnInitDialog函数,此函数并未传递参数,但是在这里我们需要让它传递参数,代码如下修改(当然头文件中,对声明也要做修改,在这里就不作赘述了)
将BOOL CDataStatus::OnInitDialog()
{
    CDialogBar::OnInitDialog();
   
    // TODO: Add extra initialization here
    return TRUE;   // return TRUE unless you set the focus to a control
               // EXCEPTION: OCX Property Pages should return FALSE
}
改为:
BOOL CDataStatus::OnInitDialog(UINT wParam,LONG lParam)

{

    //CDialogBar::OnInitDialog();

   

    // TODO: Add extra initialization here

    BOOL bRet = HandleInitDialog(wParam,lParam);

    if (!UpdateData(FALSE))

    {

           TRACE("InitCDataStatus Failed!");

    }

    return TRUE;   // return TRUE unless you set the focus to a control

                  // EXCEPTION: OCX Property Pages should return FALSE

}



二、在框架类中实现该派生类的对象化
首先,在框架类的头文件内声明实例对象,本例实例化:CDataStatus       m_wndDataStatus;当然头文件中不可避免要包含新派生类的头文件。
其次,在框架类的OnCreate函数内创建对象并将对象绑定对话框资源。形式与创建ToolBar原理一样,本例实例如下:
if (!m_wndDataStatus.Create(this,IDD_DATASTATUS,WS_VISIBLE|WS_CHILD

|CBRS_SIZE_DYNAMIC|CBRS_BOTTOM,IDD_DATASTATUS))

        {

               TRACE0("Failed to create CDataStatus bar!");

               return -1;

        }

再次,最为关键的一点就是重写框架类的OnCmdMsg虚函数。如果不重写该函数,那么不光DDX功能无法实现,连最基本的OnCommand事件都无法实现。而且还得手工添加代码,形式如下:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
        // TODO: Add your specialized code here and/or call the base class
        return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
改为:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)

{

        // TODO: Add your specialized code here and/or call the base class

        if (m_wndDataStatus.OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))

               return     TRUE;

        return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);

}
 

步骤1:添加一个CDialogBar派生类
     在资源中添加一个对话框,再采用类向导来添加类,找不到CDialogBar作为基类吧,可以先用CDialog作为基类产生一个,然后把所以的“CDialog”替换为“CDialogBar”,替换完成了。编译一下,^_^有错误吧!!请看步骤2。
     步骤2:解决编译错误并完善该类
     其实错误就是构着函数调用基类时有问题,: CDialogBar(/*CDlgBar::IDD, pParent*/)象这样注释掉就可以了,添加一个类似OnInitDialog的函数,在CDialogBar中是不存在OnInitDialog的消息的,至少我还不知道,因为初始化是在创建后调用的所以我们就重写virtual BOOL Create(CWnd* pParentWnd,UINT nIDTemplate,UINT nStyle,UINT nID);这个函数。注意哦用向导添加的Create函数的参数是不对的喔,看上面。下面是实现代码(很简单的)
BOOL CDlgXXX::Create(CWnd* pParentWnd,UINT nIDTemplate,UINT nStyle,UINT nID) 
{
// TODO: Add your specialized code here and/or call the base class
BOOL bRes= CDialogBar::Create(pParentWnd,nIDTemplate,nStyle,nID );
InitDialogBar();//在类中添加一个成员函数就可以了
return bRes;
}
BOOL CDlgXXX::InitDialogBar()
{
UpdateData(FALSE);//这个一定要啊,这样就会有和CDialog一样的数据交换效果了

return TRUE;
}
     步骤3:创建和使用
      if (!m_barAttrib.Create(this,IDD_DLG_COM_ATTRIB, CBRS_RIGHT|CBRS_GRIPPER, XXX))
{
    TRACE0("Failed to create dialogbar\n");
    return -1;
}
m_barAttrib.SetWindowText("部件属性");
XXX是一个资源id手工直接在资源的.h文件中添加一条,不会,这里就不教了 。
工具条的显示和隐藏代码如下,自己慢慢理解吧:
ShowControlBar(&m_barAttrib, (m_barAttrib.GetStyle() & WS_VISIBLE) == 0, FALSE);
     上面代码实现后DoDataExchange也是可以用,给控件添加控件就和CDialog一样的方便咯
但是还有一个要注意的是就是控件类对象的添加,我试了一下好像不行,窗口句柄好像
总是0的,不能使用。还是使用GetDlgItem(IDC_DRIVER_LIST)来取得控件指针吧。

     其他方面的心得
     利用DoDataExchange来控制自定义的输入格式控制这里就举一个文本框的例子
给文本控件添加完变量后就在DoDataExchange会出现如下代码
DDX_Text(pDX, IDC_COM_VAR, m_strVar);//系统产生的
DDV_MaxChars(pDX, m_strVar,VAR_MAX_LEN);//加入长度控制后产生的
DDV_FileNameString(pDX, m_strVar);//自定义的手工添加的实现见下面
void CXXX::DDV_FileNameString(CDataExchange *pDX, CString m_strFileName)
{
CString strError=_T(" \\/:*?\"<>|");
if(m_strFileName.SpanExcluding(strError) != m_strFileName)
{
   ::AfxMessageBox(_T("文件名中不能包含"+strError+"字符"));
   pDX->Fail();//关键是这句执行这句后就会抛出异常下面的语句就不执行了
}
}
还有几个注意点是
1.只有执行了UpdateData()才会调用DoDataExchange函数若中途 执行了pDX->Fail(); UpdateData()就返回FALSE。
2. DDX_Text(pDX, IDC_COM_VAR, m_strVar);//系统产生的
DDV_MaxChars(pDX, m_strVar,VAR_MAX_LEN);//加入长度控制后产生的
DDV_FileNameString(pDX, m_strVar);//自定义的手工添加的实现见下面
如上面几句都是对一个控件的内容的控制,他们必须放在一块,且DDX_Text要放在第一句,这样在界面上就可以正确的指出那个控件的内容有问题,控件会被设置焦点并选中全部内容。

如果你想实现有工具条的浮动和定位功能,而且可以方便的摆放任何控件上去,那就使用CDialogBar就可以拥有和CDialog一样的方便和快捷。
       添加一个CDialogBar派生类:在资源中添加一个对话框,再采用类向导来添加类,这里我们找不到CDialogBar作为基类,可以先用CDialog作为基类产生一个,然后把所以的“CDialog”替换为“CDialogBar”。
构造函数CXXXDlg::CXXXDlg(CWnd* pParent /*=NULL*/)
     : CDialog(CXXXDlg::IDD, pParent)改成: CXXXDlg::CXXXDlg()就可以了,添加一个类似OnInitDialog的函数,在CDialogBar中是不存在OnInitDialog的消息的,因为初始化是在创建后调用的,所以我们就重写virtual BOOL Create(CWnd* pParentWnd,UINT nIDTemplate,UINT nStyle,UINT nID)这个函数。注意哦用向导添加的Create函数的参数是不对的,改成上面。下面是实现代码:
BOOL CXXXDlg::Create(CWnd* pParentWnd,UINT nIDTemplate,UINT nStyle,UINT nID) 
{
BOOL bRes= CDialogBar::Create(pParentWnd,nIDTemplate,nStyle,nID );
InitDialogBar();//在类中添加一个成员函数就可以了
return bRes;
}
BOOL CXXXDlg::InitDialogBar()
{
UpdateData(FALSE);//这个一定要啊,这样就会有和CDialog一样的数据交换效果了
return TRUE;
}
创建和使用:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
    return -1;
if (!m_wndColor.Create(this, IDD_COLOR,
    CBRS_BOTTOM | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_HIDE_INPLACE,
    ID_VIEW_COLOR))
{
    TRACE0("Failed to create dialog bar m_wndContentMenu\n");
    return -1;    // fail to create
}
m_wndColor.EnableDocking(CBRS_ALIGN_BOTTOM );
DockControlBar(&m_wndColor);//没有这个,CDialogBar不可以移动,FloatControlBar的功能是浮动在窗口上。
EnableDocking(CBRS_ALIGN_ANY);
m_wndColor.SetWindowText(_T("颜色"));
return 0;
}
IDD_COLOR是CDialogBar的对话框ID,ID_VIEW_COLOR是一个菜单资源id,在
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_COMMAND_EX(ID_VIEW_COLOR, OnBarCheck)
ON_UPDATE_COMMAND_UI(ID_VIEW_COLOR, OnUpdateControlBarMenu) 
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
时,工具条可以显示和隐藏。

想在CDialogBar上添加按钮消息还需要修改如下:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
// TODO: Add your specialized code here and/or call the base class
if (m_wndColor.OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
    return TRUE;
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}   
m_wndColor为CDialogBar对象,这样,按钮的消息可以映射到自己的类中。

这里增加一条关于对话框上响应键盘按键消息,重载
BOOL CXXXDlg::PreTranslateMessage(MSG* pMsg) 
{
if( pMsg->message>=WM_KEYDOWN && pMsg->message<=WM_KEYUP)
{
    this->SendMessage(pMsg->message,pMsg->wParam,pMsg->lParam);
    return TRUE;
}
      else
          return CDialog::PreTranslateMessage(pMsg);
}
这样就可以在对话框上重载WM_KEYDOWN和WM_KEYUP来编写代码了。

 
 
第3中方法:定义 CDialogBar m_wndImagesInDB;
然后在OnCreate中增加

 // TODO: Add a menu item that will toggle the visibility of the
 // dialog bar named "ImagesInDB":
 //   1. In ResourceView, open the menu resource that is used by
 //      the CMainFrame class
 //   2. Select the View submenu
 //   3. Double-click on the blank item at the bottom of the submenu
 //   4. Assign the new item an ID: CG_ID_VIEW_IMAGESINDB
 //   5. Assign the item a Caption: ImagesInDB

 // TODO: Change the value of CG_ID_VIEW_IMAGESINDB to an appropriate value:
 //   1. Open the file resource.h
 // CG: The following block was inserted by the 'Dialog Bar' component
  // Initialize dialog bar m_wndImagesInDB
  if (!m_wndImagesInDB.Create(this, CG_IDD_IMAGESINDB,
   CBRS_LEFT | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_HIDE_INPLACE,
   CG_ID_VIEW_IMAGESINDB))
  {
   TRACE0("Failed to create dialog bar m_wndImagesInDB\n");
   return -1;  // fail to create
  }
  
  m_wndImagesInDB.EnableDocking(CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT);
  EnableDocking(CBRS_ALIGN_ANY);
  DockControlBar(&m_wndImagesInDB);

其中要在resource.h中定义#define CG_ID_VIEW_IMAGESINDB           103
CG_IDD_IMAGESINDB为对话框的名字
  CListCtrl*  pList = (CListCtrl*)m_wndImagesInDB.GetDlgItem(IDC_LIST1);
  VERIFY(pList);
做一个关联就可以了
 
 
作者:dadaadao 发表于2012/7/11 12:49:13  原文链接
阅读:2602 评论:0  查看评论
 
[转]【引用】关于图像特征提取
 
本文引用自yuweiisme 《关于图像特征提取》

 网上发现一篇不错的文章,是关于图像特征提取的,给自己做的项目有点类似,发出来供大家参考。

       特征提取是计算机视觉和图像处理中的一个概念。它指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。特征提取的结果是把图像上的点分为不同的子集,这些子集往往属于孤立的点、连续的曲线或者连续的区域。 
特征的定义 
        至今为止特征没有万能和精确的定义。特征的精确定义往往由问题或者应用类型决定。特征是一个数字图像中“有趣”的部分,它是许多计算机图像分析算法的起点。因此一个算法是否成功往往由它使用和定义的特征决定。因此特征提取最重要的一个特性是“可重复性”:同一场景的不同图像所提取的特征应该是相同的。 
        特征提取是图象处理中的一个初级运算,也就是说它是对一个图像进行的第一个运算处理。它检查每个像素来确定该像素是否代表一个特征。假如它是一个更大的算法的一部分,那么这个算法一般只检查图像的特征区域。作为特征提取的一个前提运算,输入图像一般通过高斯模糊核在尺度空间中被平滑。此后通过局部导数运算来计算图像的一个或多个特征。 
       有时,假如特征提取需要许多的计算时间,而可以使用的时间有限制,一个高层次算法可以用来控制特征提取阶层,这样仅图像的部分被用来寻找特征。 
        由于许多计算机图像算法使用特征提取作为其初级计算步骤,因此有大量特征提取算法被发展,其提取的特征各种各样,它们的计算复杂性和可重复性也非常不同。 
边缘 
        边缘是组成两个图像区域之间边界(或边缘)的像素。一般一个边缘的形状可以是任意的,还可能包括交叉点。在实践中边缘一般被定义为图像中拥有大的梯度的点组成的子集。一些常用的算法还会把梯度高的点联系起来来构成一个更完善的边缘的描写。这些算法也可能对边缘提出一些限制。 
局部地看边缘是一维结构。 
角 
        角是图像中点似的特征,在局部它有两维结构。早期的算法首先进行边缘检测,然后分析边缘的走向来寻找边缘突然转向(角)。后来发展的算法不再需要边缘检测这个步骤,而是可以直接在图像梯度中寻找高度曲率。后来发现这样有时可以在图像中本来没有角的地方发现具有同角一样的特征的区域。 
区域 
       与角不同的是区域描写一个图像中的一个区域性的结构,但是区域也可能仅由一个像素组成,因此许多区域检测也可以用来监测角。一个区域监测器检测图像中一个对于角监测器来说太平滑的区域。区域检测可以被想象为把一张图像缩小,然后在缩小的图像上进行角检测。 
脊 
        长条形的物体被称为脊。在实践中脊可以被看作是代表对称轴的一维曲线,此外局部针对于每个脊像素有一个脊宽度。从灰梯度图像中提取脊要比提取边缘、角和区域困难。在空中摄影中往往使用脊检测来分辨道路,在医学图像中它被用来分辨血管。 
特征抽取 
        特征被检测后它可以从图像中被抽取出来。这个过程可能需要许多图像处理的计算机。其结果被称为特征描述或者特征向量。 
常用的图像特征有颜色特征、纹理特征、形状特征、空间关系特征。 
一 颜色特征 
(一)特点:颜色特征是一种全局特征,描述了图像或图像区域所对应的景物的表面性质。一般颜色特征是基于像素点的特征,此时所有属于图像或图像区域的像素都有各自的贡献。由于颜色对图像或图像区域的方向、大小等变化不敏感,所以颜色特征不能很好地捕捉图像中对象的局部特征。另外,仅使用颜色特征查询时,如果数据库很大,常会将许多不需要的图像也检索出来。颜色直方图是最常用的表达颜色特征的方法,其优点是不受图像旋转和平移变化的影响,进一步借助归一化还可不受图像尺度变化的影响,基缺点是没有表达出颜色空间分布的信息。 
(二)常用的特征提取与匹配方法 
(1) 颜色直方图 
        其优点在于:它能简单描述一幅图像中颜色的全局分布,即不同色彩在整幅图像中所占的比例,特别适用于描述那些难以自动分割的图像和不需要考虑物体空间位置的图像。其缺点在于:它无法描述图像中颜色的局部分布及每种色彩所处的空间位置,即无法描述图像中的某一具体的对象或物体。 
         最常用的颜色空间:RGB颜色空间、HSV颜色空间。 
         颜色直方图特征匹配方法:直方图相交法、距离法、中心距法、参考颜色表法、累加颜色直方图法。 
(2) 颜色集 
        颜色直方图法是一种全局颜色特征提取与匹配方法,无法区分局部颜色信息。颜色集是对颜色直方图的一种近似首先将图像从 RGB颜色空间转化成视觉均衡的颜色空间(如 HSV 空间),并将颜色空间量化成若干个柄。然后,用色彩自动分割技术将图像分为若干区域,每个区域用量化颜色空间的某个颜色分量来索引,从而将图像表达为一个二进制的颜色索引集。在图像匹配中,比较不同图像颜色集之间的距离和色彩区域的空间关系 
(3) 颜色矩 
        这种方法的数学基础在于:图像中任何的颜色分布均可以用它的矩来表示。此外,由于颜色分布信息主要集中在低阶矩中,因此,仅采用颜色的一阶矩(mean)、二阶矩(variance)和三阶矩(skewness)就足以表达图像的颜色分布。 
(4) 颜色聚合向量 
        其核心思想是:将属于直方图每一个柄的像素分成两部分,如果该柄内的某些像素所占据的连续区域的面积大于给定的阈值,则该区域内的像素作为聚合像素,否则作为非聚合像素。 
(5) 颜色相关图 
二 纹理特征 
(一)特点:纹理特征也是一种全局特征,它也描述了图像或图像区域所对应景物的表面性质。但由于纹理只是一种物体表面的特性,并不能完全反映出物体的本质属性,所以仅仅利用纹理特征是无法获得高层次图像内容的。与颜色特征不同,纹理特征不是基于像素点的特征,它需要在包含多个像素点的区域中进行统计计算。在模式匹配中,这种区域性的特征具有较大的优越性,不会由于局部的偏差而无法匹配成功。作为一种统计特征,纹理特征常具有旋转不变性,并且对于噪声有较强的抵抗能力。但是,纹理特征也有其缺点,一个很明显的缺点是当图像的分辨率变化的时候,所计算出来的纹理可能会有较大偏差。另外,由于有可能受到光照、反射情况的影响,从2-D图像中反映出来的纹理不一定是3-D物体表面真实的纹理。 
        例如,水中的倒影,光滑的金属面互相反射造成的影响等都会导致纹理的变化。由于这些不是物体本身的特性,因而将纹理信息应用于检索时,有时这些虚假的纹理会对检索造成“误导”。 
        在检索具有粗细、疏密等方面较大差别的纹理图像时,利用纹理特征是一种有效的方法。但当纹理之间的粗细、疏密等易于分辨的信息之间相差不大的时候,通常的纹理特征很难准确地反映出人的视觉感觉不同的纹理之间的差别。 
(二)常用的特征提取与匹配方法 
  纹理特征描述方法分类 
(1)统计方法统计方法的典型代表是一种称为灰度共生矩阵的纹理特征分析方法Gotlieb 和 Kreyszig 等人在研究共生矩阵中各种统计特征基础上,通过实验,得出灰度共生矩阵的四个关键特征:能量、惯量、熵和相关性。统计方法中另一种典型方法,则是从图像的自相关函数(即图像的能量谱函数)提取纹理特征,即通过对图像的能量谱函数的计算,提取纹理的粗细度及方向性等特征参数 
(2)几何法 
        所谓几何法,是建立在纹理基元(基本的纹理元素)理论基础上的一种纹理特征分析方法。纹理基元理论认为,复杂的纹理可以由若干简单的纹理基元以一定的有规律的形式重复排列构成。在几何法中,比较有影响的算法有两种:Voronio 棋盘格特征法和结构法。 
(3)模型法 
        模型法以图像的构造模型为基础,采用模型的参数作为纹理特征。典型的方法是随机场模型法,如马尔可夫(Markov)随机场(MRF)模型法和 Gibbs 随机场模型法 
(4)信号处理法 
        纹理特征的提取与匹配主要有:灰度共生矩阵、Tamura 纹理特征、自回归纹理模型、小波变换等。 
        灰度共生矩阵特征提取与匹配主要依赖于能量、惯量、熵和相关性四个参数。Tamura 纹理特征基于人类对纹理的视觉感知心理学研究,提出6种属性,即:粗糙度、对比度、方向度、线像度、规整度和粗略度。自回归纹理模型(simultaneous auto-regressive, SAR)是马尔可夫随机场(MRF)模型的一种应用实例。 
三 形状特征 
(一)特点:各种基于形状特征的检索方法都可以比较有效地利用图像中感兴趣的目标来进行检索,但它们也有一些共同的问题,包括:①目前基于形状的检索方法还缺乏比较完善的数学模型;②如果目标有变形时检索结果往往不太可靠;③许多形状特征仅描述了目标局部的性质,要全面描述目标常对计算时间和存储量有较高的要求;④许多形状特征所反映的目标形状信息与人的直观感觉不完全一致,或者说,特征空间的相似性与人视觉系统感受到的相似性有差别。另外,从 2-D 图像中表现的 3-D 物体实际上只是物体在空间某一平面的投影,从 2-D 图像中反映出来的形状常不是 3-D 物体真实的形状,由于视点的变化,可能会产生各种失真。 
(二)常用的特征提取与匹配方法 
Ⅰ几种典型的形状特征描述方法 
        通常情况下,形状特征有两类表示方法,一类是轮廓特征,另一类是区域特征。图像的轮廓特征主要针对物体的外边界,而图像的区域特征则关系到整个形状区域。 
几种典型的形状特征描述方法: 
(1)边界特征法该方法通过对边界特征的描述来获取图像的形状参数。其中Hough 变换检测平行直线方法和边界方向直方图方法是经典方法。Hough 变换是利用图像全局特性而将边缘像素连接起来组成区域封闭边界的一种方法,其基本思想是点—线的对偶性;边界方向直方图法首先微分图像求得图像边缘,然后,做出关于边缘大小和方向的直方图,通常的方法是构造图像灰度梯度方向矩阵。 
(2)傅里叶形状描述符法 
        傅里叶形状描述符(Fourier shape deors)基本思想是用物体边界的傅里叶变换作为形状描述,利用区域边界的封闭性和周期性,将二维问题转化为一维问题。 
        由边界点导出三种形状表达,分别是曲率函数、质心距离、复坐标函数。 
(3)几何参数法 
        形状的表达和匹配采用更为简单的区域特征描述方法,例如采用有关形状定量测度(如矩、面积、周长等)的形状参数法(shape factor)。在 QBIC 系统中,便是利用圆度、偏心率、主轴方向和代数不变矩等几何参数,进行基于形状特征的图像检索。 
        需要说明的是,形状参数的提取,必须以图像处理及图像分割为前提,参数的准确性必然受到分割效果的影响,对分割效果很差的图像,形状参数甚至无法提取。 
(4)形状不变矩法 
利用目标所占区域的矩作为形状描述参数。 
(5)其它方法 
        近年来,在形状的表示和匹配方面的工作还包括有限元法(Finite Element Method 或 FEM)、旋转函数(Turning )和小波描述符(Wavelet Deor)等方法。 
Ⅱ 基于小波和相对矩的形状特征提取与匹配 
        该方法先用小波变换模极大值得到多尺度边缘图像,然后计算每一尺度的 7个不变矩,再转化为 10 个相对矩,将所有尺度上的相对矩作为图像特征向量,从而统一了区域和封闭、不封闭结构。 
四 空间关系特征 
(一)特点:所谓空间关系,是指图像中分割出来的多个目标之间的相互的空间位置或相对方向关系,这些关系也可分为连接/邻接关系、交叠/重叠关系和包含/包容关系等。通常空间位置信息可以分为两类:相对空间位置信息和绝对空间位置信息。前一种关系强调的是目标之间的相对情况,如上下左右关系等,后一种关系强调的是目标之间的距离大小以及方位。显而易见,由绝对空间位置可推出相对空间位置,但表达相对空间位置信息常比较简单。 
        空间关系特征的使用可加强对图像内容的描述区分能力,但空间关系特征常对图像或目标的旋转、反转、尺度变化等比较敏感。另外,实际应用中,仅仅利用空间信息往往是不够的,不能有效准确地表达场景信息。为了检索,除使用空间关系特征外,还需要其它特征来配合。 
(二)常用的特征提取与匹配方法 
        提取图像空间关系特征可以有两种方法:一种方法是首先对图像进行自动分割,划分出图像中所包含的对象或颜色区域,然后根据这些区域提取图像特征,并建立索引;另一种方法则简单地将图像均匀地划分为若干规则子块,然后对每个图像子块提取特征,并建立索引。 
姿态估计问题就是:确定某一三维目标物体的方位指向问题。姿态估计在机器人视觉、动作跟踪和单照相机定标等很多领域都有应用。 
        在不同领域用于姿态估计的传感器是不一样的,在这里主要讲基于视觉的姿态估计。 
        基于视觉的姿态估计根据使用的摄像机数目又可分为单目视觉姿态估计和多目视觉姿态估计。根据算法的不同又可分为基于模型的姿态估计和基于学习的姿态估计。 
一基于模型的姿态估计方法 
        基于模型的方法通常利用物体的几何关系或者物体的特征点来估计。其基本思想是利用某种几何模型或结构来表示物体的结构和形状,并通过提取某些物体特征,在模型和图像之间建立起对应关系,然后通过几何或者其它方法实现物体空间姿态的估计。这里所使用的模型既可能是简单的几何形体,如平面、圆柱,也可能是某种几何结构,也可能是通过激光扫描或其它方法获得的三维模型。 
        基于模型的姿态估计方法是通过比对真实图像和合成图像,进行相似度计算更新物体姿态。目前基于模型的方法为了避免在全局状态空间中进行优化搜索,一般都将优化问题先降解成多个局部特征的匹配问题,非常依赖于局部特征的准确检测。当噪声较大无法提取准确的局部特征的时候,该方法的鲁棒性受到很大影响。 
二基于学习的姿态估计方法 
        基于学习的方法借助于机器学习(machine learning)方法,从事先获取的不同姿态下的训练样本中学习二维观测与三维姿态之间的对应关系,并将学习得到的决策规则或回归函数应用于样本,所得结果作为对样本的姿态估计。基于学习的方法一般采用全局观测特征,不需检测或识别物体的局部特征,具有较好的鲁棒性。其缺点是由于无法获取在高维空间中进行连续估计所需要的密集采样,因此无法保证姿态估计的精度与连续性。 
        基于学习的姿态估计方法源于姿态识别方法的思想。姿态识别需要预先定义多个姿态类别,每个类别包含了一定的姿态范围;然后为每个姿态类别标注若干训练样本,通过模式分类的方法训练姿态分类器以实现姿态识别。 
        这一类方法并不需要对物体进行建模,一般通过图像的全局特征进行匹配分析,可以有效的避免局部特征方法在复杂姿态和遮挡关系情况下出现的特征匹配歧义性问题。然而姿态识别方法只能将姿态划分到事先定义的几个姿态类别中,并不能对姿态进行连续的精确的估计。 
        基于学习的方法一般采用全局观测特征,可以保证算法具有较好的鲁棒性。然而这一类方法的姿态估计精度很大程度依赖于训练的充分程度。要想比较精确地得到二维观测与三维姿态之间的对应关系,就必须获取足够密集的样本来学习决策规则和回归函数。而一般来说所需要样本的数量是随状态空间的维度指数级增加的,对于高维状态空间,事实上不可能获取进行精确估计所需要的密集采样。因此,无法得到密集采样而难以保证估计的精度与连续性,是基于学习的姿态估计方法无法克服的根本困难。 
        和姿态识别等典型的模式分类问题不同的是,姿态估计输出的是一个高维的姿态向量,而不是某个类别的类标。因此这一类方法需要学习的是一个从高维观测向量到高维姿态向量的映射,目前这在机器学习领域中还是一个非常困难的问题。 
        特征是描述模式的最佳方式,且我们通常认为特征的各个维度能够从不同的角度描述模式,在理想情况下,维度之间是互补完备的。 
        特征提取的主要目的是降维。特征抽取的主要思想是将原始样本投影到一个低维特征空间,得到最能反应样本本质或进行样本区分的低维样本特征。 
        一般图像特征可以分为四类:直观性特征、灰度统计特征、变换系数特征与代数特征。 
        直观性特征主要指几何特征,几何特征比较稳定,受人脸的姿态变化与光照条件等因素的影响小,但不易抽取,而且测量精度不高,与图像处理技术密切相关。 
        代数特征是基于统计学习方法抽取的特征。代数特征具有较高的识别精度,代数特征抽取方法又可以分为两类:一种是线性投影特征抽取方法;另外一种是非线性特征抽取方法。 
        习惯上,将基于主分量分析和Fisher线性鉴别分析所获得的特征抽取方法,统称为线性投影分析。 
       基于线性投影分析的特征抽取方法,其基本思想是根据一定的性能目标来寻找一线性变换,把原始信号数据压缩到一个低维子空间,使数据在子空间中的分布更加紧凑,为数据的更好描述提供手段,同时计算的复杂度得到大大降低。在线性投影分析中,以主分量分析(PCA,或称K-L变换)和Fisher线性鉴别分析(LDA)最具代表性,围绕这两种方法所形成的特征抽取算法,已成为模式识别领域中最为经典和广泛使用的方法。 
        线性投影分析法的主要缺点为:需要对大量的已有样本进行学习,且对定位、光照与物体非线性形变敏感,因而采集条件对识别性能影响较大。 
        非线性特征抽取方法也是研究的热点之一。“核技巧”最早应用在SVM中,KPCA和KFA是“核技巧”的推广应用。 
        核投影方法的基本思想是将原样本空间中的样本通过某种形式的非线性映射,变换到一个高维甚至无穷维的空间,并借助于核技巧在新的空间中应用线性的分析方法求解。由于新空间中的线性方向也对应原样本空间的非线性方向,所以基于核的投影分析得出的投影方向也对应原样本空间的非线性方向。 
        核投影方法也有一些弱点:几何意义不明确,无法知道样本在非显式映射后变成了什么分布模式;核函数中参数的选取没有相应选择标准,大多数只能采取经验参数选取;不适合训练样本很多的情况,原因是经过核映射后,样本的维数等于训练样本的个数,如果训练样本数目很大,核映射后的向量维数将会很高,并将遇到计算量上的难题。 
         就应用领域来说,KPCA远没有PCA应用的广泛。如果作为一般性的降维KPCA确实比PCA效果好,特别是特征空间不是一般的欧式空间的时候更为明显。PCA可以通过大量的自然图片学习一个子空间,但是KPCA做不到。 
        变换系数特征指先对图像进行Fourier变换、小波变换等,得到的系数后作为特征进行识别。

作者:dadaadao 发表于2012/2/21 13:18:34  原文链接
阅读:466 评论:0  查看评论
 
[转]VC 2008 Express下安装OpenCV2.3.1
 

注意:

  1. 下列文档以VC2008 Express为例,VC2010下的配置应与本文档类似。
  2. VC 6.0不被OpenCV 2.3.1支持。
  3. VC Express是微软提供的免费版,可从此处下载: http://www.microsoft.com/visualstudio/en-us/products/2010-editions/express
  4. 建议先不要自己编译,如果使用预编译好的库有问题,再尝试自己编译。

目录

[隐藏]
  • 1 安装所需要的软件
    • 1.1 下载OpenCV
    • 1.2 安装CMake(不打算自己编译无需安装)
  • 2 编译OpenCV(非必需步骤)
    • 2.1 用CMake导出VC++项目文件
    • 2.2 编译 OpenCV Debug和Release版本库
  • 3 配置VC
    • 3.1 配置include路径
    • 3.2 配置lib路径
  • 4 设置环境变量
  • 5 使用OpenCV 2.3.1编程
  • 6 作者

[ 编辑]

安装所需要的软件

[ 编辑]

下载OpenCV

  1. 从本站下载栏目 http://www.opencv.org.cn/index.php/Download 下载 OpenCV for Windows(也即 OpenCV-2.3.1-win-superpack.exe 文件)。
  2. 将 OpenCV-2.3.1-win-superpack.exe 解压并放到某个目录下,例如 D:\Program Files\OpenCV2.3.1 (无需运行setup.exe,解压则可)。解压后的目录结构如下图。
点击看大图
[ 编辑]

安装CMake(不打算自己编译无需安装)

从 http://www.cmake.org/cmake/resources/software.html 下载 Windows (Win32 Installer) 安装。


[ 编辑]

编译OpenCV(非必需步骤)

[ 编辑]

用CMake导出VC++项目文件

  • 运行cmake-gui,设置where is the source code路径为OpenCV安装路径(本文档假定安装位置为:D:\Program Files\OpenCV2.3.1),并创建子目录D:\Program Files\OpenCV2.3.1\build\my,并将cmake的"where to build the binaries"设置为这个目录。
  • 然后点 configure,在弹出的对话框内选择 Visual Studio 9 2008。
  • 你可根据你的系统修改选项,修改后再次选择“Congfigure”,完成后选择“Generate”。
2.0版本截图仅供参考,点击看大图
点击看大图
2.0版本截图仅供参考,点击看大图
[ 编辑]

编译 OpenCV Debug和Release版本库

完成上一步骤后,将在D:\Program Files\OpenCV2.3.1\build\my目录下生成OpenCV.sln的VC Solution File,请用VC++ 2008 Express打开OpenCV.sln,然后执行如下操作:

  • 在Debug下,选择Solution Explorer(解决方案资源管理器)里的 Solution OpenCV(解决方案“OpenCV”),点右键,运行"Rebuild Solution";如编译无错误,再选择INSTALL项目,运行"Build"。
  • 在Release下,选择Solution Explorer里的 Solution OpenCV,点右键,运行"Rebuild Solution";如编译无错误,再选择INSTALL项目,运行"Build"。

全部运行完毕后,针对你的系统的OpenCV库就生成了。

[ 编辑]

配置VC

[ 编辑]

配置include路径

也即告诉VC去什么地方寻找OpenCV的头文件,打开VC,选择菜单“工具”->“选项”->“项目和解决方案”->“VC++目录”->“包含文件”,包含 D:\Program Files\OpenCV2.3.1\build\include;D:\Program Files\OpenCV2.3.1\build\include\opencv;D:\Program Files\OpenCV2.3.1\build\include\opencv2如果是自己编译的则输入D:\Program Files\OpenCV2.3.1\build\my\install\include

点击看大图
[ 编辑]

配置lib路径

也即告诉VC去什么地方寻找OpenCV的库文件。

在刚才下载的文件OpenCV-2.3.1-win-superpack.exe 里,已经为VC2008和VC2010预先编译好了动态库和静态库。因此我们不需要如早先版本那样,自己用cmake编译OpenCV。

  • 自己编译的库,库目录为:D:\Program Files\OpenCV2.3.1\build\my\install\lib
  • 32位系统 & VC2008,库目录为:D:\Program Files\OpenCV2.3.1\build\x86\vc9\lib
  • 32位系统 & VC2010,库目录为:D:\Program Files\OpenCV2.3.1\build\x86\vc10\lib
  • 64位系统 & VC2008,库目录为:D:\Program Files\OpenCV2.3.1\build\x64\vc9\lib
  • 64位系统 & VC2010,库目录为:D:\Program Files\OpenCV2.3.1\build\x64\vc10\lib

请根据自己的情况四选一,将库目录输入菜单“工具”->“选项”->“项目和解决方案”->“VC++目录”->“库文件” 。如下图所示:

点击看大图
点击看大图
点击看大图
[ 编辑]

设置环境变量

刚才设置的是动态库,因此还需要将OpenCV的dll文件所在的目录加入Path环境变量。dll文件目录如下,请根据自己情况五选一:

  • 自己编译的库,dll目录为:D:\Program Files\OpenCV2.3.1\build\my\install\bin
  • 32位系统 & VC2008,dll目录为:D:\Program Files\OpenCV2.3.1\build\x86\vc9\bin
  • 32位系统 & VC2010,dll目录为:D:\Program Files\OpenCV2.3.1\build\x86\vc10\bin
  • 64位系统 & VC2008,dll目录为:D:\Program Files\OpenCV2.3.1\build\x64\vc9\bin
  • 64位系统 & VC2010,dll目录为:D:\Program Files\OpenCV2.3.1\build\x64\vc10\bin

由于有些函数需要TBB,所以需要将tbb所在的目录也加入到环境变量Path中。TBB相关的DLL路径为:

  • 32位系统 & VC2008:D:\Program Files\OpenCV2.3.1\build\common\tbb\ia32\vc9
  • 32位系统 & VC2010:D:\Program Files\OpenCV2.3.1\build\common\tbb\ia32\vc10
  • 64位系统 & VC2008:D:\Program Files\OpenCV2.3.1\build\common\tbb\intel64\vc9
  • 64位系统 & VC2010:D:\Program Files\OpenCV2.3.1\build\common\tbb\intel64\vc10

如下图所示将OpenCV和TBB的dll文件所在的目录系统环境变量Path中。加入后可能需要注销当前Windows用户(或重启)后重新登陆才生效。

点击看大图
点击看大图
[ 编辑]

使用OpenCV 2.3.1编程

  • 打开VC++ 2008 Express,创建一个Win32控制台程序helloopencv;
点击看大图
  • 选择Solution Explorer里的opencvhello项目,点击鼠标右键,选择Properties。
点击看大图
  • ,在[链接器 LINKER]的[输入INPUT]中,为项目的Debug配置增加 [附加依赖项 Additional Dependencies]:opencv_calib3d231d.lib; opencv_contrib231d.lib; opencv_core231d.lib; opencv_features2d231d.lib; opencv_flann231d.lib; opencv_gpu231d.lib; opencv_highgui231d.lib; opencv_imgproc231d.lib; opencv_legacy231d.lib; opencv_ml231d.lib; opencv_objdetect231d.lib; opencv_ts231d.lib; opencv_video231d.lib (可根据实际需要删减)注意,请打开了新编辑窗口(即点击了“...”按钮)“附加依赖项”,并一条一条分别加入,一条一行(一个回车),否则会出现类似以下错误:1>LINK : fatal error LNK1104: 无法打开文件“…….lib”
点击看大图
  • 为项目的Release配置增加[附加依赖项 Additional Dependencies]:opencv_calib3d231.lib; opencv_contrib231.lib; opencv_core231.lib; opencv_features2d231.lib; opencv_flann231.lib; opencv_gpu231.lib; opencv_highgui231.lib; opencv_imgproc231.lib; opencv_legacy231.lib; opencv_ml231.lib; opencv_objdetect231.lib; opencv_ts231.lib; opencv_video231.lib (可根据实际需要删减)
点击看大图
  • 编译运行下面的例程(需要将lena.jpg文件放在项目目录下,即与生成的.exe文件同位置)。
/***********************************************************************
 * OpenCV 2.3.1 测试例程
 * 于仕琪 提供
 ***********************************************************************/
#include "stdafx.h"
 
#include 
 
using namespace std;
using namespace cv;
 
int main(int argc, char* argv[])
{
	const char* imagename = "lena.jpg";
 
	//从文件中读入图像
	Mat img = imread(imagename);
 
	//如果读入图像失败
	if(img.empty())
	{
		fprintf(stderr, "Can not load image %s\n", imagename);
		return -1;
	}
 
	//显示图像
	imshow("image", img);
 
	//此函数等待按键,按键盘任意键就返回
	waitKey();
 
	return 0;
}
作者:dadaadao 发表于2012/2/21 8:48:17  原文链接
阅读:1138 评论:0  查看评论
 
[转]SIFT算法分析(草稿)
 
原文: http://blog.sina.com.cn/s/blog_916b71bb0100upwx.html
特征提取在CV(computer vision)领域非常重要。SIFT是非常出名的特征提取算法,它来自论文IJCV'04的“Distinctive image features from scale-invariant keypoints”,在scholar.google.com上查到的引用次数一万多次,很高了!我准备在这个帖子里,根据这篇论文和SIFT算法的一个开源实现,详细描述SIFT算法。
本文中的SIFT实现在http://blogs.oregonstate.edu/hess/,http://blogs.oregonstate.edu/hess/code/sift/,Linux版本的。
 
0. 摘要
  这里说SIFT算法如何之好,在图像有尺度变化和旋转的时候,提取的特征是不变的,在一定程度的仿射扭曲,3D视角,噪声,照明变化的情况下,匹配稳定。
 
1. 介绍
  SIFT算法用级联式的过滤方法,降低提取特征的时间成本。也就是说,它用4个步骤提取特征,对图像上的每个像素,只有通过了着4个步骤,才算是特征点。这样做的好处就是,把大部分的像素在开始就排除掉,不在它们那里浪费时间。这4个步骤如下:1)尺度空间极值;2)关键点定位;3)方向关联;4)关键点描述子。
 
2. 相关研究
  特征提取文献综述。
 
3. 尺度空间极值的检测
  开始进入正题了。尺度空间极值检测是4个步骤的第1个。为什么要检测尺度空间极值呢?如果要提取一个目标的不变特征,假设是我们要识别iphone,就要先给iphone拍张照片,然后用这个照片作为基准,提取特征。提取好特征值后,将来遇到iphone的大图,小图,旋转了一定角度的图,都能识别出来,如果只是大小和角度都不变的情况下识别,没什么实际价值。既然在大图,小图,旋转图的情况下都能识别,那么,必然在图像里必然存在一些“特性”,这些特征在大图,小图,旋转图里都“不变”的,或者近似不变。这个必然的,否则没有特征提取的必要了。尺度空间极值检测,就是找到图像上的若干候选点,这些点在图像的尺度发生变化的时候,也就是图像变大或者变小,它们和周围其他的点的关系保持不变。
 
  引用的论文说,在一定假定下,尺度空间核只可能是高斯函数。公式(1)就给出了用微分高斯函数卷积图像的计算尺度空间极值的方法。注意:这块的算法本身是很简洁,只是写的不太容易看得懂,反正我看论文的话是没看明白如何实现它,第3节的位于3.1小节上的最后那段话写的相当费解。我从源代码出发,把第3节从开始到第3.1节之间的部分,把公式(1)的DOG计算方法写成如下步骤:
  [1] 假设待处理的图像是demo.jpg,是单通道的灰度图,尺寸是512*256。
  [2] 计算octave
       什么是octave呢?octave原来意思是音乐上的一个八度。代码里octave数量计算方法是:
       octvs = log( MIN( init_img->width, init_img->height ) ) / log(2) - 2;
       显然,如果图像是512*256,按照上式计算octvs=6。
  [3] 计算图像高斯金字塔
      [3.1] 有两个参数intvls = 3, sigma = 1.6,在代码里是常量。
      [3.2] 计算高斯金字塔的函数,返回一个图像高斯金字塔的指针。图像高斯金字塔指针,指向了6个octave指针,每个octave指针指向intvls+3=6个图像指针。这也就是说,图像指针是IplImage*,那么,每个octave指针就是指针的指针,就是IplImage **,而高斯金字塔的指针是指针的指针的指针,就是IplImage ***。
      [3.3] 计算sigma:在每个octave里,有intvls+3=6个图像,每个图像都是前一个图像通过高斯卷积获得的,每次卷积的sigma不同,需要分别计算。sig[0] = sigma = 1.6。k = 2^(1/3)。其他的sig如下:
           sig[1] = ( (k^0*1.6*k)^2 - ( k^0*1.6 )^2  )^(0.5)
           sig[2] = ( (k^1*1.6*k)^2 - ( k^1*1.6 )^2  )^(0.5)
           sig[3] = ( (k^2*1.6*k)^2 - ( k^2*1.6 )^2  )^(0.5)
           sig[4] = ( (k^3*1.6*k)^2 - ( k^3*1.6 )^2  )^(0.5)
           sig[5] = ( (k^4*1.6*k)^2 - ( k^4*1.6 )^2  )^(0.5)
  对每个octave,sig都是相同的。这个sig会用在每次octave的计算中。为什么有这么奇怪的计算方式呢?因为在源代码里,作者是根据同octave的第i个图像进行高斯卷积,计算第i+1个图像,才会有这么怪异的计算sigma,如果是用第1个计算其他的图像,就不会这么麻烦了。
      [3.4] 计算过程
          [3.4.1]第0层的octave第0个图像是原始图像demo.jpg。
          [3.4.2]第0层的octave第1个图像是第0层octave的第0个图像的高斯卷积,卷积参数是sig[1]
          [3.4.3]第0层的octave第2个图像是第0层octave的第1个图像的高斯卷积,卷积参数是sig[2]
          [3.4.4]第0层的octave第3个图像是第0层octave的第2个图像的高斯卷积,卷积参数是sig[3]
          [3.4.5]第0层的octave第4个图像是第0层octave的第3个图像的高斯卷积,卷积参数是sig[4]
          [3.4.6]第0层的octave第5个图像是第0层octave的第4个图像的高斯卷积,卷积参数是sig[5]
          [3.4.7]第1层的octave第0个图像是第0层octave的第intvls=3个图像的下抽样,宽度和高度均原来一半,即256*128。
          [3.4.8]第1层的octave第1个图像是第1层octave的第0个图像的高斯卷积,卷积参数是sig[1]
          ... ...
           以下以此类推,最终计算出整个高斯金字塔。
  [4] 计算图像DOG金字塔
      [4.1]DOG金字塔指针:DOG金字塔是一个指向指针的指针的指针,即IplImage ***。DOG金字塔指针,指向6个octave指针,每个octave指针,是指向指针的指针,即IplImage **。每个octave指针指向5个图像指针。
      [4.2] 计算过程:
          [4.2.1]第0个octave第0个图像,是高斯金字塔第0个octave第1个图像减其的第0个octave第0个图像
          [4.2.2]第0个octave第1个图像,是高斯金字塔第0个octave第2个图像减其的第0个octave第1个图像
          [4.2.3]第0个octave第2个图像,是高斯金字塔第0个octave第3个图像减其的第0个octave第2个图像
          [4.2.4]第0个octave第3个图像,是高斯金字塔第0个octave第4个图像减其的第0个octave第3个图像
          [4.2.5]第0个octave第4个图像,是高斯金字塔第0个octave第5个图像减其的第0个octave第4个图像
          [4.2.6]第1个octave第0个图像,是高斯金字塔第1个octave第1个图像减其的第1个octave第0个图像
          [4.2.7]第1个octave第1个图像,是高斯金字塔第1个octave第2个图像减其的第1个octave第1个图像
          [4.2.8]第1个octave第2个图像,是高斯金字塔第1个octave第3个图像减其的第1个octave第2个图像
          [4.2.9]第1个octave第3个图像,是高斯金字塔第1个octave第4个图像减其的第1个octave第3个图像
          ... ...
          以下以此类推,最终计算出整个DOG金字塔。
3.1 局部极值检测
  从DOG金字塔里检测尺度空间极值。DOG金字塔有6个octave。对于每个octave,分别找极值。具体步骤如下:
    [5] 计算尺度空间极值点
      [5.1] 第0个octave的尺度空间极值
        [5.1.1]第0层空间极值:对于第1个图像上的所有像素点的值,如果它比它的8个邻域和第0个图像的9个邻域像素和第2个图像的9个邻域像素都大,或者都小,那么它是一个极值点。
        [5.1.2]第1层空间极值:对于第2个图像上的所有像素点的值,如果它比它的8个邻域和第1个图像的9个邻域像素和第3个图像的9个邻域像素都大,或者都小,那么它是一个极值点。
        [5.1.3]第2层空间极值:对于第3个图像上的所有像素点的值,如果它比它的8个邻域和第2个图像的9个邻域像素和第4个图像的9个邻域像素都大,或者都小,那么它是一个极值点。
        [5.1.4]第3层空间极值:对于第4个图像上的所有像素点的值,如果它比它的8个邻域和第3个图像的9个邻域像素和第5个图像的9个邻域像素都大,或者都小,那么它是一个极值点。         
      [5.2] 第1个octave的尺度空间极值
      ... ...
      以下以此类推,最终计算出所有的空间极值点。这些极值点是候选点,需要进一步过滤。
3.2 尺度抽样频率
  这一段分析抽样的各参数对性能的影响问题。
3.3 空间域的抽样频率
  这一段分析抽样的各参数对性能的影响问题。
4 精确的关键点定位
  这部分内容是SIFT的第2步。从第4节到第4.1节之间部分,是精确定位关键的坐标,候选集里的所有点,都要进一步处理。每个关键点根据泰勒级数进行二阶展开,它的像素之值是横坐标,纵坐标和sigma的函数,要求它的极值点,就要将二阶泰勒展开,求一阶导数,令导数为0,求出det_r, det_c, det_sigma,那么,就能精确定位极值点的精确的intvls,纵坐标和横坐标。求出精确坐标之后,再计算它的若干属性,如对比度,要超过一定的阈值才行。这就是公式(3)。对每个关键点,计算dD和hessen_3D,然后矩阵相乘,得出det_x,得出精确值。这一块对应的interp_extremum函数,呆定的,熟悉线性代数和微积分的这块好理解,如果不好理解....翻书去看吧。注意,着一块的推导,类似ax^2+bx+c求一阶导数,将一阶偏导和二阶偏导视为常量即可,但如果将它们视为函数,结果是不同的,是(3)的二分之一。
    [6]精确定位每个关键点的坐标
      [6.1] 对每个关键点,用一个循环,计算关键点精确坐标,最多计算5次。
        [6.1.1] 插值计算其关键点极值在intvsl, row, col上的增量xi, xr, xc。这里的计算是根据公式(3)进行的。
        [6.1.2] 如果xi, xr, xc的绝对值都小于0.5,表明够精确了,跳出循环。
        [6.1.3] 对intvl, r, c 进行更新。如果intvl, r, c超过正常值,就返回NULL。
      [6.2] 如果循环超过给定值,返回NULL。
      [6.3] 计算关键点的对比度,也就是第11页论文的公式。如果对比度小于某个常量,返回NULL。
      [6.4] 生成关键点数据结构feat,根据octave的值,对关键点的坐标进行放大,计算其在原始图像上的像素位置。同时,也把关键点所在的octave, r, c, intvl, subintvl, 并返回feat。
4.1 删除边缘点。
  如果关键点是边缘线条上的,那么要去除掉。对所有的关键点都这么处理,一个公式,呆定的。
  这个原理是这样的,如果一个点,它在线条边缘上,那么,它的2维hessian矩阵的特征值,必然是一个特征值比另外一个特征值大很多。如果它在两个线条的交点,也就是角点,那么两个特征值在同一个数量级上。对hessian矩阵直接求特征值计算量比较大,因此,论文第12页用一个替代性的方法求解。
  [7] 删除边缘上的关键点
    对于所有的关键点,即[6]里头得到的每个feat,根据第12页的最后一个公式,判断是否惟边缘上的点,如果是,删除之。
  [8] 计算特征属性尺度
    对[7]得到的每个特征,计算特征尺度,对应的函数是calc_feature_scales。在[6.4]里,要计算subintvl,这是一个小数,实际上,每个图像的octave和intvl都是整数,但整数的intvlb并不能精确表示极值点,subintvl就是极值点所在的intvl的增量,这一点在[6.4]步骤是很清楚的。在feat的数据结构里,有一个ddata数据结构,记录suvintvl,在这里用到它了。语句:feat->scl = sigma * pow( 2.0, ddata->octv + intvl / intvls )中,signa是常量1.6,octv就是极值点所在的octave的序号,intvl是极值点所在的精确intvl,它很可能是带小数点的实数。ddata->scl_octv = sigma * pow( 2.0, intvl / intvls )就含义类似。这两个值在第3步,进行方向关联的时候,取关键点的邻域时候用得到。
5. 方向关联
  每个关键点都跟关联一个常量的方向值,让特征描述子对图像旋转具有不变性。
  [9] 计算特征方向
    对应函数calc_feature_oris。
    [9.1] 对于每个[8]中获取的特征,都要对它进行方向关联。也就是说,确定它所在的高斯金字塔图像的一定大小的邻域内的主方向。
    [9.2]计算直方图函数是ori_hist,输入参数是,该特征在高斯图像金字塔所在图像的指针,该特征在这个图像上的行号和列号,直方图的桶数(bins),这里是常量36,该特征的邻域大小,注意,这里是根据[8]里计算出来的scl_octv乘以一个常量计算的,也就是说,基本上,intvl线性变化,最后一个参数是sigma,也是根据intvl线性变化。这个函数的计算是呆定的,是按照论文上写的做的。需要注意的是:源代码使用的是atan2函数,计算出来的角度,跟纵横坐标的符号相关,它的值域在[-pi, pi],这里做的处理是bin = cvRound( n * ( ori + CV_PI ) / PI2 ),ori+pi实在[0, 2pi],也就是说,所有的角度都可能,每个bin里都会可能有值。计算出直方图后,对直方图进行平滑操作。
    [9.3] 将“好的”特征压栈:对于直方图的每个bin,如果它比前一个bin和后一个bin都大且超过一个阈值,那么,就复制当前的feat,将这个bin对应的方向值跟feat做关联,然后压栈。注意,在步骤[9.1]是从栈的前面pop一个feat进行处理,而这里是将特征push,这种用法效率比开两个栈要高一些。此外,这种计算方法,一个特征点可能生成多个新特征,只要它的方向值足够大。
6.图像局部描述子
  这是SIFT算法的四个步骤的最后一个。它在源代码里对应函数compute_descriptors。
  [10]计算局部描述子
    [10.1计算描述直方图
      [10.1.1]本文将特征点的近邻区分成4*4个区块,在每个区块里,有若干个像素点,这些像素点都具有不同的方向和幅值,要将这些像素点的方向和幅值计算直方图,直方图的bin是8个,也就是说,将每个bin对应45度。
      [10.1.2]对于每个特征点,首先,计算radius,也就是它的计算描述直方图的邻域的半径。
      [10.1.3]对于邻域内的每个点,要将这个点的当前坐标,转化到主方向上去。着一块在代码里对应的是:
             c_rot = ( j * cos_t - i * sin_t ) / hist_width;
             r_rot = ( j * sin_t + i * cos_t ) / hist_width;
             rbin = r_rot + d / 2 - 0.5;
             cbin = c_rot + d / 2 - 0.5;
             if( rbin > -1.0  &&  rbin < d  &&  cbin > -1.0  &&  cbin < d )
             求c_rot和r_rot是比较费解的。从后面我们可以知道,r+i,表明i是像素纵坐标上的变量,c+j,表明j是像素横坐标上的变量。如果ori为0,那么,此时i,j不需要旋转。因为主方向不变。假设ori不是0,那么,就需要将现在的坐标系,旋转到ori的方向上去。假设有坐标为[i, j],r是极轴,sita是极角,那么,在极坐标下,i,j 点在新坐标系下面,就是x_new = r*cos(sita+ori) = r*cos(sita)cos(ori) - r*sin(sita)sin(ori) = j*cos(ori)-i*sin(ori),y_new = r*sin(sita+ori) = r*sin(sita)cos(ori) + r*cos(sita)sin(ori) = i*cos(oir) + j*sin(ori)。这个要根据图像坐标计算,也就是说,原点在左上角,x向右 为正,y向下为正。
           为什么要除以hist_width呢?在转换之后的新坐标系下,要看c_rot和r_rot跟hist_width之间的关系。因为,计算区域是限制在4个hist_width的,也就是说,c_rot和r_row的值,应该在-2*hist_width和2*hist_width之间。   
          为什么要计算rbin和cbin呢?我们用下面方式推理:if条件,其实就是说-1 < rbin < d <==> -1 < rbin<4 <==> -1 < r_rot+2-0.5 < 4 <==> -2.5 < r_rot < 2.5。而 r_rot是新坐标值与hist_width的比值。if判断,其实就是判断r_rot是不是在指定的范围内,也就是几个hist_width。
          如果if判断通过了,就是计算该像素的方向和幅值。计算方向之后,要将方向减去ori,因为此时计算出来的是当前坐标系下的方向,不是新坐标系下的方向。然后,要根据新坐标系下的坐标,计算权重。
      [10.1.4]计算每个局部区域的直方图,对应函数是interp_hist_entry。在这个函数,根据计算出的rbin和cbin,算出来这个点,应该属于4个*4区块的那个区块,然后,再根据它的方向和幅值,将它累加到不同的bin里。这样,就得到了论文p15页图的右侧的小图,唯一对区别,就是这个图不是4*4,是2*2。
    [10.2]将描述直方图转化描述子,对应函数hist_to_descr。这个就没什么好说啦,就是将4*4*8个值,逐次排列起来,然后做归一化,再转化成整数。
7. 目标识别的应用
    这个是见仁见智,不一而足,可以在原文之上做更多的发挥。
作者:dadaadao 发表于2012/2/20 15:44:56  原文链接
阅读:558 评论:0  查看评论
 
[转]BMP文件存取——C++

 

#include
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//---------------------------------------------------------------------------------------
//以下该模块是完成BMP图像(彩色图像是24bit RGB8bit)的像素获取,并存在文件名为xiang_su_zhi.txt
unsigned char *pBmpBuf;//读入图像数据的指针

int bmpWidth;//图像的宽
int bmpHeight;//图像的高
RGBQUAD *pColorTable;//颜色表指针

int biBitCount;//图像类型,每像素位数
//-------------------------------------------------------------------------------------------
//读图像的位图数据、宽、高、颜色表及每像素位数等数据进内存,存放在相应的全局变量中
bool readBmp(char *bmpName)
{
    FILE *fp=fopen(bmpName,"rb");//二进制读方式打开指定的图像文件

    if(fp==0) return 0;

    //跳过位图文件头结构BITMAPFILEHEADER

    fseek(fp, sizeof(BITMAPFILEHEADER),0);

    //定义位图信息头结构变量,读取位图信息头进内存,存放在变量head

    BITMAPINFOHEADER head;

    fread(&head, sizeof(BITMAPINFOHEADER), 1,fp); //获取图像宽、高、每像素所占位数等信息

    bmpWidth = head.biWidth;

    bmpHeight = head.biHeight;

    biBitCount = head.biBitCount;//定义变量,计算图像每行像素所占的字节数(必须是4的倍数)

    int lineByte=(bmpWidth * biBitCount/8+3)/4*4;//灰度图像有颜色表,且颜色表表项为256

    if(biBitCount==8)
{

        //申请颜色表所需要的空间,读颜色表进内存

        pColorTable=new RGBQUAD[256];

        fread(pColorTable,sizeof(RGBQUAD),256,fp);

}

    //申请位图数据所需要的空间,读位图数据进内存

    pBmpBuf=new unsigned char[lineByte * bmpHeight];

    fread(pBmpBuf,1,lineByte * bmpHeight,fp);

    fclose(fp);//关闭文件

    return 1;//读取文件成功
}
//-----------------------------------------------------------------------------------------
//给定一个图像位图数据、宽、高、颜色表指针及每像素所占的位数等信息,将其写到指定文件中
bool saveBmp(char *bmpName, unsigned char *imgBuf, int width, int height,

             int biBitCount, RGBQUAD *pColorTable)

{

    //如果位图数据指针为0,则没有数据传入,函数返回

    if(!imgBuf)

        return 0;

    //颜色表大小,以字节为单位,灰度图像颜色表为1024字节,彩色图像颜色表大小为0

    int colorTablesize=0;

    if(biBitCount==8)

        colorTablesize=1024;

    //待存储图像数据每行字节数为4的倍数

    int lineByte=(width * biBitCount/8+3)/4*4;

    //以二进制写的方式打开文件

    FILE *fp=fopen(bmpName,"wb");

    if(fp==0) return 0;

    //申请位图文件头结构变量,填写文件头信息

    BITMAPFILEHEADER fileHead;

    fileHead.bfType = 0x4D42;//bmp类型

    //bfSize是图像文件4个组成部分之和

    fileHead.bfSize= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)

        + colorTablesize + lineByte*height;

    fileHead.bfReserved1 = 0;

    fileHead.bfReserved2 = 0;

    //bfOffBits是图像文件前3个部分所需空间之和

    fileHead.bfOffBits=54+colorTablesize;

    //写文件头进文件

    fwrite(&fileHead, sizeof(BITMAPFILEHEADER),1, fp);

    //申请位图信息头结构变量,填写信息头信息

    BITMAPINFOHEADER head;

    head.biBitCount=biBitCount;

    head.biClrImportant=0;

    head.biClrUsed=0;

    head.biCompression=0;

    head.biHeight=height;

    head.biPlanes=1;

    head.biSize=40;

    head.biSizeImage=lineByte*height;

    head.biWidth=width;

    head.biXPelsPerMeter=0;

    head.biYPelsPerMeter=0;

    //写位图信息头进内存

    fwrite(&head, sizeof(BITMAPINFOHEADER),1, fp);

    //如果灰度图像,有颜色表,写入文件

    if(biBitCount==8)

        fwrite(pColorTable, sizeof(RGBQUAD),256, fp);

    //写位图数据进文件

    fwrite(imgBuf, height*lineByte, 1, fp);

    //关闭文件

    fclose(fp);

    return 1;

}

//----------------------------------------------------------------------------------------
//以下为像素的读取函数
void xiang_su_du_qu()

{

    //读入指定BMP文件进内存

    char readPath[]="nv.BMP";

    readBmp(readPath);

    //输出图像的信息

    cout<<"width="<    
    //循环变量,图像的坐标

    //每行字节数

    int lineByte=(bmpWidth*biBitCount/8+3)/4*4;

    //循环变量,针对彩色图像,遍历每像素的三个分量

    int m=0,n=0,count_xiang_su=0;

    //将图像左下角1/4部分置成黑色

ofstream outfile("图像像素.txt",ios::in|ios::trunc);

    if(biBitCount==8) //对于灰度图像
{             
//------------------------------------------------------------------------------------
//以下完成图像的分割成8*8小单元,并把像素值存储到指定文本中。由于BMP图像的像素数据是从
//左下角:由左往右,由上往下逐行扫描的
                int L1=0;
    int hang=63;
    int lie=0;
    //int L2=0;
          //int fen_ge=8;
    for(int fen_ge_hang=0;fen_ge_hang<8;fen_ge_hang++)//64*64矩阵行循环
    {
     for(int fen_ge_lie=0;fen_ge_lie<8;fen_ge_lie++)//64*64列矩阵循环
     {
      //--------------------------------------------
      for(L1=hang;L1>hang-8;L1--)//8*8矩阵行
      {
        for(int L2=lie;L2矩阵列
        {
        m=*(pBmpBuf+L1*lineByte+L2);
                             outfile<                  count_xiang_su++;
                if(count_xiang_su%8==0)//8*8矩阵读入文本文件
       {
            outfile<        }
        }
      }
      //---------------------------------------------
     hang=63-fen_ge_hang*8;//64*64矩阵行变换
     lie+=8;//64*64矩阵列变换
     //该一行(64)由88*8矩阵的行组成
     }
     hang-=8;//64*64矩阵的列变换
     lie=0;//64*64juzhen
    }
}

    //double xiang_su[2048];
//ofstream outfile("xiang_su_zhi.txt",ios::in|ios::trunc);
if(!outfile)
{
cout<<"open error!"<    exit(1);
}
    else if(biBitCount==24){//彩色图像

        for(int i=0;i {

            for(int j=0;j    {

                for(int k=0;k<3;k++)//每像素RGB三个分量分别置0才变成黑色

    {
     //*(pBmpBuf+i*lineByte+j*3+k)-=40;
                    m=*(pBmpBuf+i*lineByte+j*3+k);
        outfile<      count_xiang_su++;
     if(count_xiang_su%8==0)
     {
      outfile<      }
     //n++;
    }
              n++;
            }


        }
cout<<"总的像素个素为:"<
cout<<"----------------------------------------------------"<

    }
   
    //将图像数据存盘
   
    char writePath[]="nvcpy.BMP";//图片处理后再存储

    saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable);

    //清除缓冲区,pBmpBufpColorTable是全局变量,在文件读入时申请的空间

    delete []pBmpBuf;

    if(biBitCount==8)

        delete []pColorTable;

}
void main()
{
    xiang_su_du_qu();
}

作者:dadaadao 发表于2012/2/20 14:51:16  原文链接
阅读:427 评论:0  查看评论
 
[转]小波变换和Gabor变换

1.关于小波变换:一种多分辨率分析工具,为不同尺度上信号的的分析和表征提供了精确和统一框架。它的原理是来源于Fourier变换!但是它比传统的Fourier变换有更多优点,比如:1)小波变换可以覆盖整个频域;2)可以通过选取合适滤波器,减少或除去提取的不同特征之间的相关性;3)具有变焦特性,低频段可用高频率分辨率和低时间分辨率,在高频段可用低频率分辨率和高时间分辨率4)小波变换在实现上有快速算法(Mallat小波分析算法)。提到小波变换必须提到小波函数,简单的说,积分为0的函数都可以作为小波函数,还可以通过一系列变化得到连续的小波变换式。小波变换适用小波函数族及其相应的尺度函数将原始信号分解成不同的频带。一般所说的小波变换仅递归分解信号的低频部分,以生成下一尺度的各频道输出。层层分解(图片不附了),这样的分解通常称为金字塔结构小波变换。如果不仅仅对低通滤波器输出进行递归分解,而且也对高通滤波器的输出进行递归分解,则称之为小波包分解。(树状的图形)小波变换具有良好的时频局部化、尺度变换和方向特征,是分析纹理的有力工具。2.Gabor 变换根据模拟人类视觉系统而产生。通过模拟人类视觉系统,可以将视网膜成像分解成一组滤波图像,每个分解的图像能够反映频率和方向在局部范围内的强度变化。通过一组多通道Gabor滤波器,可以获得纹理特征。Gabor变换的根本就是Gabor滤波器的设计,而滤波器的设计又是其频率函数(U,V)和Gauss函数参数(一个)的设计。实际上,Gabor变换是为了提取信号Fourier变换的局部信息,使用了一个Gauss函数作为窗函数,因为一个Gauss函数的Fourier变换还是一个Gauss函 数,所以Fourier逆变换也是局部的。通过频率参数和高斯函数参数的选取,Gabor变换可以选取很多纹理特征,但是Gabor是非正交的,不同特征分量之间有冗余,所以在对纹理图像的分析中效率不太高。

作者:dadaadao 发表于2012/2/20 14:47:27  原文链接
阅读:3404 评论:0  查看评论
 
[转]sift概念

废话:
      如果你像我一样没有想搞图像的一本书从头看到尾(其实也没几个人能从头看到尾的,很多都是拿MatLAB扯扯淡!)的话,在SIFT算法的资料里出现的很多概念可能是你理解的障碍!因为觉得有必要说一下,希望这样会给一些像我一样没什么基础的人一些帮助!如果一下当中有的你知道了你可以跳过去呵呵!也有可能有些概念我解释的不是很对,也希望您的指正!

基本概念:

降采样:对于一幅图像而言的降采样就是每隔几行、几列得到取一点,组成一个新的图像。以比例因子为2fact of 2)的降采样来说:就是対一幅图像每隔一行一列取一点。对于n×n的图像就变为n/2×n/2的图像了。比例因子为2的降采样是SFIT要用到的!

升采样:其实一种插值,就是在一幅图像里利用相关的插值运算得到一幅大的图像!比如比例因子为2的升采样就是每个相邻像素点种插值出一个像素(这里包括XY两个方向)。对于n×n的图像就变为2n×2n的图像了。顺便说下插值,就是一种利用已有数据对位置数据的估计。比如我第10秒走了12米,第20走了30,那么我用线性插值估计我第15秒走了(30-12/2+12=21米,当然插值的方法有很多!

图像金字塔:简单的说是一个图像集,由一个原始图像经过降采样得到一幅图像,再对新的图像做降采样,重复多次构成的一组集合。如果形象的把这些图像摞起来就想一个金字塔,故此得名。

卷积:这个概念我感觉是最不好解释的,他就是一个积分,两个函数(其中一个带参数)对应点的成绩,然后吧乘积函数求积分!大致我理解就是这样,但是具体的还是请大家去查书。但是这里我想说卷积再图像里的运算。卷积是一种无限的积分运算,但是因为在一个二维平面(X轴对应一个卷积函数,Y轴对应一个卷积函数!),一般境况下围绕某一点的卷积运算距离卷几点一定远的运算对最后结果很小,所以通常忽略!这样卷积运算就变成了一种模板运算!例如3×3的一种模板运算,就是把理他最近的9个点(包括它本身9个点)分别乘以按一定的加权函数所对应的权值后加到这个中心点上成为该点的新值。那么对于3×3的模板运算每一点要做9个乘法9个加法。那么对于1000个像素的图像做模板运算就要做9000个乘法,9000加法!如果模板再大,计算次数会更多。

高斯卷积:就是权函数为高斯函数的卷积模板运算,高斯卷积有一次和二次...。通常做高斯卷积后的图像会比原图像平滑但也会模糊,所以又称高斯模糊!因为这不能写公式,很多的细节可以看下面的附件里的图片!

高斯金字塔:高斯金字塔里有两个概念:组(Octave)和层(LevelInterval),每组里有若干层!高斯金字塔的构造是这样的,第一组的第一层为原图像,然后将图像做一次高斯平滑(高斯卷积、高斯模糊)高斯平滑里有一个参数σ(详见附件图片),SIFT里作者取1.6
然后将σ乘一个比例系数k作为新的平滑因子来平滑第一组第二层得到第三层。重复若干次,得到L层他们分别对应的平滑参数为:0σkσk2σ....。然后将最后一幅图像做比例因此为2的降采样得到第二组的第一层,然后对第二组的第一层做参数是σ的高斯平滑,对第二层做kσ的平滑得到第三层.....这里一定注意:每组对应的平滑因子是一样的!而不是像有的资料上说的持续递增。这样反复形成了OL层。一般模糊的高斯模板长宽都约为6σ(这里σ为当次的平滑因子,就是可能是kσk2σ..

DoGDifference of Gaussian)金字塔:他是由高斯金字塔构造出来的,他的第一组第一层是由高斯金字塔的第一组第二层减第一组第一层,他的第一组第二层是由高斯金字塔的第一组第三层减第一组第二层得到,(说的这么繁琐是为了大家能理解的直观点)。没组都这样就生成了DoG金字塔。顺便说一下,DoG金字塔每组图像几乎都是一片黑,但仔细看你能看出轮廓的。

两个金字塔在SIFT算法里的特殊说明:1、在SIFT里高斯金字塔的第一组第一层通常是由一个原图像长宽扩大一倍开始的,这样做是为了可以得到更多的特征点2、大家可以发现如果用每组5层的高斯金字塔构造一个DoG金字塔的的话,DoG的每组的层数是43、对于DoG金字塔,特征点的搜索从每组的二层到倒数第二层的(后面说明为什么),所以如果实际用n层那么DoG金字塔应该有n+2层,那么对应的高斯金字塔应该有n+3层。4、由于这样所以高斯金字塔从第二组开始的每组第一层是由上一组的倒数第二层降采样得到的。

梯度:就是一个有方向和长度的向量,它的意义是一个函数的某一点上数值变化最大的方向和变化量。在图像中一个像素点的梯度是由它周围的8个点计算得到的。(公式见附件的图)

K-d树:一种数据结构,用于搜索高维最邻近点,他是一种二叉树,每个节点是一个高维向量。对于他的具体说明我没仔细看呢,如果做到最后需要请大家参考这个:顺便说下维基百科不错!!
http://en.wikipedia.org/wiki/Kd-tree

尺度:这个概念最让我郁闷,现在弄的不是很清楚。我现在的理解就是(这不是它的感念):1、表示同一事物所用到的图像像素量,用的多尺度就小,用的少尺度就大(说没说反?嘿嘿!)。2、它和图像的清晰程度有关,如上面说的高斯模糊,那么因子σ越大得到的图像越模糊,那么尺度越大!3、图像的旋转、平移尺度是不变的,但是放大、缩小、模糊就变了。
详细说明还请参考维基百科。

      我在想到的相关概念就这些,如果还有一些概念没说明欢迎大家告诉我!我后续会补上

 

作者:dadaadao 发表于2012/2/20 14:33:29  原文链接
阅读:606 评论:0  查看评论
 
[转]协方差矩阵的详细说明
 

黄叶权整理于2007-7-18

在做人脸识别的时候经常与协方差矩阵打交道,但一直也只是知道其形式,而对其意义却比较模糊,现在我根据单变量的协方差给出协方差矩阵的详细推导以及在不同应用背景下的不同形式。

变量说明:

设为一组随机变量,这些随机变量构成随机向量,每个随机变量有m个样本,则有样本矩阵

(1)

其中对应着每个随机向量X的样本向量,对应着第i个随机单变量的所有样本值构成的向量。

单随机变量间的协方差:

随机变量之间的协方差可以表示为

(2)

根据已知的样本值可以得到协方差的估计值如下:

(3)

可以进一步地简化为:

                           (4)

协方差矩阵:

(5)

其中从而得到了协方差矩阵表达式。

如果所有样本的均值为一个零向量,则式(5)可以表达成:

(6)

补充说明:

1、协方差矩阵中的每一个元素是表示的随机向量X的不同分量之间的协方差,而不是不同样本之间的协方差,如元素Cij就是反映的随机变量Xi, Xj的协方差。

2、协方差是反映的变量之间的二阶统计特性,如果随机向量的不同分量之间的相关性很小,则所得的协方差矩阵几乎是一个对角矩阵。对于一些特殊的应用场合,为了使随机向量的长度较小,可以采用主成分分析的方法,使变换之后的变量的协方差矩阵完全是一个对角矩阵,之后就可以舍弃一些能量较小的分量了(对角线上的元素反映的是方差,也就是交流能量)。特别是在模式识别领域,当模式向量的维数过高时会影响识别系统的泛化性能,经常需要做这样的处理。

3、必须注意的是,这里所得到的式(5)和式(6)给出的只是随机向量协方差矩阵真实值的一个估计(即由所测的样本的值来表示的,随着样本取值的不同会发生变化),故而所得的协方差矩阵是依赖于采样样本的,并且样本的数目越多,样本在总体中的覆盖面越广,则所得的协方差矩阵越可靠。

4、如同协方差和相关系数的关系一样,我们有时为了能够更直观地知道随机向量的不同分量之间的相关性究竟有多大,还会引入相关系数矩阵。


作者:dadaadao 发表于2012/2/20 13:50:00  原文链接
阅读:298 评论:0  查看评论
 
[转]凸包求法
 


#include
#include

using namespace std;

struct POINT 
{
 int x, y;
 int flag; // 表示是否在连线内部,在为0
};

POINT list[500], pk;

int stack[500], top, k, rightnum;

void swap(POINT &a, POINT &b)
{
 POINT t;
 t = a;
 a = b;
 b = t;
}

int CrossProd(POINT p0, POINT p1, POINT p2)
{
 return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}

int comp(const void *pp1, const void *pp2)
{
 //int t;
 POINT *p1 = (POINT*)pp1, *p2 = (POINT*)pp2;
 return CrossProd(list[0], *p1, *p2)*(-1);
}

void select(int n, int &num)
{
 POINT p1, p2;
 int t, i, j, f;
 for (i = 1; i < n; i++)
 {
  f = 0;
  p1 = list[i];
  if (p1.flag)
  {
   for (j = i + 1; j < n; j++)
   {
    p2 = list[j];
    if (p2.flag)
    {
     t = CrossProd(list[0], p1, p2);
     if (t == 0)
     {
      if ((p1.x - list[0].x) * (p1.x - list[0].x) + (p1.y - list[0].y) *(p1.y - list[0].y) \
       < (p2.x - list[0].x) * (p2.x - list[0].x) + (p2.y - list[0].y) *(p2.y - list[0].y))
      {
       list[i].flag = 0;
      }
      else list[j].flag = 0;
     }
    }
   }
  }
 }
 i = 1;
 for (j = 1; j < n; j++)
 {
  if (list[j].flag == 1)
  {
   list[i] = list[j];
   i++;
  }
 }
 num = i - 1;
 qsort(list + 1, num, sizeof(POINT), comp);
}

int init(int n)
{
 int i, num;
 for (i = 0; i < n; i++)
 {
  cin >> list[i].x >> list[i].y;
  list[i].flag = 1;
  if ((list[i].y < list[0].y) || (list[i].y == list[0].y) && (list[i].x < list[0].x))
  {
   swap(list[0], list[i]);
  }
 }
 select(n, num);
 return num + 1;
}

void graham(int n)
{
 int i;
 if (n == 1) cout << "(" << list[0].x << ", " << list[0].y << ")" << endl;
 if (n == 2) cout << "(" << list[0].x << ", " << list[0].y << ")" \
  "(" << list[1].x << ", " << list[1].y << ")" << endl;
 if (n > 2)
 {
  for (i = 0; i <= 2; i++) stack[i] = i;
  top = 2;
  for (i = 3; i <= n-1; i++)
  {
   while (CrossProd(list[stack[top-1]], list[stack[top]], list[i]) <= 0)
    top--;
   top++;
   stack[top] = i;
  }
  for (i = 0; i <= top; i++) cout << "(" << list[stack[i]].x << ", " << list[stack[i]].y << ")";
  cout << endl;
 }
}

void Chain(int n, int LR) // LR=1生成右链,LR=0生成左链
{
 int m = 0, i, t;
 POINT pm;
 stack[0] = 0;
 top = 0;
 while (m != k)
 {
  pm = pk;
  m = k;
  for (i = 1; i < n; i++)
  {
   t = CrossProd(list[stack[top]], list[i], pm);
   if ((t > 0 && LR==1) || (t < 0 && LR == 0) ||\
    (t == 0) && ((list[i].x - list[stack[top]].x) * (list[i].x - list[stack[top]].x) \
    + (list[i].y - list[stack[top]].y)*(list[i].y - list[stack[top]].y)
    > (pm.x - list[stack[top]].x) * (pm.x - list[stack[top]].x) \
    + (pm.y - list[stack[top]].y) * (pm.y - list[stack[top]].y)))
   {
    pm = list[i];
    m = i;
   }
  }
  top ++;
  stack[top] = m;
 }
 if (LR == 1)
 {
  for (i = 0; i <= top; i++)
  {
   cout << "(" <   }
 }
 else
 {
  for (i = top - 1; i > 0; i--)
  {
   cout << "(" <   }
  cout << endl;
 }
}

void init2(int n)
{
 int i;
 for (i = 0; i < n; i++)
 {
  cin >> list[i].x >> list[i].y;
  if (i == 0)
  {
   pk = list[0];
   k = 0;
  }
  if ((list[i].y < list[0].y) || (list[i].y == list[0].y) && (list[i].x < list[0].x))
   swap(list[0], list[i]);
  if ((list[i].y > pk.y) || (list[i].y == pk.y) && (list[i].x > pk.x))
  {
   pk = list[i];
   k = i;
  }
 }
}

void Jarvis(int n)
{
 if (n == 1) cout << "(" << list[0].x << ", " << list[0].y << ")" << endl;
 if (n == 2) cout << "(" << list[0].x << ", " << list[0].y << ")" \
  "(" << list[1].x << ", " << list[1].y << ")" << endl;
 if (n > 2)
 {
  Chain(n, 1);
  Chain(n, 0);
 }
}

int main()
{
 int count = 0, n, num;
 while (cin >> n)
 {
  if (n == 0) break;
  count++;
  cout << "set" << count << ":\n";
  cout << "graham...(\n)";
  num = init(n);
  graham(num);
  cout << "Jarvis...(\n)";
  init2(n);
  Jarvis(n);
 }
 return 0;
}


作者:dadaadao 发表于2012/2/20 13:46:52  原文链接
阅读:342 评论:0  查看评论
 
[转]Surf算法学习心得(三)——Demo分析
 

OpenCV Demo分析(find_obj.cpp

OpenCV2.1中有关于 Surf算法的简单示例(1.1以上的版本都添加了这个算法),在路径:C:\Program Files\OpenCV2.1\samples\c下,名为find_obj.cpp,运行它可以直接观察到相应结果。为了便于介绍这个示例,简单做了如下修改(只是删掉一些代码,但是对于如何使用 Surf算法没有影响)。
修改后的代码及其注释如下:(主要是介绍这个main函数)
/*
* A Demo to OpenCV Implementation of  SURF
* Further Information Refer to “ SURF: Speed-Up Robust Feature”
* Author: Liu Liu
*/
#include “stdafx.h”
#include
#include
#include
#include
#include
#include
#include
using namespace std;
IplImage *image = 0;
double compare SURFDescriptors( const float* d1, const float* d2, double best, int length )
{
double total_cost = 0;
assert( length % 4 == 0 );
for( int i = 0; i < length; i += 4 )
{
double t0 = d1[i] – d2[i];
double t1 = d1[i+1] – d2[i+1];
double t2 = d1[i+2] – d2[i+2];
double t3 = d1[i+3] – d2[i+3];
total_cost += t0*t0 + t1*t1 + t2*t2 + t3*t3;
if( total_cost > best )
break;
}
return total_cost;
}
int naiveNearestNeighbor( const float* vec, int laplacian,
const CvSeq* model_keypoints,
const CvSeq* model_descriptors )
{
int length = (int)(model_descriptors->elem_size/sizeof(float));
int i, neighbor = -1;
double d, dist1 = 1e6, dist2 = 1e6;
CvSeqReader reader, kreader;
cvStartReadSeq( model_keypoints, &kreader, 0 );
cvStartReadSeq( model_descriptors, &reader, 0 );
for( i = 0; i < model_descriptors->total; i++ )
{
const Cv SURFPoint* kp = (const Cv SURFPoint*)kreader.ptr;
const float* mvec = (const float*)reader.ptr;
CV_NEXT_SEQ_ELEM( kreader.seq->elem_size, kreader );
CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader );
if( laplacian != kp->laplacian )
continue;
d = compare SURFDescriptors( vec, mvec, dist2, length );
if( d < dist1 )
{
dist2 = dist1;
dist1 = d;
neighbor = i;
}
else if ( d < dist2 )
dist2 = d;
}
if ( dist1 < 0.6*dist2 )
return neighbor;
return -1;
}
//用于找到两幅图像之间匹配的点对,并把匹配的点对存储在 ptpairs 向量中,其中物体(object)图像的特征点
//及其相应的描述器(局部特征)分别存储在 objectKeypoints 和 objectDescriptors,场景(image)图像的特
//征点及其相应的描述器(局部特征)分别存储在 imageKeypoints和 imageDescriptors
void findPairs( const CvSeq* objectKeypoints, const CvSeq* objectDescriptors,
const CvSeq* imageKeypoints, const CvSeq* imageDescriptors, vector& ptpairs )
{
int i;
CvSeqReader reader, kreader;
cvStartReadSeq( objectKeypoints, &kreader );
cvStartReadSeq( objectDescriptors, &reader );
ptpairs.clear();
for( i = 0; i < objectDescriptors->total; i++ )
{
const Cv SURFPoint* kp = (const Cv SURFPoint*)kreader.ptr;
const float* descriptor = (const float*)reader.ptr;
CV_NEXT_SEQ_ELEM( kreader.seq->elem_size, kreader );
CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader );
int nearest_neighbor = naiveNearestNeighbor( descriptor, kp->laplacian, imageKeypoints, imageDescriptors );
if( nearest_neighbor >= 0 )
{
ptpairs.push_back(i);
ptpairs.push_back(nearest_neighbor);
}
}
}
//用于寻找物体(object)在场景(image)中的位置,位置信息保存在参数dst_corners中,参数src_corners由物
//体(object的width几height等决定,其他部分参数如上findPairs
int locatePlanarObject( const CvSeq* objectKeypoints, const CvSeq* objectDescriptors,
const CvSeq* imageKeypoints, const CvSeq* imageDescriptors,
const CvPoint src_corners[4], CvPoint dst_corners[4] )
{
double h[9];
CvMat _h = cvMat(3, 3, CV_64F, h);
vector ptpairs;
vector pt1, pt2;
CvMat _pt1, _pt2;
int i, n;
findPairs( objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, ptpairs );
n = ptpairs.size()/2;
if( n < 4 )
return 0;
pt1.resize(n);
pt2.resize(n);
for( i = 0; i < n; i++ )
{
pt1[i] = ((Cv SURFPoint*)cvGetSeqElem(objectKeypoints,ptpairs[i*2]))->pt;
pt2[i] = ((Cv SURFPoint*)cvGetSeqElem(imageKeypoints,ptpairs[i*2+1]))->pt;
}
_pt1 = cvMat(1, n, CV_32FC2, &pt1[0] );
_pt2 = cvMat(1, n, CV_32FC2, &pt2[0] );
if( !cvFindHomography( &_pt1, &_pt2, &_h, CV_RANSAC, 5 ))
return 0;
for( i = 0; i < 4; i++ )
{
double x = src_corners[i].x, y = src_corners[i].y;
double Z = 1./(h[6]*x + h[7]*y + h[8]);
double X = (h[0]*x + h[1]*y + h[2])*Z;
double Y = (h[3]*x + h[4]*y + h[5])*Z;
dst_corners[i] = cvPoint(cvRound(X), cvRound(Y));
}
return 1;
}
int main(int argc, char** argv)
{
//物体(object)和场景(scene)的图像向来源
const char* object_filename = argc == 3 ? argv[1] : “box.png”;
const char* scene_filename = argc == 3 ? argv[2] : “box_in_scene.png”;
//内存存储器
CvMemStorage* storage = cvCreateMemStorage(0);
cvNamedWindow(“Object”, 1);
cvNamedWindow(“Object Correspond”, 1);
//颜色值
static CvScalar colors[] =
{
{{0,0,255}},
{{0,128,255}},
{{0,255,255}},
{{0,255,0}},
{{255,128,0}},
{{255,255,0}},
{{255,0,0}},
{{255,0,255}},
{{255,255,255}}
};
IplImage* object = cvLoadImage( object_filename, CV_LOAD_IMAGE_GRAYSCALE );
IplImage* image = cvLoadImage( scene_filename, CV_LOAD_IMAGE_GRAYSCALE );
if( !object || !image )
{
fprintf( stderr, “Can not load %s and/or %s\n”
“Usage: find_obj [ ]\n”,
object_filename, scene_filename );
exit(-1);
}
IplImage* object_color = cvCreateImage(cvGetSize(object), 8, 3);
cvCvtColor( object, object_color, CV_GRAY2BGR );
//物体(object)和场景(scene)的图像的特征点
CvSeq *objectKeypoints = 0, *objectDescriptors = 0;
CvSeq *imageKeypoints = 0, *imageDescriptors = 0;
int i;
//定义Surf算法要用的参数分别为 threshold 和 extended
Cv SURFParams params = cv SURFParams(500, 1);
double tt = (double)cvGetTickCount();
//提取物体(object)和场景(scene)的图像的特征点及其描述器
cvExtract SURF( object, 0, &objectKeypoints, &objectDescriptors, storage, params );
printf(“Object Descriptors: %d\n”, objectDescriptors->total);
cvExtract SURF( image, 0, &imageKeypoints, &imageDescriptors, storage, params );
printf(“Image Descriptors: %d\n”, imageDescriptors->total);
//计算所消耗的时间
tt = (double)cvGetTickCount() – tt;
printf( “Extraction time = %gms\n”, tt/(cvGetTickFrequency()*1000.));
CvPoint src_corners[4] = {{0,0}, {object->width,0}, {object->width, object->height}, {0, object-
>height}};
//定义感兴趣的区域
CvPoint dst_corners[4];
IplImage* correspond = cvCreateImage( cvSize(image->width, object->height+image->height), 8, 1 );
//设置感兴趣区域
cvSetImageROI( correspond, cvRect( 0, 0, object->width, object->height ) );
cvCopy( object, correspond );
cvSetImageROI( correspond, cvRect( 0, object->height, correspond->width, correspond->height ) );
cvCopy( image, correspond );
cvResetImageROI( correspond );
//寻找物体(object)在场景(image)中的位置,并将信息保存
if( locatePlanarObject( objectKeypoints, objectDescriptors, imageKeypoints,
imageDescriptors, src_corners, dst_corners ))
{
for( i = 0; i < 4; i++ )
{
CvPoint r1 = dst_corners[i%4];
CvPoint r2 = dst_corners[(i+1)%4];
cvLine( correspond, cvPoint(r1.x, r1.y+object->height ),
cvPoint(r2.x, r2.y+object->height ), colors[8] );
}
}
//定义并保存物体(object)在场景(image)图形之间的匹配点对,并将其存储在向量 ptpairs 中,之后可以对
//ptpairs 进行操作
vector ptpairs;
findPairs( objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, ptpairs );
//显示匹配结果
for( i = 0; i < (int)ptpairs.size(); i += 2 )
{
Cv SURFPoint* r1 = (Cv SURFPoint*)cvGetSeqElem( objectKeypoints, ptpairs[i] );
Cv SURFPoint* r2 = (Cv SURFPoint*)cvGetSeqElem( imageKeypoints, ptpairs[i+1] );
cvLine( correspond, cvPointFrom32f(r1->pt),
cvPoint(cvRound(r2->pt.x), cvRound(r2->pt.y+object->height)), colors[8] );
}
cvShowImage( “Object Correspond”, correspond );
//显示物体(object)的所有特征点
for( i = 0; i < objectKeypoints->total; i++ )
{
Cv SURFPoint* r = (Cv SURFPoint*)cvGetSeqElem( objectKeypoints, i );
CvPoint center;
int radius;
center.x = cvRound(r->pt.x);
center.y = cvRound(r->pt.y);
radius = cvRound(r->size*1.2/9.*2);
cvCircle( object_color, center, radius, colors[0], 1, 8, 0 );
}
cvShowImage( “Object”, object_color );
cvWaitKey(0);
//释放窗口所占用的内存
cvDestroyWindow(“Object”);
cvDestroyWindow(“Object Correspond”);
return 0;
}
通过调试运行,可以得到dst_corners中的数据如下:
ptpairs 中的数据如下:
也就是:
[78]
(
29 484
77 134
82 274
206 797
228 210
243 203
244 203
249 404
295 105
347 451
360 142
417 190
427 191
436 198
445 204
452 211
466 218
473 105
486 684
502 133
521 169
522 178
527 190
530 190
532 450
533 198
535 197
539 205
542 202
544 208
547 483
558 412
559 412
583 623
586 624
587 624
594 748
595 654
597 657
)
总共有39对匹配点,第一列表示 物体(object)图像中匹配上的点,第一列表示场景(image)图像中匹配的点,其实也就是 物体(object)图像中第28个特征点和 场景(image)图像中第484个特征点相匹配。
通过这种索引(可以这么说ptpairs中存储的是索引 )可以求的那个特征点的坐标,如下:
//取得图像中第i个 特征点
Cv SURFPoint* r = (Cv SURFPoint*)cvGetSeqElem( objectKeypoints, i );
//通过ptpairs取得图像中第 ptpairs[i] 个特征点,这个特征点是匹配上的点
Cv SURFPoint* r1 = (Cv SURFPoint*)cvGetSeqElem( objectKeypoints, ptpairs[i] );
运行示意图如下:
关于find_obj.cpp中的
#ifdef USE_FLANN
flannFindPairs( objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, ptpairs );
#else
findPairs( objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, ptpairs );
#endif
不外乎就是在与说明是否采用 approximate nearest-neighbor 方法来寻找匹配点对。

本文链接:http://www.yongblog.com/archives/160.html 转载请注明出处。

作者:dadaadao 发表于2012/2/20 13:27:15  原文链接
阅读:2835 评论:0  查看评论
 
[转]Surf算法学习心得(二)——源码简析
 
说明:作为初学者,我对于源代码也只是简单的分析,开始和(一)中一样都叫做源码分析,后来感觉自己分析的质量不太好,还是都改为源码简析吧,结合起(一)及后面的心得来看估计效果会好点,呵呵。只是希望对于即将要学习 Surf算法的人有一定的帮助就行!对于一些介绍得不对的地方,也希望各位大虾能过指出,相互交流,共同进步!

Surf算法源代码分析

surf算法源代码分为两种文件,学过C/C++的都知道,在此不多说。头文件主要包括:imload.h、ipoint.h、image.h、fasthessian.h、 surf.h、 surflib.h,其中每个文件用于声明一个特定的相应类,下面大体进行简单介绍。
ImLoad.h——声明类ImLoad,主要封装了对图像的读取和保存函数。
Image *readImage(const char *filename);                             //从图像文件中读取图像
void saveImage(const char *filename, Image* im);           //将图像保存到文件中
ipoint.h——声明类Ipoint,主要定义关键点的相应属性。
Ipoint(){                                                                                                //构造函数
ivec = NULL;
ori = 0.0;
};
~Ipoint(){                                                                                             //析构函数
if (ivec)
delete [] ivec;
};
void allocIvec(const int si){                                                         //内存空间分配
ivec = new double[si];
};
double x, y;                                                                                          //特征点在图像中的坐标
double scale;                                                                                       // 检测范围
double strength;                                                                                //特征点的强度
double ori;                                                                                           //特征点主方向
int laplace;                                                                                           //Laplacian相关的值
double *ivec;                                                                                      //特征点描述器( 局部特征)
image.h——声明类Image,主要定义图像的相关属性。
Image(const int w, const int h);                                                 //带参数的构造函数
~Image();                                                                                            //析构函数
Image(double **pixels, int w, int h);                                      //根据已存在的像素数组构造图像
Image(Image *im, bool doubleImSize=false);                   //构造积分图像
void setFrame(unsigned char *im);                                       //通过单一的帧到(预初始化)结构
void setFrame(Image *im);
Image *HalfImage();                                                                    //将图片长宽个变为原来的1/2
double getHessian(int *x);                                                        //获得特定点的Hessian检测值
int getTrace(int *x);                                                                    //获得Hessian矩阵的迹(线性代数中学过迹)
double **getPixels() const;                                                      //获得指向图像像素的指针
double getPix(const int x, const int y) const {                 //获得某一指定位置的像素值
return _pixels[y][x];
}
double &getPix(const int x, const int y) {                          //重载getPix并返回引用
return _pixels[y][x];
}
void setPix(const int x, const int y, const double val) {  //设置指定位置的像素值
_pixels[y][x] = val;
}
int getWidth();
int getHeight();
void setWidth(int wi);
void setHeight(int hi);
void allocPixels(int w, int h);                                                  //为图像像素分配二维内存空间
double *_buf;                                                                                //指向图像实际缓冲区的指针
double **_pixels;                                                                         //指向图像像素二维数组的指针
int _height, _width;
int _orihi;                                                                                        //原始图像高度
bool _ref;                                                                                         //对图像是否为引用的一种标志
fasthessian.h——声明类FastHessian,主要定义算法中的快速Hessian检测子方法
~FastHessian();
//带参数的构造方法
FastHessian(Image *im, std::vector< Ipoint >& ip, double thres = 0.2, bool doub = false,
short int initMasksize = 9, short int samplingStep = 2,
short int octaves = 4);
void setIimage( Image *iim );                                                  //传入积分图像
void getInterestPoints();                                                           //检测图像的所有特征点
//在特定位置创建新的点,并在一定范围内
void makeIpoint(double x, double y, double scale, double strength=0);
void allocateOctave();                                                                //为Octave分配内存空间
//Fast non-maximum-suppression
void findMaximum(int *borders, int o, int octave);
void interpFeature(int s, int row, int col, Image *map,
int o, int octave, int movesRemain,
int *borders);
int fitQuadrat(int s, int r, int c, double &res);
Image *_Iimage;
Image **_scaleLevel;                                                               //Octaves
int _vas[9];                                                                                    //向量变量
double _threshold;                                                                    //检测特征点时的阈值
bool _doubled;                                                                            //图像是否放大
std::vector< Ipoint >& _ipts;                                               //从外部传进来的指向特征点向量的引用
short int _initLobe;                                               //在某一方向第二次求导时的初始lobe值,默认为3
short int _maxScales;                                                              //Number scales
short int _maxOctaves;                                                          //Number octaves
short int _sampling;                                                                 //The sampling step
int _width;                                                                                   //积分图像的宽
int _height;
double _offset[3];                                                                      //二次拟合的结果
surf.h——声明 surf,主要用于定义 Surf中关键点相应的描述器
Surf();
Surf(Image *im, bool dbl=false, bool u surf=false,
bool ext=false, int insi=4);
~ Surf();
int getVectLength();                                                              //获得特征描述器向量的长度
void setIpoint(Ipoint *ipt);                                               //为一个需要计算的描述器设置相应点
void assignOrientation();                                                   // 定向分配重现
void makeDescriptor();                                                      //计算不变图像特征
void createVector(double scale,                                    //创建向量
double row, double col);
void createUprightVector(double scale,
double row, double col);
void AddSample(int r, int c, double rpos,                   //向向量添加样本
double cpos, double rx, double cx, int step);
void AddUprightSample(int r, int c, double rpos,
double cpos, double rx, double cx, int step);
void PlaceInIndex(double mag1, int ori1,                  //为向量中样本设置索引值
double mag2, int ori2, double rx, double cx);
//! Normalise descriptor vector for illumination invariance for Lambertian  surfaces
void normalise();
void createLookups();                                                       //创建查找表
surflib.h——声明 surf算法要用到的库函数。
//针对整个图像定义关键点及其相应描述器(关键点附加的详细信息(局部特征))
//其中关键点信息保存在向量ipts当中
inline void  surfDetDes(Image *im, std::vector< Ipoint >& ipts,
double thres = 4.0, bool doubleImageSize = false,
int initLobe = 3, int samplingStep = 2, int octaves = 4,
bool upright = false, bool extended = false, int indexSize = 4)
//针对一个给定的特征点,计算相应的描述器(局部特征)
inline void  surfDes(Image *im, std::vector< Ipoint >& ipts,
bool doubleImageSize = false,
bool upright = false, bool extended = false, int indexSize = 4)

编译运行

cmd下进入可执行文件目录,输入可执行文件名,得到如下提示:
可以在相应的提示下进行运行,如:
注意:输入文件 -i 参数后面的文件必须是PGM格式的图像文件,可以自行网上下载,有个“人脸pgm图片库”可以拿来使用,输出不限,如本程序中是output.txt
运行完成后,打开文件output.txt可以看到文件中的如下数据:
这只是一个简单的示例,直接运行可执行文件名出现的帮助里面还有很多选项,别的选项也就是与 Surf算法源代码中的那些参数对应的,大家应该都懂的,要学习的可以试一下,这样就能更深入的了解 Surf算法。另外源文件中有文件README的说明 ,里面有关于数据格式以及数据输入输出格式的说明,有兴趣的朋友可以自行研究下。

本文链接: http://www.yongblog.com/archives/152.html 转载请注明出处。

作者:dadaadao 发表于2012/2/20 13:06:07  原文链接
阅读:1447 评论:2  查看评论
 
[原]在VC中调色板的作用
调色板一般是为了显示256色图象时使用的。图象(BMP图象)按颜色种类分类可以分为: 

1、黑白图象。不使用调色板; 

2、256色图象(包括256级灰度图象),使用调色板。调色板中记录的是图象中使用的256种颜色,图象数据中记录的是颜色索引,通过这个索引值就可以找到对应的颜色。 

3、24bit真彩色图象,不使用调色板。图象数据中保留RGB三种颜色组合,可以直接显示。

//int Color = 256 * 4;//256灰度图的默认调色板大小,灰度图像用
int Color = 0;//彩色图的默认调色板大小

MyBmpFile.bfType = 0x4d42;
MyBmpFile.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + Color + Width*Height*3;
MyBmpFile.bfReserved1 = 0;
MyBmpFile.bfReserved2 = 0;
MyBmpFile.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + Color;

MyBmpInfo->biSize = sizeof(BITMAPINFOHEADER);
MyBmpInfo->biWidth = Width;
MyBmpInfo->biHeight = Height;
MyBmpInfo->biPlanes = 1;
MyBmpInfo->biBitCount = 24;//灰度图像为8
MyBmpInfo->biCompression = BI_RGB;
MyBmpInfo->biSizeImage = Height * ((Width * 8 + 31)/32*4) *3;//灰度图像的设置也不一样
MyBmpInfo->biXPelsPerMeter = 0;
MyBmpInfo->biYPelsPerMeter = 0;
MyBmpInfo->biClrUsed = 0;
MyBmpInfo->biClrImportant = 0;
//灰度图的默认调色板
//for (int i=0; i<256; i++)
//{
// *((BYTE *)MyBmpInfo+sizeof(BITMAPINFOHEADER)+i*4+0) = i;
// *((BYTE *)MyBmpInfo+sizeof(BITMAPINFOHEADER)+i*4+1) = i;
// *((BYTE *)MyBmpInfo+sizeof(BITMAPINFOHEADER)+i*4+2) = i;
// *((BYTE *)MyBmpInfo+sizeof(BITMAPINFOHEADER)+i*4+3) = 0;
//}

作者:dadaadao 发表于2011/9/6 17:50:47  原文链接
阅读:1222 评论:1  查看评论
 
[原]VC控件MSComm编写串口通信程序(转)

在众多网友的支持下,串口调试助手从2001521日发布至今,短短一个月,在全国各地累计下载量近5000人次,在近200多个电子邮件中,20多人提供了使用测试意见,更有50多位朋友提出要串口调试助手的源代码,为了答谢谢朋友们的支持,公开推出我最初用VC控件MSComm编写串口通信程序的源代码,并写出详细的编程过程,姑且叫串口调试助手源程序V1.0VC串口通讯源程序吧,我相信,如果你用VC编程,那么有了这个代码,就可以轻而易举地完成串口编程任务了。(也许本文过于详细,高手就不用看)

开始吧:

1.建立项目:打开VC++6.0,建立一个基于对话框的MFC应用程序SCommTest(与我源代码一致,等会你会方便一点);

2.在项目中插入MSComm控件  选择Project菜单下Add To Project子菜单中的 Components and Controls…选项,在弹出的对话框中双击Registered ActiveX Controls项(稍等一会,这个过程较慢),则所有注册过的ActiveX控件出现在列表框中。选择Microsoft Communications Control, version 6.0,,单击Insert按钮将它插入到我们的Project中来,接受缺省的选项。(如果你在控件列表中看不到Microsoft Communications Control, version 6.0,那可能是你在安装VC6时没有把ActiveX一项选上,重新安装VC6,选上ActiveX就可以了),

这时在ClassView视窗中就可以看到CMSComm类了,(注意:此类在ClassWizard中看不到,重构clw文件也一样),并且在控件工具栏Controls中出现了电话图标(如图1所示),现在要做的是用鼠标将此图标拖到对话框中,程序运行后,这个图标是看不到的。


内容来至(http://blog.csdn.net/dadaadao/rss/list)_第1张图片
 

 

 

3.利用ClassWizard定义CMSComm类控制对象 打开ClassWizard>Member Viariables选项卡,选择CSCommTestDlg类,为IDC_MSCOMM1添加控制变量:m_ctrlComm,这时你可以看一看,在对话框头文件中自动加入了//{{AFX_INCLUDES()  #include "mscomm.h"  //}}AFX_INCLUDES(这时运行程序,如果有错,那就再从头开始)。

4.在对话框中添加控件 向主对话框中添加两个编辑框,一个用于接收显示数据IDIDC_EDIT_RXDATA,另一个用于输入发送数据,IDIDC_EDIT_TXDATA,再添加一个按钮,功能是按一次就把发送编辑框中的内容发送一次,将其ID设为IDC_BUTTON_MANUALSEND。别忘记了将接收编辑框的Properties>Styles中把MiltilineVertical Scroll属性选上,发送编辑框若你想输入多行文字,也可选上Miltiline

再打开ClassWizard>Member Viariables选项卡,选择CSCommTestDlg类,为IDC_EDIT_RXDATA添加CString变量m_strRXData, 为IDC_EDIT_TXDATA添加CString变量m_strTXData。说明: m_strRXDatam_strTXData分别用来放入接收和发送的字符数据。

      休息一会吧?我们天天与电脑打交道,要注意保重,我现在在单杠上做引体向上可以来40次,可我都32了,佩服吗?。。。。。。好了,再接着来,下面是关键了:

5.添加串口事件消息处理函数OnComm()打开ClassWizard>Message Maps,选择类CSCommTestDlg,选择IDC_MSCOMM1,双击消息OnComm,将弹出的对话框中将函数名改为OnComm,(好记而已)OK

 这个函数是用来处理串口消息事件的,如每当串口接收到数据,就会产生一个串口接收数据缓冲区中有字符的消息事件,我们刚才添加的函数就会执行,我们在OnComm()函数加入相应的处理代码就能实现自已想要的功能了。请你在函数中加入如下代码:

void CSCommTestDlg::OnComm() 
{
    // TODO: Add your control notification handler code here
    VARIANT variant_inp;
    COleSafeArray safearray_inp;
    LONG len,k;
    BYTE rxdata[2048]; //
设置BYTE数组 An 8-bit integerthat is not signed.
    CString strtemp;
    if(m_ctrlComm.GetCommEvent()==2) //
事件值为2表示接收缓冲区内有字符
    {             ////////
以下你可以根据自己的通信协议加入处理代码
        variant_inp=m_ctrlComm.GetInput(); //
读缓冲区
        safearray_inp=variant_inp; //VARIANT
型变量转换为ColeSafeArray型变量
        len=safearray_inp.GetOneDimSize(); //
得到有效数据长度
        for(k=0;k             safearray_inp.GetElement(&k,rxdata+k);//
转换为BYTE型数组
        for(k=0;k将数组转换为Cstring型变量
        {
            BYTE bt=*(char*)(rxdata+k); //
字符型
            strtemp.Format("%c",bt); //
将字符送入临时变量strtemp存放
            m_strRXData+=strtemp; //
加入接收编辑框对应字符串 
        }
    }
    UpdateData(FALSE); //
更新编辑框内容
}

到目前为止还不能在接收编辑框中看到数据,因为我们还没有打开串口,但运行程序不应该有任何错误,不然,你肯定哪儿没看仔细,因为我是打开VC6对照着做一步写一行的,运行试试。没错吧?那么做下一步:

6.打开串口和设置串口参数 你可以在你需要的时候打开串口,例如在程序中做一个开始按钮,在该按钮的处理函数中打开串口。现在我们在主对话框的CSCommTestDlg::OnInitDialog()打开串口,加入如下代码:

// TODO: Add extra initialization here
if(m_ctrlComm.GetPortOpen())
m_ctrlComm.SetPortOpen(FALSE);

m_ctrlComm.SetCommPort(1); //
选择com1
if( !m_ctrlComm.GetPortOpen())
m_ctrlComm.SetPortOpen(TRUE);//
打开串口
else
AfxMessageBox("cannot open serial port");

m_ctrlComm.SetSettings("9600,n,8,1"); //
波特率9600,无校验,8个数据位,1个停止位

m_ctrlComm.SetInputModel(1); //1:表示以二进制方式检取数据
m_ctrlComm.SetRThreshold(1); 
//
参数1表示每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件
m_ctrlComm.SetInputLen(0); //
设置当前接收区数据长度为0
m_ctrlComm.GetInput();//
先预读缓冲区以清除残留数据

现在你可以试试程序了,将串口线接好后(不会接?去看看我写的串口接线基本方法),打开串口调试助手,并将串口设在com2,选上自动发送,也可以等会手动发送。再执行你编写的程序,接收框里应该有数据显示了。

7.发送数据 先为发送按钮添加一个单击消息即BN_CLICKED处理函数,打开ClassWizard>Message Maps,选择类CSCommTestDlg,选择IDC_BUTTON_MANUALSEND,双击BN_CLICKED添加OnButtonManualsend()函数,并在函数中添加如下代码:

void CSCommTestDlg::OnButtonManualsend() 
{
// TODO: Add your control notification handler code here
UpdateData(TRUE); //读取编辑框内容
m_ctrlComm.SetOutput(COleVariant(m_strTXData));//
发送数据
}

运行程序,在发送编辑框中随意输入点什么,单击发送按钮,啊!看看,在另一端的串口调试助手(或别的调试工具)接收框里出现了什么。

如果你真是初次涉猎串口编程,又一次成功,那该说声谢谢我了,因为我第一次做串口程序时可费劲了,那时网上的资料也不好找。开开玩笑,谢谢你的支持,有什么好东西别忘了给我寄一份。

最后说明一下,由于用到VC控件,在没有安装VC的计算机上运行时要从VC中把mscomm32.ocxmsvcrt.dllmfc42.dll拷到Windows目录下的System子目录中(win2000System32)并再进行注册设置,请参考

如何手工注册MSComm控件。

龚建伟 2001.6.20

8.发送十六进制字符

   在主对话框中加入一个复选接钮,IDIDC_CHECK_HEXSEND Caption:十六进制发送,再利用ClassWizard为其添加控制变量:m_ctrlHexSend

   ClassView中为SCommTestDlg类添加以下两个PUBLIC成员函数,并输入相应代码;

 

//由于这个转换函数的格式限制,在发送框中的十六制字符应该每两个字符之间插入一个空隔
//
如:A1 23 45 0B 00 29
//CByteArray是一个动态字节数组,可参看MSDN帮助
int CSCommTestDlg::String2Hex(CString str, CByteArray &senddata)
{
int hexdata,lowhexdata;
int hexdatalen=0;
int len=str.GetLength();
senddata.SetSize(len/2);
for(int i=0;i {
char lstr,hstr=str[i];
if(hstr==' ')
{
i++;
continue;
}
i++;
if(i>=len)
break;
lstr=str[i];
hexdata=ConvertHexChar(hstr);
lowhexdata=ConvertHexChar(lstr);
if((hexdata==16)||(lowhexdata==16))
break;
else 
hexdata=hexdata*16+lowhexdata;
i++;
senddata[hexdatalen]=(char)hexdata;
hexdatalen++;
}
senddata.SetSize(hexdatalen);
return hexdatalen;
}

//这是一个将字符转换为相应的十六进制值的函数
//
好多C语言书上都可以找到
//
功能:若是在0-F之间的字符,则转换为相应的十六进制字符,否则返回-1
char CSCommTestDlg::ConvertHexChar(char ch) 
{
if((ch>='0')&&(ch<='9'))
return ch-0x30;
else if((ch>='A')&&(ch<='F'))
return ch-'A'+10;
else if((ch>='a')&&(ch<='f'))
return ch-'a'+10;
else return (-1);
}

 

 再将CSCommTestDlg::OnButtonManualsend()修改成以下形式:

void CSCommTestDlg::OnButtonManualsend() 
{
// TODO: Add your control notification handler code here
UpdateData(TRUE); //
读取编辑框内容
if(m_ctrlHexSend.GetCheck())
{
CByteArray hexdata;
int len=String2Hex(m_strTXData,hexdata); //
此处返回的len可以用于计算发送了多少个十六进制数
m_ctrlComm.SetOutput(COleVariant(hexdata)); //
发送十六进制数据
}
else 
m_ctrlComm.SetOutput(COleVariant(m_strTXData));//
发送ASCII字符数据

}

现在,你先将串口线接好并打开串口调试助手V2.1,选上以十六制显示,设置好相应串口,然后运行我们这个程序,在发送框中输入00 01 02 03 A1 CC等十六进制字符,并选上以十六进制发送,单击手动发送,在串口调试助手的接收框中应该可以看到00 01 02 03 A1 CC了。

 

9.在接收框中以十六进制显示

   这就容易多了:  在主对话框中加入一个复选接钮,IDC_CHECK_HEXDISPLAY Caption:十六进制显示,再利用ClassWizard为其添加控制变量:m_ctrlHexDiaplay。 然后修改CSCommTestDlg::OnComm()函数:

void CSCommTestDlg::OnComm() 
{
// TODO: Add your control notification handler code here
VARIANT variant_inp;
COleSafeArray safearray_inp;
LONG len,k;
BYTE rxdata[2048]; //
设置BYTE数组 An 8-bit integerthat is not signed.
CString strtemp;
if(m_ctrlComm.GetCommEvent()==2) //
事件值为2表示接收缓冲区内有字符
{
variant_inp=m_ctrlComm.GetInput(); //
读缓冲区
safearray_inp=variant_inp; //VARIANT
型变量转换为ColeSafeArray型变量
len=safearray_inp.GetOneDimSize(); //
得到有效数据长度
for(k=0;k safearray_inp.GetElement(&k,rxdata+k);//
转换为BYTE型数组
for(k=0;k将数组转换为Cstring型变量
{
BYTE bt=*(char*)(rxdata+k); //
字符型
if(m_ctrlHexDisplay.GetCheck())
strtemp.Format("%02X ",bt); //
将字符以十六进制方式送入临时变量strtemp存放,注意这里加入一个空隔
else 
strtemp.Format("%c",bt); //
将字符送入临时变量strtemp存放

m_strRXData+=strtemp; //加入接收编辑框对应字符串 
}
}
UpdateData(FALSE); //
更新编辑框内容
}

测试:在串口调试助手发送框中输入00 01 02 03 A1 CC等十六进制字符,并选上以十六进制发送,单击手动发送,在本程序运行后选上以十六进制显示,在串口调试助手中单击手动发送或自动发送,则在本程序的接收框中应该可以看到00 01 02 03 A1 CC了。

 

10.如何设置自动发送

    最简单的设定自动发送周期是用SetTimer()函数,这在数据采集中很有用,在控制中指令的传送也可能用到定时发送。

   方法是:在ClassWizard中选上MessageMap卡,然后在Objects IDs选中CSCommTestDlg类,再在Messages框中选上WM_TIMER消息,单击ADD_FUNCTION加入void CSCommTestDlg::OnTimer(UINT nIDEvent) 函数,这个函数是放入“时间到”后要处理的代码:

void CSCommTestDlg::OnTimer(UINT nIDEvent) 
{
// TODO: Add your message handler code here and/or call default
OnButtonManualsend()
CDialog::OnTimer(nIDEvent);
}

再在在主对话框中加入一个复选接钮,IDIDC_CHECK_AUTOSEND Caption:自动发送(周期1秒),再利用ClassWizard为其添加BN_CLICK消息处理函数void CSCommTestDlg::OnCheckAutosend():

void CSCommTestDlg::OnCheckAutosend() 
{
// TODO: Add your control notification handler code here
m_bAutoSend=!m_bAutoSend;
if(m_bAutoSend)
{
SetTimer(1,1000,NULL);//
时间为1000毫秒
}
else
{
KillTimer(1);  //
取消定时
}
}

其中:m_bAutoSendBOOL型变量,在CLASSVIEW中为CSCommTestDlg类加入,并在构造函数中初始化:

      m_bAutoSen=FALSE;
现在可以运行程序测试了。

 

11.什么是VARIANT数据类型?如何使用VARIANT数据类型?

    不知如何使用VARIANT数据类型, 有不少朋友对VARIANT这个新的数据类型大感头疼。SetOutput()函数中 需要的VARIANT参数还可以使用COleVariant类的构造函数简单生成,现在GetInput()函数的返回值也成了VARIANT类型,那么如何从返回的值中提取有用的内容。 VARIANT及由之而派生出的COleVariant类主要用于在OLE自动化中传递数据。实际上VARIANT也只不过是一个新定义的结构罢了,它的主要成员包括一个联合体及一个变量。该联合体由各种类型的数据成员构成,而该变量则用来指明联合体中目前起作用的数据类型。我们所关心的接收到的数据就存储在该联合体的某个数据成员中。 该联合体中包含的数据类型很多,从一些简单的变量到非常复杂的数组和指针。由于通过串口接收到的内容常常是一个字节串,我们将使用其中的某个数组或指针来访问接收到的数据。这里推荐给大家的是指向一个SAFEARRAYCOleSafeArray)类型变量。新的数据类型SAFEARRAY正如其名字一样,是一个“安全数组”,它能根据系统环境自动调整其16位或32位的定义,并且不会被OLE改变(某些类型如BSTR16位或32位应用程序间传递时会被OLE翻译从而破坏其中的二进制数据)。大家无须了解SAFEARRAY的具体定义,只要知道它是另外一个结构,其中包含一个 (void *)类型的指针pvData,其指向的内存就是存放有用数据的地方。简而言之,从GetInput()函数返回的VARIANT类型变量中,找出parray指针,再从该指针指向的SAFEARRAY变量中找出pvData指针,就可以向访问数组一样取得所接收到的数据了。具体应用请参见void CSCommTestDlg::OnComm()函数。

   大概我现在也说不清这个问题,我自己从第一次接触这个东西,到现在还是给别人讲不清。

另:二进制收发设置请参考MSComm控件说明

作者:dadaadao 发表于2011/8/30 14:09:17  原文链接
阅读:3854 评论:1  查看评论
 
[原]VC6下用控件进行串口通信

打开VC++6.0,建立一个基于对话框的MFC应用程序。

  菜单中依次选择Project -> Add To Project -> Components and Controls

  在弹出的Components and Controls Gallery 窗口中双击Registered Activex Controls文件夹

  选中Microsoft Communications Control,version 6.0,点击Insert添加控件,如下图

  在这里如果没有找到Microsoft Communications Control,version 6.0怎么办?

  如果没有,说明这个控件还没有注册。那么需要先注册此控件,方法如下:

  开始 -> 运行 中输入 regsvr32 mscomm32.ocx

  点击确定注册

  添加控件后会弹出一个确认框

  点击“确定”

  再点击"OK",控件就添加成功了

  把此控件拖入界面中,利用MFC ClassWizard添加成员变量(我在这里用的变量名是m_msCom,可变),如图

  然后在控件的属性里进行一些必要的配置:

  CommPort设置串口号

  InputMode设置为1-Binary,表示以二进制方式检取数据

  RThreshold设置为1,表示每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件

  其它的用默认值。

  也可以在OnInitDialog()函数中用代码设置,如下:

 m_msCom.SetCommPort(1);  // 指定串口号为1(视实际情况而定)

 if (m_msCom.GetPortOpen())

 {

  m_msCom.SetPortOpen(FALSE);

 }

 m_msCom.SetInputMode(1); //1:表示以二进制方式检取数据

 m_msCom.SetRThreshold(1);

  //参数1表示每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件

  设置好以后,要在程序的开始打开串口,不然是没发使用的。在OnInitDialog中加入以下代码:

 m_msCom.SetPortOpen(TRUE);  // 打开串口

 m_msCom.GetInput();//先预读缓冲区以清除残留数据

  OK,在你需要的地方可以随时发送串口数据了,很简单:

m_msCom.SetOutput(COleVariant(TXData));   //发送数据TXData

  接收串口数据:

  首先要添加串口事件消息处理函数,双击控件,弹出添加成员函数对话框,我们用默认的函数名OnOnCommMscomm1,这里也可以更改函数名。

  这个函数是用来处理串口消息事件的,如每当串口接收到数据(正如我们前面设置的RThreshold,每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件),就会产生一个串口接收数据缓冲区中有字符的消息事件,刚才添加的函数就会执行,我们在OnOnCommMscomm1函数加入相应的处理代码就能实现自已想要的功能了。在该函数中加入如下代码以接收数据:

 // TODO: Add your control notification handler code here

 if (m_msCom.GetCommEvent() == 2)   //事件值为2表示接收缓冲区内有字符

 {

  Sleep(100);

  VARIANT rec_data;

  int data_len;

  char cData[1024];

  rec_data = m_msCom.GetInput();  // 读取缓冲区

  data_len = rec_data.parray->rgsabound->cElements;

  memcpy(cData,(char *)rec_data.parray->pvData,data_len);

  cData[data_len] = 0;

  // cData就是指向接收到的字符串的指针

  m_list.AddString(cData); //在listBox控件中显示接收到的数据

 }
作者:dadaadao 发表于2011/8/30 14:00:25  原文链接
阅读:1315 评论:1  查看评论
 
[原]保存图像


void saveFile(LPBYTE lpBits,int nWidth,int nHeight, CString strPath)
{
 BITMAPFILEHEADER bmpFileHdr;
 BITMAPINFOHEADER bmpInfoHdr;
 RGBQUAD rgbQuad[256];
 
 memset(&bmpFileHdr,0,sizeof(BITMAPFILEHEADER));
 memset(&bmpInfoHdr,0,sizeof(BITMAPINFOHEADER));
 
 bmpFileHdr.bfType=19778;
 bmpFileHdr.bfOffBits=1078;
 bmpFileHdr.bfSize=1078+nWidth*nHeight;
 
 bmpInfoHdr.biSize=sizeof(BITMAPINFOHEADER);
 bmpInfoHdr.biWidth=nWidth;
 bmpInfoHdr.biHeight=nHeight;
 bmpInfoHdr.biPlanes=1;
 bmpInfoHdr.biBitCount=8;
 bmpInfoHdr.biSizeImage=nWidth*nHeight;
 bmpInfoHdr.biClrUsed=256;
 
 for(int i=0;i<256;i++)
 {
  rgbQuad[i].rgbBlue=rgbQuad[i].rgbGreen=rgbQuad[i].rgbRed=i;
  rgbQuad[i].rgbReserved=0;
 }
 
 FILE* fp=fopen(strPath,"wb");
 ASSERT(fp);
 fwrite(&bmpFileHdr,1,sizeof(BITMAPFILEHEADER),fp);
 fwrite(&bmpInfoHdr,1,sizeof(BITMAPINFOHEADER),fp);
 fwrite(rgbQuad,sizeof(RGBQUAD),256,fp);
 fwrite(lpBits,1,nHeight*nWidth,fp);
 fclose(fp);
 
}

作者:dadaadao 发表于2011/6/10 12:53:00  原文链接
阅读:347 评论:0  查看评论
 
[原]怎样“调试” Release 版的程序


遇到Debug成功但Release失败,显然是一件很沮丧的事,而且往往无从下手。如果你看了以上的分析,结合错误的具体表现,很快找出了错误,固然很好。但如果一时找不出,以下给出了一些在这种情况下的策略。

1. 前面已经提过,Debug和Release只是一组编译选项的差别,实际上并没有什么定义能区分二者。我们可以修改Release版的编译选项来缩小错误 范围。如上所述,可以把Release 的选项逐个改为与之相对的Debug选项,如/MD改为/MDd、/O1改为/Od,或运行时间优化改为程序大小优化。注意,一次只改一个选项,看改哪个选项时错误消失,再对应该选项相关的错误,针对性地查找。这些选项在ProjectSettings...中都可以直接通过列表选取,通常不要手动修改。由于以上的分析已相当全面,这个方法是最有效的。

2. 在编程过程中就要时常注意测试 Release 版本,以免最后代码太多,时间又很紧。

3. 在 Debug 版中使用 /W4 警告级别,这样可以从编译器获得最大限度的错误信息,比如 if( i =0 )就会引起 /W4 警告。不要忽略这些警告,通常这是你程序中的 Bug 引起的。但有时 /W4 会带来很多冗余信息,如 未使用的函数参数警告,而很多消息处理函数都会忽略某些参数。我们可以用:



#progma warning(disable: 4702)
//禁止
//...
#progma warning(default: 4702)
//重新允许来暂时禁止某个警告,或使用
#progma warning(push, 3)
//设置警告级别为 /W3
//...
#progma warning(pop)
//重设为 /W4





来暂时改变警告级别,有时你可以只在认为可疑的那一部分代码使用 /W4。

4. 你也可以像Debug一样调试你的Release版,只要加入调试符号。在Project/Settings... 中,选中 Settings for "Win32 Release",选中 C/C++ 标签,Category 选 General,Debug Info 选 Program Database。再在 Link 标签 Project options 最后加上 "/OPT:REF" (引号不要输)。这样调试器就能使用 pdb 文件中的调试符号。

但调试时你会发现断点很难设置,变量也很难找到??这些都被优化过了。不过令人庆幸的是,Call Stack窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。

转自:创意安天论坛

作者:dadaadao 发表于2011/6/8 9:50:00  原文链接
阅读:430 评论:0  查看评论
 
 
公司简介| 广告服务| 银行汇款帐号| 联系方式| 版权声明| 法律顾问| 问题报告
北京创新乐知信息技术有限公司 版权所有, 京 ICP 证 070598 号
世纪乐知(北京)网络技术有限公司 提供技术支持
Copyright © 1999-2011, CSDN.NET, All Rights Reserved
GongshangLogo

你可能感兴趣的:(内容来至(http://blog.csdn.net/dadaadao/rss/list))