opencv论坛基础教程

OpenCV 入门教程 于仕琪 http://www.opencv.org.cn

1 预备知识

C++语言中的 main 函数,经常带有参数 argc, argv,

int main(int argc, char** argv)
int main(int argc, char* argv[])

argc 表示命令行输入参数的个数(以空白符分隔),argv 中存储了所有的命令行参数。

常见错误

  1. 找不到头文件。一个是头文件的文件名拼写错误;或者未将头文件所在路径添加到开发环境中。
  2. 拼写错误。
  3. 链接错误。要解决这一问题,需要将依赖的库文件添加到项目设置中。
  4. 运行错误。比较常见的运行时错误是内存错误。

2 OpenCV 介绍

协议对用户的唯一约束是要在软件的文档或者说明中注明使用了OpenCV,并附上 OpenCV 的协议。

3 图像的基本操作

3.1图像的表示

灰度图用 2 维矩阵表示,彩色(多通道)图像用 3 维矩阵(M× N × 3)表示。目前大部分设备都是用无符号 8 位整数(类型为 CV_8U) 表示像素亮度。

在这里插入图片描述

在这里插入图片描述

3.2 Mat 类

早期的 OpenCV 中,使用 IplImage 和 CvMat 数据结构来表示图像。 一些新增加的函数只提供了 Mat 接口。

3.3 创建 Mat 对象

Mat 除了是个图像类,还是一个通用的矩阵类,可以用来创建和操作多维矩阵。

3.3.1 构造函数方法

Mat M(3,2, CV_8UC3, Scalar(0,0,255));
第一行代码创建一个行数(高度)为 3,列数(宽度)为 2 的图像,图像元素是 8 位无符号整数类型,且有三个通道。图像的所有像素值被初始化为(0, 0,255)。
OpenCV 中默认的颜色顺序为 BGR 。
常用的构造函数有:

Mat::Mat()
//无参数构造方法;
Mat::Mat(int rows, int cols, int type)
//创建行数为 rows,列数为 col,类型为 type 的图像;
Mat::Mat(Size size, int type)
//创建大小为 size,类型为 type 的图像;
Mat::Mat(int rows, int cols, int type, const Scalar& s)
//创建行数为 rows,列数为 col,类型为 type 的图像,并将所有元素初始化为值 s;
Mat::Mat(Size size, int type, const Scalar& s)
//创建大小为 size,类型为 type 的图像,并将所有元素初始化为值 s;
Mat::Mat(const Mat& m)
//将m赋值给新创建的对象,此处不会对图像数据进行复制, m 和新对象共用图像数据;
Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
//创建行数为 rows,列数为 col,类型为 type 的图像,此构造函数不创建图像数据所需内存,而是直接使用 data 所指内存,图像的行步长由 step指定。
Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
//创建大小为 size,类型为 type 的图像,此构造函数不创建图像数据所需内存,而是直接使用 data 所指内存,图像的行步长由 step 指定。
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
//创建的新图像为 m 的一部分,具体的范围由 rowRange 和 colRange 指定,此构造函数也不进行图像数据的复制操作,新图像与 m 共用图像数据;
Mat::Mat(const Mat& m, const Rect& roi)
//创建的新图像为 m 的一部分,具体的范围 roi 指定,此构造函数也不进行图像数据的复制操作,新图像与 m 共用图像数据。

type可以是CV_8UC1, CV_16SC1, …,CV_64FC4 等。里面的 8U 表示 8 位无符号整数, 16S 表示 16 位有符号整数, 64F表示 64 位浮点数(即 double 类型); C 后面的数表示通道数, 如果你需要更多的通道数,需要用宏CV_8UC(n).

3.3.2 create()函数创建对象

也可以使用 Mat 类的 create()函数创建图像。如果 create()函数指定的参数与图像之前的参数相同,则不进行实质的内存申请操作;如果参数不同,则减少原始数据内存的索引,并重新申请内存。

Mat M(2,2, CV_8UC3);//构造函数创建图像
M.create(3,2, CV_8UC2);//释放内存重新创建图像

使用 create()函数无法设置图像像素的初始值。

