配色: 字号:
OpenCV入门教程
2016-12-19 | 阅:  转:  |  分享 
  
OpenCV入门教程作者:于仕琪shiqi.yu@szu.edu.cnhttp://www.opencv.
org.cn/http://www.opencv.org.cnhttp://www.opencv.org.cn/2012年8月
版权所有?于仕琪本作品采用知识共享署名-相同方式共享4.0国际许可协议进行许可。前言OpenCV是一个广受
欢迎的开源计算机视觉库,它提供了很多函数,实现了很多计算机视觉算法,算法从最基本的滤波到高级的物体检测皆有涵盖。很多初学者希望快速
掌握OpenCV的使用方法,但往往会遇到各种各样的困难。其实仔细分析,造成这些困难的原因有两类:第一类是C/C++编程基础不
过关;第二类是不了解算法原理。解决这些困难无非提升编程能力,以及提升理论基础知识。提升编程能力需要多练习编程,提升理论知识需要系统
学习《数字图像处理》、《计算机视觉》和《模式识别》等课程,所有这些都不能一蹴而就,需要耐下心来认真修炼。同时我们也需要认识到O
penCV只是一个算法库,能为我们搭建计算机视觉应用提供“砖头”。我们并不需要完全精通了算法原理之后才去使用OpenCV,只要
了解了“砖头”的功能,就可以动手了。在实践中学习才是最高效的学习方式。本小册子希望为初学者提供引导,使初学者快速了解OpenCV
的基本数据结构以及用法。此外,如您发现有错误之处,欢迎来信指正。于仕琪深圳大学插播广告:欢迎有能力、有激情以及对计算
机视觉有兴趣的同学报考我的研究生。欲了解详情可以访问深圳大学招生网http://zsb.szu.edu.cn/http://zs
b.szu.edu.cn/http://zsb.szu.edu.cn/或者给我发email。目录第1章预备知识.
.................................................................
.............................51.1编程的流程........................
................................................................
51.2什么叫编辑......................................................
..................................61.3什么叫编译...................
.................................................................
....61.4什么叫连接.................................................
.......................................71.5什么叫运行..............
.................................................................
.........71.6VisualC++是什么...................................
...............................................81.7头文件........
.................................................................
.......................91.8库文件................................
..............................................................10
1.9OpenCV是什么.................................................
..................................111.10什么是命令行参数..............
............................................................121
.11常见编译错误.......................................................
...........................131.11.1找不到头文件....................
.....................................................131.11.2拼
写错误.............................................................
....................141.12常见链接错误..............................
....................................................151.13运行时错
误................................................................
......................17第2章OpenCV介绍.......................
............................................................192
.1OpenCV的来源...................................................
................................192.2OpenCV的协议..............
.................................................................
....19第3章图像的基本操作...........................................
......................................213.1图像的表示..............
.................................................................
.......213.2Mat类............................................
.....................................................233.3创建M
at对象...........................................................
......................243.3.1构造函数方法..........................
.................................................243.3.2create
()函数创建对象........................................................
.......253.3.3Matlab风格的创建对象方法...............................
....................263.4矩阵的基本元素表达............................
..........................................263.5像素值的读写.........
.................................................................
........273.5.1at()函数........................................
..............................................283.5.2使用迭代器...
.................................................................
...........293.5.3通过数据指针.....................................
......................................303.6选取图像局部区域...........
...............................................................3
23.6.1单行或单列选择.................................................
......................323.6.2用Range选择多行或多列.................
......................................333.6.3感兴趣区域...........
.................................................................
...333.6.4取对角线元素.............................................
..............................343.7Mat表达式...................
.................................................................
.....343.8Mat_类..............................................
..................................................363.9Mat类的内
存管理.............................................................
................383.10输出......................................
............................................................403
.11Mat与IplImage和CvMat的转换..................................
...............423.11.1Mat转为IplImage和CvMat格式............
..............................423.11.2IplImage和CvMat格式转为Ma
t...........................................42第4章数据获取与存储..
.................................................................
..............444.1读写图像文件.....................................
.............................................444.1.1读图像文件....
.................................................................
..........444.1.2写图像文件.......................................
........................................454.2读写视频.............
.................................................................
............474.2.1读视频.......................................
................................................474.2.2写视频...
.................................................................
...................49第1章预备知识OpenCV是一个功能强大的计算机视觉库,要用好它,除了要
具有相关的计算机视觉理论知识外,还需要具有一定的编程能力。本书作者通过对OpenCV中文论坛中的大量问题观察,发现有很大比例的
问题是因为用户对C/C++语言不熟练,导致出错,或出错后不知如何解决。如果对C/C++语言不熟悉,那使用OpenCV时会满
头雾水瞎摸索,费心费力。在这一章中,将介绍一些编程的基本概念,让读者对编程的流程有一个基本了解。这样在出现错误时,可以快速确定错
误的类型,并知道该如何解决。1.1编程的流程一个编程的基本流程包括编辑、编译和连接三大步骤。其流程图如图1.1所示。编
辑编译a.cppa.objopencv_core.lib编辑编译b.cppb.obj连接main.exe图
1.1编程的基本流程1.2什么叫编辑编辑(edit)代码即编写代码,是编程的第一步。你可以任意一个编辑器进行代码的编写。你
可以使用Windows自带的“记事本”来编写代码,也可以使用Notepad++,或者VisualStudio提供的编辑器
。图1.2使用Windows自带的记事本编辑代码虽然可以使用记事本软件编辑代码,但是记事本软件的功能非常有限。缺少常
用的语法高亮,自动缩进等功能。所以可以使用其他功能更丰富的编辑器,如Notepad++(图1.3)等。图1.3使用No
tepad++软件编辑代码1.3什么叫编译编译(compile)是将用某种编程语言(如C++语言)写成的源代码,转换成目标
文件。目标文件包含着机器代码(可直接被计算机中央处理器执行)以及代码在运行时使用的数据。编译器(compiler)是实现这一目的的
软件。编译器有很多,如在Windows下有微软公司的cl.exe,在Linux下有gcc和g++。在命令行下使用
cl.exe对hello.cpp源代码进行编译,如图1.4所示。编译后,将得到目标文件hello.obj,如图1.5
所示。图1.4在命令行下使用cl.exe对hello.cpp进行编译图1.5编译后,将新生成hello
.obj目标文件1.4什么叫连接连接(link)是将多个目标文件,以及库文件生成可执行的文件(或静态库、或动态库)的过程。
连接器(linker)是实现这一目的的软件。常用的连接器有Windows下的link.exe,Linux下的ld等。在
Windows下可以使用link.exe将前面生成的hello.obj连接为可执行文件。在命令行下效果如所图1.6
示。连接后,将生成可执行文件,如图1.7所示。图1.6在命令行下使用link.exe对hello.obj进行连
接图1.7连接后,将新生成hello.exe可执行文件1.5什么叫运行运行(run)较容易理解,我们在Wind
ows资源管理器里用鼠标双击exe可执行程序,可以使程序被载入CPU运行。我们也可以在命令行窗口中输入可执行程序的文件名
运行,如图1.8所示。图1.8在命令行窗口中运行hello.exe,可以看到程序打印到标准输出的结果。1.6Vi
sualC++是什么通过前面的介绍,可以看到一个编程的流程:编辑->编译->连接->运行。更具体来说,完成这个流程需要你:打
开记事本软件,编辑代码,并保存;在命令行下运行编译器,对代码进行编译,生成目标文件;在命令行下运行连接器,将目标文件连接起来,
生成可执行程序;在命令行下,或Windows资源管理器中运行程序,验证程序的正确性。如果你的项目只有一个源代码文件,完成上
面四个步骤尚可接受。但是如果你的项目包括几十个甚至几百个源文件,如无其他软件辅助,只用上面四个非常基本的步骤进行编程开发,会让人抓
狂。集成开发环境(IntegratedDevelopmentEnvironment,简称IDE)可以帮助你对项目进行管理。
常用的IDE有微软公司的VisualStudio,里面包含VisualC++,VisualC#等,其他的还有Ec
lipse、NetBeans、Delphi等。因此我们平时所说的VC不是一种编程语言,也不是编译器,它只是一个IDE。I
DE一般包含编辑器。IDE自带的编辑器一般都针对编程语言进行了定制,实现语法高亮、自动缩进、自动补全等方便的功能。IDE还提
供丰富的菜单和按钮工具,如图1.9、图1.10和图1.11所示。如果你点击IDE中的“生成(build)”按钮(图
1.11),或者点击菜单“生成(build)”中的菜单项“生成项目(buildproject)”,那么IDE会去调用编译器
cl.exe和连接器link.exe来生成可执行程序。如果你在调试状态下,还会去调用调试器(debugger)。IDE会
提升程序开发的效率,特别是调试程序的效率。图1.9微软VisualStdio集成开发环境图1.10Visua
lStdio中的编辑按钮图1.11VisualStdio中的生成程序按钮1.7头文件在编程过程中,程序代码往
往被拆成很多部分,每部分放在一个独立的源文件中,而不是将所有的代码放在一个源文件中。考虑一个简单的小例子:程序中有两个函数mai
n()和foo()。main()函数位于main.cpp,foo()函数位于foo.cpp,main()函数中调用foo
()函数。在编译阶段,由于编译是对单个文件进行编译,所以编译main.cpp时,编译器不知道是否存在foo()函数以及fo
o()调用是否正确,因此需要头文件辅助。也就是说,在编译命令:cl.exe/cmain.cpp运行时,编译器不知道foo
的用法是否正确(因为foo在另一个文件foo.cpp中),只有借助头文件中的函数声明来判断。对main.cpp进行编
译时,不会涉及foo.cpp文件,只会涉及main.cpp和foo.h(因为foo.h被include)文件。头文
件的作用如图1.1所示。#include"foo.h"intmain(){inti=foo(3,4);
returni;}.foo()1函数这样用对main.cpp#include"foo.h"intfo
o(inti,intj){……}intfoo(inti,intj);2.函数声明是intfoo
(inti,intj),你的调用看上去是对的。foo.hfoo.cpp图1.12对main.cpp进行编译时
,需要利用头文件中的foo()函数声明来确认main.cpp中对foo()的调用是正确的1.8库文件库文件中包含一系
列的子程序。例如在上一节的例子中,foo.cpp源文件中实现了foo()函数,我们假设foo()函数是包含重要算法的函数,我
们需要将foo()函数提供给客户使用,但是不希望客户看到算法源代码。为了达到这一目的,我们可以将foo.cpp编译程库文件
(图1.13),库文件是二进制的,在库文件中是看不到原始的源代码的。库和可执行文件的区别是,库不是独立程序,他们是向其他程序提供
服务的代码。当然使用库文件的好处不仅仅是对源代码进行保密,使用库文件还可以减少重复编译的时间,增强程序的模块化。将库文件连接到程
序中,有两种方式,一种是静态连接库,另一种是动态连接库。如果希望了解更多关于库文件的知识,请查阅相关资料,再次不详细分析它们之间的
异同。编辑main.cpp编译foo.libmain.objmain.exe连接编辑foo.cpp编译foo
.obj用户开发者图1.13库是二进制的文件,里面包含一系列子程序(图有问题)1.9OpenCV是什么Open
CV其实就是一堆C和C++语言的源代码文件,这些源代码文件中实现了许多常用的计算机视觉算法。例如C接口函数cvCan
ny()实现了Canny边缘提取算法。可以直接将这些源代码添加到我们自己的软件项目中,而不需要自己再去写代码实现Canny
算法,也就是不需要重复“造轮子”。由于OpenCV中源代码文件巨多,根据算法的功能,将这些源文件分到多个模块中:core、i
mgproc、highgui等。将每个模块中的源文件编译成一个库文件(如opencv_core.lib、opencv_imgp
roc.lib、opencv_highgui.lib等),用户在使用时,仅将所需的库文件添加到自己的项目中,与自己的源文件一起连
接成可执行程序则可。1.10什么是命令行参数C/C++语言中的main函数,经常带有参数argc,argv,如下:i
ntmain(intargc,charargv)或者intmain(intargc,charargv[]
)在上面代码中,argc表示命令行输入参数的个数(以空白符分隔),argv中存储了所有的命令行参数。假如你的程序是hell
o.exe,如果在命令行运行该程序(如图1.14。首先应该在命令行下用cd命令进入到hello.exe文件所在目录),运
行命令为:hello.exeShiqiYu那么,argc的值是3,argv[0]是"hello.exe",argv[1
]是"Shiqi",argv[2]是"Yu"。图1.14使用命令行参数运行程序下面的程序演示argc和argv的
使用:#includeintmain(intargc,charargv){inti;
for(i=0;igv[i]);return0;}假如上述代码编译为hello.exe,那么运行hello.exeabc
de将得到Argument0ishello.exe.Argument1isa.Argument2is
b.Argument3isc.Argument4isd.Argument5ise.运行hello.ex
elena.jpg将得到Argument0ishello.exe.Argument1islena.jpg.1
.11常见编译错误在编程中,经常会出现各种错误。出现错误后,不要闭眼抱头作痛苦状。出现错误后,需要做的第一件事情是阅读出错信息
。出错信息虽然看似凌乱,但是能够提供很多有价值的信息,帮你解决问题。1.11.1找不到头文件找不到头文件往往会提示如下错误:
hello.cpp(2):fatalerrorC1083:Cannotopenincludefile:''open
cv2/opencv.hppp'':Nosuchfileordirectory找不到头文件一般有两个原因:一个是头文件的
文件名拼写错误;或者未将头文件所在路径添加到开发环境中。上例中的错误是文件名拼写错误,opencv2/opencv.hpp被错
误地拼写为opencv2/opencv.hppp。如果文件名拼写正确,编译器还是找不到头文件,则需要将头文件所在路径添加到相应的
变量中。如在VisualStudio2010中,需要在项目属性(ProjectProperty)对话框中设置头文件路径。
具体位置在对话框“VC++Directories”里面的“IncludeDirectories”中,如图1.15所示。
图1.15头文件所在路径设置1.11.2拼写错误在编程中,拼写错误也是一类常见错误。如图1.16所示代码中,将i
mread函数错误地拼成imreadd,编译器会提示错误:hello.cpp(9):errorC3861:''imread
d'':identifiernotfound这句错误提示的意思是说无法找到imreadd标识符,因此我们需要仔细检查i
mreadd找不到的原因。假如你真的有一个函数是imreadd,但是找不到,可能的原因是声明imreadd的头文件未使用
include语句包含到源文件中。图1.16拼写错误,将imread拼成了imreadd,会造成编译时错误。如果
源代码不符合语法规则,则会造成编译错误。编译错误往往是由于编写代码不仔细造成,比如拼写错误、漏了半个括号、漏了分号等。因此一旦遇到
便宜错误,你需要按照错误提示,定位到出错的位置,仔细检查语法是否符合规范。1.12常见链接错误如果你的代码符合语法规则,则会
通过编译过程。编译完所有源代码之后,下一步是连接目标文件,以形成可执行文件。连接过程中最常见的错误如下(图1.17):1>he
llo.obj:errorLNK2019:unresolvedexternalsymbol"classcv::Ma
t__cdeclcv::imread(classstd::basic_stringr_traits,classstd::allocator>const&,int)"(?imre
ad@cv@@YA?AVMat@1@ABV?$basic_string@DU?$char_traits@D@std@@V?$all
ocator@D@2@@std@@H@Z)referencedinfunction_main这个错误信息里最关键的词是“
unresolvedexternalsymbol”,更具体的意思是在main函数中使用了imread函数,但是无法从外
部找到imread。imread函数是OpenCV的函数,不是用户自己实现的函数。opencv.hpp头文件告诉编译器有
个imread函数可以用,编译通过;但是到了连接时,连接器却找不到imread的具体实现,故出错。图1.17连接错
误,无法找到imread等函数的实现要解决这一问题,需要将依赖的库文件添加到项目设置中。具体位置在对话框“Linker-
Input”里面的“AdditionalDependencies”中,如图1.18所示。图1.18添加依赖的库文件
1.13运行时错误经过编译和连接过程,生成了可执行的文件(如exe文件)之后,在运行这个可执行文件所产生的错误是运行时错误
。比较常见的运行时错误是内存错误。比如下面这段代码:#include#includepencv.hpp>usingnamespacecv;intmain(){printf("Hello,Open
CV!\n");Matimg=imread("lena.jpg");Matgray;cv::cvtColor
(img,gray,CV_BGR2GRAY);return0;}编译和连接过程无任何问题,但在运行时弹出如图1.
19所示对话框,并在命令行窗口输出错误信息(图1.20)。图1.19运行时错误对话框图1.20运行时错误的出错
信息错误信息中提示color.cpp文件的第2834行有错,错误原因是条件(scn==3||scn==4)不成立。很多
OpenCV用户看到此错误信息一头雾水,不知如何下手解决。根据程序源代码的意思,是将三通道的BGR图像img转为单通道
的图像gray。但是程序说img既不是3通道,也不是4通道。而根据imread函数的文档,imread将图像作
为彩色图像读入,条件(scn==3||scn==4)肯定成立。这个程序的问题出现在当前目录下无lena.jpg文件,这样程序
无法读到图像,造成cvtColor函数出错。因此对于读入图像时,需要检查图像读入是否成功,以免造成运行时错误。在程序编写中,
对于数组和指针等,要特别地小心。因为对于空指针以及数组越界等问题,编译器无法在编译时给出错误提示。这类错误一旦在运行时发生,排除起
来非常困难。第2章OpenCV介绍OpenCV的全称是OpenSourceComputerVisionL
ibrary,是一个开放源代码的计算机视觉库。OpenCV是最初由英特尔公司发起并开发,以BSD许可证授权发行,可以在商业和
研究领域中免费使用,现在美国WillowGarage为OpenCV提供主要的支持。OpenCV可用于开发实时的图像处理
、计算机视觉以及模式识别程序,目前在工业界以及科研领域广泛采用。2.1OpenCV的来源OpenCV诞生于Intel。
Intel最初希望提供一个计算机视觉库,使之能充分发掘CPU的计算能力,当然更希望以此促进Intel的产品的销售。Ope
nCV最初的开发工作是由Intel在俄罗斯的团队实现。这里面有两个关键人物,一个是Intel性能团队(Intel’sP
erformanceLibraryTeam)的李信弘(ShinnLee)先生,他是团队的经理,负责IPP等库,给予Op
enCV很大的支持。另一个关键人物是VadimPisarevsky,Vadim在Intel负责OpenCV的项目管
理、代码集成、代码优化等工作。在后期Intel支持渐少的时候,是VadimPisarevsky一直在维护着OpenCV
。2007年6月,受本书作者之邀,李信弘和VadimPisarevsky作为嘉宾参加了在北京举行的“开放源代码计算机
视觉库(OpenCV)研讨会”,并做了非常有价值的报告。在2008年,一家美国公司,WillowGarage,开始大力支持
OpenCV,VadimPisarevsky和GaryBradski都加入了WillowGarage。GaryB
radski也是OpenCV开发者中的元老级人物,他曾出版《LeaningOpenCV》一书,广受欢迎。WillowG
arage是一家机器人公司,致力于为个人机器人开发开放的硬件平台和软件。现在已经开发了PR2机器人,并支持ROS、Open
CV、PCL等软件。ROS(RobotOperatingSystem)是用于机器人的操作系统,是一个开放源代码的软件,O
penCV作为ROS的视觉模块嵌入。自从获得WillowGarage支持后,OpenCV的更新速度明显加快。大量的
新特性被被加入OpenCV中,很多算法都是最近一两年的新的科研成果。OpenCV正日益成为算法研究和产品开发不可缺少的工具。
2.2OpenCV的协议OpenCV采用BSD协议,这是一个非常宽松的协议。简而言之,用户可以修改OpenCV的
源代码,可以将OpenCV嵌入到自己的软件中,可以将包含OpenCV的软件销售,可以用于商业产品,也可以用于科研领域。BS
D协议并不具有“传染性”,如果你的软件中使用了OpenCV,你不需要公开代码。你可以对OpenCV做任何操作,协议对用户的
唯一约束是要在软件的文档或者说明中注明使用了OpenCV,并附上OpenCV的协议。在这个宽松协议下,企业可以在OpenC
V基础之上进行产品开发,而不需要担心版权问题(当然你要注明使用了OpenCV,并附上OpenCV的协议)。科研领域的研究者
,可以使用OpenCV快速地实现系统原型。因此可以这样说,OpenCV的协议保证了计算机视觉技术快速的传播,让更多的人从O
penCV受益。第3章图像的基本操作3.1图像的表示在正式介绍之前,先简单介绍一下数字图像的基本概念。如图3.1
中所示的图像,我们看到的是Lena的头像,但是计算机看来,这副图像只是一堆亮度各异的点。一副尺寸为M×N的图像可以用
一个M×N的矩阵来表示,矩阵元素的值表示这个位置上的像素的亮度,一般来说像素值越大表示该点越亮。如图3.1中白色圆圈内
的区域,进行放大并仔细查看,将会如图3.2所示。图3.1Lena的照片图3.2图3.1中圆圈处的放大效果
一般来说,灰度图用2维矩阵表示,彩色(多通道)图像用3维矩阵(M×N×3)表示。对于图像显示来说,目前大部分设备
都是用无符号8位整数(类型为CV_8U)表示像素亮度。图像数据在计算机内存中的存储顺序为以图像最左上点(也可能是最左下点)
开始,存储如表3-1所示。表3-1灰度图像的存储示意图I00I01…I0N-1I10I11…I
1N-1…………IM-10IM-11…IM-1N-1Iij表示第i行j列的像素值。如果是多通道
图像,比如RGB图像,则每个像素用三个字节表示。在OpenCV中,RGB图像的通道顺序为BGR,存储如表3-2所
示。表3-2彩色RGB图像的存储示意图B00G00R00B01G01R01…B10G10R10B1
1G11R11……………………3.2Mat类早期的OpenCV中,使用IplImage和Cv
Mat数据结构来表示图像。IplImage和CvMat都是C语言的结构。使用这两个结构的问题是内存需要手动管理,开发者
必须清楚的知道何时需要申请内存,何时需要释放内存。这个开发者带来了一定的负担,开发者应该将更多精力用于算法设计,因此在新版本的O
penCV中引入了Mat类。新加入的Mat类能够自动管理内存。使用Mat类,你不再需要花费大量精力在内存管理上。而
且你的代码会变得很简洁,代码行数会变少。但C++接口唯一的不足是当前一些嵌入式开发系统可能只支持C语言,如果你的开发平台支持
C++,完全没有必要再用IplImage和CvMat。在新版本的OpenCV中,开发者依然可以使用IplImage和
CvMat,但是一些新增加的函数只提供了Mat接口。本书中的例程也都将采用新的Mat类,不再介绍IplImage和
CvMat。Mat类的定义如下所示,关键的属性如下方代码所示:classCV_EXPORTSMat{public:
//一系列函数.../flag参数中包含许多关于矩阵的信息,如:-Mat的标识-数据是否连续-深度
-通道数目/intflags;//矩阵的维数,取值应该大于或等于2intdims;//矩阵的行
数和列数,如果矩阵超过2维,这两个变量的值都为-1introws,cols;//指向数据的指针uchar
data;//指向引用计数的指针//如果数据是由用户分配的,则为NULLintrefcount;//其他成
员变量和成员函数...};3.3创建Mat对象Mat是一个非常优秀的图像类,它同时也是一个通用的矩阵类,可以用来
创建和操作多维矩阵。有多种方法创建一个Mat对象。3.3.1构造函数方法Mat类提供了一系列构造函数,可以方便的根据需
要创建Mat对象。下面是一个使用构造函数创建对象的例子。MatM(3,2,CV_8UC3,Scalar(0,0,255
));cout<<"M="<3,列数(宽度)为2的图像,图像元素是8位无符号整数类型,且有三个通道。图像的所有像素值被初始化为(0,0,255)
。由于OpenCV中默认的颜色顺序为BGR,因此这是一个全红色的图像。第二行代码是输出Mat类的实例M的所有像素值
。Mat重定义了<<操作符,使用这个操作符,可以方便地输出所有像素值,而不需要使用for循环逐个像素输出。该段代码的输出如
图3.3所示。图3.3例程输出内容常用的构造函数有:Mat::Mat()无参数构造方法;Mat::Mat(i
ntrows,intcols,inttype)创建行数为rows,列数为col,类型为type的图像;Mat
::Mat(Sizesize,inttype)创建大小为size,类型为type的图像;Mat::Mat(int
rows,intcols,inttype,constScalar&s)创建行数为rows,列数为col,类型为
type的图像,并将所有元素初始化为值s;Mat::Mat(Sizesize,inttype,constScal
ar&s)创建大小为size,类型为type的图像,并将所有元素初始化为值s;Mat::Mat(constMat&
m)将m赋值给新创建的对象,此处不会对图像数据进行复制,m和新对象共用图像数据;Mat::Mat(introws,
intcols,inttype,voiddata,size_tstep=AUTO_STEP)创建行数为rows
,列数为col,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由s
tep指定。Mat::Mat(Sizesize,inttype,voiddata,size_tstep=AUT
O_STEP)创建大小为size,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存
,图像的行步长由step指定。Mat::Mat(constMat&m,constRange&rowRange,c
onstRange&colRange)创建的新图像为m的一部分,具体的范围由rowRange和colRange指
定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据;Mat::Mat(constMat&m,const
Rect&roi)创建的新图像为m的一部分,具体的范围roi指定,此构造函数也不进行图像数据的复制操作,新图像与m
共用图像数据。这些构造函数中,很多都涉及到类型type。type可以是CV_8UC1,CV_16SC1,…,CV_64FC4
等。里面的8U表示8位无符号整数,16S表示16位有符号整数,64F表示64位浮点数(即double类型
);C后面的数表示通道数,例如C1表示一个通道的图像,C4表示4个通道的图像,以此类推。如果你需要更多的通道数,需要
用宏CV_8UC(n),例如:MatM(3,2,CV_8UC(5));//创建行数为3,列数为2,通道数为5的图像3.3
.2create()函数创建对象除了在构造函数中可以创建图像,也可以使用Mat类的create()函数创建图像。如果c
reate()函数指定的参数与图像之前的参数相同,则不进行实质的内存申请操作;如果参数不同,则减少原始数据内存的索引,并重新申请内
存。使用方法如下面例程所示:MatM(2,2,CV_8UC3);//构造函数创建图像M.create(3,2,CV_8U
C2);//释放内存重新创建图像需要注意的时,使用create()函数无法设置图像像素的初始值。3.3.3Matlab风
格的创建对象方法OpenCV2中提供了Matlab风格的函数,如zeros(),ones()和eyes()。这种方法
使得代码非常简洁,使用起来也非常方便。使用这些函数需要指定图像的大小和类型,使用方法如下:MatZ=Mat::zeros(
2,3,CV_8UC1);cout<<"Z="<=Mat::ones(2,3,CV_32F);cout<<"O="<O<endl<<""<表示单通道。上面代码的输出结果如图3.4所示。图3.4Matlab风格的函数例程的输出结果3.4矩阵的基本元素
表达对于单通道图像,其元素类型一般为8U(即8位无符号整数),当然也可以是16S、32F等;这些类型可以直接用uch
ar、short、float等C/C++语言中的基本数据类型表达。如果多通道图像,如RGB彩色图像,需要用三个通道来表示
。在这种情况下,如果依然将图像视作一个二维矩阵,那么矩阵的元素不再是基本的数据类型。OpenCV中有模板类Vec,可以表示一
个向量。OpenCV中使用Vec类预定义了一些小向量,可以将之用于矩阵元素的表达。typedefVec>Vec2b;typedefVecVec3b;typedefVecVec4b
;typedefVecVec2s;typedefVecVec3s;type
defVecVec4s;typedefVecVec2i;typedefVec<
int,3>Vec3i;typedefVecVec4i;typedefVec
Vec2f;typedefVecVec3f;typedefVecVec4f;
typedefVecVec6f;typedefVecVec2d;typed
efVecVec3d;typedefVecVec4d;typedefVe
cVec6d;例如8U类型的RGB彩色图像可以使用Vec3b,3通道float类型的矩阵
可以使用Vec3f。对于Vec对象,可以使用[]符号如操作数组般读写其元素,如:Vec3bcolor;//用colo
r变量描述一种RGB颜色color[0]=255;//B分量color[1]=0;//G分量color[2]=0;//
R分量3.5像素值的读写很多时候,我们需要读取某个像素值,或者设置某个像素值;在更多的时候,我们需要对整个图像里的所有像素进
行遍历。OpenCV提供了多种方法来实现图像的遍历。3.5.1at()函数函数at()来实现读去矩阵中的某个像素,或者对
某个像素进行赋值操作。下面两行代码演示了at()函数的使用方法。ucharvalue=grayim.at(
i,j);//读出第i行第j列像素值grayim.at(i,j)=128;//将第i行第j列像素值设置为128
如果要对图像进行遍历,可以参考下面的例程。这个例程创建了两个图像,分别是单通道的grayim以及3个通道的colorim
,然后对两个图像的所有像素值进行赋值,最后现实结果。#include#include"opencv2/
opencv.hpp"usingnamespacestd;usingnamespacecv;intmain(i
ntargc,charargv[]){Matgrayim(600,800,CV_8UC1);Matcol
orim(600,800,CV_8UC3);//遍历所有像素,并设置像素值for(inti=0;irayim.rows;++i)for(intj=0;jgrayim.at(i,j)=(i+j)%255;//遍历所有像素,并设置像素值
for(inti=0;ipixel[1]=j%255;//Greenpixel[2]=0;
//Redcolorim.at(i,j)=pixel;}//显示结果im
show("grayim",grayim);imshow("colorim",colorim);waitK
ey(0);return0;}需要注意的是,如果要遍历图像,并不推荐使用at()函数。使用这个函数的优点是代码的可读性
高,但是效率并不是很高。这段代码的运行结果如图3.5所示。图3.5使用at()函数遍历图像的例程的输出结果3.5.
2使用迭代器如果你熟悉C++的STL库,那一定了解迭代器(iterator)的使用。迭代器可以方便地遍历所有元素。Mat
也增加了迭代器的支持,以便于矩阵元素的遍历。下面的例程功能跟上一节的例程类似,但是由于使用了迭代器,而不是使用行数和列数来遍历,
所以这儿没有了i和j变量,图像的像素值设置为一个随机数。#include#include"op
encv2/opencv.hpp"usingnamespacestd;usingnamespacecv;int
main(intargc,charargv[]){Matgrayim(600,800,CV_8UC1);M
atcolorim(600,800,CV_8UC3);//遍历所有像素,并设置像素值MatIterator_har>grayit,grayend;for(grayit=grayim.begin(),graye
nd=grayim.end();grayit!=grayend;++grayit)grayit=
rand()%255;//遍历所有像素,并设置像素值MatIterator_colorit,colo
rend;for(colorit=colorim.begin(),colorend=colorim.en
d();colorit!=colorend;++colorit){(colorit)[0]=r
and()%255;//Blue(colorit)[1]=rand()%255;//Green(colorit
)[2]=rand()%255;//Red}//显示结果imshow("grayim",grayim);
imshow("colorim",colorim);waitKey(0);return0;}例程的输出
结果如图3.6所示。图3.6使用迭代器遍历图像的例程的输出结果3.5.3通过数据指针使用IplImage结构的
时候,我们会经常使用数据指针来直接操作像素。通过指针操作来访问像素是非常高效的,但是你务必十分地小心。C/C++中的指针操作是不进
行类型以及越界检查的,如果指针访问出错,程序运行时有时候可能看上去一切正常,有时候却突然弹出“段错误”(segmentfault
)。当程序规模较大,且逻辑复杂时,查找指针错误十分困难。对于不熟悉指针的编程者来说,指针就如同噩梦。如果你对指针使用没有自信,则
不建议直接通过指针操作来访问像素。虽然at()函数和迭代器也不能保证对像素访问进行充分的检查,但是总是比指针操作要可靠一些。如
果你非常注重程序的运行速度,那么遍历像素时,建议使用指针。下面的例程演示如何使用指针来遍历图像中的所有像素。此例程实现的操作跟第
3.5.1节中的例程完全相同。例程代码如下:#include#include"opencv2/ope
ncv.hpp"usingnamespacestd;usingnamespacecv;intmain(int
argc,charargv[]){Matgrayim(600,800,CV_8UC1);Matcolori
m(600,800,CV_8UC3);//遍历所有像素,并设置像素值for(inti=0;iim.rows;++i){//获取第i行首像素指针ucharp=grayim.ptr(i);
//对第i行的每个像素(byte)操作for(intj=0;jp[j]=(i+j)%255;}//遍历所有像素,并设置像素值for(inti=0;
iVec3b>(i);for(intj=0;j[0]=i%255;//Bluep[j][1]=j%255;//Green
p[j][2]=0;//Red}}//显示结果imshow("grayim",grayi
m);imshow("colorim",colorim);waitKey(0);return0;}
例程的输出结果如图3.7所示。图3.7使用指针遍历图像的例程的输出结果3.6选取图像局部区域Mat类提供了多种方
便的方法来选择图像的局部区域。使用这些方法时需要注意,这些方法并不进行内存的复制操作。如果将局部区域赋值给新的Mat对象,新对
象与原始对象共用相同的数据区域,不新申请内存,因此这些方法的执行速度都比较快。3.6.1单行或单列选择提取矩阵的一行或者一列
可以使用函数row()或col()。函数的声明如下:MatMat::row(inti)constMatMat::c
ol(intj)const参数i和j分别是行标和列标。例如取出A矩阵的第i行可以使用如下代码:Matli
ne=A.row(i);例如取出A矩阵的第i行,将这一行的所有元素都乘以2,然后赋值给第j行,可以这样写:A
.row(j)=A.row(i)2;3.6.2用Range选择多行或多列Range是OpenCV中新增的类,
该类有两个关键变量star和end。Range对象可以用来表示矩阵的多个连续的行或者多个连续的列。其表示的范围为从sta
rt到end,包含start,但不包含end。Range类的定义如下:classRange{public:.
..intstart,end;};Range类还提供了一个静态方法all(),这个方法的作用如同Matlab中
的“:”,表示所有的行或者所有的列。//创建一个单位阵MatA=Mat::eye(10,10,CV_32S);//
提取第1到3列(不包括3)MatB=A(Range::all(),Range(1,3));//提取B的第5至9行(不
包括9)//其实等价于C=A(Range(5,9),Range(1,3))MatC=B(Range(5,9
),Range::all());3.6.3感兴趣区域从图像中提取感兴趣区域(Regionofinterest)有两种方
法,一种是使用构造函数,如下例所示://创建宽度为320,高度为240的3通道图像Matimg(Size(320,240),
CV_8UC3);//roi是表示img中Rect(10,10,100,100)区域的对象Matroi(img,Rect(
10,10,100,100));除了使用构造函数,还可以使用括号运算符,如下:Matroi2=img(Rect(10,1
0,100,100));当然也可以使用Range对象来定义感兴趣区域,如下://使用括号运算符Matroi3=im
g(Range(10,100),Range(10,100));//使用构造函数Matroi4(img,Range(10,1
00),Range(10,100));3.6.4取对角线元素矩阵的对角线元素可以使用Mat类的diag()函数获取,该
函数的定义如下:MatMat::diag(intd)const参数d=0时,表示取主对角线;当参数d>0是,表示
取主对角线下方的次对角线,如d=1时,表示取主对角线下方,且紧贴主多角线的元素;当参数d<0时,表示取主对角线上方的次对角
线。如同row()和col()函数,diag()函数也不进行内存复制操作,其复杂度也是O(1)。3.7Mat表达式
利用C++中的运算符重载,OpenCV2中引入了Mat运算表达式。这一新特点使得使用C++进行编程时,就如同写Mat
lab脚本,代码变得简洁易懂,也便于维护。如果矩阵A和B大小相同,则可以使用如下表达式:C=A+B+1;
其执行结果是A和B的对应元素相加,然后再加1,并将生成的矩阵赋给C变量。下面给出Mat表达式所支持的运算。下
面的列表中使用A和B表示Mat类型的对象,使用s表示Scalar对象,alpha表示double值。加
法,减法,取负:A+B,A-B,A+s,A-s,s+A,s-A,-A缩放取值范围:Aalpha矩阵对应元素的乘法和除法:A
.mul(B),A/B,alpha/A矩阵乘法:AB(注意此处是矩阵乘法,而不是矩阵对应元素相乘)矩阵转置:A.t()矩
阵求逆和求伪逆:A.inv()矩阵比较运算:AcmpopB,Acmpopalpha,alphacmpopA。此处c
mpop可以是>,>=,==,!=,<=,<。如果条件成立,则结果矩阵(8U类型矩阵)的对应元素被置为255;否则置0。
矩阵位逻辑运算:AlogicopB,Alogicops,slogicopA,~A,此处logicop可以是&,|和
^。矩阵对应元素的最大值和最小值:min(A,B),min(A,alpha),max(A,B),max(A,alpha
)。矩阵中元素的绝对值:abs(A)叉积和点积:A.cross(B),A.dot(B)下面例程展示了Mat表达式的使用方
法,例程的输出结果如图3.8所示。#include#include"opencv2/opencv.
hpp"usingnamespacestd;usingnamespacecv;intmain(intargc
,charargv[]){MatA=Mat::eye(4,4,CV_32SC1);MatB=A
3+1;MatC=B.diag(0)+B.col(1);cout<<"A="<endl<<"C="<.dot(B.diag(0))<at_类Mat_类是对Mat类的一个包装,其定义如下:templateclassMat_
:publicMat{public://只定义了几个方法//没有定义新的属性};这是一个非常轻量级的包装,既然
已经有Mat类,为何还要定义一个Mat_?下面我们看这段代码:MatM(600,800,CV_8UC1);for(
inti=0;i(i);
for(intj=0;j%255);M.at(i,j)=d1;doubled2=M.at(i
,j);//此行有错}}在读取矩阵元素时,以及获取矩阵某行的地址时,需要指定数据类型。这样首先需要不停地写“
”,让人感觉很繁琐,在繁琐和烦躁中容易犯错,如上面代码中的错误,用at()获取矩阵元素时错误的使用了double类型。这种错
误不是语法错误,因此在编译时编译器不会提醒。在程序运行时,at()函数获取到的不是期望的(i,j)位置处的元素,数据已经越界,但是
运行时也未必会报错。这样的错误使得你的程序忽而看上去正常,忽而弹出“段错误”,特别是在代码规模很大时,难以查错。如果使用Mat
_类,那么就可以在变量声明时确定元素的类型,访问元素时不再需要指定元素类型,即使得代码简洁,又减少了出错的可能性。上面代码可以用
Mat_实现,实现代码如下面例程里的第二个双重for循环。#include#include"ope
ncv2/opencv.hpp"#includeusingnamespacestd;usingna
mespacecv;intmain(intargc,charargv[]){MatM(600,800,
CV_8UC1);for(inti=0;icharp=M.ptr(i);for(intj=0;j+j){doubled1=(double)((i+j)%255);//用at()读写像素时,需要指定类型M.
at(i,j)=d1;//下面代码错误,应该使用at()//但编译时不会提醒错误//运
行结果不正确,d2不等于d1doubled2=M.at(i,j);}}//在变量声明时指定
矩阵元素类型Mat_M1=(Mat_&)M;for(inti=0;i<
M1.rows;++i){//不需指定元素类型,语句简洁ucharp=M1.ptr(i);
for(intj=0;j)%255);//直接使用Matlab风格的矩阵元素读写,简洁M1(i,j)=d1;doub
led2=M1(i,j);}}return0;}3.9Mat类的内存管理使用Mat类,内
存管理变得简单,不再像使用IplImage那样需要自己申请和释放内存。虽然不了解Mat的内存管理机制,也无碍于Mat类
的使用,但是如果清楚了解Mat的内存管理,会更清楚一些函数到底操作了哪些数据。Mat是一个类,由两个数据部分组成:矩阵头(
包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵的指针,如图3.9所示。矩阵头的尺寸是常数值,但矩阵本身
的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。复制矩阵数据往往花费较多时间,因此除非有必要,不要复制大的矩阵。为了
解决矩阵数据的传递,OpenCV使用了引用计数机制。其思路是让每个Mat对象有自己的矩阵头信息,但多个Mat对象可以共享
同一个矩阵数据。让矩阵指针指向同一地址而实现这一目的。很多函数以及很多操作(如函数参数传值)只复制矩阵头信息,而不复制矩阵数据。
前面提到过,有很多中方法创建Mat类。如果Mat类自己申请数据空间,那么该类会多申请4个字节,多出的4个字节存储数
据被引用的次数。引用次数存储于数据空间的后面,refcount指向这个位置,如图3.9所示。当计数等于0时,则释放该空间
。图3.9Mat类中的数据存储示意图,refcount变量指向数据区后面,用4个字节(int类型)存储引用数目。
关于多个矩阵对象共享同一矩阵数据,我们可以看这个例子:MatA(100,100,CV_8UC1);MatB=A;
MatC=A(Rect(50,50,30,30));上面代码中有三个Mat对象,分别是A,B和C。这三者共有同一
矩阵数据,其示意图如图3.10所示。ABC矩阵头矩阵数据图3.10三个矩阵头共用共用同一矩阵数据3.10
输出从前面的例程中,可以看到Mat类重载了<<操作符,可以方便得使用流操作来输出矩阵的内容。默认情况下输出的格式是类似Ma
tlab中矩阵的输出格式。除了默认格式,Mat也支持其他的输出格式。代码如下:首先创建一个矩阵,并用随机数填充。填充的范围由
randu()函数的第二个参数和第三个参数确定,下面代码是介于0到255之间。MatR=Mat(3,2,CV
_8UC3);randu(R,Scalar::all(0),Scalar::all(255));默认格式输出的代码如下:
cout<<"R(default)="<1所示。图3.11默认格式的矩阵输出Python格式输出的代码如下:cout<<"R(python)=
"<式的矩阵输出以逗号分割的输出的代码如下:cout<<"R(csv)="<R,"csv")<:cout<<"R(numpy)="<<="<格式的矩阵输出除了Mat对象可以使用<<符号输出,其他的很多类型也支持<<输出。二维点:Point2fP(5,1);
cout<<"Point(2D)="<维点:Point3fP3f(2,6,7);cout<<"Point(3D)="<<V2中虽然引入了方便的Mat类,出于兼容性的考虑,OpenCV依然是支持C语言接口的IplImage和CvMa
t结构。如果你要与以前的代码兼容,将会涉及Mat与IplImage和CvMat的转换。3.11.1Mat转为
IplImage和CvMat格式假如你有一个以前写的函数,函数的定义为:voidmycvOldFunc(IplImag
ep,...);函数的参数需要IplImage类型的指针。Mat转为IplImage,可以用简单的等号赋值操作来
进行类型转换,这样实现:Matimg(Size(320,240),CV_8UC3);...IplImageiplim
g=img;//转为IplImage结构mycvOldFunc(&iplimg,...);//对iplimg取地址
如果要转为CvMat类型,操作类似:CvMatcvimg=img;//转为CvMat结构需要特别注意的是,类型转换
后,IplImage和CvMat与Mat共用同一矩阵数据,而IplImage和CvMat没有引用计数功能,如果上
例中的img中数据被释放,iplimg和cvimg也就失去了数据。因此要牢记不可将Mat对象提前释放。3.11.
2IplImage和CvMat格式转为MatMat类有两个构造函数,可以实现IplImage和CvMat到
Mat的转换。这两个函数都有一个参数copyData。如果copyData的值是false,那么Mat将与IplImage或
CvMat共用同一矩阵数据;如果值是true,Mat会新申请内存,然后将IplImage或CvMat的数据复制到M
at的数据区。如果共用数据,Mat也将不会使用引用计数来管理内存,需要开发者自己来管理。本书建议做此转换是将参数置为tru
e,这样内存管理变得简单。Mat::Mat(constCvMatm,boolcopyData=false)Mat::
Mat(constIplImageimg,boolcopyData=false)例子代码如下:IplImage
iplimg=cvLoadImage("lena.jpg");Matim(iplimg,true);第4章数据获取与
存储4.1读写图像文件将图像文件读入内存,可以使用imread()函数;将Mat对象以图像文件格式写入内存,可以使用
imwrite()函数。4.1.1读图像文件imread()函数返回的是Mat对象,如果读取文件失败,则会返回一个空矩
阵,即Mat::data的值是NULL。执行imread()之后,需要检查文件是否成功读入,你可以使用Mat::empt
y()函数进行检查。imread()函数的声明如下:Matimread(conststring&filename,int
flags=1)很明显参数filename是被读取或者保存的图像文件名;在imread()函数中,flag参数值有三
种情况:flag>0,该函数返回3通道图像,如果磁盘上的图像文件是单通道的灰度图像,则会被强制转为3通道;flag=0
,该函数返回单通道图像,如果磁盘的图像文件是多通道图像,则会被强制转为单通道;?flag<0,则函数不对图像进行通道转换。i
mread()函数支持多种文件格式,且该函数是根据图像文件的内容来确定文件格式,而不是根据文件的扩展名来确定。所只是的文件格式如下
:Windows位图文件-BMP,DIB;JPEG文件-JPEG,JPG,JPE;便携式网络图片-PN
G;便携式图像格式-PBM,PGM,PPM;Sunrasters-SR,RAS;TIFF文件-TIFF,TI
F;OpenEXRHDR图片-EXR;JPEG2000图片-jp2。你所安装的OpenCV并不一定能支
持上述所有格式,文件格式的支持需要特定的库,只有在编译OpenCV添加了相应的文件格式库,才可支持其格式。4.1.2写图像
文件将图像写入文件,可使用imwrite()函数,该函数的声明如下:boolimwrite(conststring&f
ilename,InputArrayimage,constvector¶ms=vector
())文件的格式由filename参数指定的文件扩展名确定。推荐使用PNG文件格式。BMP格式是无损格式,但是一般不进
行压缩,文件尺寸非常大;JPEG格式的文件娇小,但是JPEG是有损压缩,会丢失一些信息。PNG是无损压缩格式,推荐使用。
imwrite()函数的第三个参数params可以指定文件格式的一些细节信息。这个参数里面的数值是跟文件格式相关的:JPEG
:表示图像的质量,取值范围从0到100。数值越大表示图像质量越高,当然文件也越大。默认值是95。PNG:表示压缩级别,取
值范围是从0到9。数值越大表示文件越小,但是压缩花费的时间也越长。默认值是3。PPM,PGM或PBM:表示文件是以二
进制还是纯文本方式存储,取值为0或1。如果取值为1,则表示以二进制方式存储。默认值是1。并不是所有的Mat对象都可
以存为图像文件,目前支持的格式只有8U类型的单通道和3通道(颜色顺序为BGR)矩阵;如果需要要保存16U格式图像,只
能使用PNG、JPEG2000和TIFF格式。如果希望将其他格式的矩阵保存为图像文件,可以先用Mat::convert
To()函数或者cvtColor()函数将矩阵转为可以保存的格式。另外需要注意的是,在保存文件时,如果文件已经存在,imwri
te()函数不会进行提醒,将直接覆盖掉以前的文件。下面例程展示了如何读入一副图像,然后对图像进行Canny边缘操作,最后将结
果保存到图像文件中。#include#include"opencv2/opencv.hpp"usi
ngnamespacestd;usingnamespacecv;intmain(intargc,chara
rgv[]){//读入图像,并将之转为单通道图像Matim=imread("lena.jpg",0);//
请一定检查是否成功读图if(im.empty()){cout<<"Cannotloadimage."<<
endl;return-1;}//进行Canny操作,并将结果存于resultMatresul
t;Canny(im,result,50,150);//保存结果imwrite("lena-canny.png
",result);return0;}将lena.jpg文件放在当前目录,运行该例程后,lena-canny.pn
g将会出现在当前目录。lena-canny.png图像如图4.1所示,是lena.jpg的边缘提取结果。图4.1
Lena图像的边缘提取结果4.2读写视频介绍OpenCV读写视频之前,先介绍一下编解码器(codec)。如果是图像
文件,我们可以根据文件扩展名得知图像的格式。但是此经验并不能推广到视频文件中。有些OpenCV用户会碰到奇怪的问题,都是av
i视频文件,有的能用OpenCV打开,有的不能。视频的格式主要由压缩算法决定。压缩算法称之为编码器(coder),解压算法
称之为解码器(decoder),编解码算法可以统称为编解码器(codec)。视频文件能读或者写,关键看是否有相应的编解码器。编解码
器的种类非常多,常用的有MJPG、XVID、DIVX等,完整的列表请参考FOURCC网站。因此视频文件的扩展名(如avi等)往往只能表示这是一个视频文件。OpenCV2中提供了两个类来实现视频的读写。读视频的类是VideoCapture,写视频的类是VideoWriter。4.2.1读视频VideoCapture既可以从视频文件读取图像,也可以从摄像头读取图像。可以使用该类的构造函数打开视频文件或者摄像头。如果VideoCapture对象已经创建,也可以使用VideoCapture::open()打开,VideoCapture::open()函数会自动调用VideoCapture::release()函数,先释放已经打开的视频,然后再打开新视频。如果要读一帧,可以使用VideoCapture::read()函数。VideoCapture类重载了>>操作符,实现了读视频帧的功能。下面的例程演示了使用VideoCapture类读视频。#include#include"opencv2/opencv.hpp"usingnamespacestd;usingnamespacecv;intmain(intargc,charargv){//打开第一个摄像头//VideoCapturecap(0);//打开视频文件VideoCapturecap("video.short.raw.avi");//检查是否成功打开if(!cap.isOpened()){cerr<<"Cannotopenacameraorfile."<>frame;//如果未读到图像if(frame.empty())break;//将读到的图像转为灰度图cvtColor(frame,edges,CV_BGR2GRAY);//进行边缘提取操作Canny(edges,edges,0,30,3);//显示结果imshow("edges",edges);//等待30秒,如果按键则推出循环if(waitKey(30)>=0)break;}//退出时会自动释放cap中占用资源return0;}图4.2读视频例程的运行界面4.2.2写视频使用OpenCV创建视频也非常简单,与读视频不同的是,你需要在创建视频时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。编解码器使用四个字符表示,可以是CV_FOURCC(''M'',''J'',''P'',''G'')、CV_FOURCC(''X'',''V'',''I'',''D'')及CV_FOURCC(''D'',''I'',''V'',''X'')等。如果使用某种编解码器无法创建视频文件,请尝试其他的编解码器。将图像写入视频可以使用VideoWriter::write()函数,VideoWriter类中也重载了<<操作符,使用起来非常方便。另外需要注意:待写入的图像尺寸必须与创建视频时指定的尺寸一致。下面例程演示了如何写视频文件。本例程将生成一个视频文件,视频的第0帧上是一个红色的“0”,第1帧上是个红色的“1”,以此类推,共100帧。生成视频的播放效果如图4.3所示。#include#include#include"opencv2/opencv.hpp"usingnamespacestd;usingnamespacecv;intmain(intargc,charargv){//定义视频的宽度和高度Sizes(320,240);//创建writer,并指定FOURCC及FPS等参数VideoWriterwriter=VideoWriter("myvideo.avi",CV_FOURCC(''M'',''J'',''P'',''G''),25,s);//检查是否成功创建if(!writer.isOpened()){cerr<<"Cannotcreatevideofile.\n"<
献花(0)
+1
(本文系沧海一粟lh首藏)