opencv_opencv python - CSDN
opencv 订阅
OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS操作系统上。 [1]  它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。OpenCV用C++语言编写,它具有C ++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac OS,OpenCV主要倾向于实时视觉应用,并在可用时利用MMX和SSE指令, 如今也提供对于C#、Ch、Ruby,GO的支持。 [2] 展开全文
OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS操作系统上。 [1]  它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。OpenCV用C++语言编写,它具有C ++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac OS,OpenCV主要倾向于实时视觉应用,并在可用时利用MMX和SSE指令, 如今也提供对于C#、Ch、Ruby,GO的支持。 [2]
信息
开发商
OpenCV
软件授权
Intel、Willow Garage、NVIDIA等 [3]
软件名称
OpenCV
更新时间
2020-04-06
软件版本
OpenCV–4.3.0 [2]
软件平台
Linux、Windows、Android、Mac OS等
软件语言
C++
软件大小
1.16GB
opencv历史发展
OpenCV 拥有包括 500 多个C函数的跨平台的中、高层 API。它不依赖于其它的外部库——尽管也可以使用某些外部库。OpenCV 为Intel® Integrated Performance Primitives(IPP)提供了透明接口。这意味着如果有为特定处理器优化的 IPP 库,OpenCV 将在运行时自动加载这些库。 [4]  (注:OpenCV 2.0版的代码已显著优化,无需IPP来提升性能,故2.0版不再提供IPP接口) [5]  最新版本是3.4 ,2017年12月23日发布 [6]  。1999年1月,CVL项目启动。主要目标是人机界面,能被UI调用的实时计算机视觉库,为Intel处理器做了特定优化。2000年6月,第一个开源版本OpenCV alpha 3发布。2000年12月,针对linux平台的OpenCV beta 1发布。2006年,支持Mac OS的OpenCV 1.0发布。2009年9月,OpenCV 1.2(beta2.0)发布。2009年10月1日,Version 2.0发布。2010年12月6日,OpenCV 2.2发布。2011年8月,OpenCV 2.3发布。2012年4月2日,发布OpenCV 2.4。2014年8月21日,发布OpenCv 3.0 alpha。2014年11月11日,发布OpenCV 3.0 beta。2015年6月4日,发布OpenCV 3.0。2016年12月,发布OpenCV 3.2版(合并969个修补程序,关闭478个问题)2017年8月3日,发布OpenCV 3.3版(最重要的更新是把DNN模块从contrib里面提到主仓库)OpenCV 使用类BSDlicense,所以对非商业应用和商业应用都是免费(FREE)的。(细节参考 license)OpenCV提供的视觉处理算法非常丰富,并且它部分以C语言编写,加上其开源的特性,处理得当,不需要添加新的外部支持也可以完整的编译链接生成执行程序,所以很多人用它来做算法的移植,OpenCV的代码经过适当改写可以正常的运行在DSP系统和ARM嵌入式系统中,这种移植在大学中经常作为相关专业本科生毕业设计或者研究生课题的选题。
收起全文
精华内容
参与话题
  • Python+OpenCV计算机视觉

    万人学习 2018-12-28 17:42:46
    Python+OpenCV计算机视觉系统全面的介绍。
  •   文章链接: http://blog.csdn.net/poem_qianmo/article/details/19809337 作者:毛星云(浅墨) 邮箱: happylifemxy@163.com  写作当前博文时配套使用OpenCV版本:2.4.8因为读研期间的研究方向是图像处理,...


    本系列文章由@浅墨_毛星云 出品,转载请注明出处。  

    文章链接: http://blog.csdn.net/poem_qianmo/article/details/19809337

    作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

    邮箱: happylifemxy@163.com

    知乎:http://www.zhihu.com/people/mao-xing-yun

    写作当前博文时配套使用的OpenCV版本: 2.4.8、2.4.9、3.0

    2014年4月28更新OpenCV 2.4.9的配置。

    2014年9月12更新OpenCV 3.0的配置

    2014年9月12日本文第6次修订完毕)


    OpenCV2.4.9和2.4.8的配置几乎一样,唯一的区别在下文中的第五步,链接库的配置,把对应的248改成249即可。

    OpenCV 3.0配置更是被简化了。和2.4.8、2.4.9的区别就是下文第五步,链接库的配置,只用添加

    opencv_ts300d.lib、opencv_world300d.lib(debug版本的库)

    或opencv_ts300.lib、opencv_world300.lib(release版本的库)即可。


    ----------------------------浅墨于2014年9月12日注



    因为读研期间的研究方向是图像处理,所以浅墨这段时间闭门研究了很多OpenCV和图像处理相关的知识与内容。眼看自己积累到一定的程度了,于是决定开始开设这个OpenCV系列专栏,总结自己所学,也分享知识给大家。

    好了,这篇文章作为OpenCV的启程篇,自然少不了先系统地介绍OpenCV开发环境的配置。
    浅墨前后经历过OpenCV 2.4.6,OpenCV 2.4.7,OpenCV 2.4.8这三个版本的配置,有时候还要涉及到三个版本之间的转换,所以还是对OpenCV的配置有一定的理解的,希望自己的一点拙见能帮到大家。

    还是先放出待会儿的测试用图(如果要另存为这张图并配合文章后面给出的代码进行测试,注意后缀名要为jpg,而不是jpeg或其他):



    那么,开始吧。


    1.下载和安装OpenCV SDK



    VS2010不用说,肯定都安装了吧。来说说当前最新的OpenCV版本2.4.8(2014年2月24日),2.4.9 (2014年4月)的下载和安装。与其说是安装,不如叫解压更加合适,因为我们下载的exe安装文件就是一个自解压程序而已。

    在官网:http://opencv.org/上找到OpenCV windows版下载下来。

    下载完后得到文件OpenCV 2.4.X,双击后会提示解压到某个地方,推荐放到D:\Program Files\下,比如D:\Program Files,(因为OpenCV项目文件打包的时候,根目录就是opencv,所以我们不需要额外的新建一个名为opencv的文件夹,然后再解压,那是多此一举的事情)然后点击Extract按钮。

    等一段时间,OpenCV2.4.8近3个多G的文件就解压到了D:\Program Files下。


     其中,build里面是使用OpenCV相关的文件,我们如果只是使用OpenCV的话呢,就只用管build里面的内容。下面的sources文件夹你嫌烦,你嫌占硬盘空间,完全可以删掉。但是需要注意的是,官方示例集,也就是samples文件夹里面的示例程序,在sources文件夹里面躺着呢,所以,如果真是要删的话,还是想清楚哦。

    sources里面是源代码。可以直接查看。如何生成sln解决方案浅墨在这篇博文中有详细讲到:

     【OpenCV入门教程之七】 玩转OpenCV源代码:生成OpenCV工程解决方案与OpenCV源码编译



    2.配置环境变量


      

     

    这步的配置方法如下:

    【计算机】->【(右键)属性】->【高级系统设置】->【高级(标签)】->【环境变量】->“双击”系统变量中的PATH->在变量值里面添加相应的路径。如图:

    <1>


    <2>


    <3>


    <4>


    <5>



    对于32位系统,就添加:

    ”;…… opencv\build\x86\vc10\bin”(和之前的就有有的环境变量用英文的分号“;”进行分隔)

     

    而对于64位系统,可以两个都添加上:

    ”;…… opencv\build\x86\vc10\bin”

    和”…… opencv\build\x64\vc10\bin”,

    这样,到时候才可以在编译器Win32和X64中来回切换都吃得开,游刃有余~


    例如,浅墨的就是

    D:\Program Files\opencv\build\x64\vc10\bin;D:\Program Files\opencv\build\x86\vc10\bin


     PS:有童鞋亲测说64位系统也只需添加”…… opencv\build\x86\vc10\bin”即可,大家不妨一试。


    注:变量值实际为bin文件夹的路径;D表示OpenCV安装于D盘;X64表示运行系统环境位64位系统,若安装于32位系统,应为X86;vc10表示编译环境为Microsoft Visual Studio 2010;变量添加完成后最好注销系统,才会生效。

     


     



    3.工程包含(include)目录的配置



    之前看过的好多博文都说“每次新建工程都要重新配置”,其实不用这样麻烦的。

    首先是在Visual Studio里面新建一个控制台应用程序,最好是勾好空项目那个勾。

    (考虑到看这篇博文的童鞋很少接触vs,那么浅墨在这里将过程详细截图出来——浅墨2014年6月11日注)

    <1>打开visual studio,新建win32控制台项目,取个名字,比如叫test1,然后选好路径,点确定.


    <2>点一次“下一步”。


    <3>勾上空项目那个勾。


    <4>接着在解决方案资源管理器的【源文件】处右击->添加->新建项,准备在工程中新建一个cpp源文件。



    <5>选定C++源文件,取个名字,比如叫“main”,然后点【添加】,那么,一个新的cpp文件就添加到了工程中。

    <6>看过浅墨之前DirectX配置的相关博文的朋友们应该都知道,有一招叫属性管理器。在属性管理器中进行一次配置,就相当于进行了通用的配置过程,以后新建的工程就不用再额外的进行重新配置了。

    在菜单栏里面点<视图>--<属性管理器>,那么就会在visual studio中多出一个属性管理器工作区来。



    <7>在新出现的“属性管理器”工作区中,点击项目->Debug|Win32->Microsoft.Cpp.Win32.userDirectories(右键属性,或者双击)即可打开属性页面。

     



    <8>打开属性页面后,就是一番配置了。首先是在

    【通用属性】 ->【VC++目录】 ->【包含目录】中


    添加上

    D:\Program Files\opencv\build\include

    D:\Program Files\opencv\build\include\opencv

    D:\Program Files\opencv\build\include\opencv2 这三个目录。


    当然,这是之前把OpenCV解压到D:\Program Files\下的情况。实际的路径还要看你自己把OpenCV解压到了哪个目录下,根据你的实际情况来调节。







    4.工程库(lib)目录的配置



    其实这步和上一步差不多,属性管理器”工作区中,点击项目->Debug|Win32->Microsoft.Cpp.Win32.userDirectories(反键属性,或者双击)打开属性页面。

    接着上步,就是在【通用属性】 ->【VC++目录】 ->【库目录】中,


    添加上D:\Program Files\opencv\build\x86\vc10\lib这个路径。(最好不要复制粘贴浅墨给出的路径,而是自己去预览里面指定出来,这样会准确得多)


    (感谢小如风童鞋,之前给出的路径D:\Program Files\opencv\build\x86\vc10\lib里少个空格,直接复制浅墨给出的上面路径的童鞋就会出现“无法打开错误 1 error LNK1104: 无法打开文件“opencv_calib3d248.lib”类似的错误”,现已更正)

    ——————浅墨于2014年4月3日


    这里选择x86还是x64是一个常常令人困惑的问题。当然,对于32位操作系统,铁定就是选x86了。

    如果是64位操作系统,很多童鞋会想当然自作聪明地选择x64,其实不然。正确的理解是这样的:


    不管你是32位还是64位操作系统,只用管你用win32编译器还是X64编译器。

    其实配置选择什么跟64位还是32位系统没有直接的关系,而是在于你在编译你的程序的时候是使用那个编译器。


    编译器选的是win32,就用x86

    编译器选的是X64,就用X64。不过一般情况下,都是用的win32的X86编译器。所以,无论32还是64位操作系统,配置文件最好都选择x86版的


    另外,这里的vc10表示vs2010,如果是其他版本的visual studio,稍微要微调一下。

    感谢JJBomb 童鞋,这里我们补充完整:vc8 = Visual Studio 2005,vc9 = Visual Studio 2008,vc10 = Visual Studio 2010,vc11 = Visual Studio 2012,vc12 = Visual Studio 2013。

     




    5.链接库的配置


     

    依然是“属性管理器”工作区中,点击项目->Debug|Win32->Microsoft.Cpp.Win32.userDirectories(反键属性,或者双击)即可打开属性页面。【通用属性】 ->【链接器】->【输入】->【附加的依赖项】


    对于OpenCV2.4.8】,添加如下248版本的lib(这样的lib顺序是:19个带d的debug版的lib写在前面,19个不带d的release版的lib写在后面)


    opencv_ml248d.lib
    opencv_calib3d248d.lib
    opencv_contrib248d.lib
    opencv_core248d.lib
    opencv_features2d248d.lib
    opencv_flann248d.lib
    opencv_gpu248d.lib
    opencv_highgui248d.lib
    opencv_imgproc248d.lib
    opencv_legacy248d.lib
    opencv_objdetect248d.lib
    opencv_ts248d.lib
    opencv_video248d.lib
    opencv_nonfree248d.lib
    opencv_ocl248d.lib
    opencv_photo248d.lib
    opencv_stitching248d.lib
    opencv_superres248d.lib
    opencv_videostab248d.lib

    opencv_objdetect248.lib
    opencv_ts248.lib
    opencv_video248.lib
    opencv_nonfree248.lib
    opencv_ocl248.lib
    opencv_photo248.lib
    opencv_stitching248.lib
    opencv_superres248.lib
    opencv_videostab248.lib
    opencv_calib3d248.lib
    opencv_contrib248.lib
    opencv_core248.lib
    opencv_features2d248.lib
    opencv_flann248.lib
    opencv_gpu248.lib
    opencv_highgui248.lib
    opencv_imgproc248.lib
    opencv_legacy248.lib

    opencv_ml248.lib


    对于【OpenCV2.4.9】,添加如下249版本的lib(这样的lib顺序是:19个带d的debug版的lib写在前面,19个不带d的release版的lib写在后面)


    opencv_ml249d.lib
    opencv_calib3d249d.lib
    opencv_contrib249d.lib
    opencv_core249d.lib
    opencv_features2d249d.lib
    opencv_flann249d.lib
    opencv_gpu249d.lib
    opencv_highgui249d.lib
    opencv_imgproc249d.lib
    opencv_legacy249d.lib
    opencv_objdetect249d.lib
    opencv_ts249d.lib
    opencv_video249d.lib
    opencv_nonfree249d.lib
    opencv_ocl249d.lib
    opencv_photo249d.lib
    opencv_stitching249d.lib
    opencv_superres249d.lib
    opencv_videostab249d.lib

    opencv_objdetect249.lib
    opencv_ts249.lib
    opencv_video249.lib
    opencv_nonfree249.lib
    opencv_ocl249.lib
    opencv_photo249.lib
    opencv_stitching249.lib
    opencv_superres249.lib
    opencv_videostab249.lib
    opencv_calib3d249.lib
    opencv_contrib249.lib
    opencv_core249.lib
    opencv_features2d249.lib
    opencv_flann249.lib
    opencv_gpu249.lib
    opencv_highgui249.lib
    opencv_imgproc249.lib
    opencv_legacy249.lib
    opencv_ml249.lib


     


    需要注意的是,所粘贴内容即为之前我们解压的OpencV目录D:\opencv\build\x86\vc10\lib下所有lib库文件的名字;其中的248代表我们的OpenCV版本为2.4.8,若是其他版本的配置则在这里要进行相应的更改,比如说2.4.6版的OpenCV,那么这里的opencv_calib3d248d.lib就要改成opencv_calib3d246d.lib了.

    Debug文件库名有d结尾,Release没有,如 opencv_ts248d.lib(debug版本的lib)和opencv_ts248.lib(release版本的lib)

    不过我一般是在这里把带d和不带d的统统写在这里,因为这里是以后创建所有工程时都会继承的公共属性。


    对于【OpenCV 3.0】添加3.0版本的lib,新版的lib非常简单。想用debug版本的库,添加

    opencv_ts300d.lib

    opencv_world300d.lib 这两个库即可。

    而想用release版本的库,添加

    opencv_ts300.lib

    opencv_world300.lib即可。


    其实,对已经发行和未来即将发布的新版OpenCV,只需看opencv\build\x86\vc10\lib下的库是哪几个,添加成依赖项就可以了。




    另外注意按照如上的这种方式来配置,也许会出现debug下可以运行但是release下不能运行的情况(因为字符串读取问题引起的诸如图片载入不了,报指针越界,内存错误等等),这算是OpenCV自2.4.1以来的一个bug。

    解决方案:想在release模式下运行出程序结果,在工程的release模式下,将不带d的lib(全部是19个)添加到【项目】->【属性】(注意这样打开的是当前工程的属性页,我们只需在这里将release版(即不带d)的相关lib添加进去)->【配置属性】->【链接器】->【输入】->【附加的依赖项】下即可。


    ——————浅墨 2014年3月17日注&2014年6月11日更新

    类似错误如图:


     

     



    6.在Windows文件夹下加入OpenCV动态链接库



    PS:经过 hellofuturecyj 同学提醒,如果配置环境变量那步配置准确,且配置之后经过重启,就没有进行这步配置的必要了。即做完上面第五步的配置,重启一次,就可以直接跳到第七步,进行测试,看出不出图。

    当然,如果需不重启而马上来看配置的结果,就可以试试这里的方式。

    ——————浅墨 2014年6月16日注


    这一步是各种介绍OpenCV的配置的相关博文中都没写出来的。根据这些博文配置出来的环境,在运行基于OpenCV的程序的时候,往往会得到这样类似的结果:

     

    这种问题最简单粗暴的方法,是把相关的dll都拷贝到Windows操作系统的目录下。如果你的Windows安装在C盘,那么路径就是c:\Windows\System32。按照之前我的OpenCV的存放环境,这些dll存放在

    D:\Program Files\opencv\build\x86\vc10\bin目录下。


    到这个目录下,【Alt+A】全选,【Alt+C】拷贝,然后转到c:\Windows\System32下面,【Alt+V】复制,简单粗暴地就完成了。

    PS:对于Windows 8,则要放在要在SysWOW64文件夹中。

     

     

    恩,环境配置大功告成,我们来测试一下成果吧~





    7.最终的测试


     

    就用载入并显示一张图片到窗口的程序,用于我们的配置测试吧。

    新建一个空项目的控制台应用程序,新建一个cpp文件,然后粘贴如下代码:

    #include<iostream>
    #include <opencv2/core/core.hpp>
    #include <opencv2/highgui/highgui.hpp>
    
    
    using namespace cv;
    
    
    int main()
    {
    	// 读入一张图片(游戏原画)
    	Mat img=imread("pic.jpg");
    	// 创建一个名为 "游戏原画"窗口
    	namedWindow("游戏原画");
    	// 在窗口中显示游戏原画
    	imshow("游戏原画",img);
    	// 等待6000 ms后窗口自动关闭
    	waitKey(6000);
    }


    放置一张名为pic.jpg的图片到工程目录中,然后点击“运行“按钮,如果配置成功,就不会报错,得到预想的运行结果:



    为了大家的考虑,还是把工程文件发一下吧,虽然这次只有简单的几句代码:


    OpenCV+VS2010开发环境配置 测试用例 by浅墨】下载

     



    7.可能遇到的问题和解决方案


     

    呼,生活不可能是一帆风顺的,我们的配置过程也是。浅墨在几次的配置过程中,出现了如下的几种典型问题,我相信你可能会出现,就在这里集中列举一下吧,希望能为大家解惑:


    1. 找不到core.h

    出现这个问题是因为include的时候粗心大意了。

    如果你的版本是2.4.6。在这个版本下,opencv根文件夹下面就有个include,但我们配置的时候如果包含的是他就坑爹了。

    正确的应该填build里面的那个include。

     

    2.无法解析的外部命令

    这个问题其实上面有过解释了,不管你是32位还是64位操作系统,只用管你用win32编译器还是X64 编译器。

    其实配置选择什么跟64位还是32位系统没有直接的关系,而是在于你在编译你的程序的时候是使用那个编译器。

    编译器是win32,就用x86

    编译器是X64,就用X64。不过一般情况下,都是用的win32的X86编译器。所以,

    无论32还是 64位操作系统,配置文件最好都选择x86版的。

     

    3.关于形如--error LNK2005:xxx 已经在 msvcrtd.lib ( MSVCR90D.dll ) 中定义

    出现这个问题,把静态库不包含就行了。

     

    4. 应用程序无法正常启动0xc000007b

    Lib包含的问题。也许你同时包含了X86和X64的,或者包含出错错了。或者是对于windows 8 64位,dll要放在和System32文件夹同级的SysWOW64文件夹中。


    5.明明图片路径是对的,却载入不进去图片,提示指针越界,有未经处理的异常

    类似错误如图:



    这算是opencv的一个bug,工程属性里面关于带d和不带d的lib文件的附加依赖项的问题,就算配置好了每次想debug和release下都运行还得手动在工程属性里面加。当得到这样的错误时,可以把调试方式改一改,debug和release互换:


    或者打开当前工程(注意是当前工程的属性页,不是通用属性页)的属性页,debug或者release哪个报错,就把对应的带d或不带d的lib添加到【“当前”工程属性】->【链接器】->【输入】->【附加的依赖项】下即可。


    关于问题五的另一种错法:图片后缀问题

    昨天晚上一个学弟和大家一样在参考这篇文章配置OpenCV环境,弄了一下午一直报这个错误。最后浅墨给他看了下,发现是图片后缀的问题,在这里更新一下,以免有童鞋是因为这个问题而一直得不出结果。确保你载入的图片是和代码中imread的路径、名称、后缀是一模一样的。例如,学弟遇到的问题就是,在xp系统下面,没有显示后缀名,下了浅墨在文章末尾给出的配置参考示例程序,估计是xp系统默认会把jpg当做jpeg对待,然后他在代码中imread的是“1.jpg”,硬是报错。我给他看的时候,在文件夹选项中调出文件后缀名,发现在win7下面给大家准备的名为“1.jpg”的文件,在xp下面变成了“1.jpeg”。然后浅墨将学弟代码中的“1.jpg”改成“1.jpeg”,立马出结果。或许这就是一部分朋友们调不出结果的另一种原因吧。

    -----------------------浅墨于2014年9月20日补充




    6.无法打开文件“opencv_ml249d.lib”系列的错误


    关于LINK : fatal error LNK1104: 无法打开文件“opencv_ml249d.lib”,在评论区和大家给浅墨发的邮件中是普遍的一个错误。


    PS: jzh258fits 同学提出的一个新的解决方法是:项目->属性管理器->Debug|Win32->Microsoft.Cpp.Win32.userDirectories中的属性页面->连接器->常规里面的附加库目录中加入相应的lib文件目录。

    这个错误主要是因为包含的库目录中,和包含的附加依赖项不能相互对应照成的。

    也许是“opencv_ml249d.lib“多加了一个空格,成了”opencv_ml249d. lib“,就会报错。
    遇到这个问题,检查三个方面:

    1.检查第四步”4.工程库(lib)目录的配置“库目录中的路径是否准确。
    2.检查第五步”5.链接库的配置“中附加依赖项”的格式有没有问题,有没有多空格,版本号248,249什么的有没有问题,有没有多一个空格,少一个点什么的。
    3.第二步环境变量的配置是否准确。


    ——————————浅墨2014年4月28日更新




    好了,OpenCV的配置大概就是这些。

    下篇文章见 :)

     

     

    展开全文
  • OpenCV 概述

    千次阅读 多人点赞 2014-03-09 16:36:39
    OpenCV概述 OpenCV是Intel开元计算机视觉库,它有一系列开源的C函数和少量C++类构成,实现了图像处理和计算机视觉很多通用的算法。 OpenCV特点: 1.拥有包括300多个C函数的跨平台的中、高层API; 2.跨平台:...

    OpenCV概述

    OpenCV是Intel开元计算机视觉库,它有一系列开源的C函数和少量C++类构成,实现了图像处理和计算机视觉很多通用的算法。


    OpenCV特点:

    1.拥有包括300多个C函数的跨平台的中、高层API;

    2.跨平台:Windows/Linux;

    3.Free:无论是对于商业还是非商业应用;

    4.速度快;

    5.使用方便。


    OpenCV结构:


    CXCORE:包含数据结构、矩阵运算、数据变换、对象持久(Object Persistence)、内存管理、错误处理、动态连接、绘图、文本和基本的数学功能。

    CV:包含图形处理、图像结构分析、运动描述和跟踪、模式识别和摄像机标定。

    Machine Learning(ML):包含许多聚类,分类和数据分析函数。

    HighGUI:包含图形用户界面和图像/视频的读/写。

    CVCAM:摄像机接口,在OpenCV1.0以后的版本中被移除。


    OpenCV编码样式指南:

    文件命名:有CV和CVAUS库文件的命名必须服从于以下规则:

    1.所有的CV库文件名前缀为cv;

    2.混合的C/C++接口头文件扩展名为.h;

    3.纯C++接口头文件扩展名为.hpp;

    4.实现头文件扩展名为.cpp;

    5.为了与POSIX兼容,文件名都以小写字符组成。


    命名约定:

    1.OpenCV中使用大小写混合样式来标识外部函数、数据类型和类方法。

    2.宏全部使用大写字符,词间用下划线分隔。

    3.所有的外部或内部名称,若在多个文件中可见,则必须含有前缀:

       外部函数使用前缀cv;

       内部函数使用前缀lcv;

       数据结构(C结构体、枚举、联合体和类)使用前缀CV

       外部或某些内部宏使用前缀CV_

       内部宏使用前缀ICV_


    接口函数设计:

       为了保持库的一致性,以如下方式设计接口非常关键。函数接口元素包括:功能、名称、返回值、参数类型、参数顺序和参数默认值。

       函数功能必须定义良好并保持精简。函数应该容易镶入到使用其它OpenCV函数的不同处理过程。函数名称应该简单并能体现函数的功能。

       大多数函数名形式:cv<ActionName>


    图像处理---平滑处理

      “平滑处理”也称为“模糊处理”,是一项简单且使用频率很高的图像处理方法。平滑处理最常见的用途是用来减少图像上的噪声或者失真。目前OpenCV中,所有平滑处理操作都由cvSmooth函数实现。

    函数说明:

    void cvSmooth(const CvArr* src, CvArr* dst, int smoothtype=CV_GAUSSIAN, int param1=3, int param2=0, double param3=0, double param4=0);

      src和dst分别是平滑操作的输入头像和结果,cvSmooth()函数包含4个参数:param1-param4. 这些参数的含义取决于smoothtype的值。


    图像处理---图像形态学

      OpenCV为进行图像形态学变换提供了快速、方便的函数,基于形态学的转换是膨胀与腐蚀,他们能够实现多种功能:消除噪声,分隔出独立的图像元素以及在图像中连接相邻的元素。

      膨胀是指将一个图像(或图像中的一部分区域,称之为A)与核(称之为B)进行卷积。即,计算核B覆盖区域的像素最大值,并把这个最大值赋值给参考点指定的坐标。这样就会使图像中的高亮区域逐渐增大。

      腐蚀是膨胀的反操作。腐蚀操作要求计算核区像素的最小值。即,当核B与图像卷积时,计算被核B覆盖区域的最小像素值,并把这个值放到参考点。

      在OpenCV,利用cvErode()和cvDilate()函数实现上述变换:

    void cvErode(lplImage* src, lplIMage* dst, lpIlConvKernel* B=NULL, int iterations=1);

    void cvDilate(lplImage* src, lplIMage* dst, lpIlConvKernel* B=NULL, int iterations=1);

    该两个函数的第三个参数是核,默认值是NULL。当为空时,所使用的是参考点位于中心的3*3核。第四个参数是迭代的次数。

    开运算:将二值图像先腐蚀,然后,再膨胀,通常用来统计二值图像中的区域数。开运算则去除一些小的斑点。

    闭运算:将二值图像先膨胀,然后,再腐蚀,通常用来去除噪声引起的区域。闭运算使得小洞被填上,临近的目标连接到了一起(任何结构元容纳不下的小洞或者缝隙都会被填充)。

      对于连通区域分析,通常先采用腐蚀或者闭运算来消除纯粹由噪声引起的部分,然后用开运算来连接临近的区域。最显著的效果是,闭运算消除了低于临近点的孤立点,而,开运算是消除高于其临近点的孤立点。 


    图像处理---拉普拉斯变换

    OpenCV的拉普拉斯函数实现了拉普拉斯算子的离散模拟。

    OpenCV中,拉普拉斯变换函数为:

    void cvLaplace(const CvArr* src, CvArr* dst, int apertureSize=3);

    该函数通常把源图像和目标图像以及中控大小作为变量。源图像既可以是8位(无符号)图像,也可以是32位(浮点)图像。而目标图像必须是16位(有符号)或者32位(浮点)图像。因为拉普拉斯算子的形式是沿着X轴和Y轴的二次导数的和,这就意味着周围是更高值的单电或者小块会将使这个函数值最大化。反过来,周围是更低值的点将会是函数的负值最大化。反过来,周围是更低值的点将会是函数的负值最大化。基于这种思想,拉普拉斯也可以用于边缘检测。


    图像处理---Cannay算子

      Canny边缘检测法与基于拉普拉斯算法的不同点之一是在Canny算法中,首先在x和y方向求一阶导数,然后,组合为4个方向的导数。这些方向导数达到局部最大值的点就是组成边缘的候选点。另外,Canny算法的最终要的新特点是阈值的概念,如果一个像素的梯度大于上线阈值,则被认为是边缘像素,如果低于下限阈值,则被抛弃,如果介于二者时间,只有当其与高于上限阈值的像素连接时才会被接受。Canny推荐的上下限阈值比为2:1到3:1之间。

      void cvCanny(const CvArr* img, CvArr* edges, double lowThresh, double highThresh, int apertureSize=3);

      此函数是需要输入一张灰度图,输出也是灰度的。


      形态学操作

      形态学操作主要有四种:腐蚀、膨胀、开和闭。最基本的形态学操作是腐蚀和膨胀。其他的操作都可以通过腐蚀和膨胀推导出来。

      用集合论的观点介绍他们很繁琐。这里换一种思路,如下:

      我们先做一定的假设:对于一幅图像,前景(我们感兴趣的部分);背景(不感兴趣的部分)是黑色的。然后就可以望文生义一下了,腐蚀操作会使得前景变小,而膨胀会使得前景变大。这主要是当结构元(用来对图像处理的基本模板)作用于图像的边沿时,两种操作的定义引起的。腐蚀操作时,只有当整个结构元都在图像边沿内时,锚点(结构元与图像中每个像素对其的点,通常取作结构元的几何中心)对准的像素才会被保留,判为前景;否则,这个点判为背景。膨胀操作则是只要结构元与图像有交集时,锚点对准的像素就会被保留,判为前景。腐蚀可以用来消除一些小的误检测的前景;而膨胀则可以填充一些小洞。

      注意到,用3*3的模板腐蚀3次与用7*7的模板腐蚀一次效果是相同的。膨胀的结果可以类推。

      

    展开全文
  • OpenCV4 图像处理与视频分析实战教程

    千人学习 2019-05-27 11:04:45
    基于OpenCV最新版本OpenCV4开始,从基础开始,详解OpenCV核心模块Core、Imgproc、video analysis核心API与相关知识点,讲解从图像组成,像素操作开始,一步一步教你如何写代码,如何用API解决实际问题,从图像处理到...
  • Opencv学习(一)

    千次阅读 2018-10-05 16:12:06
    Opencv学习笔记(一) 1、Image Watch 图像监视是微软VisualStudio的插件,它允许您在调试应用程序时在内存映像中可视化。这有助于跟踪错误,或者简单地理解给定代码的操作。 可以在vs里面的工具--&amp;...

    1、Image Watch

    图像监视是微软VisualStudio的插件,它允许您在调试应用程序时在内存映像中可视化。这有助于跟踪错误,或者简单地理解给定代码的操作。

    可以在vs里面的工具-->扩展和更新里面获取Image Watch

    然后再视图-->其他窗口调用。

    2、Load and Display an Image

    Code

    #include <opencv2/core/core.hpp>
    #include <opencv2/imgcodecs.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include <iostream>
    #include <string>
    using namespace cv;
    using namespace std;
    int main(int argc, char** argv)
    {
    	string imageName("1.jpg"); // by default
    	if (argc > 1)
    	{
    		imageName = argv[1];
    	}
    	Mat image;
    	image = imread(imageName.c_str(), IMREAD_COLOR); // Read the file
    	if (image.empty())                      // Check for invalid input
    	{
    		cout << "Could not open or find the image" << std::endl;
    		return -1;
    	}
    	namedWindow("Display window", WINDOW_AUTOSIZE); // Create a window for display.
    	imshow("Display window", image);                // Show our image inside it.
    	waitKey(0); // Wait for a keystroke in the window
    	return 0;
    }
    

    vs2017中使用命令行参数调试程序

    在这里插入图片描述

    Explanation

    现在我们调用CV::imRead函数,它加载由第一个参数指定的图像名称(ARGV〔1〕)。第二个参数指定了我们想要的图像的格式。

    IimeRead不变(<0)按原样加载图像(包括alpha通道,如果存在)

    IimeRead灰度(0)将图像加载为强度图像。

    IdRead颜色(> 0)以RGB格式加载图像

    image = imread(imageName.c_str(), IMREAD_COLOR); // Read the file
    

    在检查图像数据被正确加载之后,我们想要显示我们的图像,所以我们使用CV::NAMEDLE窗口函数创建OpenCV窗口。一旦创建了OpenCV,它们就会自动管理。为此,您需要指定它的名称以及它应该如何从大小的角度来处理它包含的图像的变化。

    如果不使用QT后端,则只支持WistWOWAutoSead。在这种情况下,窗口大小将占用它所显示的图像的大小。不允许调整大小!

    Qt上的Windows WORKWORD,您可以使用它来允许窗口调整大小。图像将根据当前窗口大小调整自身大小。通过使用该运算符,还需要指定是否希望图像保持其纵横比(WOWDOWKEEPRATIO)或不(WOWDOWFRIERATIO)。

    namedWindow( "Display window", WINDOW_AUTOSIZE ); // Create a window for display.
    

    最后,使用新的图像更新OpenCV窗口的内容,使用imshow函数。指定要更新的OpenCV窗口名称和在此操作期间要使用的图像:

     imshow( "Display window", image ); // Show our image inside it.
    

    因为我们希望我们的窗口显示出来,直到用户按下一个键(否则程序将结束得太快),我们使用CV::WaWiKEY函数,它的唯一参数是它等待用户输入的时间(毫秒测量)。零意味着永远等待。

    waitKey(0); // Wait for a keystroke in the window
    

    3、Load, Modify, and Save an Image

    Code:

    #include <opencv2/opencv.hpp>
    using namespace cv;
    using namespace std;
    int main(int argc, char** argv)
    {
    	char* imageName = argv[1];
    	Mat image;
    	image = imread(imageName, 1);
    	if (argc != 2 || !image.data)
    	{
    		printf(" No image data \n ");
    		return -1;
    	}
    	Mat gray_image;
    	cvtColor(image, gray_image, COLOR_BGR2GRAY);
    	imwrite("Gray_Image.jpg", gray_image);
    	namedWindow(imageName, WINDOW_AUTOSIZE);
    	namedWindow("Gray image", WINDOW_AUTOSIZE);
    	imshow(imageName, image);
    	imshow("Gray image", gray_image);
    	waitKey(0);
    	return 0;
    }
    

    Explanation

    1.imread不在赘述

    2. cv::cvtColor

    cvtColor( image, gray_image, COLOR_BGR2GRAY );

    参数:

    源图像(图像)

    目的地图像(GrayIX图像),在其中我们将保存转换后的图像。

    指示将执行什么类型的转换的附加参数。在这种情况下,我们使用CurryBGR2GRY(因为在彩色图像的情况下具有BGR默认信道顺序)。

    3.imwrite

    imwrite( “…/…/images/Gray_Image.jpg”, gray_image );

    参数:

    写入的图片名字

    写入的Mat

    4、Mat - The Basic Image Container

    MAT基本上是一个具有两个数据部分的类:矩阵标头(包含诸如矩阵的大小、用于存储的方法、地址是存储的矩阵等)和指向包含像素值的矩阵的指针(取决于选择用于存储的方法。矩阵头的大小是恒定的,但是矩阵本身的大小可以从图像到图像变化,并且通常是按数量级大的。

    OpenCV是一个图像处理库。它包含了大量的图像处理功能集合。为了解决计算上的挑战,大多数情况下,您将最终使用库的多个函数。因此,将图像传递给函数是一种常见的做法。我们不应该忘记,我们正在谈论图像处理算法,这往往是相当沉重的计算。我们想做的最后一件事是通过制作不必要的大图像拷贝来进一步降低程序的速度。

    为了解决这个问题,OpenCV使用一个参考计数系统。其思想是每个Mat对象都有自己的标头,但是矩阵可以通过它们的矩阵指针指向相同的地址而在它们的两个实例之间共享。此外,复制运算符只将标题和指针复制到大矩阵,而不是数据本身。

    Mat A, C;                          // creates just the header parts
    A = imread(argv[1], IMREAD_COLOR); // here we'll know the method used (allocate matrix)
    Mat B(A);                                 // Use the copy constructor
    C = A;                                    // Assignment operator
    

    最后,所有这些对象指向相同的单个数据矩阵。然而,它们的标题是不同的,使用它们中的任何一个进行修改也会影响所有其他的标题。在实际应用中,不同的对象只对同一基础数据提供不同的访问方法。然而,它们的标题部分是不同的。真正有趣的部分是,您可以创建仅引用完整数据的一个子段的标题。例如,为了在图像中创建感兴趣区域(ROI),您只需创建具有新边界的新标题:

    Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle
    Mat E = A(Range::all(), Range(1,3)); // using row and column boundaries
    

    现在你可能会问,矩阵本身是否可能属于多个物体,当它不再需要时,它负责清理它。简短的回答是:最后一个使用它的对象。这是通过使用参考计数机制来处理的。每当有人复制垫对象的头时,矩阵的计数器就会增加。每当清空报头时,计数器就会减少。当计数器达到零时,矩阵也被释放。有时,您也希望复制矩阵本身,因此OpenCV提供CV::MAT::clone()CV::MAT::CopyTo()函数。

    Mat F = A.clone();
    Mat G;
    A.copyTo(G);
    

    现在修改F或G不会影响垫头所指向的矩阵。你需要记住的是:

    OpenCV函数的输出图像分配是自动的(除非另有说明)。

    你不需要考虑内存管理与opencvs C++接口。

    赋值运算符和复制构造函数只复制报头。

    可以使用CV::Mat:clone()CV::MAT::CopyTo()函数复制图像的底层矩阵。

    构造函数

    CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]

    CVY8UC3意味着我们使用8位长的无符号字符类型,每个像素都有三个来形成三个通道。这是预定义的多达四个频道号码。CV::标量是四元短向量。指定这个值,并且可以用自定义值初始化所有矩阵点。如果需要更多,可以用上宏创建类型,在括号中设置通道编号,如下所示。

    5.How to scan images, lookup tables and time measurement with OpenCV

    The iterator (safe) method

    迭代器方法被认为是一种更安全的方法,因为它从用户那里接管这些任务。所有你需要做的是询问图像矩阵的开始和结束,然后只需增加开始迭代器,直到达到结束。若要获取迭代器所指向的值,请使用*运算符(在它之前加上它)。

    Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
    {
        // accept only char type matrices
        CV_Assert(I.depth() != sizeof(uchar));
        const int channels = I.channels();
        switch(channels)
        {
        case 1:
            {
                MatIterator_<uchar> it, end;
                for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                    *it = table[*it];
                break;
            }
        case 3:
            {
                MatIterator_<Vec3b> it, end;
                for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
                {
                    (*it)[0] = table[(*it)[0]];
                    (*it)[1] = table[(*it)[1]];
                    (*it)[2] = table[(*it)[2]];
                }
            }
        }
        return I;
    }
    

    在彩色图像的情况下,每列有三个UCHAR项。这可能被认为是一个短向量uChar项目。要访问第n个子列,我们使用简单的运算符[]访问。记住OpenCV迭代器经过列并自动跳转到下一行是很重要的。因此,在彩色图像的情况下,如果使用简单的UCHAR迭代器,则只能访问蓝色通道值。

    Mask operations on matrices

    矩阵上的掩码运算是相当简单的。想法是根据掩模矩阵(也称为内核)重新计算图像中的每个像素值。该掩模保持将调整相邻像素(和当前像素)对新像素值有多大影响的值。从数学的角度,我们用我们的指定值做加权平均。

    void Sharpen(const Mat& myImage, Mat& Result)
    {
        CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images
        Result.create(myImage.size(), myImage.type());
        const int nChannels = myImage.channels();
        for(int j = 1; j < myImage.rows - 1; ++j)
        {
            const uchar* previous = myImage.ptr<uchar>(j - 1);
            const uchar* current  = myImage.ptr<uchar>(j    );
            const uchar* next     = myImage.ptr<uchar>(j + 1);
            uchar* output = Result.ptr<uchar>(j);
            for(int i = nChannels; i < nChannels * (myImage.cols - 1); ++i)
            {
                *output++ = saturate_cast<uchar>(5 * current[i]
                             -current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
            }
        }
        Result.row(0).setTo(Scalar(0));
        Result.row(Result.rows - 1).setTo(Scalar(0));
        Result.col(0).setTo(Scalar(0));
        Result.col(Result.cols - 1).setTo(Scalar(0));
    }
    

    6、Adding (blending) two images using OpenCV

    合成两张图片

    Code:

    #include <opencv2/opencv.hpp>
    #include <iostream>
    using namespace cv;
    int main(int argc, char** argv)
    {
    	double alpha = 0.5; double beta; double input;
    	Mat src1, src2, dst;
    	std::cout << " Simple Linear Blender " << std::endl;
    	std::cout << "-----------------------" << std::endl;
    	std::cout << "* Enter alpha [0-1]: ";
    	std::cin >> input;
    	if (input >= 0.0 && input <= 1.0)
    	{
    		alpha = input;
    	}
    	src1 = imread("1.jpg");
    	src2 = imread("2.jpg");
    	if (!src1.data) { printf("Error loading src1 \n"); return -1; }
    	if (!src2.data) { printf("Error loading src2 \n"); return -1; }
    	namedWindow("Linear Blend", 1);
    	beta = (1.0 - alpha);
    	addWeighted(src1, alpha, src2, beta, 0.0, dst);
    	imshow("Linear Blend", dst);
    	imwrite("3.jpg", dst);
    	waitKey(0);
    	return 0;
    }
    

    Explanation

    addWeighted( src1, alpha, src2, beta, 0.0, dst);

    图片:src1,src2

    比例:alpha,beta,和为1

    新图片:dst

    7、Changing the contrast and brightness of an image! :

    一般的图像处理算子是一个获取一个或多个输入图像并产生输出图像的函数。

    图像变换可以看作:

    点算子(像素变换)

    邻域(基于区域)算子

    像素变换

    在这种图像处理变换中,每个输出像素的值仅取决于相应的输入像素值。

    这样的例子包括亮度和对比度调整以及颜色校正和变换。

    亮度和对比度调整

    两个常用的点过程是乘法和加法:

    g(x)=αf(x)+β

    参数α>0和β通常称为增益和偏置参数,有时这些参数分别用来控制对比度和亮度。

    可以将f(x)作为源图像像素和g(x)作为输出图像像素。然后,我们更方便地将表达式写成:

    g(i,j)=αf(i,j)+β

    Code

    #include <opencv2/opencv.hpp>
    #include <iostream>
    using namespace cv;
    double alpha; /*< Simple contrast control */
    int beta;  /*< Simple brightness control */
    int main(int argc, char** argv)
    {
    	Mat image = imread(argv[1]);
    	Mat new_image = Mat::zeros(image.size(), image.type());
    	std::cout << " Basic Linear Transforms " << std::endl;
    	std::cout << "-------------------------" << std::endl;
    	std::cout << "* Enter the alpha value [1.0-3.0]: "; std::cin >> alpha;
    	std::cout << "* Enter the beta value [0-100]: "; std::cin >> beta;
    	for (int y = 0; y < image.rows; y++) {
    		for (int x = 0; x < image.cols; x++) {
    			for (int c = 0; c < 3; c++) {
    				new_image.at<Vec3b>(y, x)[c] =
    					saturate_cast<uchar>(alpha*(image.at<Vec3b>(y, x)[c]) + beta);
    			}
    		}
    	}
    	namedWindow("Original Image", 1);
    	namedWindow("New Image", 1);
    	imshow("Original Image", image);
    	imshow("New Image", new_image);
    	imwrite("2.jpg", new_image);
    	waitKey();
    	return 0;
    }
    

    Explanation

    1.我们首先创建参数来保存用户输入的α和β:

    double alpha;
    int beta;
    

    2.我们使用cv::imread加载图像并将其保存在一个MAT对象中:

    Mat image = imread( argv[1] );
    

    3.我们使用CV::iMead加载图像并将其保存在一个Mat对象中:现在,因为我们将对该图像进行一些转换,所以我们需要一个新的Mat对象来存储它。此外,我们希望这具有以下特征:

    初始像素值等于零

    与原始图像相同大小和类型

    Mat new_image = Mat::zeros( image.size(), image.type() );
    

    4.现在,为了执行操作G(i,j)=αf(i,j)+β,我们将访问图像中的每个像素。由于我们正在使用BGR图像,我们将有三个像素(B,G和R)的值,所以我们也将分别访问它们。

    for( int y = 0; y < image.rows; y++ ) {
        for( int x = 0; x < image.cols; x++ ) {
            for( int c = 0; c < 3; c++ ) {
                new_image.at<Vec3b>(y,x)[c] =
                  saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
            }
        }
    }
    

    注意以下事项:

    为了访问图像中的每个像素,我们使用这个语法:图像。在<Vec3b>(Y,X)[C],其中Y是行,X是列,C是R,G或B(0, 1或2)。

    因为运算 αp(i,j)+β可以给出超出范围的值,或者不是整数(如果α是浮点数),我们使用cv::SudialType强制转换来确保值是有效的。

    5.最后,我们创建窗口和显示图像,通常的方式。

    	namedWindow("Original Image", 1);
    	namedWindow("New Image", 1);
    	imshow("Original Image", image);
    	imshow("New Image", new_image);
    	imwrite("2.jpg", new_image);
    	waitKey();
    

    8、Basic Drawing

    Point

    它代表一个2D点,由它的图像坐标x和y指定。我们可以将它定义为:

    Point pt;
    pt.x = 10;
    pt.y = 8;
    //Point pt = Point(10, 8);
    

    Scalar

    表示一个4元向量。在OpenCV中,类型标量被广泛用于传递像素值;

    在本教程中,我们将广泛使用它来表示BGR颜色值(3个参数)。如果不使用最后一个参数,则不必定义最后一个参数。

    让我们来看看一个例子,如果我们要求一个颜色参数,我们给出:

    Scalar( a, b, c )
    

    我们将定义一个BGR颜色,例如:蓝色= A,绿色= B和红色= C。

    Code

    #include <opencv2/core.hpp>
    #include <opencv2/imgproc.hpp>
    #include <opencv2/highgui.hpp>
    
    #define w 400
    
    using namespace cv;
    
    /// Function headers
    void MyEllipse( Mat img, double angle );
    void MyFilledCircle( Mat img, Point center );
    void MyPolygon( Mat img );
    void MyLine( Mat img, Point start, Point end );
    
    /**
     * @function main
     * @brief Main function
     */
    int main( void ){
    
      //![create_images]
      /// Windows names
      char atom_window[] = "Drawing 1: Atom";
      char rook_window[] = "Drawing 2: Rook";
    
      /// Create black empty images
      Mat atom_image = Mat::zeros( w, w, CV_8UC3 );
      Mat rook_image = Mat::zeros( w, w, CV_8UC3 );
      //![create_images]
    
      /// 1. Draw a simple atom:
      /// -----------------------
    
      //![draw_atom]
      /// 1.a. Creating ellipses
      MyEllipse( atom_image, 90 );
      MyEllipse( atom_image, 0 );
      MyEllipse( atom_image, 45 );
      MyEllipse( atom_image, -45 );
    
      /// 1.b. Creating circles
      MyFilledCircle( atom_image, Point( w/2, w/2) );
      //![draw_atom]
    
      /// 2. Draw a rook
      /// ------------------
    
      //![draw_rook]
      /// 2.a. Create a convex polygon
      MyPolygon( rook_image );
    
      //![rectangle]
      /// 2.b. Creating rectangles
      rectangle( rook_image,
             Point( 0, 7*w/8 ),
             Point( w, w),
             Scalar( 0, 255, 255 ),
             FILLED,
             LINE_8 );
      //![rectangle]
    
      /// 2.c. Create a few lines
      MyLine( rook_image, Point( 0, 15*w/16 ), Point( w, 15*w/16 ) );
      MyLine( rook_image, Point( w/4, 7*w/8 ), Point( w/4, w ) );
      MyLine( rook_image, Point( w/2, 7*w/8 ), Point( w/2, w ) );
      MyLine( rook_image, Point( 3*w/4, 7*w/8 ), Point( 3*w/4, w ) );
      //![draw_rook]
    
      /// 3. Display your stuff!
      imshow( atom_window, atom_image );
      moveWindow( atom_window, 0, 200 );
      imshow( rook_window, rook_image );
      moveWindow( rook_window, w, 200 );
    
      waitKey( 0 );
      return(0);
    }
    
    /// Function Declaration
    
    /**
     * @function MyEllipse
     * @brief Draw a fixed-size ellipse with different angles
     */
    //![my_ellipse]
    void MyEllipse( Mat img, double angle )
    {
      int thickness = 2;
      int lineType = 8;
    
      ellipse( img,
           Point( w/2, w/2 ),
           Size( w/4, w/16 ),
           angle,
           0,
           360,
           Scalar( 255, 0, 0 ),
           thickness,
           lineType );
    }
    //![my_ellipse]
    
    /**
     * @function MyFilledCircle
     * @brief Draw a fixed-size filled circle
     */
    //![my_filled_circle]
    void MyFilledCircle( Mat img, Point center )
    {
      circle( img,
          center,
          w/32,
          Scalar( 0, 0, 255 ),
          FILLED,
          LINE_8 );
    }
    //![my_filled_circle]
    
    /**
     * @function MyPolygon
     * @brief Draw a simple concave polygon (rook)
     */
    //![my_polygon]
    void MyPolygon( Mat img )
    {
      int lineType = LINE_8;
    
      /** Create some points */
      Point rook_points[1][20];
      rook_points[0][0]  = Point(    w/4,   7*w/8 );
      rook_points[0][1]  = Point(  3*w/4,   7*w/8 );
      rook_points[0][2]  = Point(  3*w/4,  13*w/16 );
      rook_points[0][3]  = Point( 11*w/16, 13*w/16 );
      rook_points[0][4]  = Point( 19*w/32,  3*w/8 );
      rook_points[0][5]  = Point(  3*w/4,   3*w/8 );
      rook_points[0][6]  = Point(  3*w/4,     w/8 );
      rook_points[0][7]  = Point( 26*w/40,    w/8 );
      rook_points[0][8]  = Point( 26*w/40,    w/4 );
      rook_points[0][9]  = Point( 22*w/40,    w/4 );
      rook_points[0][10] = Point( 22*w/40,    w/8 );
      rook_points[0][11] = Point( 18*w/40,    w/8 );
      rook_points[0][12] = Point( 18*w/40,    w/4 );
      rook_points[0][13] = Point( 14*w/40,    w/4 );
      rook_points[0][14] = Point( 14*w/40,    w/8 );
      rook_points[0][15] = Point(    w/4,     w/8 );
      rook_points[0][16] = Point(    w/4,   3*w/8 );
      rook_points[0][17] = Point( 13*w/32,  3*w/8 );
      rook_points[0][18] = Point(  5*w/16, 13*w/16 );
      rook_points[0][19] = Point(    w/4,  13*w/16 );
    
      const Point* ppt[1] = { rook_points[0] };
      int npt[] = { 20 };
    
      fillPoly( img,
            ppt,
            npt,
            1,
            Scalar( 255, 255, 255 ),
            lineType );
    }
    //![my_polygon]
    
    /**
     * @function MyLine
     * @brief Draw a simple line
     */
    //![my_line]
    void MyLine( Mat img, Point start, Point end )
    {
      int thickness = 2;
      int lineType = LINE_8;
    
      line( img,
        start,
        end,
        Scalar( 0, 0, 0 ),
        thickness,
        lineType );
    }
    

    Explanation

    1.由于我们计划画两个例子(原子和一个木棒),我们必须创建两个图像和两个窗口来显示它们。

    char atom_window[] = "Drawing 1: Atom";
    char rook_window[] = "Drawing 2: Rook";
    Mat atom_image = Mat::zeros( w, w, CV_8UC3 );
    Mat rook_image = Mat::zeros( w, w, CV_8UC3 );
    

    2.我们创建了绘制不同几何形状的函数。例如,为了画原子,我们使用了MyEngEs和MyFilledCircle:

    MyEllipse( atom_image, 90 );
    MyEllipse( atom_image, 0 );
    MyEllipse( atom_image, 45 );
    MyEllipse( atom_image, -45 );
    MyFilledCircle( atom_image, Point( w/2.0, w/2.0) );
    

    3.为了绘制木屋,我们采用了MyLoinrectangleMyPosion

    
    MyPolygon( rook_image );
    rectangle( rook_image,
           Point( 0, 7*w/8.0 ),
           Point( w, w),
           Scalar( 0, 255, 255 ),
           -1,
           8 );
    MyLine( rook_image, Point( 0, 15*w/16 ), Point( w, 15*w/16 ) );
    MyLine( rook_image, Point( w/4, 7*w/8 ), Point( w/4, w ) );
    MyLine( rook_image, Point( w/2, 7*w/8 ), Point( w/2, w ) );
    MyLine( rook_image, Point( 3*w/4, 7*w/8 ), Point( 3*w/4, w ) );
    

    4.让我们检查一下这些函数的内部内容:

    MyLine

    void MyLine( Mat img, Point start, Point end )
    {
        int thickness = 2;
        int lineType = 8;
        line( img, start, end,
              Scalar( 0, 0, 0 ),
              thickness,
              lineType );
    }
    

    正如我们所看到的,MyLayle只调用函数cv::Load,它执行以下操作:

    从点到点画一条线

    该线显示在图像IMG中。

    线条颜色由标量(0, 0, 0)定义,这是布莱克的RGB值。

    线的厚度设置为厚度(在这种情况下为2)。

    该线是8连通的(LyeType=8)。

    MyEllipse

    void MyEllipse( Mat img, double angle )
    {
        int thickness = 2;
        int lineType = 8;
        ellipse( img,
           Point( w/2.0, w/2.0 ),
           Size( w/4.0, w/16.0 ),
           angle,
           0,
           360,
           Scalar( 255, 0, 0 ),
           thickness,
           lineType );
    }
    

    从上面的代码中,我们可以看到函数cv::fillPoly绘制椭圆,使得:

    椭圆在图像IMG中显示。

    椭圆中心位于点**(w/2,w/2),并被封装在大小为的框中(w/4,w/16)**。

    椭圆旋转角度

    椭圆在0度和360度之间延伸一个弧。

    图形的颜色将是标量(255, 0, 0),这意味着蓝色在RGB值。

    椭圆的厚度为2。

    MyFilledCircle

    void MyFilledCircle( Mat img, Point center )
    {
        int thickness = -1;
        int lineType = 8;
        circle( img,
            center,
            w/32.0,
            Scalar( 0, 0, 255 ),
            thickness,
            lineType );
    }
    

    类似于椭圆函数,我们可以观察到圆圈作为参数接收:

    将显示圆的图像(IMG)

    以圆心为中心的圆的中心

    圆的半径:W/32

    圆的颜色:标量(0, 0, 255),表示BGR中的红色。

    由于厚度=1,将画圆填充。

    MyPolygon

    void MyPolygon( Mat img )
    {
        int lineType = 8;
        /* Create some points */
        Point rook_points[1][20];
        rook_points[0][0] = Point( w/4.0, 7*w/8.0 );
        rook_points[0][1] = Point( 3*w/4.0, 7*w/8.0 );
        rook_points[0][2] = Point( 3*w/4.0, 13*w/16.0 );
        rook_points[0][3] = Point( 11*w/16.0, 13*w/16.0 );
        rook_points[0][4] = Point( 19*w/32.0, 3*w/8.0 );
        rook_points[0][5] = Point( 3*w/4.0, 3*w/8.0 );
        rook_points[0][6] = Point( 3*w/4.0, w/8.0 );
        rook_points[0][7] = Point( 26*w/40.0, w/8.0 );
        rook_points[0][8] = Point( 26*w/40.0, w/4.0 );
        rook_points[0][9] = Point( 22*w/40.0, w/4.0 );
        rook_points[0][10] = Point( 22*w/40.0, w/8.0 );
        rook_points[0][11] = Point( 18*w/40.0, w/8.0 );
        rook_points[0][12] = Point( 18*w/40.0, w/4.0 );
        rook_points[0][13] = Point( 14*w/40.0, w/4.0 );
        rook_points[0][14] = Point( 14*w/40.0, w/8.0 );
        rook_points[0][15] = Point( w/4.0, w/8.0 );
        rook_points[0][16] = Point( w/4.0, 3*w/8.0 );
        rook_points[0][17] = Point( 13*w/32.0, 3*w/8.0 );
        rook_points[0][18] = Point( 5*w/16.0, 13*w/16.0 );
        rook_points[0][19] = Point( w/4.0, 13*w/16.0) ;
        const Point* ppt[1] = { rook_points[0] };
        int npt[] = { 20 };
        fillPoly( img,
                  ppt,
                  npt,
                      1,
                  Scalar( 255, 255, 255 ),
                  lineType );
    }
    

    为了画一个填充多边形,我们使用函数cv:fIyPull。我们注意到:

    多边形将绘制在IMG上。

    多边形的顶点是PPT中的点集。

    要绘制的顶点的总数是NPT

    绘制的多边形数仅为1个。

    多边形的颜色由标量(255, 255, 255)定义,这是白色的BGR值。

    rectangle

    rectangle( rook_image,
               Point( 0, 7*w/8.0 ),
               Point( w, w),
               Scalar( 0, 255, 255 ),
               -1, 8 );
    

    最后,我们有了cv::rectangle函数(我们没有为这个家伙创建一个特殊的函数)。我们注意到:

    矩形将绘制在Rookx图像上。

    矩形的两个相对顶点由**点(0, 7×w/8)**和点(w,w)**定义。

    矩形的颜色由标量(0, 255, 255)给出,这是黄色的BGR值。

    由于厚度值由** -1 **给出,矩形将被填充。

    9、Put Text, Point or Circle

        //插入文字  
        //参数为:承载的图片,插入的文字,文字的位置(文本框左下角),字体,大小,颜色  
        string words= "good luck";  
        putText( picture, words, Point( picture.rows/2,picture.cols/4),CV_FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 0) );  
        imshow("1.jpg",picture);  
    
    //参数为:承载的图像、圆心、半径、颜色、粗细、线型、最后一个0  
    circle(picture,center,r,Scalar(0,0,0));  
    

    椭圆

       //参数为:承载的图像、圆心、长短轴、径向夹角(水平面到长轴的夹角)、起始角度(长轴到起始边沿的夹角)、结束角度(长轴到结束点的夹角)、倾斜的矩形(可选项)、颜色、粗细、线性、偏移  
        ellipse(picture,center,Size( 250, 100 ),0,30,240,Scalar(0,0,0));  
    

    line:

        //画线  
        Point a = Point (600,600);  
        //参数为:承载的图像、起始点、结束点、颜色、粗细、线型  
        line(picture,a,center,Scalar(255,0,0));  
        imshow("1.jpg",picture);  
    

    矩形:

    //画矩形  
    //参数为:承载的图像、顶点、对角点、颜色(这里是蓝色)、粗细、大小  
    rectangle(picture,a,center,Scalar(255,0,0));  
    imshow("底板",picture); 
    

    10、Discrete Fourier Transform

    傅立叶变换将图像分解成其正弦和余弦分量。换句话说,它将将图像从其空间域转换到其频域。其思想是任何函数都可以用无穷窦和余弦函数之和精确地近似。

    变换的结果是复数。通过真实图像和复数图像或通过幅度和相位图像来显示这一点是可能的。然而,在整个图像处理算法中,只有幅度图像是有趣的,因为这包含了关于图像几何结构所需的所有信息。然而,如果你打算对这些表单中的图像做一些修改,然后你需要重新变换它,你就需要保存这两个。

    在这个示例中,我将展示如何计算和显示傅立叶变换的幅度图像。在数字图像的情况下是离散的。这意味着它们可以从给定的域值中获取一个值。例如,在基本灰度值中,图像值通常介于0和255之间。因此,傅立叶变换也需要是离散的类型,导致离散傅立叶变换(DFT)。每当你需要从几何的角度来确定图像的结构时,你就需要使用它。这里是要遵循的步骤(在灰度输入图像I的情况下):

    1.Expand the image to an optimal size将图像扩展到最佳大小。

    DFT的性能取决于图像的大小。它往往是最快的图像大小是倍数的二,三和五。因此,为了达到最大的性能,通常将一个边界值映射到图像以获得具有这些特征的大小是一个好主意。CV::GoDestaldDftsie()返回这个最佳大小,我们可以使用CV::CopyMaBurdReand()函数来扩展图像的边框:

    Mat padded;                            //expand input image to optimal size
    int m = getOptimalDFTSize( I.rows );
    int n = getOptimalDFTSize( I.cols ); // on the border add zero pixels
    copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
    

    2.Make place for both the complex and the real values为复杂的和真实的价值创造位置。

    傅立叶变换的结果是复杂的。这意味着对于每个图像值,结果是两个图像值(每个分量一个)。此外,频域范围远大于其空间对应部分。因此,我们通常至少以浮点格式存储这些文件。因此,我们将将输入图像转换为这种类型,并将其扩展到另一个通道以保存复数值:

    Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
    Mat complexI;
    merge(planes, 2, complexI);         // Add to the expanded another plane with zeros
    

    3.Make the Discrete Fourier Transform进行离散傅立叶变换。

    有可能进行就地计算(与输出相同的输入):

    
    

    4.Transform the real and complex values to magnitude将实数和复值变换为幅值。

    split(complexI, planes);                   // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
    magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
    Mat magI = planes[0];
    

    5.Switch to a logarithmic scale切换到对数刻度。

    结果表明,傅立叶系数的动态范围太大,不能在屏幕上显示。我们有一些小的和一些高度变化的值,我们不能像这样观察到。因此,高值将全部变为白色点,而小值则变为黑色。为了使用灰度值来可视化,我们可以将我们的线性尺度转换成对数尺度

    翻译成OpenCV代码:

    magI += Scalar::all(1);                    // switch to logarithmic scale
    log(magI, magI);
    

    6.Crop and rearrange

    magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
    int cx = magI.cols/2;
    int cy = magI.rows/2;
    Mat q0(magI, Rect(0, 0, cx, cy));   // Top-Left - Create a ROI per quadrant
    Mat q1(magI, Rect(cx, 0, cx, cy));  // Top-Right
    Mat q2(magI, Rect(0, cy, cx, cy));  // Bottom-Left
    Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
    Mat tmp;                           // swap quadrants (Top-Left with Bottom-Right)
    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);
    q1.copyTo(tmp);                    // swap quadrant (Top-Right with Bottom-Left)
    q2.copyTo(q1);
    tmp.copyTo(q2);
    
    

    请记住,在第一步,我们扩大了图像?是时候抛弃新引进的价值观了。为了可视化目的,我们也可以重新排列结果的象限,以便原点(0,0)对应于图像中心。

    7.Normalize.归一化。

    这是为了可视化目的再次进行的。我们现在有了大小,但是这仍然是我们的图像显示范围为0至1。我们使用CV::标准化()函数将我们的值标准化到这个范围

    normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
                                              // viewable image form (float between values 0 and 1).
    

    11、文件的读入与写入XML

        cv::FileStorage(const string& source, int flags, const string& encoding=string());  
    

    参数:

    **source –**存储或读取数据的文件名(字符串),其扩展名(.xml 或 .yml/.yaml)决定文件格式。

    flags – 操作模式,包括:

    • FileStorage::READ 打开文件进行读操作
    • FileStorage::WRITE 打开文件进行写操作
    • FileStorage::APPEND打开文件进行附加操作
    • FileStorage::MEMORY 从source读数据,或向内部缓存写入数据(由FileStorage::release返回)

    encoding – 文件编码方式。目前不支持UTF-16 XML 编码,应使用 8-bit 编码

    演示写入数值、矩阵、多个变量、当前时间和关闭文件:

        // 1.create our writter  
            cv::FileStorage fs("test.yml", FileStorage::WRITE);  
              
            // 2.Save an int  
            int imageWidth= 5;  
            int imageHeight= 10;  
            fs << "imageWidth" << imageWidth;  
            fs << "imageHeight" << imageHeight;  
          
            // 3.Write a Mat  
            cv::Mat m1= Mat::eye(3,3, CV_8U);  
            cv::Mat m2= Mat::ones(3,3, CV_8U);  
            cv::Mat resultMat= (m1+1).mul(m1+2);  
            fs << "resultMat" << resultMat;  
          
            // 4.Write multi-variables   
            cv::Mat cameraMatrix = (Mat_<double>(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);  
            cv::Mat distCoeffs = (Mat_<double>(5,1) << 0.1, 0.01, -0.001, 0, 0);  
            fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;  
          
            // 5.Save local time  
            time_t rawtime; time(&rawtime); //#include <time.h>  
            fs << "calibrationDate" << asctime(localtime(&rawtime));  
          
            // 6.close the file opened  
            fs.release();  
    
    展开全文
  • “人脸识别”是一个在计算机视觉和生物特征识别领域十分活跃的话题。这个主题已经被给力地研究了25年,并且最终在安全、机器人学、人机交互、数码摄像机、游戏和娱乐领域得到了广泛应用。 ...

    http://www.educity.cn/wenda/358439.html

    “人脸识别”是一个在计算机视觉和生物特征识别领域十分活跃的话题。这个主题已经被给力地研究了25年,并且最终在安全、机器人学、人机交互、数码摄像机、游戏和娱乐领域得到了广泛应用。

      “人脸识别”大致可分为两个阶段:

      1.人脸检测 搜索一幅图像,寻找一切人脸区域(此处以绿色矩形显示),然后进行图像处理,清理脸部图像以便于更好地识别。

      2.人脸识别 把上一阶段检测处理得到的人脸图像与数据174x197库中的已知人脸进行比对,判定人脸对应的人是谁(此处以红色文本显示)。

      2002年后,人脸检测已经可以相当可靠地运作。比如OpenCV的Face Detector,对于一个人直视摄像头得到的较清晰图片,大约有90-95%的准确度。通常来说,当人以侧面对准摄像头或与摄像头成一定角度时,较难检测到人脸,有时需要3D Head Pose Estimation。假如图片亮度不是很好,也较难检测到人脸。脸部的部分区域比另一部分区域明亮,带有阴影,模糊,或者戴眼镜,也会影响检测效果。

      然而,人脸识别却比人脸检测不可靠得多,一般只有30-70%的准确度。20世纪90年代以来,人脸识别一直是一个很重要的研究领域,但仍然十分不可靠,并且每一年都有更多的识别技术被创造,如文章底部所列出的(Alternatives to Eigenfaces such as 3D face recognition or recognition from video.)

      我将向你展示如何使用“特征脸”(Eigenfaces),也称为主元分析法(Principal Component Analysis or PCA)。相对于普通的神经网络方法(Neural Networks)和Fisher Faces方法来说,这是一个简单和流行的对图片进行的二维人脸识别的方法。

      要学习特征脸方法的理论,你需要阅读Face Recognition With Eigenface from Servo Magazine (April 2007),可能还需要一些数学算法。

      首先我将向你解释,怎样实现特征脸的命令行离线训练(offline training from the command-line),基于Servo Magazine tutorial and source-code (May 2007)。

      之后,我将说明如何将此扩展成为从网络摄像头进行实时的在线训练:-)

      使用OpenCV的Face Detector检测人脸

      如上所述,人脸识别的第一个阶段是人脸检测。OpenCV库使得使用它的Haar Cascade Face Detector(也称为Viola-Jones方法)检测正面人脸变得相当简单。

      OpenCV里的“cvHaarDetectObjects”函数执行人脸检测,但是这个函数直接用没有意义,所以最好用这个包装好的函数:

      // Perform face detection on the input image, using the given Haar Cascade. // Returns a rectangle for the detected region in the given image. CvRect detectFaceInImage(IplImage *inputImg, CvHaarClassifierCascade* cascade) { // Smallest face size. CvSize minFeatureSize = cvSize(20, 20); // Only search for 1 face. int flags = CV_HAAR_FIND_BIGGEST_OBJECT | CV_HAAR_DO_ROUGH_SEARCH; // How detailed should the search be. float search_scale_factor = 1.1f; IplImage *detectImg; IplImage *greyImg = 0; CvMemStorage* storage; CvRect rc; double t; CvSeq* rects; CvSize size; int i, ms, nFaces; storage = cvCreateMemStorage(0); cvClearMemStorage( storage ); // If the image is color, use a greyscale copy of the image. detectImg = (IplImage*)inputImg; if (inputImg->nChannels > 1) { size = cvSize(inputImg->width, inputImg->height); greyImg = cvCreateImage(size, IPL_DEPTH_8U, 1 ); cvCvtColor( inputImg, greyImg, CV_BGR2GRAY ); detectImg = greyImg; // Use the greyscale image. } // Detect all the faces in the greyscale image. t = (double)cvGetTickCount(); rects = cvHaarDetectObjects( detectImg, cascade, storage, search_scale_factor, 3, flags, minFeatureSize); t = (double)cvGetTickCount() - t; ms = cvRound( t / ((double)cvGetTickFrequency() * 1000.0) ); nFaces = rects->total; printf("Face Detection took %d ms and found %d objectsn", ms, nFaces); // Get the first detected face (the biggest). if (nFaces > 0) rc = *(CvRect*)cvGetSeqElem( rects, 0 ); else rc = cvRect(-1,-1,-1,-1); // Couldn't find the face. if (greyImg) cvReleaseImage( &greyImg ); cvReleaseMemStorage( &storage ); //cvReleaseHaarClassifierCascade( &cascade ); return rc; // Return the biggest face found, or (-1,-1,-1,-1). } // Perform face detection on the input image, using the given Haar Cascade. // Returns a rectangle for the detected region in the given image. CvRect detectFaceInImage(IplImage *inputImg, CvHaarClassifierCascade* cascade) { // Smallest face size. CvSize minFeatureSize = cvSize(20, 20); // Only search for 1 face. int flags = CV_HAAR_FIND_BIGGEST_OBJECT | CV_HAAR_DO_ROUGH_SEARCH; // How detailed should the search be. float search_scale_factor = 1.1f; IplImage *detectImg; IplImage *greyImg = 0; CvMemStorage* storage; CvRect rc; double t; CvSeq* rects; CvSize size; int i, ms, nFaces; storage = cvCreateMemStorage(0); cvClearMemStorage( storage ); // If the image is color, use a greyscale copy of the image. detectImg = (IplImage*)inputImg; if (inputImg->nChannels > 1) { size = cvSize(inputImg->width, inputImg->height); greyImg = cvCreateImage(size, IPL_DEPTH_8U, 1 ); cvCvtColor( inputImg, greyImg, CV_BGR2GRAY ); detectImg = greyImg; // Use the greyscale image. } // Detect all the faces in the greyscale image. t = (double)cvGetTickCount(); rects = cvHaarDetectObjects( detectImg, cascade, storage, search_scale_factor, 3, flags, minFeatureSize) t = (double)cvGetTickCount() - t; ms = cvRound( t / ((double)cvGetTickFrequency() * 1000.0) ); nFaces = rects->total; printf("Face Detection took %d ms and found %d objectsn", ms, nFaces); // Get the first detected face (the biggest). if (nFaces > 0) rc = *(CvRect*)cvGetSeqElem( rects, 0 ); else rc = cvRect(-1,-1,-1,-1); // Couldn't find the face. if (greyImg) cvReleaseImage( &greyImg ); cvReleaseMemStorage( &storage ); //cvReleaseHaarClassifierCascade( &cascade ); return rc; // Return the biggest face found, or (-1,-1,-1,-1). }



      现在如果你想要在一张图片里寻找人脸,只需要简单地调用“detectFaceInImage”函数。你也需要指定OpenCV使用的人脸分类器(Face Classifier)。比如,OpenCV自带了一些用于正面脸的分类器,也有一些用于侧面脸的,还有眼睛检测,鼻检测,嘴检测,全身检测等等。你实际上可以任意把其它的分类检测器用于此函数,甚至创造你自己定制的分类检测器,比如车或人的检测(阅读此处),但既然正脸检测是唯一十分可靠的,这将是我们唯一要讨论的。

      对于正面人脸检测,你应该选取这些OpenCV自带的haar级联分类器(Haar Cascade Classifiers,in the “datahaarcascades” folder)。

    • “haarcascade_frontalface_default.xml”
    • “haarcascade_frontalface_alt.xml”
    • “haarcascade_frontalface_alt2.xml”
    • “haarcascade_frontalface_alt_tree.xml”

      每个haar级联分类器都将给出略微不同的结果,这依赖于你的环境因素,所以你甚至可以用全部分类器,把结果结合在一起(如果你想要做尽可能多地检测)。有一些更多的用于眼睛,头部,嘴巴,鼻子的分类器在Modesto’s page下载。

      你可以在你的程序里这样做来进行人脸检测:

      // Haar Cascade file, used for Face Detection. char *faceCascadeFilename = "haarcascade_frontalface_alt.xml"; // Load the HaarCascade classifier for face detection. CvHaarClassifierCascade* faceCascade; faceCascade = (CvHaarClassifierCascade*)cvLoad(faceCascadeFilename, 0, 0, 0); if( !faceCascade ) { printf("Couldnt load Face detector '%s'n", faceCascadeFilename); exit(1); } // Grab the next frame from the camera. IplImage *inputImg = cvQueryFrame(camera); // Perform face detection on the input image, using the given Haar classifier CvRect faceRect = detectFaceInImage(inputImg, faceCascade); // Make sure a valid face was detected. if (faceRect.width > 0) { printf("Detected a face at (%d,%d)!n", faceRect.x, faceRect.y); } .... Use 'faceRect' and 'inputImg' .... // Free the Face Detector resources when the program is finished cvReleaseHaarClassifierCascade( &cascade );

      对脸部图像进行预处理以便于识别

      现在你已经检测到一张人脸,你可以使用那张人脸图片进行人脸识别。然而,假如你尝试这样简单地从一张普通图片直接进行人脸识别的话,你将会至少损失10%的准确率!

      在一个人脸识别系统中,应用多种预处理技术对将要识别的图片进行标准化处理是极其重要的。多数人脸识别算法对光照条件十分敏感,所以假如在暗室训练,在明亮的房间就可能不会被识别出来等等。这个问题可归于“lumination dependent”,并且还有其它很多例子,比如脸部也应当在图片的一个十分固定的位置(比如眼睛位置为相同的像素坐标),固定的大小,旋转角度,头发和装饰,表情(笑,怒等),光照方向(向左或向上等),这就是在进行人脸识别前,使用好的图片预处理过滤器十分重要的原因。你还应该做一些其它事情,比如去除脸部周围的多余像素(如用椭圆遮罩,只显示其内部的人脸区域而不是头发或图片背景,因为他们的变化多于脸部区域)。

      为简单起见,我展示给你的人脸识别系统是使用灰度图像的特征脸方法。所以我将向你说明怎样简单地把彩色图像转化为灰度图像,并且之后简单地使用直方图均衡化(Histogram Equalization)作为一种自动的标准化脸部图像亮度和对比度的方法。为了得到更好的结果,你可以使用彩色人脸识别(color face recognition,ideally with color histogram fitting in HSV or another color space instead of RGB),或者使用更多的预处理,比如边缘增强(edge enhancement),轮廓检测(contour detection),手势检测(motion detection),等等。这份代码把图片调整成一个标准的大小,但是可能会改变脸的纵横比(aspect ratio)。你可以阅读我这里的教程HERE,来了解怎样调整图像大小而不改变它的纵横比。

      你可以看到一个预处理阶段的例子:

      343x96

      这是把一幅RGB格式的图像或灰度图像转变为灰度图像的基本代码。它还把图像调整成了固定的维度,然后应用直方图均衡化来实现固定的亮度和对比度。

      // Either convert the image to greyscale, or use the existing greyscale image. IplImage *imageGrey; if (imageSrc->nChannels == 3) { imageGrey = cvCreateImage( cvGetSize(imageSrc), IPL_DEPTH_8U, 1 ); // Convert from RGB (actually it is BGR) to Greyscale. cvCvtColor( imageSrc, imageGrey, CV_BGR2GRAY ); } else { // Just use the input image, since it is already Greyscale. imageGrey = imageSrc; } // Resize the image to be a consistent size, even if the aspect ratio changes. IplImage *imageProcessed; imageProcessed = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1); // Make the image a fixed size. // CV_INTER_CUBIC or CV_INTER_LINEAR is good for enlarging, and // CV_INTER_AREA is good for shrinking / decimation, but bad at enlarging. cvResize(imageGrey, imageProcessed, CV_INTER_LINEAR); // Give the image a standard brightness and contrast. cvEqualizeHist(imageProcessed, imageProcessed); ..... Use 'imageProcessed' for Face Recognition .... if (imageGrey) cvReleaseImage(&imageGrey); if (imageProcessed) cvReleaseImage(&imageProcessed);



      把“特征脸”用于人脸识别

      现在你已经有了一张经过预处理后的脸部图片,你可以使用特征脸(PCA)进行人脸识别。OpenCV自带了执行PCA操作的”cvEigenDecomposite()”函数,然而你需要一个图片数据库(训练集)告诉机器怎样识别当中的人。

      所以你应该收集每个人的一组预处理后的脸部图片用于识别。比如,假如你想要从10人的班级当中识别某个人,你可以为每个人存储20张图片,总共就有200张大小相同(如100×100像素)的经预处理的脸部图片。

      特征脸的理论在Servo Magazine的两篇文章(Face Recognition with Eigenface)中解释了,但我仍会在这里尝试着向你解释。

      我们使用“主元分析”把你的200张训练图片转换成一个代表这些训练图片主要区别的“特征脸”集。首先它将会通过获取每个像素的平均值,生成这些图片的“平均人脸图片”。然后特征脸将会与“平均人脸”比较。第一个特征脸是最主要的脸部区别,第二个特征脸是第二重要的脸部区别,等等……直到你有了大约50张代表大多数训练集图片的区别的特征脸。

      130x150130x150130x150

      在上面这些示例图片中你可以看到平均人脸和第一个以及最后一个特征脸。它们是从一个四人的每人30幅图片的训练集中生成的。注意到,平均人脸显示的是一个普通人的平滑脸部结构,排在最前的一些特征脸显示了一些主要的脸部特征,而最后的特征脸(比如Eigenface 119)主要是图像噪声。你可以在下面看到前32张特征脸。

      532x307

      使用主元分析法进行人脸识别

      简单地说,特征脸方法(Principal Component Analysis)计算出了训练集中图片的主要区别,并且用这些“区别”的组合来代表每幅训练图片。

      比如,一张训练图片可能是如下的组成:

      (averageFace) + (13.5% of eigenface0) – (34.3% of eigenface1) + (4.7% of eigenface2) + … + (0.0% of eigenface199).

      一旦计算出来,就可以认为这张训练图片是这200个比率(ratio):

      {13.5, -34.3, 4.7, …, 0.0}.

      用特征脸图片分别乘以这些比率,并加上平均人脸图片 (average face),从这200个比率还原这张训练图片是完全可以做到的。但是既然很多排在后面的特征脸是图像噪声或者不会对图片有太大作用,这个比率表可以被降低到只剩下最主要的,比如前30个,不会对图像质量有很大影响。所以现在可以用30个特征脸,平均人脸图片,和一个含有30个比率的表,来代表全部的200张训练图片。

      有趣的是,这意味着,我们已经找到了一种方法把200张图片压缩成31张图片再加上一点点数据,而不丢失很多的图像质量。但是这个教程是关于人脸识别的,而不是图像压缩的,所以我们将会忽略它:-)

      在另一幅图片中识别一个人,可以应用相同的PCA计算,使用相同的200个特征脸来寻找200个代表输入图片的比率。并且仍然可以只保留前30个比率而忽略其余的比率,因为它们是次要的。然后通过搜索这些比率的表,寻找在数据库中已知的20个人,来看谁的前30个比率与输入图片的前30个比率最接近。这就是寻找与输入图片最相似的训练图片的基本方法,总共提供了200张训练图片。

      离线命令行训练的实现

      为了实现离线训练,也就是通过命令行(command-line)使用文件作为输入输出,我使用了与Servo Magazine里Face Recognition with Eigenface相同的实现,所以你可以先阅读这篇文章,但是我做了一些小的改动。

      基本上,从训练图片创建一个人脸识别数据库,就是创建一个列出图片文件和每个文件代表的人的文本文件。

      比如,你可以把这些输入一个名为”4_images_of_2_people.txt”的文本文件:

      1 Shervin dataShervinShervin1.bmp 1 Shervin dataShervinShervin2.bmp 1 Shervin dataShervinShervin3.bmp 1 Shervin dataShervinShervin4.bmp 2 Chandan dataChandanChandan1.bmp 2 Chandan dataChandanChandan2.bmp 2 Chandan dataChandanChandan3.bmp 2 Chandan dataChandanChandan4.bmp

      它告诉这个程序,第一个人的名字叫“Shervin”,而Shervin的四张预处理后的脸部图像在”dataShervin”文件夹,第二个人的名字叫”Chandan”,在”dataChandan”中有她的四张图片。这个程序可以使用”loadFaceImgArray()”函数把这些图片加载到一个图片数组中。注意,为了简单起见,它不允许空格或特殊字符出现在人名中,< >所以你可能想要实现这一功能,或者把人名中的空格用下划线代替(比如 Shervin_Emami)。

      为了从这些加载好的图片中创建一个数据库,你可以使用OpenCV的”cvCalcEigenObjects()”和”cvEigenDecomposite()”函数,比如:

      // Tell PCA to quit when it has enough eigenfaces. CvTermCriteria calcLimit = cvTermCriteria( CV_TERMCRIT_ITER, nEigens, 1); // Compute average image, eigenvectors (eigenfaces) and eigenvalues (ratios). cvCalcEigenObjects(nTrainFaces, (void*)faceImgArr, (void*)eigenVectArr, CV_EIGOBJ_NO_CALLBACK, 0, 0, &calcLimit, pAvgTrainImg, eigenValMat->data.fl); // Normalize the matrix of eigenvalues. cvNormalize(eigenValMat, eigenValMat, 1, 0, CV_L1, 0); // Project each training image onto the PCA subspace. CvMat projectedTrainFaceMat = cvCreateMat( nTrainFaces, nEigens, CV_32FC1 ); int offset = projectedTrainFaceMat->step / sizeof(float); for(int i=0; i<nTrainFaces; i++) { cvEigenDecomposite(faceImgArr[i], nEigens, eigenVectArr, 0, 0, pAvgTrainImg, projectedTrainFaceMat->data.fl + i*offset); }



      现在你有了:

    • 平均人脸图片”pAvgTrainImg”,
    • 包含特征脸图片的数组”eigenVectArr[]“(如:假如你使用了nEigens=200 张训练图片,将得到200 个特征脸),
    • 特征值矩阵 (即特征脸的比率,eigenface ratios) 每张图片的”projectedTrainFaceMat” 。

      现在这些可以被储存成一个文件,也就是人脸识别的数据库。代码中的”storeTrainingData()”函数将会把这些数据储存到”facedata.xml“文件里,它可以随时被重新载入来识别经训练过的人。代码中也有一个”storeEigenfaceImages()”的函数,生成前面提到的图片,平均人脸图片被保存到”out_averageImage.bmp”,特征脸被保存到”out_eigenfaces.bmp”。

      离线命令行识别的实现

      在离线训练阶段,系统尝试从一个文本文件中的列表读取若干张图像中的人脸,并进行识别。为了实现它,我仍然使用Servo Magazine的Face Recognition with Eigenface的实现,在此基础上扩展。

      用于离线训练的相同格式的文本文件也可用于离线识别。这个文本文件列出了用于测试的图像文件和对应于这张图像的正确的人名。随后这个程序就对每一幅图片进行识别,并且检验文本文件中的真实值(图片对应的正确人名)来确认其是否识别正确,并统计它的准确率。

      离线识别的实现几乎与离线训练完全相同:

      1. 读取原来的用于训练的文本文件(现在用于识别),把若干个图片文件(预处理后的脸部图片)和名字载入一个图片数组。这些在代码中用“loadFaceImgArray()”函数执行。

      2. 平均人脸,特征脸和特征值(比率)使用函数“loadTrainingData()” 从人脸识别数据库文件(the face recognition database fil)“facedata.xml”载入。

      3. 使用OpenCV的函数“cvEigenDecomposite()”,每张输入的图片都被投影到PCA子空间,来观察哪些特征脸的比率最适合于代表这张图片。

      4. 现在有了特征值(特征脸图片的比率)代表这张输入图片,程序需要查找原始的训练图片,找出拥有最相似比率的图片。这些用数学的方法在“findNearestNeighbor()”函数中执行,采用的是“欧几里得距离(Euclidean Distance)”,但是它只是基本地检查输入图片与每张训练图片的相似性,找到最相似的一张:一张在欧几里得空间上与输入图片距离最近的图片。就像在 Servo Magazine的文章上提到的那样,如果使用马氏距离( the Mahalanobis space,需要在代码里定义 USE_MAHALANOBIS_DISTANCE),你可以得到更准确的结果。

      5. 在输入图片与最相似图片之间的距离用于确定可信度(confidence),作为是否识别出某人的指导。1.0的可信度意味着完全相同,0.0或者负的可信度意味着非常不相似。但是需要注意,我在代码中用到的可信度公式只是一个非常基本的可信度测量,不是很可靠,但是我觉得多数人会想要看到一个粗略的可信度值。你可能发现它对你的图片给出错误的值,所以你可以禁用它(比如:把可信度设为恒定的1.0)。

      一旦指导哪张训练图片和输入图片最相似,并假定可信度值不是太低(应该至少是0.6或更高),那么它就指出了那个人是谁,换句话说,它识别出了那个人!

      摄像头实时识别的实现

      要让一个摄像头视频流输入取代文件列表是十分简单的。基本上,你只要从摄像头抓取一帧,而不是读取一个文件,并且一直运行下去直到用户退出,而不是等待文件读取到头就行了。OpenCV为此提供了“cvCreateCameraCapture()”函数(或cvCaptureFromCAM())。

      从摄像头抓取一帧可以简单地用下面的函数实现:

      // Grab the next camera frame. Waits until the next frame is ready, and // provides direct access to it, so do NOT modify or free the returned image! // Will automatically initialize the camera on the first frame. IplImage* getCameraFrame(CvCapture* &camera) { IplImage *frame; int w, h; // If the camera hasn't been initialized, then open it. if (!camera) { printf("Acessing the camera ...\n"); camera = cvCreateCameraCapture( 0 ); if (!camera) { printf("Couldn't access the camera.\n"); exit(1); } // Try to set the camera resolution to 320 x 240. cvSetCaptureProperty(camera, CV_CAP_PROP_FRAME_WIDTH, 320); cvSetCaptureProperty(camera, CV_CAP_PROP_FRAME_HEIGHT, 240); // Get the first frame, to make sure the camera is initialized. frame = cvQueryFrame( camera ); if (frame) { w = frame->width; h = frame->height; printf("Got the camera at %dx%d resolution.\n", w, h); } // Wait a little, so that the camera can auto-adjust its brightness. Sleep(1000); // (in milliseconds) } // Wait until the next camera frame is ready, then grab it. frame = cvQueryFrame( camera ); if (!frame) { printf("Couldn't grab a camera frame.\n"); exit(1); } return frame; }



      这个函数可以这样用:

      CvCapture* camera = 0; // The camera device. while ( cvWaitKey(10) != 27 ) { // Quit on "Escape" key. IplImage *frame = getCameraFrame(camera); ... } // Free the camera. cvReleaseCapture( &camera );



      请注意,假如你是为windows操作系统开发,你可以使用 Theo Watson 的 videoInput Library v0.1995 达到两倍于这些代码的速度。它使用了DirectShow硬件加速,然而OpenCV使用VFW已经15年不变了!
    把我已经解释的这些部分放到一起,人脸识别系统运行步骤如下:

      1. 从摄像头抓取一帧图片。

      2. 转换彩色图片帧为灰度图片帧。

      3. 检测灰度图片帧的人脸。

      4. 处理图片以显示人脸区域(使用 cvSetImageROI() 和 cvCopyImage())。

      5. 预处理脸部图片。

      6. 识别图片中的人。

      摄像头实时训练的实现

      现在你已经有了一个用摄像头实时识别人脸的方法,但是要学习新人脸,你不得不关闭这个程序,把摄像头的图片保存成图片文件,更新图片列表,使用离线命令行训练的方法,然后以实时摄像头识别的模式再次运行这个程序。所以实际上,你完全可以用程序来执行实时的摄像头训练!

      这里就是用摄像头视频流把一个新的人加入人脸识别数据库而不关闭程序的一个最简单的方法:

      1. 从摄像头收集一些图片(预处理后的脸部图片),也可以同时执行人脸识别。

      2. 用“cvSaveImage()”函数保存这些脸部图片作为图片文件存入磁盘。

      3. 加入每张脸部图片的文件名到训练图片列表(用于离线命令行训练的文本文件)的底部。

      4. 一旦你准备实时训练,你将从所有图片文件形成的数据库重新训练。这个文本文件列出了新加入的训练图片文件,并且这些图片被电脑存为了图片文件,所以实时训练工作起来跟离线训练一样。

      5. 但是在重新训练之前,释放任何正在使用的资源和重新初始化也很必要。应该像你重新启动了这个程序一样。比如,在图片被存储成文件并且加入训练列表的文本文件后,你应该再执行相同的离线训练(包括从训练列表文件载入图片,用PCA方法找出新训练集的特征脸和比率)之前释放特征脸数组。 这个实时训练的方法相当低效,因为假如在训练集中有50个人,而你多加了一个人,它将为51个人重新训练,这是非常不好的,因为训练的时间随着用户或图片数量的增加呈指数级增长。但是假如你只是处理百来张图片,它不需要多少秒就可以完成。

      文件下载请转到原文。

      The article source is 

    展开全文
  • 前一段时间写了一个人脸相关的算法,包括视频...使用的是VS2013和opencv。 首先创建头文件common.h #ifndef _COMMON_H #define _COMMON_H #include #include #include #include #include #include #include #
  • 【C++】基于 OpenCV 的人脸识别

    千次阅读 2018-06-01 16:18:53
    原文网址:...根据这个项目的关于页面,OpenCV 已被广泛运用在各种项目上,从谷歌街景的图片拼接,到交互艺术展览的技术实现中,都有 OpenCV 的身影。OpenCV 起始于 1999 年 Int...
  • Opencv+C++之人脸识别

    千次阅读 2016-07-22 10:06:52
    最近一直在忙课程,老师让我看看他的论文也没放在心上。总算闲下来,看了他在人脸识别方面的相关论文,拿出一篇放在博客上跟大家共同分析下。在看以下内容前,首先要阅读下徐勇老师的这篇论文 ...
  • opencv是什么

    万次阅读 多人点赞 2011-05-02 15:27:00
    OpenCV是一个用于图像处理、分析、机器视觉方面的开源函数库. 无论你是做科学研究,还是商业应用,opencv都可以作为你理想的工具库,因为,对于这两者,它完全是免费的。该库采用C及C++语言编写,可以在windows, ...
  • 我们还将用C ++和Python共享OpenCV代码。 物体检测与物体识别 对象识别算法识别图像中存在哪些对象。它将整个图像作为输入,并输出该图像中存在的对象的类标签和类概率。例如,类标签可以是“狗”,相关的类概率...
  • 史上最全的OpenCV入门教程

    万次阅读 多人点赞 2018-06-21 16:06:36
    一、Python OpenCV 入门欢迎阅读系列教程,内容涵盖 OpenCV,它是一个图像和视频处理库,包含 C ++,C,Python 和 Java 的绑定。 OpenCV 用于各种图像和视频分析,如面部识别和检测,车牌阅读,照片编辑,高级机器人...
  • 什么是opencv

    千次阅读 2016-07-25 16:46:40
    What is OpenCV? OpenCV 是一个开源C++ 库,在图像处理和计算机视觉领域发挥了巨大作用,最初是由Intel 公司开发,现在由Willow Garage公司维护。 opencv在商业和非商业使用中都是开源免费的,但并不要求...
  • python opencv 读取视频文件并显示

    万次阅读 2018-03-13 20:00:37
    06-python opencv 读取视频文件并显示 06-python opencv 读取视频文件并显示 概述 实现过程 引用 创建摄像头对象 逐帧显示实现视频播放 释放摄像头对象和窗口 源代码 运行结果 参考 概述 本节实现...
  • python opencv 将两张图片融合

    万次阅读 2018-03-13 19:57:07
    12-python opencv 将两张图片融合 12-python opencv 将两张图片融合 概述 实现过程 引用与读取图片 融合图片 显示图片 源代码 运行结果 参考 概述 本节实现的是使用OpenCV里自带的函数,将两幅图片...
  • python opencv 使用摄像头捕获视频并显示

    万次阅读 多人点赞 2018-03-13 20:01:06
    06-python opencv 使用摄像头捕获视频并显示 06-python opencv 使用摄像头捕获视频并显示 概述 实现过程 引用 创建摄像头对象 逐帧显示实现视频播放 释放摄像头对象和窗口 源代码 运行结果 参考 ...
  • Python OpenCV 读取并显示图像

    万次阅读 2018-03-13 20:03:11
    01-Python OpenCV 读取并显示图像 01-Python OpenCV 读取并显示图像 概述 实现过程 引用 打开图片并显示 创建一个空图 将彩色图像转成灰度图像 等待键盘输入并关闭所有窗口 源代码 运行结果 参考 ...
  • python opencv 使用 Matplotlib显示图像

    千次阅读 2018-03-13 20:01:37
    05-python opencv 使用 Matplotlib显示图像 05-python opencv 使用 Matplotlib显示图像 概述 实现过程 引用 测试灰度图像 测试彩色图像 等待键盘输入并关闭所有窗口 源代码 运行结果 灰度图像 彩色图像 ...
  • 掌握Spring Boot的使用和常见文件配置的作用 对于Spring Boot基本原理和源码有一定的认知
  • python opencv 读取视频文件并保存

    万次阅读 2018-03-13 20:00:00
    06-python opencv 读取视频文件并保存 06-python opencv 读取视频文件并保存 概述 实现过程 引用 创建摄像头对象 定义写入视频格式和写入对象 逐帧显示视频并写入 释放摄像头对象和窗口 源代码 运行结果 参考...
1 2 3 4 5 ... 20
收藏数 3,962
精华内容 1,584
关键字:

opencv