3.3.3 Matlab 风格的创建对象方法
Mat Z = Mat::zeros(2,3, CV_8UC1);//零矩阵
cout << "Z = " << endl << " " << Z << endl;
Mat O = Mat::ones(2, 3, CV_32F);//全是1
cout << "O = " << endl << " " << O << endl;
Mat E = Mat::eye(2, 3, CV_64F);//单位矩阵
cout << "E = " << endl << " " << E << endl;

未注明通道数目,这种情况下它表示单通道。

3.4 矩阵的基本元素表达

对于单通道图像,其元素类型一般为 8U(即 8 位无符号整数),当然也可以是 16S、 32F 等;这些类型可以直接用 uchar、 short、 float 等 C/C++语言中的基本数据类型表达。

多通道图像,如 RGB 彩色图像,需要用三个通道来表示。 OpenCV 中有模板类 Vec,可以表示一个向量。

typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;

8U 类型的 RGB 彩色图像可以使用 Vec3b, 3 通道 float 类型的矩阵可以使用 Vec3f。

Vec3b color; //用 color 变量描述一种 RGB 颜色
color[0]=255; //B 分量
color[1]=0; //G 分量
color[2]=0; //R 分量
3.5 像素值的读写

多种像素遍历方法

3.5.1 at()函数
uchar value = grayim.at<uchar>(i,j);//读出第 i 行第 j 列像素值
grayim.at<uchar>(i,j)=128; //将第 i 行第 j 列像素值设置为 128

如果要遍历图像,并不推荐使用 at()函数。使用这个函数的优点是代码的可读性高,但是效率并不是很高。

3.5.2使用迭代器

Mat 也增加了迭代器的支持,以便于矩阵元素的遍历。

//遍历所有像素,并设置像素值
MatIterator_<uchar> grayit, grayend;//图片开始,图片结束,图片为单通道8位无字符整数
for( grayit = grayim.begin<uchar>(), grayend =grayim.end<uchar>(); grayit != grayend; ++grayit)
*grayit = rand()%255;//随机赋值

//遍历所有像素,并设置像素值,3通道8位无字符
MatIterator_<Vec3b> colorit, colorend;//迭代器
for( colorit = colorim.begin<Vec3b>(), colorend =
colorim.end<Vec3b>(); colorit != colorend; ++colorit)
{
(*colorit)[0] = rand()%255; //Blue
(*colorit)[1] = rand()%255; //Green
(*colorit)[2] = rand()%255; //Red
}

3.5.3 通过数据指针

通过指针操作来访问像素是非常高效的。

C/C++中的指针操作是不进行类型以及越界检查的,如果指针访问出错, 程序运行时有时候可能看上去一切正常,有时候却突然弹出“段错误”(segment fault)。 不建议直接通过指针操作来访问像素。

3.6 选取图像局部区域
3.6.1 单行或单列选择

提取矩阵的一行或者一列可以使用函数 row()或 col()。函数的声明如下:

Mat Mat::row(int i) const
Mat Mat::col(int j) const
Mat line = A.row(i);//取出 A 矩阵的第 i 行可以使用如下代码
A.row(j) = A.row(i)*2;//取出 A 矩阵的第 i 行,将这一行的所有元素都乘以 2,然后赋值给第 j行

3.6.2 用 Range 选择多行或多列

Range 有两个关键变量 star 和 end。 Range 对象可以用来表示矩阵的多个连续的行或者多个连续的列。其表示的范围为从 start到 end,包含 start,但不包含 end。
Range 类还提供了一个静态方法 all(),这个方法的作用如同 Matlab 中的“:”,表示所有的行或者所有的列。

//创建一个单位阵
Mat A = Mat::eye(10, 10, CV_32S);
//提取第 1 到 3 列(不包括 3),第一个参数为选取的行,第二个参数是选取的列。
Mat B = A(Range::all(), Range(1, 3));
//提取 B 的第 5 至 9 行(不包括 9)
//其实等价于 C = A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range::all());

3.6.3 感兴趣区域

从图像中提取感兴趣区域(Region of interest) 有两种方法,一种是使用构造函数,

