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嵌入式系统中,这种移植在大学中经常作为相关专业本科生毕业设计或者研究生课题的选题。
收起全文
精华内容
参与话题
问答
  •   文章链接: 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入门教程

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

    一、Python OpenCV 入门

    欢迎阅读系列教程,内容涵盖 OpenCV,它是一个图像和视频处理库,包含 C ++,C,Python 和 Java 的绑定。 OpenCV 用于各种图像和视频分析,如面部识别和检测,车牌阅读,照片编辑,高级机器人视觉,光学字符识别等等。

    你将需要两个主要的库,第三个可选:python-OpenCV,Numpy 和 Matplotlib。

    Windows 用户:

    python-OpenCV:有其他的方法,但这是最简单的。 下载相应的 wheel(.whl)文件,然后使用pip进行安装。 观看视频来寻求帮助。

    pip install numpypip install matplotlib

    不熟悉使用pip? 请参阅pip安装教程来获得帮助。

    Linux/Mac 用户

    pip3 install numpy

    或者

    apt-get install python3-numpy

    你可能需要apt-get来安装python3-pip。

    pip3 install matplotlib

    或者

    apt-get install python3-matplotlibapt-get install python-OpenCV

    Matplotlib 是用于展示来自视频或图像的帧的可选选项。 我们将在这里展示几个使用它的例子。 Numpy 被用于“数值和 Python”的所有东西。 我们主要利用 Numpy 的数组功能。 最后,我们使用python-OpenCV,它是 Python 特定的 OpenCV 绑定。

    OpenCV 有一些操作,如果没有完整安装 OpenCV (大小约 3GB),你将无法完成,但是实际上你可以用 python-OpenCV 最简安装。 我们将在本系列的后续部分中使用 OpenCV 的完整安装,如果你愿意的话,你可以随意获得它,但这三个模块将使我们忙碌一段时间!

    通过运行 Python 并执行下列命令来确保你安装成功:

    import cv2import matplotlibimport numpy

    如果你没有错误,那么你已经准备好了。好了嘛?让我们下潜吧!

    首先,在图像和视频分析方面,我们应该了解一些基本的假设和范式。对现在每个摄像机的记录方式来说,记录实际上是一帧一帧地显示,每秒 30-60 次。但是,它们的核心是静态帧,就像图像一样。因此,图像识别和视频分析大部分使用相同的方法。有些东西,如方向跟踪,将需要连续的图像(帧),但像面部检测或物体识别等东西,在图像和视频中代码几乎完全相同。

    接下来,大量的图像和视频分析归结为尽可能简化来源。这几乎总是起始于转换为灰度,但也可以是彩色滤镜,渐变或这些的组合。从这里,我们可以对来源执行各种分析和转化。一般来说,这里发生的事情是转换完成,然后是分析,然后是任何覆盖,我们希望应用在原始来源上,这就是你可以经常看到,对象或面部识别的“成品”在全色图像或视频上显示。然而,数据实际上很少以这种原始形式处理。有一些我们可以在基本层面上做些什么的例子。所有这些都使用基本的网络摄像头来完成,没有什么特别的:

    背景提取

    颜色过滤

    边缘检测

    用于对象识别的特征匹配

    一般对象识别

    在边缘检测的情况下,黑色对应于(0,0,0)的像素值,而白色线条是(255,255,255)。视频中的每个图片和帧都会像这样分解为像素,并且像边缘检测一样,我们可以推断,边缘是基于白色与黑色像素对比的地方。然后,如果我们想看到标记边缘的原始图像,我们记录下白色像素的所有坐标位置,然后在原始图像或视频上标记这些位置。

    到本教程结束时,你将能够完成上述所有操作,并且能够训练你的机器识别你想要的任何对象。就像我刚开始说的,第一步通常是转换为灰度。在此之前,我们需要加载图像。因此,我们来做吧!在整个教程中,我极力鼓励你使用你自己的数据来玩。如果你有摄像头,一定要使用它,否则找到你认为很有趣的图像。如果你有麻烦,这是一个手表的图像:

    import cv2import numpy as npfrom matplotlib import pyplot as pltimg = cv2.imread('watch.jpg',cv2.IMREAD_GRAYSCALE)cv2.imshow('image',img)cv2.waitKey(0)cv2.destroyAllWindows()

    首先,我们正在导入一些东西,我已经安装了这三个模块。接下来,我们将img定义为cv2.read(image file, parms)。默认值是IMREAD_COLOR,这是没有任何 alpha 通道的颜色。如果你不熟悉,alpha 是不透明度(与透明度相反)。如果你需要保留 Alpha 通道,也可以使用IMREAD_UNCHANGED。很多时候,你会读取颜色版本,然后将其转换为灰度。如果你没有网络摄像机,这将是你在本教程中使用的主要方法,即加载图像。

    你可以不使用IMREAD_COLOR ...等,而是使用简单的数字。你应该熟悉这两种选择,以便了解某个人在做什么。对于第二个参数,可以使用-1,0或1。颜色为1,灰度为0,不变为-1。因此,对于灰度,可以执行cv2.imread('watch.jpg', 0)。

    一旦加载完成,我们使用cv2.imshow(title,image)来显示图像。从这里,我们使用cv2.waitKey(0)来等待,直到有任何按键被按下。一旦完成,我们使用cv2.destroyAllWindows()来关闭所有的东西。

    正如前面提到的,你也可以用 Matplotlib 显示图像,下面是一些如何实现的代码:

    import cv2import numpy as npfrom matplotlib import pyplot as pltimg = cv2.imread('watch.jpg',cv2.IMREAD_GRAYSCALE)plt.imshow(img, cmap = 'gray', interpolation = 'bicubic')plt.xticks([]), plt.yticks([]) # to hide tick values on X and Y axisplt.plot([200,300,400],[100,200,300],'c', linewidth=5)plt.show()

    请注意,你可以绘制线条,就像任何其他 Matplotlib 图表一样,使用像素位置作为坐标的。 不过,如果你想绘制你的图片,Matplotlib 不是必需的。 OpenCV 为此提供了很好的方法。 当你完成修改后,你可以保存,如下所示:

    cv2.imwrite('watchgray.png',img)

    将图片导入 OpenCV 似乎很容易,加载视频源如何? 在下一个教程中,我们将展示如何加载摄像头或视频源。

    二、加载视频源

    在这个 Python OpenCV 教程中,我们将介绍一些使用视频和摄像头的基本操作。 除了起始行,处理来自视频的帧与处理图像是一样的。 我们来举例说明一下:

    import numpy as npimport cv2cap = cv2.VideoCapture(0) while(True): ret, frame = cap.read() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) cv2.imshow('frame',gray) if cv2.waitKey(1) & 0xFF == ord('q'): breakcap.release()cv2.destroyAllWindows()

    首先,我们导入numpy和cv2,没有什么特别的。 接下来,我们可以cap = cv2.VideoCapture(0)。 这将从你计算机上的第一个网络摄像头返回视频。 如果你正在观看视频教程,你将看到我正在使用1,因为我的第一个摄像头正在录制我,第二个摄像头用于实际的教程源。

    while(True): ret, frame = cap.read()

    这段代码启动了一个无限循环(稍后将被break语句打破),其中ret和frame被定义为cap.read()。 基本上,ret是一个代表是否有返回的布尔值,frame是每个返回的帧。 如果没有帧,你不会得到错误,你会得到None。

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    在这里,我们定义一个新的变量gray,作为转换为灰度的帧。 注意这个BGR2GRAY。 需要注意的是,OpenCV 将颜色读取为 BGR(蓝绿色红色),但大多数计算机应用程序读取为 RGB(红绿蓝)。 记住这一点。

    cv2.imshow('frame',gray)

    请注意,尽管是视频流,我们仍然使用imshow。 在这里,我们展示了转换为灰色的源。 如果你想同时显示,你可以对原始帧和灰度执行imshow,将出现两个窗口。

    if cv2.waitKey(1) & 0xFF == ord('q'): break

    这个语句每帧只运行一次。 基本上,如果我们得到一个按键,那个键是q,我们将退出while循环,然后运行:

    cap.release()cv2.destroyAllWindows()

    这将释放网络摄像头,然后关闭所有的imshow()窗口。

    在某些情况下,你可能实际上需要录制,并将录制内容保存到新文件中。 以下是在 Windows 上执行此操作的示例:

    import numpy as npimport cv2cap = cv2.VideoCapture(1)fourcc = cv2.VideoWriter_fourcc(*'XVID')out = cv2.VideoWriter('output.avi',fourcc, 20.0, (640,480))while(True): ret, frame = cap.read() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) out.write(frame) cv2.imshow('frame',gray) if cv2.waitKey(1) & 0xFF == ord('q'): breakcap.release()out.release()cv2.destroyAllWindows()

    这里主要要注意的是正在使用的编解码器,以及在while循环之前定义的输出信息。 然后,在while循环中,我们使用out.write()来输出帧。 最后,在while循环之外,在我们释放摄像头之后,我们也释放out。

    太好了,现在我们知道如何操作图像和视频。 如果你没有网络摄像头,你可以使用图像甚至视频来跟随教程的其余部分。 如果你希望使用视频而不是网络摄像头作为源,则可以为视频指定文件路径,而不是摄像头号码。

    现在我们可以使用来源了,让我们来展示如何绘制东西。 此前你已经看到,你可以使用 Matplotlib 在图片顶部绘制,但是 Matplotlib 并不真正用于此目的,特别是不能用于视频源。 幸运的是,OpenCV 提供了一些很棒的工具,来帮助我们实时绘制和标记我们的源,这就是我们将在下一个教程中讨论的内容。

    三、在图像上绘制和写字

    在这个 Python OpenCV 教程中,我们将介绍如何在图像和视频上绘制各种形状。 想要以某种方式标记检测到的对象是相当普遍的,所以我们人类可以很容易地看到我们的程序是否按照我们的希望工作。 一个例子就是之前显示的图像之一:

    对于这个临时的例子,我将使用下面的图片:

    鼓励你使用自己的图片。 像往常一样,我们的起始代码可以是这样的:

    import numpy as npimport cv2img = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)

    下面,我们可以开始绘制,这样:

    cv2.line(img,(0,0),(150,150),(255,255,255),15)cv2.imshow('image',img)cv2.waitKey(0)cv2.destroyAllWindows()

    cv2.line()接受以下参数:图片,开始坐标,结束坐标,颜色(bgr),线条粗细。

    结果在这里:

    好吧,很酷,让我们绘制更多形状。 接下来是一个矩形:

    cv2.rectangle(img,(15,25),(200,150),(0,0,255),15)

    这里的参数是图像,左上角坐标,右下角坐标,颜色和线条粗细。

    圆怎么样?

    cv2.circle(img,(100,63), 55, (0,255,0), -1)

    这里的参数是图像/帧,圆心,半径,颜色和。 注意我们粗细为-1。 这意味着将填充对象,所以我们会得到一个圆。

    线条,矩形和圆都很酷,但是如果我们想要五边形,八边形或十八边形? 没问题!

    pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)# OpenCV documentation had this code, which reshapes the array to a 1 x 2. I did not # find this necessary, but you may:#pts = pts.reshape((-1,1,2))cv2.polylines(img, [pts], True, (0,255,255), 3)

    首先,我们将坐标数组称为pts(点的简称)。 然后,我们使用cv2.polylines来画线。 参数如下:绘制的对象,坐标,我们应该连接终止的和起始点,颜色和粗细。

    你可能想要做的最后一件事是在图像上写字。 这可以这样做:

    font = cv2.FONT_HERSHEY_SIMPLEXcv2.putText(img,'OpenCV Tuts!',(0,130), font, 1, (200,255,155), 2, cv2.LINE_AA)

    目前为止的完整代码:

    import numpy as npimport cv2img = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)cv2.line(img,(0,0),(200,300),(255,255,255),50)cv2.rectangle(img,(500,250),(1000,500),(0,0,255),15)cv2.circle(img,(447,63), 63, (0,255,0), -1)pts = np.array([[100,50],[200,300],[700,200],[500,100]], np.int32)pts = pts.reshape((-1,1,2))cv2.polylines(img, [pts], True, (0,255,255), 3)font = cv2.FONT_HERSHEY_SIMPLEXcv2.putText(img,'OpenCV Tuts!',(10,500), font, 6, (200,255,155), 13, cv2.LINE_AA)cv2.imshow('image',img)cv2.waitKey(0)cv2.destroyAllWindows()

    结果:

    在下一个教程中,我们将介绍我们可以执行的基本图像操作。

    四、图像操作

    在 OpenCV 教程中,我们将介绍一些我们可以做的简单图像操作。 每个视频分解成帧。 然后每一帧,就像一个图像,分解成存储在行和列中的,帧/图片中的像素。 每个像素都有一个坐标位置,每个像素都由颜色值组成。 让我们列举访问不同的位的一些例子。

    我们将像往常一样读取图像(如果可以,请使用自己的图像,但这里是我在这里使用的图像):

    import cv2import numpy as npimg = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)

    现在我们可以实际引用特定像素,像这样:

    px = img[55,55]

    下面我们可以实际修改像素:

    img[55,55] = [255,255,255]

    之后重新引用:

    px = img[55,55]print(px)

    现在应该不同了,下面我们可以引用 ROI,图像区域:

    px = img[100:150,100:150]print(px)

    我们也可以修改 ROI,像这样:

    img[100:150,100:150] = [255,255,255]

    我们可以引用我们的图像的特定特征:

    print(img.shape)print(img.size)print(img.dtype)

    我们可以像这样执行操作:

    watch_face = img[37:111,107:194]img[0:74,0:87] = watch_facecv2.imshow('image',img)cv2.waitKey(0)cv2.destroyAllWindows()

    这会处理我的图像,但是可能不能用于你的图像,取决于尺寸。这是我的输出:

    这些是一些简单的操作。 在下一个教程中,我们将介绍一些我们可以执行的更高级的图像操作。

    五、图像算术和逻辑运算

    欢迎来到另一个 Python OpenCV 教程,在本教程中,我们将介绍一些简单算术运算,我们可以在图像上执行的,并解释它们的作用。 为此,我们将需要两个相同大小的图像来开始,然后是一个较小的图像和一个较大的图像。 首先,我将使用:

    首先,让我们看看简单的加法会做什么:

    import cv2import numpy as np# 500 x 250img1 = cv2.imread('3D-Matplotlib.png')img2 = cv2.imread('mainsvmimage.png')add = img1+img2cv2.imshow('add',add)cv2.waitKey(0)cv2.destroyAllWindows()

    结果:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    你不可能想要这种混乱的加法。 OpenCV 有一个“加法”方法,让我们替换以前的“加法”,看看是什么:

    add = cv2.add(img1,img2)

    结果:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这里可能不理想。 我们可以看到很多图像是非常“白色的”。 这是因为颜色是 0-255,其中 255 是“全亮”。 因此,例如:(155,211,79) + (50, 170, 200) = 205, 381, 279...转换为(205, 255,255)。

    接下来,我们可以添加图像,并可以假设每个图像都有不同的“权重”。 这是如何工作的:

    import cv2import numpy as npimg1 = cv2.imread('3D-Matplotlib.png')img2 = cv2.imread('mainsvmimage.png')weighted = cv2.addWeighted(img1, 0.6, img2, 0.4, 0)cv2.imshow('weighted',weighted)cv2.waitKey(0)cv2.destroyAllWindows()

    对于addWeighted方法,参数是第一个图像,权重,第二个图像,权重,然后是伽马值,这是一个光的测量值。 我们现在就把它保留为零。

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这些是一些额外的选择,但如果你真的想将一个图像添加到另一个,最新的重叠在哪里? 在这种情况下,你会从最大的开始,然后添加较小的图像。 为此,我们将使用相同的3D-Matplotlib.png图像,但使用一个新的 Python 标志:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    现在,我们可以选取这个标志,并把它放在原始图像上。 这很容易(基本上使用我们在前一个教程中使用的相同代码,我们用一个新的东西替换了图像区域(ROI)),但是如果我们只想要标志部分而不是白色背景呢? 我们可以使用与之前用于 ROI 替换相同的原理,但是我们需要一种方法来“去除”标志的背景,使得白色不会不必要地阻挡更多背景图像。 首先我将显示完整的代码,然后解释:

    import cv2import numpy as np# Load two imagesimg1 = cv2.imread('3D-Matplotlib.png')img2 = cv2.imread('mainlogo.png')# I want to put logo on top-left corner, So I create a ROIrows,cols,channels = img2.shaperoi = img1[0:rows, 0:cols ]# Now create a mask of logo and create its inverse maskimg2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)# add a thresholdret, mask = cv2.threshold(img2gray, 220, 255, cv2.THRESH_BINARY_INV)mask_inv = cv2.bitwise_not(mask)# Now black-out the area of logo in ROIimg1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)# Take only region of logo from logo image.img2_fg = cv2.bitwise_and(img2,img2,mask = mask)dst = cv2.add(img1_bg,img2_fg)img1[0:rows, 0:cols ] = dstcv2.imshow('res',img1)cv2.waitKey(0)cv2.destroyAllWindows()

    这里发生了很多事情,出现了一些新的东西。 我们首先看到的是一个新的阈值:ret, mask = cv2.threshold(img2gray, 220, 255, cv2.THRESH_BINARY_INV)。

    我们将在下一个教程中介绍更多的阈值,所以请继续关注具体内容,但基本上它的工作方式是根据阈值将所有像素转换为黑色或白色。 在我们的例子中,阈值是 220,但是我们可以使用其他值,或者甚至动态地选择一个,这是ret变量可以使用的值。 接下来,我们看到:mask_inv = cv2.bitwise_not(mask)。 这是一个按位操作。 基本上,这些操作符与 Python 中的典型操作符非常相似,除了一点,但我们不会在这里触及它。 在这种情况下,不可见的部分是黑色的地方。 然后,我们可以说,我们想在第一个图像中将这个区域遮住,然后将空白区域替换为图像 2 的内容。

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    下个教程中,我们深入讨论阈值。

    六、阈值

    欢迎阅读另一个 OpenCV 教程。在本教程中,我们将介绍图像和视频分析的阈值。阈值的思想是进一步简化视觉数据的分析。首先,你可以转换为灰度,但是你必须考虑灰度仍然有至少 255 个值。阈值可以做的事情,在最基本的层面上,是基于阈值将所有东西都转换成白色或黑色。比方说,我们希望阈值为 125(最大为 255),那么 125 以下的所有内容都将被转换为 0 或黑色,而高于 125 的所有内容都将被转换为 255 或白色。如果你像平常一样转换成灰度,你会变成白色和黑色。如果你不转换灰度,你会得到二值化的图片,但会有颜色。

    虽然这听起来不错,但通常不是。我们将在这里介绍多个示例和不同类型的阈值来说明这一点。我们将使用下面的图片作为我们的示例图片,但可以随意使用你自己的图片:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这个书的图片就是个很好的例子,说明为什么一个人可能需要阈值。 首先,背景根本没有白色,一切都是暗淡的,而且一切都是变化的。 有些部分很容易阅读,另一部分则非常暗,需要相当多的注意力才能识别出来。 首先,我们尝试一个简单的阈值:

    retval, threshold = cv2.threshold(img, 10, 255, cv2.THRESH_BINARY)

    二元阈值是个简单的“是或不是”的阈值,其中像素为 255 或 0。在很多情况下,这是白色或黑色,但我们已经为我们的图像保留了颜色,所以它仍然是彩色的。 这里的第一个参数是图像。 下一个参数是阈值,我们选择 10。下一个是最大值,我们选择为 255。最后是阈值类型,我们选择了THRESH_BINARY。 通常情况下,10 的阈值会有点差。 我们选择 10,因为这是低光照的图片,所以我们选择低的数字。 通常 125-150 左右的东西可能效果最好。

    import cv2import numpy as npimg = cv2.imread('bookpage.jpg')retval, threshold = cv2.threshold(img, 12, 255, cv2.THRESH_BINARY)cv2.imshow('original',img)cv2.imshow('threshold',threshold)cv2.waitKey(0)cv2.destroyAllWindows()

    结果:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    现在的图片稍微更便于阅读了,但还是有点乱。 从视觉上来说,这样比较好,但是仍然难以使用程序来分析它。 让我们看看我们是否可以进一步简化。

    首先,让我们灰度化图像,然后使用一个阈值:

    import cv2import numpy as npgrayscaled = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)retval, threshold = cv2.threshold(grayscaled, 10, 255, cv2.THRESH_BINARY)cv2.imshow('original',img)cv2.imshow('threshold',threshold)cv2.waitKey(0)cv2.destroyAllWindows()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    更简单,但是我们仍然在这里忽略了很多背景。 接下来,我们可以尝试自适应阈值,这将尝试改变阈值,并希望弄清楚弯曲的页面。

    import cv2import numpy as npth = cv2.adaptiveThreshold(grayscaled, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 115, 1)cv2.imshow('original',img)cv2.imshow('Adaptive threshold',th)cv2.waitKey(0)cv2.destroyAllWindows()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    还有另一个版本的阈值,可以使用,叫做大津阈值。 它在这里并不能很好发挥作用,但是:

    retval2,threshold2 = cv2.threshold(grayscaled,125,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)cv2.imshow('original',img)cv2.imshow('Otsu threshold',threshold2)cv2.waitKey(0)cv2.destroyAllWindows()

    七、颜色过滤

    在这个 Python OpenCV 教程中,我们将介绍如何创建一个过滤器,回顾按位操作,其中我们将过滤特定的颜色,试图显示它。或者,你也可以专门筛选出特定的颜色,然后将其替换为场景,就像我们用其他方法替换ROI(图像区域)一样,就像绿屏的工作方式。

    为了像这样过滤,你有几个选项。通常,你可能会将你的颜色转换为 HSV,即“色调饱和度纯度”。例如,这可以帮助你根据色调和饱和度范围,使用变化的值确定一个更具体的颜色。如果你希望的话,你可以实际生成基于 BGR 值的过滤器,但是这会有点困难。如果你很难可视化 HSV,不要感到失落,查看维基百科页面上的 HSV,那里有一个非常有用的图形让你可视化它。我最好亲自描述颜色的色调饱和度和纯度。现在让我们开始:

    import cv2import numpy as npcap = cv2.VideoCapture(0)while(1): _, frame = cap.read() hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([30,150,50]) upper_red = np.array([255,255,180]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(frame,frame, mask= mask) cv2.imshow('frame',frame) cv2.imshow('mask',mask) cv2.imshow('res',res) k = cv2.waitKey(5) & 0xFF if k == 27: breakcv2.destroyAllWindows()cap.release()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这只是一个例子,以红色为目标。 它的工作方式是,我们所看到的是我们范围内的任何东西,基本上是 30-255,150-255 和 50-180。 它用于红色,但可以随便尝试找到自己的颜色。 HSV 在这里效果最好的原因是,我们想要范围内的颜色,这里我们通常需要相似的颜色。 很多时候,典型的红色仍然会有一些绿色和蓝色分量,所以我们必须允许一些绿色和蓝色,但是我们会想要几乎全红。 这意味着我们会在这里获得所有颜色的低光混合。

    为了确定 HSV 的范围,我认为最好的方法就是试错。 OpenCV 内置了将 BGR 转换为 HSV 的方法。 如果你想挑选单一的颜色,那么 BGR 到 HSV 将会很好用。 为了教学,下面是这个代码的一个例子:

    dark_red = np.uint8([[[12,22,121]]]) dark_red = cv2.cvtColor(dark_red,cv2.COLOR_BGR2HSV)

    这里的结果是一个 HSV 值,与dark_red值相同。这很棒...但是,同样...你遇到了颜色范围和 HSV 范围的基本问题。他们根本不同。你可能合理使用 BGR 范围,它们仍然可以工作,但是对于检测一种“颜色”,则无法正常工作。

    回到主代码,然而,我们首先要把帧转换成 HSV。那里没什么特别的。接下来,我们为红色指定一些 HSV 值。我们使用inRange函数,为我们的特定范围创建掩码。这是真或假,黑色或白色。接下来,我们通过执行按位操作来“恢复”我们的红色。基本上,我们显示了frame and mask。掩码的白色部分是红色范围,被转换为纯白色,而其他一切都变成黑色。最后我们展示所有东西。我选择了显示原始真,掩码和最终结果,以便更好地了解发生的事情。

    在下一个教程中,我们将对这个主题做一些介绍。你可能看到了,我们在这里还是有一些“噪音”。东西有颗粒感,红色中的黑点很多,还有许多其他的小色点。我们可以做一些事情,试图通过模糊和平滑来缓解这个问题,接下来我们将讨论这个问题。

    八、模糊和平滑

    在这个 Python OpenCV 教程中,我们将介绍如何尝试从我们的过滤器中消除噪声,例如简单的阈值,或者甚至我们以前的特定的颜色过滤器:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    正如你所看到的,我们有很多黑点,其中我们喜欢红色,还有很多其他的色点散落在其中。 我们可以使用各种模糊和平滑技术来尝试弥补这一点。 我们可以从一些熟悉的代码开始:

    import cv2import numpy as npcap = cv2.VideoCapture(0)while(1): _, frame = cap.read() hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([30,150,50]) upper_red = np.array([255,255,180]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(frame,frame, mask= mask)

    现在,让我们应用一个简单的平滑,我们计算每个像素块的均值。 在我们的例子中,我们使用15x15正方形,这意味着我们有 225 个总像素。

    kernel = np.ones((15,15),np.float32)/225 smoothed = cv2.filter2D(res,-1,kernel) cv2.imshow('Original',frame) cv2.imshow('Averaging',smoothed) k = cv2.waitKey(5) & 0xFF if k == 27: breakcv2.destroyAllWindows()cap.release()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这个很简单,但是结果牺牲了很多粒度。 接下来,让我们尝试一些高斯模糊:

    blur = cv2.GaussianBlur(res,(15,15),0) cv2.imshow('Gaussian Blurring',blur)
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    另一个选项是中值模糊:

    median = cv2.medianBlur(res,15) cv2.imshow('Median Blur',median)
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    最后一个选项是双向模糊:

    bilateral = cv2.bilateralFilter(res,15,75,75) cv2.imshow('bilateral Blur',bilateral)

    所有模糊的对比:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    至少在这种情况下,我可能会使用中值模糊,但是不同的照明,不同的阈值/过滤器,以及其他不同的目标和目标可能会决定你使用其中一个。

    在下一个教程中,我们将讨论形态变换。

    九、形态变换

    在这个 Python OpenCV 教程中,我们将介绍形态变换。 这些是一些简单操作,我们可以基于图像形状执行。

    我们要谈的第一对是腐蚀和膨胀。 腐蚀是我们将“腐蚀”边缘。 它的工作方式是使用滑块(核)。 我们让滑块滑动,如果所有的像素是白色的,那么我们得到白色,否则是黑色。 这可能有助于消除一些白色噪音。 另一个版本是膨胀,它基本上是相反的:让滑块滑动,如果整个区域不是黑色的,就会转换成白色。 这是一个例子:

    import cv2import numpy as npcap = cv2.VideoCapture(0)while(1): _, frame = cap.read() hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([30,150,50]) upper_red = np.array([255,255,180]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(frame,frame, mask= mask) kernel = np.ones((5,5),np.uint8) erosion = cv2.erode(mask,kernel,iterations = 1) dilation = cv2.dilate(mask,kernel,iterations = 1) cv2.imshow('Original',frame) cv2.imshow('Mask',mask) cv2.imshow('Erosion',erosion) cv2.imshow('Dilation',dilation) k = cv2.waitKey(5) & 0xFF if k == 27: breakcv2.destroyAllWindows()cap.release()

    结果:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    下一对是“开放”和“关闭”。 开放的目标是消除“假阳性”。 有时在背景中,你会得到一些像素“噪音”。 “关闭”的想法是消除假阴性。 基本上就是你检测了你的形状,例如我们的帽子,但物体仍然有一些黑色像素。 关闭将尝试清除它们。

    cap = cv2.VideoCapture(1)while(1): _, frame = cap.read() hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([30,150,50]) upper_red = np.array([255,255,180]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(frame,frame, mask= mask) kernel = np.ones((5,5),np.uint8) opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) closing = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) cv2.imshow('Original',frame) cv2.imshow('Mask',mask) cv2.imshow('Opening',opening) cv2.imshow('Closing',closing) k = cv2.waitKey(5) & 0xFF if k == 27: breakcv2.destroyAllWindows()cap.release()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    另外两个选项是tophat和blackhat,对我们的案例并不有用:

    # It is the difference between input image and Opening of the image cv2.imshow('Tophat',tophat) # It is the difference between the closing of the input image and input image. cv2.imshow('Blackhat',blackhat)

    在下一个教程中,我们将讨论图像渐变和边缘检测。

    十、边缘检测和渐变

    欢迎阅读另一个 Python OpenCV 教程。 在本教程中,我们将介绍图像渐变和边缘检测。 图像渐变可以用来测量方向的强度,边缘检测就像它所说的:它找到了边缘! 我敢打赌你肯定没看到。

    首先,我们来展示一些渐变的例子:

    import cv2import numpy as npcap = cv2.VideoCapture(1)while(1): # Take each frame _, frame = cap.read() hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([30,150,50]) upper_red = np.array([255,255,180]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(frame,frame, mask= mask) laplacian = cv2.Laplacian(frame,cv2.CV_64F) sobelx = cv2.Sobel(frame,cv2.CV_64F,1,0,ksize=5) sobely = cv2.Sobel(frame,cv2.CV_64F,0,1,ksize=5) cv2.imshow('Original',frame) cv2.imshow('Mask',mask) cv2.imshow('laplacian',laplacian) cv2.imshow('sobelx',sobelx) cv2.imshow('sobely',sobely) k = cv2.waitKey(5) & 0xFF if k == 27: breakcv2.destroyAllWindows()cap.release()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    如果你想知道什么是cv2.CV_64F,那就是数据类型。 ksize是核大小。 我们使用 5,所以每次查询5×5的渔区。

    虽然我们可以使用这些渐变转换为纯边缘,但我们也可以使用 Canny 边缘检测!

    import cv2import numpy as npcap = cv2.VideoCapture(0)while(1): _, frame = cap.read() hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower_red = np.array([30,150,50]) upper_red = np.array([255,255,180]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(frame,frame, mask= mask) cv2.imshow('Original',frame) edges = cv2.Canny(frame,100,200) cv2.imshow('Edges',edges) k = cv2.waitKey(5) & 0xFF if k == 27: breakcv2.destroyAllWindows()cap.release()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这真是太棒了! 但是,这并不完美。 注意阴影导致了边缘被检测到。 其中最明显的是蓝狗窝发出的阴影。

    在下一个 OpenCV 教程中,我们将讨论如何在其他图像中搜索和查找相同的图像模板。

    十一、模板匹配

    欢迎阅读另一个 Python OpenCV 教程,在本教程中,我们将介绍对象识别的一个基本版本。 这里的想法是,给出一定的阈值,找到匹配我们提供的模板图像的相同区域。 对于具体的对象匹配,具有精确的照明/刻度/角度,这可以工作得很好。 通常会遇到这些情况的例子就是计算机上的任何 GUI。 按钮等东西总是相同的,所以你可以使用模板匹配。 结合模板匹配和一些鼠标控制,你已经实现了一个基于 Web 的机器人!

    首先,你需要一个主要图像和一个模板。 你应该从你正在图像中查找的“东西”选取你的模板。 我将提供一个图像作为例子,但随意使用你最喜爱的网站的图像或类似的东西。

    主要图像:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    我们要搜索的模板:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这只是其中一个端口,但我们很好奇,看看我们是否可以匹配任何其他端口。 我们确实要选择一个阈值,其中某种东西可能是 80% 匹配,那么我们说这就匹配。 所以,我们将开始加载和转换图像:

    import cv2import numpy as npimg_rgb = cv2.imread('opencv-template-matching-python-tutorial.jpg')img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)template = cv2.imread('opencv-template-for-matching.jpg',0)w, h = template.shape[::-1]

    到目前为止,我们加载了两个图像,转换为灰度。 我们保留原始的 RGB 图像,并创建一个灰度版本。 我之前提到过这个,但是我们这样做的原因是,我们在灰度版本上执行所有的处理,然后在彩色图像上使用相同的标签来标记。

    对于主要图像,我们只有彩色版本和灰度版本。 我们加载模板并记下尺寸。

    res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)threshold = 0.8loc = np.where( res >= threshold)

    在这里,我们用img_gray(我们的主图像),模板,和我们要使用的匹配方法调用matchTemplate,并将返回值称为res。 我们指定一个阈值,这里是 80%。 然后我们使用逻辑语句,找到res大于或等于 80% 的位置。

    最后,我们使用灰度图像中找到的坐标,标记原始图像上的所有匹配:

    for pt in zip(*loc[::-1]): cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,255), 2)cv2.imshow('Detected',img_rgb)
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    所以我们得到了几个匹配。也许需要降低阈值?我们试试 0.7。

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    这里有一些假阳性。 你可以继续调整门槛,直到你达到 100%,但是如果没有假阳性,你可能永远不会达到它。 另一个选择就是使用另一个模板图像。 有时候,使用相同对象的多个图像是有用的。 这样,你可以使阈值足够高的,来确保你的结果准确。

    在下一个教程中,我们将介绍前景提取。

    十二、GrabCut 前景提取

    欢迎阅读 Python OpenCV 前景提取教程。 这里的想法是找到前景,并删除背景。 这很像绿屏,只是这里我们实际上不需要绿屏。

    首先,我们将使用一个图像:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    随意使用你自己的。

    让我们加载图像并定义一些东西:

    import numpy as npimport cv2from matplotlib import pyplot as pltimg = cv2.imread('opencv-python-foreground-extraction-tutorial.jpg')mask = np.zeros(img.shape[:2],np.uint8)bgdModel = np.zeros((1,65),np.float64)fgdModel = np.zeros((1,65),np.float64)rect = (161,79,150,150)

    到目前为止,我们已经导入了cv2,numpy和matplotlib。 然后我们加载图像,创建一个掩码,指定算法内部使用的背景和前景模型。 真正重要的部分是我们定义的矩形。 这是rect = (start_x, start_y, width, height)。

    这是包围我们的主要对象的矩形。 如果你正在使用我的图片,那就是要使用的矩阵。 如果你使用自己的,找到适合你的图像的坐标。

    下面:

    cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')img = img*mask2[:,:,np.newaxis]plt.imshow(img)plt.colorbar()plt.show()

    所以在这里我们使用了cv2.grabCut,它用了很多参数。 首先是输入图像,然后是掩码,然后是主要对象的矩形,背景模型,前景模型,要运行的迭代量以及使用的模式。

    这里改变了掩码,使得所有像素 0 和 2 转换为背景,而像素 1 和 3 现在是前景。 从这里,我们乘以输入图像,得到我们的最终结果:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    下个教程中,我们打算讨论如何执行角点检测。

    十三、角点检测

    欢迎阅读 Python OpenCV 角点检测教程。 检测角点的目的是追踪运动,做 3D 建模,识别物体,形状和角色等。

    对于本教程,我们将使用以下图像:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    我们的目标是找到这个图像中的所有角点。 我会注意到,在这里我们有一些别名问题(斜线的锯齿),所以,如果我们允许的话,会发现很多角点,而且是正确的。 和往常一样,OpenCV 已经为我们完成了难题,我们需要做的就是输入一些参数。 让我们开始加载图像并设置一些参数:

    import numpy as npimport cv2img = cv2.imread('opencv-corner-detection-sample.jpg')gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)gray = np.float32(gray)corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 10)corners = np.int0(corners)

    到目前为止,我们加载图像,转换为灰度,然后是float32。 接下来,我们用goodFeaturesToTrack函数检测角点。 这里的参数是图像,检测到的最大角点数量,品质和角点之间的最小距离。 如前所述,我们在这里的锯齿问题将允许找到许多角点,所以我们对其进行了限制。 下面:

    for corner in corners: x,y = corner.ravel() cv2.circle(img,(x,y),3,255,-1) cv2.imshow('Corner',img)

    现在我们遍历每个角点,在我们认为是角点的每个点上画一个圆。

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    在下一个教程中,我们将讨论功能匹配/单映射。

    十四、特征匹配(单映射)爆破

    欢迎阅读 Python OpenCV 特征匹配教程。 特征匹配将是稍微更令人印象深刻的模板匹配版本,其中需要一个完美的,或非常接近完美的匹配。

    我们从我们希望找到的图像开始,然后我们可以在另一幅图像中搜索这个图像。 这里的完美是图像不需要相同的光照,角度,旋转等。 特征只需要匹配。

    首先,我们需要一些示例图像。 我们的“模板”,或者我们将要尝试匹配的图像:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    之后是我们用于搜索这个模板的图像:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    在这里,我们的模板图像在模板中,比在我们要搜索的图像中要小一些。 它的旋转也不同,阴影也有些不同。

    现在我们将使用一种“爆破”匹配的形式。 我们将在这两个图像中找到所有特征。 然后我们匹配这些特征。 然后,我们可以绘制我们想要的,尽可能多的匹配。 但是要小心。 如果你绘制 500 个匹配,你会有很多误报。 所以只绘制绘制前几个。

    import numpy as npimport cv2import matplotlib.pyplot as pltimg1 = cv2.imread('opencv-feature-matching-template.jpg',0)img2 = cv2.imread('opencv-feature-matching-image.jpg',0)

    到目前为止,我们已经导入了要使用的模块,并定义了我们的两个图像,即模板(img1)和用于搜索模板的图像(img2)。

    orb = cv2.ORB_create()

    这是我们打算用于特征的检测器。

    kp1, des1 = orb.detectAndCompute(img1,None)kp2, des2 = orb.detectAndCompute(img2,None)

    在这里,我们使用orb探测器找到关键点和他们的描述符。

    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

    这就是我们的BFMatcher对象。

    matches = bf.match(des1,des2)matches = sorted(matches, key = lambda x:x.distance)

    这里我们创建描述符的匹配,然后根据它们的距离对它们排序。

    img3 = cv2.drawMatches(img1,kp1,img2,kp2,matches[:10],None, flags=2)plt.imshow(img3)plt.show()

    这里我们绘制了前 10 个匹配。输出:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    十五、MOG 背景减弱

    在这个 Python OpenCV 教程中,我们将要讨论如何通过检测运动来减弱图像的背景。 这将要求我们回顾视频的使用,或者有两个图像,一个没有你想要追踪的人物/物体,另一个拥有人物/物体。 如果你希望,你可以使用你的摄像头,或者使用如下的视频:

    人们行走的样例视频

    这里的代码实际上很简单,就是我们现在知道的:

    import numpy as npimport cv2cap = cv2.VideoCapture('people-walking.mp4')fgbg = cv2.createBackgroundSubtractorMOG2()while(1): ret, frame = cap.read() fgmask = fgbg.apply(frame) cv2.imshow('fgmask',frame) cv2.imshow('frame',fgmask) k = cv2.waitKey(30) & 0xff if k == 27: break cap.release()cv2.destroyAllWindows()

    结果:

    pythonprogramming.net/static/imag…

    这里的想法是从静态背景中提取移动的前景。 你也可以使用这个来比较两个相似的图像,并立即提取它们之间的差异。

    在我们的例子中,我们可以看到我们确实已经检测到了一些人,但是我们确实有一些“噪音”,噪音实际上是树叶在周围的风中移动了一下。 只要我们知道一种减少噪音的方法。 等一下! 我们的确知道! 一个疯狂的挑战已经出现了你面前!

    接下来的教程开始让我们远离滤镜或变换的应用,并让我们使用 Haar Cascades 来检测一般对象,例如面部检测等等。

    十六、Haar Cascade 面部检测

    在这个 Python OpenCV 教程中,我们将讨论 Haar Cascades 对象检测。我们将从脸部和眼睛检测来开始。为了使用层叠文件进行对象识别/检测,首先需要层叠文件。对于非常流行的任务,这些已经存在。检测脸部,汽车,笑脸,眼睛和车牌等东西都是非常普遍的。

    首先,我会告诉你如何使用这些层叠文件,然后我将告诉你如何开始创建你自己的层叠,这样你就可以检测到任何你想要的对象,这很酷!

    你可以使用 Google 来查找你可能想要检测的东西的各种 Haar Cascades。对于找到上述类型,你应该没有太多的麻烦。我们将使用面部层叠和眼睛层叠。你可以在 Haar Cascades 的根目录找到更多。请注意用于使用/分发这些 Haar Cascades 的许可证。

    让我们开始我们的代码。我假设你已经从上面的链接中下载了haarcascade_eye.xml和haarcascade_frontalface_default.xml,并将这些文件放在你项目的目录中。

    import numpy as npimport cv2# multiple cascades: https://github.com/Itseez/opencv/tree/master/data/haarcascades#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xmlface_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_eye.xmleye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')cap = cv2.VideoCapture(0)

    在这里,我们从导入cv2和numpy开始,然后加载我们的脸部和眼部的层叠。 目前为止很简单。

    while 1: ret, img = cap.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    现在我们开始我们的典型循环,这里唯一的新事物就是脸部的创建。 更多信息请访问detectMultiScale函数的文档。 基本上,它找到了面部! 我们也想找到眼睛,但是在一个假阳性的世界里,在面部里面寻找眼睛,从逻辑上来说是不是很明智? 我们希望我们不寻找不在脸上的眼睛! 严格来说,“眼睛检测”可能不会找到闲置的眼球。 大多数眼睛检测使用周围的皮肤,眼睑,眼睫毛,眉毛也可以用于检测。 因此,我们的下一步就是先去拆分面部,然后才能到达眼睛:

    for (x,y,w,h) in faces: cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2) roi_gray = gray[y:y+h, x:x+w] roi_color = img[y:y+h, x:x+w]

    在这里,我们找到了面部,它们的大小,绘制矩形,并注意 ROI。 接下来,我们找了一些眼睛:

    eyes = eye_cascade.detectMultiScale(roi_gray) for (ex,ey,ew,eh) in eyes: cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

    如果我们找到这些,我们会继续绘制更多的矩形。 接下来我们完成:

    cv2.imshow('img',img) k = cv2.waitKey(30) & 0xff if k == 27: breakcap.release()cv2.destroyAllWindows()

    完整代码:

    import numpy as npimport cv2# multiple cascades: https://github.com/Itseez/opencv/tree/master/data/haarcascades#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xmlface_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_eye.xmleye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')cap = cv2.VideoCapture(0)while 1: ret, img = cap.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.3, 5) for (x,y,w,h) in faces: cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2) roi_gray = gray[y:y+h, x:x+w] roi_color = img[y:y+h, x:x+w] eyes = eye_cascade.detectMultiScale(roi_gray) for (ex,ey,ew,eh) in eyes: cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2) cv2.imshow('img',img) k = cv2.waitKey(30) & 0xff if k == 27: breakcap.release()cv2.destroyAllWindows()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    不错。你可能会注意到我不得不取下我的眼镜。这些造成了一些麻烦。我的嘴也经常被检测为眼睛,有时甚至是一张脸,但你明白了。面部毛发和其他东西经常可以欺骗基本面部检测,除此之外,皮肤的颜色也会造成很大的麻烦,因为我们经常试图尽可能简化图像,从而失去了很多颜色值。甚至还有一个小型行业,可以避免人脸检测和识别。CVDazzle 网站就是一个例子。其中有些非常古怪,但他们很有效。你也可以总是走完整的面部重建手术的路线,以避免自动跟踪和检测,所以总是这样,但是这更永久。做个发型比较短暂也容易做到。

    好吧,检测面部,眼睛和汽车是可以的,但我们是程序员。我们希望能够做任何事情。事实证明,事情会变得相当混乱,建立自己的 Haar Cascades 有一定的难度,但是其他人也这么做......你也可以!这就是在下一个教程中所讨论的。

    十七、创建自己的 Haar Cascade

    欢迎使用 Python OpenCV 对象检测教程。在本教程中,你将看到如何创建你自己的 Haar Cascades,以便你可以跟踪任何你想要的对象。由于这个任务的本质和复杂性,本教程将比平时稍长一些,但奖励是巨大的。

    虽然你可以在 Windows 中完成,我不会建议这样。因此,对于本教程,我将使用 Linux VPS,并且我建议你也这样做。你可以尝试使用 Amazon Web Services 提供的免费套餐,但对你来说可能太痛苦了,你可能需要更多的内存。你还可以从 Digital Ocean 获得低至五美元/月的 VPS。我推荐至少 2GB 的内存用于我们将要做的事情。现在大多数主机按小时收费,包括 DO。因此,你可以购买一个 20 美元/月的服务器,使用它一天,获取你想要的文件,然后终止服务器,并支付很少的钱。你需要更多的帮助来设置服务器?如果是的话,看看这个具体的教程。

    一旦你的服务器准备就绪,你会打算获取实际的 OpenCV 库。

    将目录更改到服务器的根目录,或者你想放置工作区的地方:

    cd ~sudo apt-get updatesudo apt-get upgrade

    首先,让我们为自己制作一个漂亮的工作目录:

    mkdir opencv_workspacecd opencv_workspace

    既然我们完成了,让我们获取 OpenCV。

    sudo apt-get install gitgit clone https://github.com/Itseez/opencv.git

    我们这里克隆了 OpenCV 的最新版本。现在获取一些必需品。

    编译器:sudo apt-get install build-essential

    库:sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev

    Python 绑定:sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev

    最后,让我们获取 OpenCV 开发库:

    sudo apt-get install libopencv-dev

    现在,我们该如何完成这个过程呢?所以当你想建立一个 Haar Cascade 时,你需要“正片”图像和“底片”图像。 “正片”图像是包含要查找的对象的图像。这可以是具有对象的主要图像,也可以是包含对象的图像,并指定对象所在的 ROI(兴趣区域)。有了这些正片图像,我们建立一个矢量文件,基本上是所有这些东西放在一起。正片图像的一个好处是,你可以实际只有一个你想要检测的对象的图像,然后有几千个底片图像。是的,几千。底片图像可以是任何东西,除了他们不能包含你的对象。

    在这里,使用你的底片图像,你可以使用opencv_createsamples命令来创建一堆正片的示例。你的正片图像将叠加在这些底片上,而且会形成各种各样的角度。它实际上可以工作得很好,特别是如果你只是在寻找一个特定的对象。但是,如果你正在寻找所有螺丝刀,则需要拥有数千个螺丝刀的独特图像,而不是使用opencv_createsamples为你生成样品。我们将保持简单,只使用一个正片图像,然后用我们的底片创建一堆样本。

    我们的正片图像:

    这是另外一个场景,如果你使用自己的图像,你可能会更喜欢这个。如果事情出错了,试试看我的,但是我建议你自己画一下。保持较小。 50x50像素应该可以。

    好吧,获得正片图像是没有问题的!只有一个问题。我们需要成千上万的底片图像。可能在未来,我们也可能需要成千上万的正片图像。我们可以在世界的哪个地方实现它?基于 WordNet 的概念,有一个非常有用的站点叫做 ImageNet。从这里,你可以找到几乎任何东西的图像。我们这里,我们想要手表,所以搜索手表,你会发现大量种类的手表。让我们检索电子表。真棒!看看下载标签!存在用于所有电子表手表的 URL!很酷。好吧,但我说过我们只会使用一个正片,所以我们只是检测一个手表。如果你想检测“全部”手表,需要准备获取多余 50,000 个手表图像,至少 25000 个“底片”的图像。之后,准备足够的服务器,除非你想要你的 Haar Cascade 训练花一个星期。那么我们如何得到底片? ImageNet 的全部重点是图像训练,所以他们的图像非常具体。因此,如果我们搜索人,汽车,船只,飞机......无论什么,都不会有手表。你可能会看到一些人或类似的东西,但你明白了。既然你可能看到人周围或上面的手表,我其实认为你也会得到人的图像。我的想法是寻找做运动的人,他们可能没有戴电子表。所以,我们来找一些批量图片的 URL 链接。我发现体育/田径链接有 1,888 张图片,但你会发现很多这些都是完全损坏的。让我们再来找一个:

    好吧,我们拥有所有这些图片,现在呢?那么,首先,我们希望所有这些大小都相同,而且要小很多!天哪,只要我们知道一个方法来操作图像...嗯...哦,这是一个 OpenCV 教程!我们可以处理它。所以,首先,我们要做的就是编写一个简单的脚本,访问这些 URL 列表,获取链接,访问链接,拉取图像,调整大小,保存它们,然后重复,直到完成。当我们的目录充满图像时,我们还需要一种描述图像的描述文件。对于正片,手动创建这个文件特别痛苦,因为你需要指定你的对象,每个图像的具体的兴趣区域。幸运的是,create_samples方法将图像随机放置,并为我们做了所有工作。我们只需要一个用于底片的简单描述符,但是这不是问题,在拉伸和操作图像时我们可以实现。

    www.youtube.com/embed/z_6fP…

    在任何你喜欢的地方随意运行这个代码。 我要在我的主机上运行它,因为它应该快一点。 你可以在你的服务器上运行。 如果你想使用cv2模块,请执行sudo apt-get install python-OpenCV。 目前,我不知道在 Linux 上为 Python 3 获得这些绑定的好方法。 我将要写的脚本是 Python 3,所以记住这一点。 主要区别是Urllib处理。

    # download-image-by-link.pyimport urllib.requestimport cv2import numpy as npimport osdef store_raw_images(): neg_images_link = '//image-net.org/api/text/imagenet.synset.geturls?wnid=n00523513' neg_image_urls = urllib.request.urlopen(neg_images_link).read().decode() pic_num = 1 if not os.path.exists('neg'): os.makedirs('neg') for i in neg_image_urls.split(' '): try: print(i) urllib.request.urlretrieve(i, "neg/"+str(pic_num)+".jpg") img = cv2.imread("neg/"+str(pic_num)+".jpg",cv2.IMREAD_GRAYSCALE) # should be larger than samples / pos pic (so we can place our image on it) resized_image = cv2.resize(img, (100, 100)) cv2.imwrite("neg/"+str(pic_num)+".jpg",resized_image) pic_num += 1 except Exception as e: print(str(e))

    很简单,这个脚本将访问链接,抓取网址,并继续访问它们。从这里,我们抓取图像,转换成灰度,调整大小,然后保存。我们使用一个简单的计数器来命名图像。继续运行它。你可能看到,有很多确实的图片等。没关系。这些错误图片中的一些更有问题。基本上都是白色,带有一些文本,说他们不再可用,而不是服务和 HTTP 错误。现在,我们有几个选择。我们可以忽略它们,或者修复它。嘿,这是一个没有手表的图像,所以什么是对的呢?当然,你可以采取这种观点,但如果你为正片使用这种拉取方式的话,这肯定是一个问题。你可以手动删除它们...或者我们可以使用我们新的图像分析知识,来检测这些愚蠢的图像,并将其删除!

    我继续生成了一个新的目录,称之为“uglies(丑陋)”。在那个目录中,我点击并拖动了所有丑陋的图像版本(只是其中之一)。在底片中我只发现了一个主犯,所以我只有一个。让我们编写一个脚本来查找这个图像的所有实例并删除它。

    www.youtube.com/embed/t0HOV…

    def find_uglies(): match = False for file_type in ['neg']: for img in os.listdir(file_type): for ugly in os.listdir('uglies'): try: current_image_path = str(file_type)+'/'+str(img) ugly = cv2.imread('uglies/'+str(ugly)) question = cv2.imread(current_image_path) if ugly.shape == question.shape and not(np.bitwise_xor(ugly,question).any()): print('That is one ugly pic! Deleting!') print(current_image_path) os.remove(current_image_path) except Exception as e: print(str(e))

    现在我们只有底片,但是我留下了空间让你轻易在那里添加'pos'(正片)。 你可以运行它来测试,但我不介意先抓住更多的底片。 让我们再次运行图片提取器,仅仅使用这个 url://image-net.org/api/text/imagenet.synset.geturls?wnid=n07942152。 最后一张图像是#952,所以让我们以 953 开始pic_num,并更改网址。

    def store_raw_images(): neg_images_link = '//image-net.org/api/text/imagenet.synset.geturls?wnid=n07942152' neg_image_urls = urllib.request.urlopen(neg_images_link).read().decode() pic_num = 953 if not os.path.exists('neg'): os.makedirs('neg') for i in neg_image_urls.split(' '): try: print(i) urllib.request.urlretrieve(i, "neg/"+str(pic_num)+".jpg") img = cv2.imread("neg/"+str(pic_num)+".jpg",cv2.IMREAD_GRAYSCALE) # should be larger than samples / pos pic (so we can place our image on it) resized_image = cv2.resize(img, (100, 100)) cv2.imwrite("neg/"+str(pic_num)+".jpg",resized_image) pic_num += 1 except Exception as e: print(str(e))

    现在我们有超过2000张照片。 最后一步是,我们需要为这些底片图像创建描述符文件。 我们将再次使用一些代码!

    def create_pos_n_neg(): for file_type in ['neg']: for img in os.listdir(file_type): if file_type == 'pos': line = file_type+'/'+img+' 1 0 0 50 50 ' with open('info.dat','a') as f: f.write(line) elif file_type == 'neg': line = file_type+'/'+img+' ' with open('bg.txt','a') as f: f.write(line)

    运行它,你有了个bg.txt文件。 现在,我知道有些人的互联网连接可能不是最好的,所以我做个好人,在这里上传底片图片和描述文件。 你应该通过这些步骤。 如果你对本教程感到困扰,则需要知道如何执行这部分。 好吧,所以我们决定我们将一个图像用于正片前景图像。 因此,我们需要执行create_samples。 这意味着,我们需要将我们的neg目录和bg.txt文件移动到我们的服务器。 如果你在服务器上运行所有这些代码,不要担心。

    www.youtube.com/embed/eay7C…

    如果你是一个术士,并已经想出了如何在 Windows 上运行create_samples等,恭喜! 回到服务器的领地,我的文件现在是这样的:

    opencv_workspace--neg----negimages.jpg--opencv--info--bg.txt--watch5050.jpg

    你可能没有info目录,所以继续并mkdir info。 这是我们放置所有正片图像的地方。

    我们现在准备根据watch5050.jpg图像创建一些正片样本。 为此,请在工作区中通过终端运行以下命令:

    opencv_createsamples -img watch5050.jpg -bg bg.txt -info info/info.lst -pngoutput info -maxxangle 0.5 -maxyangle 0.5 -maxzangle 0.5 -num 1950

    这样做是基于我们指定的img创建样本,bg是背景信息,我们将输出info.list(很像bg.txt文件)的信息,然后-pngoutput就是我们想要放置新生成的图像的任何地方。 最后,我们有一些可选的参数,使我们的原始图像更加动态一些,然后用= num来表示我们想要创建的样本数量。 太棒了,让我们来运行它。 现在你的info目录应该有约 2,000 个图像,还有一个名为info.lst的文件。 这个文件基本上是你的“正片”文件。 打开它,并且看看它怎么样:

    0001_0014_0045_0028_0028.jpg 1 14 45 28 28

    首先你有文件名,之后是图像中有多少对象,其次是它们的所有位置。 我们只有一个,所以它是图像中对象矩形的x,y,宽度和高度。 这是一个图像:

    很难看到它,但如果你很难看到,手表就是这个图像。 图像中最左侧人物的左下方。 因此,这是一个“正片”图像,从另外一个“底片”图像创建,底片图像也将用于训练。 现在我们有了正片图像,现在我们需要创建矢量文件,这基本上是一个地方,我们将所有正片图像拼接起来。我们会再次为此使用opencv_createsamples!

    opencv_createsamples -info info/info.lst -num 1950 -w 20 -h 20 -vec positives.vec

    这是我们的矢量文件。 在这里,我们只是让它知道信息文件的位置,我们想要在文件中包含多少图像,在这个矢量文件中图像应该是什么尺寸,然后才能输出结果。 如果你愿意的话,你可以做得更大一些,20×20可能足够好了,你做的越大,训练时间就会越长。 继续,我们现在只需要训练我们的层叠。

    首先,我们要把输出放在某个地方,所以让我们创建一个新的数据目录:

    mkdir data,你的工作空间应该如下所示:

    opencv_workspace--neg----negimages.jpg--opencv--info--data--positives.vec --bg.txt--watch5050.jpg

    现在让我们运行训练命令:

    opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 1800 -numNeg 900 -numStages 10 -w 20 -h 20

    在这里,我们表明了,我们想要数据去的地方,矢量文件的位置,背景文件的位置,要使用多少个正片图像和底片图像,多少个阶段以及宽度和高度。请注意,我们使用的numPos比我们少得多。这是为了给阶段腾出空间。

    有更多的选择,但这些就够了。这里主要是正片和底片的数量。一般认为,对于大多数实践,你需要 2:1 比例的正片和底片图像。有些情况可能会有所不同,但这是人们似乎遵循的一般规则。接下来,我们拥有阶段。我们选择了 10 个。你至少要 10-20 个,越多需要的时间越长,而且是指数级的。第一阶段通常很快,第五阶段要慢得多,第五十个阶段永远不会做完!所以,我们现在执行 10 个阶段。这里不错的事情是你可以训练 10 个阶段,稍后再回来,把数字改成 20,然后在你离开的地方继续。同样的,你也可以放一些像 100 个阶段的东西,上床睡觉,早上醒来,停下来,看看你有多远,然后用这些阶段“训练”,你会立即得到一个层叠文件。你可能从最后一句话中得出,这个命令的结果确实很棒,是个不错的层叠文件。我们希望能检测到我的手表,或者你决定检测的任何东西。我所知道的是,在输出这一段的时候,我还没有完成第一阶段的工作。如果你真的想要在一夜之间运行命令,但不想让终端打开,你可以使用nohup:

    nohup opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 1800 -numNeg 900 -numStages 10 -w 20 -h 20 &

    这使命令即使在关闭终端之后也能继续运行。 你可以使用更多,但你可能会或可能不会用完你的 2GB RAM。

    www.youtube.com/embed/-Mhy-…

    在我的 2GB DO 服务器上,10 个阶段花了不到 2 个小时的时间。 所以,要么有一个cascade.xml文件,要么停止脚本运行。 如果你停止运行,你应该在你的data目录下有一堆stageX.xml文件。 打开它,看看你有多少个阶段,然后你可以使用这些阶段,再次运行opencv_traincascade,你会立即得到一个cascade.xml文件。 这里,我只想说出它是什么,以及有多少个阶段。 对我来说,我做了 10 个阶段,所以我将它重命名为watchcascade10stage.xml。 这就是我们所需的,所以现在将新的层次文件传回主计算机,放在工作目录中,让我们试试看!

    import numpy as npimport cv2face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')#this is the cascade we just made. Call what you wantwatch_cascade = cv2.CascadeClassifier('watchcascade10stage.xml')cap = cv2.VideoCapture(0)while 1: ret, img = cap.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.3, 5) # add this # image, reject levels level weights. watches = watch_cascade.detectMultiScale(gray, 50, 50) # add this for (x,y,w,h) in watches: cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,0),2) for (x,y,w,h) in faces: cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2) roi_gray = gray[y:y+h, x:x+w] roi_color = img[y:y+h, x:x+w] eyes = eye_cascade.detectMultiScale(roi_gray) for (ex,ey,ew,eh) in eyes: cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2) cv2.imshow('img',img) k = cv2.waitKey(30) & 0xff if k == 27: breakcap.release()cv2.destroyAllWindows()
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    你可能注意到,手表的方框很小。 它似乎并没有达到整个手表。 回想一下我们的训练规模是20x20。 因此,我们最多有个20x20的方框。 你可以做100x100,但是,要小心,这将需要很长时间来训练。 因此,我们不绘制方框,而是,为什么不在手表上写字或什么东西? 这样做相对简单。 我们不在手表上执行cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),2),我们可以执行:

    font = cv2.FONT_HERSHEY_SIMPLEX cv2.putText(img,'Watch',(x-w,y-h), font, 0.5, (11,255,255), 2, cv2.LINE_AA)
    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    很酷! 所以你可能没有使用我的手表,你是怎么做的? 如果遇到麻烦,请尝试使用与我完全相同的所有内容。 检测图像,而不是检测摄像头,这里是一个:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    在图像上运行检测会给你:

    史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    Haar Cascades 往往是 100-2000 KB 的大小。大于等于 2,000 KB 的 Haar Cascades 应该非常准确。考虑你的情况,你可能会遇到约 5000 个一般物体。考虑 Haar Cascades 平均可能是约 500 KB。我们需要:0.5 MB * 5,000 = 2,500 MB或 2.5 GB。你需要 2.5 GB 的内存来识别 5000 个对象,并且可能是你在一天中遇到的最多对象。这让我着迷。考虑到我们可以访问所有的 image-net,并可以立即拉取很大范围的对象的一般图像。考虑 image-net 上的大多数图像,基本上都是 100% 的“跟踪”对象,因此,你可以通过手动标注位置,并仅使用 0,0 和图像的全部大小来得到正确的结果。

    展开全文
  • Python+OpenCV计算机视觉

    万人学习 2018-12-28 16:25:49
    Python+OpenCV计算机视觉系统全面的介绍。
  • 本次公开课讲师屈老师是哈尔滨工业大学副教授,视觉技术研究室负责人。...并使用OpenCV,带领大家从零开始一步一步的搭建一个简单但可用的实际的视觉跟踪系统,使大家从中学习视觉系统的构建及OpenCV系统实现。
  • OpenCV 概述

    千次阅读 多人点赞 2014-03-09 14:35:51
    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的模板腐蚀一次效果是相同的。膨胀的结果可以类推。

      

    展开全文
  • Opencv学习(一)

    千次阅读 2018-09-22 15:25:28
    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();  
    
    展开全文
  • 前一段时间写了一个人脸相关的算法,包括视频...使用的是VS2013和opencv。 首先创建头文件common.h #ifndef _COMMON_H #define _COMMON_H #include #include #include #include #include #include #include #
  • 【C++】基于 OpenCV 的人脸识别(强烈推荐)

    万次阅读 多人点赞 2016-01-23 14:18:40
    根据这个项目的关于页面,OpenCV 已被广泛运用在各种项目上,从谷歌街景的图片拼接,到交互艺术展览的技术实现中,都有 OpenCV 的身影。 OpenCV 起始于 1999 年 Intel 的一个内部研究项目。从那时起,它的开发就一直...
  • C++下使用OpenCV实现人脸检测

    千次阅读 2018-10-24 10:41:41
    没有安装OpenCV的可以找个教程安装一下,然后安装一下visual studio2015或2017   人脸识别主要有三个步骤: 人脸图像采集及检测 人脸图像预处理 人脸图像特征提取以及匹配与识别 以下代码实现了读取本地图像,...
  • opencv是什么

    万次阅读 多人点赞 2010-08-18 20:22:00
    OpenCV是一个用于图像处理、分析、机器视觉方面的开源函数库. 无论你是做科学研究,还是商业应用,opencv都可以作为你理想的工具库,因为,对于这两者,它完全是免费的。该库采用C及C++语言编写,可以在windows, ...
  • OpenCV3入门教程(一)基础知识

    万次阅读 多人点赞 2018-10-12 15:25:56
    想学习图像处理,不管是机器学习也好,深度学习也好,不会点OpenCV好像有点说不过去吧?所以,现在开始OpenCV的学习。 2、读写图片 先从图片的读写开始,opencv读取图片的函数是imread,默认情况下,imread函数...
  • windows下OpenCV的安装配置部署详细教程

    万次阅读 多人点赞 2018-08-07 09:09:25
    OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库。OpenCV是由英特尔公司发起并参与开发,以BSD许可证授权发行,可以在商业和研究领域中免费使用。OpenCV可用于开发实时的图像处理、...
  • VS配置OpenCV教程(超详细)

    万次阅读 多人点赞 2017-12-09 15:59:15
    目录 安装环境说明 下载OpenCV ...配置OpenCV开发环境 ...OpenCV版本:OpenCV2和OpenCV3 VS版本:VS2015或者VS2017 下载OpenCV OpenCV下载地址:http://opencv.org/或者直接搜OpenCV下...
  • 我们还将用C ++和Python共享OpenCV代码。 物体检测与物体识别 对象识别算法识别图像中存在哪些对象。它将整个图像作为输入,并输出该图像中存在的对象的类标签和类概率。例如,类标签可以是“狗”,相关的类概率...
  • VS2017配置opencv教程(超详细!!!)

    万次阅读 多人点赞 2018-06-03 23:48:23
    前言:刚开始是不打算写这篇博客的,但是我最近为了完成对老师布置的区域生长算法,强行要配置一波opencv,因为换了电脑,所以选择了有黑黑主题酷酷的VS2017,但无奈网上的博客关于vs2017配置Opencv不够全(vs2010...
  • Opencv安装教程

    万次阅读 多人点赞 2019-05-24 09:45:57
    Opencv3.4.6安装教程下载opencv下载opencv_contribCmake配置、生成Visual Studio 2017环境配置测试如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义...
  • Aanconda(或者Minianaconda)安装完成后,python版本固定(2019年8月27日,安装好anaconda后python默认版本是3.7),但我们可以自己指定所需要的python版本: 创建需要的python版本虚拟环境: conda create -n vm...
  • 本篇文章记录了安装OpenCV 3.4.2及配置VS2017的方法,以及利用CMake从源码编译适用于老版本VS2013的OpenCV3.4.2的编译及配置流程。本文也可以作为其他OpenCV及VS版本安装配置方法的参考。 1 安装OpenCV 3.4.2 ...
  • opencv安装教程

    万次阅读 2016-09-14 15:44:21
    OpenCv是C++图像处理的重要工具,这个深度网络的项目都得借助OpenCv进行开发的。虽然网上已经有了很多关于OpenCv的配置教程,但... 一、OpenCv的下载安装  首先,给出OpenCv的官方下载地址:OpenCv下载。  
  • Opencv安装与环境配置

    万次阅读 热门讨论 2018-07-27 15:46:24
     首先到opencv官网下载win平台的opencv,解压后你可以看到下面两个文件夹——build和sources,build是官方已经为我们编译好的可以直接使用的,而sources中是opencv的源码,仅供高级玩家尝试。 2、计算机环境变量...
  • OpenCV安装过程

    万次阅读 2019-05-31 10:03:23
    参考:Linux(Ubuntu 16.04)中安装OpenCV + OpenCV_Contrib 从github中down下OPenCV + OpenCV_Contrib库,指令下载: git clone https://github.com/Itseez/opencv.git git clone ...
  • opencv安装及测试

    千次阅读 2019-01-12 18:51:06
    1下载安装Anaconda3 官网地址https://www.anaconda.com 点击图片中的download 2:下载好了打开Anaconda Prompt 3:设置镜像源 我用的是清华的镜像 conda config --add channels ...
  • windows环境下的Anaconda安装OpenCV机器视觉环境搭建

    万次阅读 多人点赞 2018-05-30 17:43:32
    windows环境下的Anaconda安装OpenCV机器视觉环境搭建 本文介绍win7和win10系统下通过Anaconda配置基于python语言的机器视觉编程环境(博主测试了两个系统下的安装基本相同),主要内容包括: Anaconda下载安装...
  • opencv安装配置及依赖项

    千次阅读 2015-11-13 16:48:21
    文章链接:http://blog.csdn.net/poem_qianmo/article/details/19809337 作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442 邮箱: happylifemxy@163.com 知乎:...写作当前博文时配
  • win+vs下opencv安装与说明

    千次阅读 2019-04-26 22:46:18
    win+opencv+vs安装。一劳永逸配置,每次创建新项目无需修改。 因为刚接触opencv,再加上对VS不熟悉,所以刚接触时候有些抽象,故记录下来。 装机环境:win10-64位,opencv346,VS2017 opencv电子书:...
  • linux下查看opencv安装路径以及版本号

    万次阅读 多人点赞 2018-03-20 16:12:37
    查看linux下的opencv安装库:pkg-config opencv --libs查看linux下的opencv安装版本:pkg-config opencv --modversion查看linux下的opencv安装路径:sudo find / -iname "*opencv*"在全盘上不区分大小写,...
  • Ubuntu下Opencv安装总结

    千次阅读 2019-05-27 16:43:23
    opencv是一个开源图形图像处理库,在计算机视觉中有着广泛应用,本篇主要总结opencv的各种安装:源码安装以及在线安装 Opencv在线安装方式 在线安装opencv十分简单,通过命令行就可以直接下载对应的库文件,便可以...
  • opencv_contrib安装笔记

    万次阅读 多人点赞 2019-06-23 01:15:50
    文章目录一、下载与安装二、编译opencv三、编译opencv_contrib四、visual studio 编译五、配置opencv环境配置系统环境变量重新配置项目环境六、测试 近来由于需要用到opencv的SIFT特征,但是SIFT等功能已经移入了...
  • 为啥配置opencv2.4.4后,用VS2012运行下面代码时会出现“fatal error LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突”? #define _CRT_SECURE_NO_DEPRECATE #include #include "opencv/cv.h" #...
  • Ubuntu下openCV安装

    千次阅读 2015-06-12 11:35:11
    目标:如题 ...2.openCV : linux版opencv-2.4.9(opencv-2.4.9.zip) 3.cmake : cmake version 2.8.12.2 步骤: 1.安装依赖包 更新 sudo apt-get update 安装 sudo apt-get install libgtk

空空如也

1 2 3 4 5 ... 20
收藏数 293,057
精华内容 117,222
关键字:

opencv