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继续。
稍等片刻出现该图 第一轮配置完后往下拉,勾选WITH_TBB,点击Configure进入第二轮。
修改红色部分TBB路径为D:/Program Files/tbb41_20121003oss/include,再次点击Configure;继续点击Configure,直到没有红色标记。
点击Generate生成Opencv工程,退出CMake。 4 用Opencv VS2012工程编译生成自己的Opencv库打开生成的Opencv工程,选择CMakeTargets下INSTALL,右键“生成”,生成Debug版dll,lib。
切换编译模式为Release模式,重复上一步生成Release版dll,lib。
上面两步后就能看到最终Debug版和Release版的dll,lib,以及文档目录doc,头文件目录include(bin内为两个版本dll,lib内为两个版本lib)。
目标达成,在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
项目->xxx属性->链接器->输入->附加依赖项,添加lib文件名列表如下图。
对于配置方案为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 <opencv2\opencv.hpp>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,编译运行,显示出图片。
通过设置断点发现,能够跟踪进入Opencv内部函数。
作者:dadaadao 发表于2014/11/5 15:52:35 原文链接
阅读:453 评论:1 查看评论
|
|||||||||
[转]Win32 编程入门 | |||||||||
Win32 程序开发的流程
message based, event drivenWin32程序是message based, event driven。也就是说Win32程序的运行是依靠外部不断发生的事件来驱动的,也就是说,程序不断等待(有一个while循环),等待任何可能的输入,然后做判断,再做适当的处理。因此Win32程序只需要做好如下几件事情就可以了: 1. 定义窗口的外观; 2. 定义当不同的事件发生时,程序做什么样的反应(定义窗口处理函数); 3. 写一个While循环,不断检测新事件的发生,并将其发送给不同的窗口处理函数
程序进入点WinMainmain是一般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 {
消息循环是怎么工作的应用程序可以获得的输入有两种类型:硬件装置产生的消息房子系统队列中、由Windows系统或者其他Windows程序传送过来的消息,放在程序队列中。以应用程序的眼光来看,消息就是消息,来自哪里或放在哪里没有太大区别,程序每次GetMessage就取得一个消息。 每个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的窗口函数(Window Procedure),如果窗口获得一个消息,则这个窗口必须判断消息的类别,决定处理方式。下面是主消息循环的例子: // Main message loop: 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对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不得字符消息,需要键盘映射转换为字符的消息。
windows消息处理机制是这样的: 其实问题的关键在于DispatchMessage到底干了什么 GetMessage: 从线程的消息队列取出一个消息 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上添加按钮消息还需要修改如下: 这里增加一条关于对话框上响应键盘按键消息,重载
第3中方法:定义 CDialogBar m_wndImagesInDB;
然后在OnCreate中增加
// TODO: Add a menu item that will toggle the visibility of the // TODO: Change the value of CG_ID_VIEW_IMAGESINDB to an appropriate value:
其中要在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
《关于图像特征提取》
网上发现一篇不错的文章,是关于图像特征提取的,给自己做的项目有点类似,发出来供大家参考。 特征提取是计算机视觉和图像处理中的一个概念。它指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。特征提取的结果是把图像上的点分为不同的子集,这些子集往往属于孤立的点、连续的曲线或者连续的区域。
作者:dadaadao 发表于2012/2/21 13:18:34 原文链接
阅读:466 评论:0 查看评论
|
|||||||||
[转]VC 2008 Express下安装OpenCV2.3.1 | |||||||||
注意:
[ 编辑]
安装所需要的软件
[ 编辑]
下载OpenCV
[ 编辑]
安装CMake(不打算自己编译无需安装)从 http://www.cmake.org/cmake/resources/software.html 下载 Windows (Win32 Installer) 安装。
[ 编辑]
编译OpenCV(非必需步骤)
[ 编辑]
用CMake导出VC++项目文件
[ 编辑]
编译 OpenCV Debug和Release版本库完成上一步骤后,将在D:\Program Files\OpenCV2.3.1\build\my目录下生成OpenCV.sln的VC Solution File,请用VC++ 2008 Express打开OpenCV.sln,然后执行如下操作:
全部运行完毕后,针对你的系统的OpenCV库就生成了。
[ 编辑]
配置VC
[ 编辑]
配置include路径也即告诉VC去什么地方寻找OpenCV的头文件,打开VC,选择菜单“工具”->“选项”->“项目和解决方案”->“VC++目录”->“包含文件”,包含
[ 编辑]
配置lib路径也即告诉VC去什么地方寻找OpenCV的库文件。 在刚才下载的文件OpenCV-2.3.1-win-superpack.exe 里,已经为VC2008和VC2010预先编译好了动态库和静态库。因此我们不需要如早先版本那样,自己用cmake编译OpenCV。
请根据自己的情况四选一,将库目录输入菜单“工具”->“选项”->“项目和解决方案”->“VC++目录”->“库文件” 。如下图所示:
[ 编辑]
设置环境变量刚才设置的是动态库,因此还需要将OpenCV的dll文件所在的目录加入Path环境变量。dll文件目录如下,请根据自己情况五选一:
由于有些函数需要TBB,所以需要将tbb所在的目录也加入到环境变量Path中。TBB相关的DLL路径为:
如下图所示将OpenCV和TBB的dll文件所在的目录系统环境变量Path中。加入后可能需要注销当前Windows用户(或重启)后重新登陆才生效。
[ 编辑]
使用OpenCV 2.3.1编程
/*********************************************************************** * OpenCV 2.3.1 测试例程 * 于仕琪 提供 ***********************************************************************/ #include "stdafx.h" #include <opencv2/opencv.hpp> 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<math.h> int bmpWidth;//图像的宽 int biBitCount;//图像类型,每像素位数 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;//读取文件成功 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; } //---------------------------------------------------------------------------------------- { //读入指定BMP文件进内存 char readPath[]="nv.BMP"; readBmp(readPath); //输出图像的信息 cout<<"width="<<bmpWidth<<" height="<<bmpHeight<<" biBitCount="<<biBitCount<<endl; //每行字节数 int lineByte=(bmpWidth*biBitCount/8+3)/4*4; //循环变量,针对彩色图像,遍历每像素的三个分量 int m=0,n=0,count_xiang_su=0; //将图像左下角1/4部分置成黑色 if(biBitCount==8) //对于灰度图像 //double xiang_su[2048]; for(int i=0;i<bmpHeight;i++) for(int j=0;j<bmpWidth;j++) for(int k=0;k<3;k++)//每像素RGB三个分量分别置0才变成黑色 {
} saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable); //清除缓冲区,pBmpBuf和pColorTable是全局变量,在文件读入时申请的空间 delete []pBmpBuf; if(biBitCount==8) delete []pColorTable; }
作者: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概念 | |||||||||
废话:
作者:dadaadao 发表于2012/2/20 14:33:29 原文链接
阅读:606 评论:0 查看评论
|
|||||||||
[转]协方差矩阵的详细说明 | |||||||||
黄叶权整理于2007-7-18 在做人脸识别的时候经常与协方差矩阵打交道,但一直也只是知道其形式,而对其意义却比较模糊,现在我根据单变量的协方差给出协方差矩阵的详细推导以及在不同应用背景下的不同形式。 变量说明: 设为一组随机变量,这些随机变量构成随机向量,每个随机变量有m个样本,则有样本矩阵 (1) 其中对应着每个随机向量X的样本向量,对应着第i个随机单变量的所有样本值构成的向量。 单随机变量间的协方差: 随机变量之间的协方差可以表示为 (2) 根据已知的样本值可以得到协方差的估计值如下: 可以进一步地简化为: 协方差矩阵: 如果所有样本的均值为一个零向量,则式(5)可以表达成: 补充说明: 1、协方差矩阵中的每一个元素是表示的随机向量X的不同分量之间的协方差,而不是不同样本之间的协方差,如元素Cij就是反映的随机变量Xi, Xj的协方差。 2、协方差是反映的变量之间的二阶统计特性,如果随机向量的不同分量之间的相关性很小,则所得的协方差矩阵几乎是一个对角矩阵。对于一些特殊的应用场合,为了使随机向量的长度较小,可以采用主成分分析的方法,使变换之后的变量的协方差矩阵完全是一个对角矩阵,之后就可以舍弃一些能量较小的分量了(对角线上的元素反映的是方差,也就是交流能量)。特别是在模式识别领域,当模式向量的维数过高时会影响识别系统的泛化性能,经常需要做这样的处理。 3、必须注意的是,这里所得到的式(5)和式(6)给出的只是随机向量协方差矩阵真实值的一个估计(即由所测的样本的值来表示的,随着样本取值的不同会发生变化),故而所得的协方差矩阵是依赖于采样样本的,并且样本的数目越多,样本在总体中的覆盖面越广,则所得的协方差矩阵越可靠。 4、如同协方差和相关系数的关系一样,我们有时为了能够更直观地知道随机向量的不同分量之间的相关性究竟有多大,还会引入相关系数矩阵。
作者:dadaadao 发表于2012/2/20 13:50:00 原文链接
阅读:298 评论: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 <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>
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<int>& 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<int> ptpairs;
vector<CvPoint2D32f> 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 [<object_filename> <scene_filename>]\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<int> 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三种颜色组合,可以直接显示。
作者:dadaadao 发表于2011/9/6 17:50:47 原文链接
阅读:1222 评论:1 查看评论
|
|||||||||
[原]VC控件MSComm编写串口通信程序(转) | |||||||||
在众多网友的支持下,串口调试助手从2001年5月21日发布至今,短短一个月,在全国各地累计下载量近5000人次,在近200多个电子邮件中,20多人提供了使用测试意见,更有50多位朋友提出要串口调试助手的源代码,为了答谢谢朋友们的支持,公开推出我最初用VC控件MSComm编写串口通信程序的源代码,并写出详细的编程过程,姑且叫串口调试助手源程序V1.0或VC串口通讯源程序吧,我相信,如果你用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所示),现在要做的是用鼠标将此图标拖到对话框中,程序运行后,这个图标是看不到的。
3.利用ClassWizard定义CMSComm类控制对象 打开ClassWizard->Member Viariables选项卡,选择CSCommTestDlg类,为IDC_MSCOMM1添加控制变量:m_ctrlComm,这时你可以看一看,在对话框头文件中自动加入了//{{AFX_INCLUDES() #include "mscomm.h" //}}AFX_INCLUDES(这时运行程序,如果有错,那就再从头开始)。 4.在对话框中添加控件 向主对话框中添加两个编辑框,一个用于接收显示数据ID为IDC_EDIT_RXDATA,另一个用于输入发送数据,ID为IDC_EDIT_TXDATA,再添加一个按钮,功能是按一次就把发送编辑框中的内容发送一次,将其ID设为IDC_BUTTON_MANUALSEND。别忘记了将接收编辑框的Properties->Styles中把Miltiline和Vertical Scroll属性选上,发送编辑框若你想输入多行文字,也可选上Miltiline。 再打开ClassWizard->Member Viariables选项卡,选择CSCommTestDlg类,为IDC_EDIT_RXDATA添加CString变量m_strRXData, 为IDC_EDIT_TXDATA添加CString变量m_strTXData。说明: m_strRXData和m_strTXData分别用来放入接收和发送的字符数据。 休息一会吧?我们天天与电脑打交道,要注意保重,我现在在单杠上做引体向上可以来40次,可我都32了,佩服吗?。。。。。。好了,再接着来,下面是关键了: 5.添加串口事件消息处理函数OnComm()打开ClassWizard->Message Maps,选择类CSCommTestDlg,选择IDC_MSCOMM1,双击消息OnComm,将弹出的对话框中将函数名改为OnComm,(好记而已)OK。 这个函数是用来处理串口消息事件的,如每当串口接收到数据,就会产生一个串口接收数据缓冲区中有字符的消息事件,我们刚才添加的函数就会执行,我们在OnComm()函数加入相应的处理代码就能实现自已想要的功能了。请你在函数中加入如下代码: void CSCommTestDlg::OnComm() 到目前为止还不能在接收编辑框中看到数据,因为我们还没有打开串口,但运行程序不应该有任何错误,不然,你肯定哪儿没看仔细,因为我是打开VC6对照着做一步写一行的,运行试试。没错吧?那么做下一步: 6.打开串口和设置串口参数 你可以在你需要的时候打开串口,例如在程序中做一个开始按钮,在该按钮的处理函数中打开串口。现在我们在主对话框的CSCommTestDlg::OnInitDialog()打开串口,加入如下代码: // TODO: Add extra initialization here m_ctrlComm.SetInputModel(1); //1:表示以二进制方式检取数据 现在你可以试试程序了,将串口线接好后(不会接?去看看我写的串口接线基本方法),打开串口调试助手,并将串口设在com2,选上自动发送,也可以等会手动发送。再执行你编写的程序,接收框里应该有数据显示了。 7.发送数据 先为发送按钮添加一个单击消息即BN_CLICKED处理函数,打开ClassWizard->Message Maps,选择类CSCommTestDlg,选择IDC_BUTTON_MANUALSEND,双击BN_CLICKED添加OnButtonManualsend()函数,并在函数中添加如下代码: void CSCommTestDlg::OnButtonManualsend() 运行程序,在发送编辑框中随意输入点什么,单击发送按钮,啊!看看,在另一端的串口调试助手(或别的调试工具)接收框里出现了什么。 如果你真是初次涉猎串口编程,又一次成功,那该说声谢谢我了,因为我第一次做串口程序时可费劲了,那时网上的资料也不好找。开开玩笑,谢谢你的支持,有什么好东西别忘了给我寄一份。 最后说明一下,由于用到VC控件,在没有安装VC的计算机上运行时要从VC中把mscomm32.ocx、msvcrt.dll、mfc42.dll拷到Windows目录下的System子目录中(win2000为System32)并再进行注册设置,请参考 如何手工注册MSComm控件。 龚建伟 2001.6.20 8.发送十六进制字符 在主对话框中加入一个复选接钮,ID为IDC_CHECK_HEXSEND Caption:十六进制发送,再利用ClassWizard为其添加控制变量:m_ctrlHexSend; 在ClassView中为SCommTestDlg类添加以下两个PUBLIC成员函数,并输入相应代码;
//由于这个转换函数的格式限制,在发送框中的十六制字符应该每两个字符之间插入一个空隔
再将CSCommTestDlg::OnButtonManualsend()修改成以下形式: void CSCommTestDlg::OnButtonManualsend() 现在,你先将串口线接好并打开串口调试助手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() 测试:在串口调试助手发送框中输入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) 再在在主对话框中加入一个复选接钮,ID为IDC_CHECK_AUTOSEND Caption:自动发送(周期1秒),再利用ClassWizard为其添加BN_CLICK消息处理函数void CSCommTestDlg::OnCheckAutosend(): void CSCommTestDlg::OnCheckAutosend() 其中:m_bAutoSend为BOOL型变量,在CLASSVIEW中为CSCommTestDlg类加入,并在构造函数中初始化: m_bAutoSen=FALSE;
11.什么是VARIANT数据类型?如何使用VARIANT数据类型? 不知如何使用VARIANT数据类型, 有不少朋友对VARIANT这个新的数据类型大感头疼。SetOutput()函数中 需要的VARIANT参数还可以使用COleVariant类的构造函数简单生成,现在GetInput()函数的返回值也成了VARIANT类型,那么如何从返回的值中提取有用的内容。 VARIANT及由之而派生出的COleVariant类主要用于在OLE自动化中传递数据。实际上VARIANT也只不过是一个新定义的结构罢了,它的主要成员包括一个联合体及一个变量。该联合体由各种类型的数据成员构成,而该变量则用来指明联合体中目前起作用的数据类型。我们所关心的接收到的数据就存储在该联合体的某个数据成员中。 该联合体中包含的数据类型很多,从一些简单的变量到非常复杂的数组和指针。由于通过串口接收到的内容常常是一个字节串,我们将使用其中的某个数组或指针来访问接收到的数据。这里推荐给大家的是指向一个SAFEARRAY(COleSafeArray)类型变量。新的数据类型SAFEARRAY正如其名字一样,是一个“安全数组”,它能根据系统环境自动调整其16位或32位的定义,并且不会被OLE改变(某些类型如BSTR在16位或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 查看评论
|
|||||||||
[原]保存图像 | |||||||||
作者:dadaadao 发表于2011/6/10 12:53:00 原文链接
阅读:347 评论:0 查看评论
|
|||||||||
[原]怎样“调试” Release 版的程序 | |||||||||
作者:dadaadao 发表于2011/6/8 9:50:00 原文链接
阅读:430 评论:0 查看评论
|
|||||||||