//创建宽度为 320,高度为 240 的 3 通道图像
Mat img(Size(320,240),CV_8UC3);
//roi 是表示 img 中 Rect(10,10,100,100)区域的对象
Mat roi(img, Rect(10,10,100,100));

除了使用构造函数,还可以使用括号运算符,
Mat roi2 = img(Rect(10,10,100,100));
也可以使用 Range 对象来定义感兴趣区域,

//使用括号运算符
Mat roi3 = img(Range(10,100),Range(10,100));
//使用构造函数
Mat roi4(img, Range(10,100),Range(10,100));

3.6.4 取对角线元素

矩阵的对角线元素可以使用 Mat 类的 diag()函数获取,该函数的定义如下:

Mat Mat::diag(int d) const

参数 d=0 时,表示取主对角线;当参数 d>0 是,表示取主对角线下方的次对角线,如 d=1 时,表示取主对角线下方,且紧贴主多角线的元素;当参数 d<0 时,表示取主对角线上方的次对角线。 其复杂度也是 O(1)。

3.7 Mat 表达式

Mat 表达式所支持的运算 ,使用 A 和 B 表示 Mat 类型的对象,使用 s 表示 Scalar 对象, alpha 表示 double 值。

加法,减法,取负: A+B, A-B, A+s, A-s, s+A, s-A, -A
缩放取值范围: A*alpha
矩阵对应元素的乘法和除法: A.mul(B), A/B, alpha/A
矩阵乘法: A*B (注意此处是矩阵乘法,而不是矩阵对应元素相乘)
矩阵转置: A.t()
矩阵求逆和求伪逆: A.inv()
矩阵比较运算: A cmpop B, A cmpop alpha, alpha cmpop A。此处 cmpop可以是>, >=, ==, !=, <=, <。如果条件成立,则结果矩阵(8U 类型矩阵)的对应元素被置为 255;否则置 0。
矩阵位逻辑运算: A logicop B, A logicop s, s logicop A, ~A,此处 logicop可以是&,I和^。
矩阵对应元素的最大值和最小值: min(A, B), min(A, alpha), max(A, B),max(A, alpha)。
矩阵中元素的绝对值: abs(A)
叉积和点积: A.cross(B), A.dot(B)
3.8 Mat_ 类

Mat_类是对 Mat 类的一个包装,是一个非常轻量级的包装。 如果使用 Mat_类,那么就可以在变量声明时确定元素的类型, 访问元素时不再需要指定元素类型,即使得代码简洁,又减少了出错的可能性。

3.9 Mat 类的内存管理

Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵的指针。

复制矩阵数据往往花费较多时间, 为了解决矩阵数据的传递, OpenCV 使用了引用计数机制。 很多函数以及很多操作(如函数参数传值)只复制矩阵头信息,而不复制矩阵数据 。

如果 Mat 类自己申请数据空间,那么该类会多申请 4 个字节,多出的 4 个字节存储数据被引用的次数。引用次数存储于数据空间的后面, refcount 指向这个位置, 当计数等于 0时,则释放该空间。

在这里插入图片描述

Mat A(100,100, CV_8UC1);
Mat B = A;
Mat C = A(Rect(50,50,30,30));

上面代码中有三个 Mat 对象,分别是 A, B 和 C。这三者共有同一矩阵数据,
其示意图如图 3.10 所示。

在这里插入图片描述

3.10 输出

Mat 类重载了<<操作符, 默认情况下输出的格式是类似 Matlab 中矩阵的输出格式。 Mat 也支持其他的输出格式。
首先创建一个矩阵,并用随机数填充。填充的范围由 randu()函数的第二个参数和第三个参数确定,下面代码是介于 0 到 255 之间。

Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));//第二个参数与第三个参数为随机数范围。
cout << "R (default) = " << endl << R << endl << endl;

在这里插入图片描述

cout << "R (python) = " << endl << format(R,"python") << endl<< endl;//Python 格式输出的代码 

在这里插入图片描述

cout << "R (csv) = " << endl << format(R,"csv" ) << endl<< endl;//以逗号分割的输出的代码

在这里插入图片描述

cout << "R (numpy) = " << endl << format(R,"numpy" ) << endl<< endl;//numpy 格式输出的代码

在这里插入图片描述

cout << "R (c) = " << endl << format(R,"C" ) << endl <<endl;//C 语言格式输出的代码

在这里插入图片描述
除了 Mat 对象可以使用<<符号输出,其他的很多类型也支持<<输出。

Point2f P(5, 1);//二维点
cout << "Point (2D) = " << P << endl << endl;
Point3f P3f(2, 6, 7);//三维点
cout << "Point (3D) = " << P3f << endl << endl;

在这里插入图片描述

在这里插入图片描述

3.11Mat 与 IplImage 和 CvMat 的转换
3.11.1Mat 转为 IplImage 和 CvMat 格式

Mat 转为 IplImage,可以用简单的等号赋值操作来进行类型转换。

Mat img(Size(320, 240), CV_8UC3);
...
IplImage iplimg = img; //转为 IplImage 结构
mycvOldFunc( & iplimg, ...);//对 iplimg 取地址

如果要转为 CvMat 类型,操作类似:

CvMat cvimg = img; //转为 CvMat 结构
3.11.2IplImage 和 CvMat 格式转为 Mat

Mat 类有两个构造函数,可以实现 IplImage 和 CvMat 到 Mat 的转换。这两个函数都有一个参数copyData。如果copyData的值是false,那么Mat将与IplImage或 CvMat 共用同一矩阵数据;如果值是 true, Mat 会新申请内存,然后将 IplImage或 CvMat 的数据复制到 Mat 的数据区。

如果共用数据, Mat 也将不会使用引用计数来管理内存,需要开发者自己来管理。建议做此转换是将参数置为 true,这样内存管理变得简单。

Mat::Mat(const CvMat* m, bool copyData=false)
Mat::Mat(const IplImage* img, bool copyData=false)
IplImage * iplimg = cvLoadImage("lena.jpg");//例子
Mat im(iplimg, true);

4 数据获取与存储

4.1 读写图像文件

将图像文件读入内存,可以使用 imread()函数;将 Mat 对象以图像文件格式写入内存,可以使用 imwrite()函数。

4.1.1 读图像文件

imread()函数返回的是 Mat 对象,如果读取文件失败,则会返回一个空矩阵,即 Mat::data 的值是 NULL。执行 imread()之后,需要检查文件是否成功读入,你可以使用 Mat::empty()函数进行检查。 imread()函数的声明如下:

Mat imread(const string& filename, int flags=1 )
    /*flag>0,该函数返回 3 通道图像,flag=0,该函数返回单通道图像,flag<0,则函数不对图像进行通道转换。*/

imread()函数支持多种文件格式,且该函数是根据图像文件的内容来确定文件格式,而不是根据文件的扩展名来确定。

Windows 位图文件 - BMP, DIB;
JPEG 文件 - JPEG, JPG, JPE;
便携式网络图片 - PNG;
便携式图像格式 - PBM, PGM, PPM;
Sun rasters - SR, RAS;
TIFF 文件 - TIFF, TIF;
OpenEXR HDR 图片 - EXR;
JPEG 2000 图片- jp2

所安装的 OpenCV 并不一定能支持上述所有格式,文件格式的支持需要特定的库,只有在编译 OpenCV 添加了相应的文件格式库,才可支持其格式。

4.1.2 写图像文件

将图像写入文件,可使用 imwrite()函数,该函数的声明如下

bool imwrite(const string& filename, InputArray image,
const vector<int>& params=vector<int>())

文件的格式由 filename 参数指定的文件扩展名确定。推荐使用 PNG 文件格式。

imwrite()函数的第三个参数 params 可以指定文件格式的一些细节信息。这个参数里面的数值是跟文件格式相关的:

  1. JPEG:表示图像的质量,取值范围从 0 到 100。数值越大表示图像质量
    越高,当然文件也越大。默认值是 95。
  2. PNG:表示压缩级别,取值范围是从 0 到 9。数值越大表示文件越小,
    但是压缩花费的时间也越长。默认值是 3。
  3. PPM, PGM 或 PBM:表示文件是以二进制还是纯文本方式存储,取值为
    0 或 1。如果取值为 1,则表示以二进制方式存储。默认值是 1。

并不是所有的 Mat 对象都可以存为图像文件,目前支持的格式只有 8U 类型的单通道和 3 通道(颜色顺序为 BGR)矩阵;如果需要要保存 16U 格式图像, 只能使用 PNG、 JPEG 2000 和 TIFF 格式。 如果希望将其他格式的矩阵保存为图像文件,可以先用 Mat::convertTo()函数或者 cvtColor()函数将矩阵转为可以保存的格式。

在保存文件时,如果文件已经存在, imwrite()函数不会进行提醒,将直接覆盖掉以前的文件。

4.2 读写视频

视频的格式主要由压缩算法决定。压缩算法称之为编码器(coder),解压算法称之为解码器(decoder),编解码算法可以统称为编解码器(codec)。视频文件能读或者写,关键看是否有相应的编解码器。编解码器的种类非常多,常用的有 MJPG、 XVID、 DIVX 等。

视频文件的扩展名(如 avi 等)往往只能表示这是一个视频文件。

4.2.1 读视频

VideoCapture 既可以从视频文件读取图像,也可以从摄像头读取图像。

如果要读一帧,可以使用 VideoCapture::read()函数。VideoCapture 类重载了>>操作符,实现了读视频帧的功能。

#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
    //打开第一个摄像头
    //VideoCapture cap(0);
    //打开视频文件
    VideoCapture cap("video.short.raw.avi");  
    if(!cap.isOpened()) //检查是否成功打开
    {  cerr << "Can not open a camera or file." << endl;
        return -1;
    }
    Mat edges;//创建窗口
    namedWindow("edges",1);
    for(;;)
    {  Mat frame;
    cap >> frame;//从 cap 中读一帧,存到 frame
    if(frame.empty())//如果未读到图像
    break;
    cvtColor(frame, edges, CV_BGR2GRAY); //将读到的图像转为灰度图
    Canny(edges, edges, 0, 30, 3); //进行边缘提取操作
    imshow("edges", edges);//显示结果
    if(waitKey(30) >= 0) //等待 30 秒,如果按键则推出循环
    break;
    }
    return 0;
}

4.2.2 写视频

在创建视频时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。编解码器使用四个字符表示, 可以是 CV_FOURCC(‘M’,‘J’,‘P’,‘G’)、 CV_FOURCC(‘X’,‘V’,‘I’,‘D’)及CV_FOURCC(‘D’,‘I’,‘V’,‘X’)等。 使用某种编解码器无法创建视频文件,请尝试其他的编解码器。

将图像写入视频可以使用 VideoWriter::write()函数, VideoWriter 类中也重载了<<操作符, 待写入的图像尺寸必须与创建视频时指定的尺寸一致。

#include <stdio.h>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc, char** argv)50
{
//定义视频的宽度和高度
Size s(320, 240);
//创建 writer,并指定 FOURCC 及 FPS 等参数
VideoWriter writer = VideoWriter("myvideo.avi",
CV_FOURCC('M','J','P','G'), 25, s);
//检查是否成功创建
if(!writer.isOpened())
{
cerr << "Can not create video file.\n" << endl;
return -1;
}
//视频帧
Mat frame(s, CV_8UC3);
for(int i = 0; i < 100; i++)
{
//将图像置为黑色
frame = Scalar::all(0);
//将整数 i 转为 i 字符串类型
char text[128];
snprintf(text, sizeof(text), "%d", i);
//将数字绘到画面上
putText(frame, text, Point(s.width/3, s.height/3),
FONT_HERSHEY_SCRIPT_SIMPLEX, 3,
Scalar(0,0,255), 3, 8);
//将图像写入视频
writer << frame;
}
//退出程序时会自动关闭视频文件
return 0;
}

putText的参数详解:图片,添加的文字(string类型),左下角的位置,字体,字体大小,颜色,字体粗细,线型(默认8邻域)。

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值