1.1基础图像容器Mat类的使用

1.1.1基本图像容器Mat

在正式讲解OpenCV之前,首先要介绍的就是Mat。
在2001年刚刚出现的时候,OpenCV基于 C 语言接口而建。为了在内存(memory)中存放图像,当时采用名为 IplImage 的C语言结构体,时至今日这仍出现在大多数的旧版教程和教学材料。但这种方法必须接受C语言所有的不足,这其中最大的不足要数手动内存管理,其依据是用户要为开辟和销毁内存负责。虽然对于小型的程序来说手动管理内存不是问题,但一旦代码开始变得越来越庞大,你需要越来越多地纠缠于这个问题,而不是着力解决你的开发目标。
幸运的是,C++出现了,并且带来类的概念,这给用户带来另外一个选择:自动的内存管理(不严谨地说)。这是一个好消息,如果C++完全兼容C的话,这个变化不会带来兼容性问题。为此,OpenCV在2.0版本中引入了一个新的C++接口,利用自动内存管理给出了解决问题的新方法。使用这个方法,你不需要纠结在管理内存上,而且你的代码会变得简洁(少写多得)。但C++接口唯一的不足是当前许多嵌入式开发系统只支持C语言。所以,当目标不是这种开发平台时,没有必要使用旧方法。
关于 Mat ,首先要知道的是你不必再手动地(1)为其开辟空间(2)在不需要时立即将空间释放。但手动地做还是可以的:大多数OpenCV函数仍会手动地为输出数据开辟空间。当传递一个已经存在的 Mat 对象时,开辟好的矩阵空间会被重用。也就是说,我们每次都使用大小正好的内存来完成任务。
基本上讲 Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。因此,当在程序中传递图像并创建拷贝时,大的开销是由矩阵造成的,而不是信息头。OpenCV是一个图像处理库,囊括了大量的图像处理函数,为了解决问题通常要使用库中的多个函数,因此在函数中传递图像是家常便饭。同时不要忘了我们正在讨论的是计算量很大的图像处理算法,因此,除非万不得已,我们不应该拷贝 大 的图像,因为这会降低程序速度。
为了搞定这个问题,OpenCV使用引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则只拷贝信息头和矩阵指针 ,而不拷贝矩阵。

Mat A, C;                                 // 只创建信息头部分
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存
Mat B(A);                                 // 使用拷贝构造函数
C = A;                                    // 赋值运算符

以上代码中的所有Mat对象最终都指向同一个也是唯一一个数据矩阵。虽然它们的信息头不同,但通过任何一个对象所做的改变也会影响其它对象。实际上,不同的对象只是访问相同数据的不同途径而已。这里还要提及一个比较棒的功能:你可以创建只引用部分数据的信息头。比如想要创建一个感兴趣区域( 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

Rect(10,10,100,100):创建一个矩形对象,通过使用四个整数来初始化矩形左上角的横坐标、纵坐标以及右下角的横坐标、纵坐标。
Range:确定一个连续的序列,Range:all()表示获取整个序列,Range(1,3)表示获取第一列到第三列。
现在你也许会问,如果矩阵属于多个 Mat 对象,那么当不再需要它时谁来负责清理?简单的回答是:最后一个使用它的对象。通过引用计数机制来实现。无论什么时候有人拷贝了一个 Mat 对象的信息头,都会增加矩阵的引用次数;反之当一个头被释放之后,这个计数被减一;当计数值为零,矩阵会被清理。但某些时候你仍会想拷贝矩阵本身(不只是信息头和矩阵指针),这时可以使用函数 clone() 或者 “`

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

现在改变 F 或者 G 就不会影响 Mat 信息头所指向的矩阵。总结一下,你需要记住的是
 OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)。
 使用OpenCV的C++接口时不需要考虑内存释放问题。
 赋值运算符和拷贝构造函数( ctor )只拷贝信息头。
 使用函数 clone() 或者 copyTo() 来拷贝一副图像的矩阵。
关于Mat更详细的内容请参考…\opencv\sources\modules\core\include\opencv2\core\mat.hpp文件的730行开始,源码中注释很详细,笔者就不细说了,由于内容太多,笔者只是截取部分内容,有兴趣的请去看源码吧。
这里写图片描述
前面讲解了什么是Mat,那么对于Mat,是如何存储图像的呢,在这里要讲述如何存储像素值。首先需要指定颜色空间和数据类型。颜色空间是指对一个给定的颜色,如何组合颜色元素以对其编码。最简单的颜色空间要属灰度级空间,只处理黑色和白色,对它们进行组合可以产生不同程度的灰色。
对于彩色方式则有更多种类的颜色空间,但不论哪种方式都是把颜色分成三个或者四个基元素,通过组合基元素可以产生所有的颜色。RGB颜色空间是最常用的一种颜色空间,这归功于它也是人眼内部构成颜色的方式。它的基色是红色、绿色和蓝色,有时为了表示透明颜色也会加入第四个元素 alpha (A)。
有很多的颜色系统,各有自身优势:
 RGB是最常见的,这是因为人眼采用相似的工作机制,它也被显示设备所采用。
 HSV和HLS把颜色分解成色调、饱和度和亮度/明度。这是描述颜色更自然的方式,比如可以通过抛弃最后一个元素,使算法对输入图像的光照条件不敏感。
 YCrCb在JPEG图像格式中广泛使用。
 CIE L*a*b*是一种在感知上均匀的颜色空间,它适合用来度量两个颜色之间的距离 。
关于各个颜色之间的相互转换,笔者会在以后的文章中会详细讲解。
每个组成元素都有其自己的定义域,取决于其数据类型。如何存储一个元素决定了我们在其定义域上能够控制的精度。最小的数据类型是 char ,占一个字节或者8位,可以是有符号型(0到255之间)或无符号型(-127到+127之间)。尽管使用三个 char 型元素已经可以表示1600万种可能的颜色(使用RGB颜色空间),但若使用float(4字节,32位)或double(8字节,64位)则能给出更加精细的颜色分辨能力。但同时也要切记增加元素的尺寸也会增加了图像所占的内存空间。
接下来,Mat里面depth,dims,channels,step,data,elemSize和数据地址计算的理解,矩阵 (M) 中数据元素的地址计算公式:
这里写图片描述
其中,其中 m = M.dims是 M的维度。
 data:Mat对象中的一个指针,指向内存中存放矩阵数据的一块内存 (uchar* data);
 dims:Mat所代表的矩阵的维度,如 3 * 4 的矩阵为 2 维, 3 * 4 * 5 的为3维;
 channels:通道,矩阵中的每一个矩阵元素拥有的值的个数,比如说 3 * 4 矩阵中一共 12 个元素,如果每个元素有三个值,那么就说这个矩阵是 3 通道的,即 channels = 3。常见的是一张彩色图片有红、绿、蓝三个通道。;depth:深度,即每一个像素的位数(bits),在opencv的Mat.depth()中得到的是一个 0 – 6 的数字,分别代表不同的位数:enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 可见 0和1都代表8位, 2和3都代表16位,4和5代表32位,6代表64位;;
 step:是一个数组,定义了矩阵的布局,具体见下面图片分析,另外注意 step1 (step / elemSize1),M.step[m-1] 总是等于 elemSize,M.step1(m-1)总是等于 channels;;
 elemSize : 矩阵中每一个元素的数据大小,如果Mat中的数据的数据类型是 CV_8U 那么 elemSize = 1,CV_8UC3 那么 elemSize = 3,CV_16UC2 那么 elemSize = 4;记住另外有个 elemSize1 表示的是矩阵中数据类型的大小,即 elemSize / channels 的大小。
先看看一些Mat结构。

这里写图片描述

图1

【注】上图中ch表示通道;dim表示维度;
从上图可以看出第1个表示双通道的2维图像,第2个表示单通道的二维图像;第3张表示3通道的2维图像;第4张表示4通道的3维图像。
接下来笔者对二维和三维的情况具体分析。先看看二维的情况,下图是二维情况(stored row by row)按行存储。
这里写图片描述

图2二维图像表示

上面是一个 3 X 4 的矩阵,假设其数据类型为 CV_8U,也就是单通道的 uchar 类型。这是一个二维矩阵,那么维度为 2 (M.dims == 2);
M.rows == 3; M.cols == 4;
sizeof(uchar) = 1,那么每一个数据元素大小为 1 (M.elemSize() == 1, M.elemSize1() == 1);
CV_8U 得到 M.depth() == 0, M.channels() == 1;
因为是二维矩阵,那么 step 数组只有两个值, step[0] 和 step[1] 分别代表一行的数据大小和一个元素的数据大小,则 M.step[0] == 4, M.step[1] == 1;
M.step1(0) == M.cols = 4; M.step1(1) == 1;
假设上面的矩阵数据类型是 CV_8UC3,也就是三通道
M.dims == 2; M.channels() == 3;M.depth() == 0;
M.elemSize() == 3 (每一个元素包含3个uchar值) M.elemSize1() == 1 (elemSize / channels)
M.step[0] == M.cols * M.elemSize() == 12, M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;
M.step(0) == M.cols * M.channels() == 12 ; M.step(1) == M.channels() == 3;
三维情况(stored plane by plane)按面存储。
这里写图片描述

图3三维图像表示

上面是一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型。
M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;
M.rows == M.cols == –1;
M.elemSize() == M.elemSize1() * M.channels() == M.step[M.dims-1] == M.step[2] == 2 * 4 == 8;
M.step[0] == 4 * 6 * M.elemSize() == 192;
M.step[1] == 6 * M.elemSize() == 48;
M.step[2] == M.elemSize() == 8;
M.step1(0) == M.step[0] / M.elemSize() == 48 / 2 == 96 (第一维度(即面的元素个数) * 通道数);
M.step1(1) == M.step[1] / M.elemSize() == 12 / 2 == 24(第二维度(即行的元素个数/列宽) * 通道数);
M.step1(2) == M.step[2] / M.elemSize() == M.channels() == 4(第三维度(即元素) * 通道数);
说了这这么多来个例子吧。

int main(int argc, char** argv)
{
    //声明一个uchar类型的单通道矩阵(灰度图像)
    Mat m(400, 400, CV_8U, Scalar(0));
    for (int col = 0; col < 400; col++)
    {
        //将图像的中间几行改为白色
        for (int row = 195; row < 205; row++)
        {
            cout << (int)(*(m.data + m.step[0] * row + m.step[1] * col)) << "==>";
            *(m.data + m.step[0] * row + m.step[1] * col) = 255;
            cout << (int)(*(m.data + m.step[0] * row + m.step[1] * col)) << endl;
        }
    }

    imshow("Test", m);
    waitKey(1000);
    return 0;
}

运行效果如下图所示。

这里写图片描述

图4

可以看到中间几行改为了白色。
在来个三通道的实例。直接上代码。
效果如下图所示。
这里写图片描述

图5

可以看到其中有一块区域改为了蓝色。

1.1.2创建Mat的七种方法

1、【方法一】使用Mat()构造函数
最常用的就是使用Mat()构造函数。

Mat M(2,2, CV_8UC3, Scalar(0,0,255));  
cout << "M = " << endl << " " << M << endl << endl;  

运行结果:
这里写图片描述
对于二维多通道图像,首先要定义其尺寸,即行数和列数。 然后,需要指定存储元素的数据类型以及每个矩阵点的通道数 。规则如下。
CV_[The number of this bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
即:CV_[位数][带符号与否][类型前缀]C[通道数]
上文的CV_8UC3表示8位无符号,每个像素由三个元素组成。
Scalar(0,0,255)表示输入的像素值。

2、【方法二】在C/C++中通过构造函数进行初始化

int sz[3] = {2,2,2};  
Mat L(3,sz, CV_8UC(1), Scalar::all(0));  

如何创建一个超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸。

3、【方法三】为已经存在的IPlImage创建信息头
具体实现的的代码如下:

IplImage * img = cvLoadImage(“1.jpg”,1);
Mat mtx(img);//转换IplImage*->Mat

4、【方法四】利用Create()函数

Mat M;
M.create(4,4, CV_8UC(2));  
cout << "M = "<< endl << " "  << M << endl << endl;  

这里写图片描述
这个创建方法不能为矩阵设初值,它只是在改变尺寸时重新为矩阵数据开辟内存。

5、【方法五】采用MATLAB式的方法

Mat E = Mat::eye(4, 4, CV_64F);  
cout << "E = " << endl << " " << E << endl << endl;  
Mat O = Mat::ones(2, 2, CV_32F);  
cout << "O = " << endl << " " << O << endl << endl;  
Mat Z = Mat::zeros(3,3, CV_8UC1);  
cout << "Z = " << endl << " " << Z << endl << endl;  

这里写图片描述
MATLAB形式的初始化方式: zeros(), ones(), :eyes()

6、【方法六】对于小矩阵使用逗号分隔式初始化

Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);  
cout << "C = " << endl << " " << C << endl << endl;  

这里写图片描述
对于小矩阵你可以用逗号分隔的初始化函数。

7、【方法七】为已经存在的创建新的信息头

Mat RowClone = C.row(1).clone();  
cout << "RowClone = " << endl << " " << RowClone << endl << endl;  

使用 clone() 或者 copyTo() 为一个存在的 Mat 对象创建一个新的信息头。

1.1.3 OpenCV格式化输出

首先定义r矩阵 ,通过randu()为矩阵随机分配像素值,分配数字的范围为0-255;

Mat r = Mat(10, 3, CV_8UC3);
randu(r, Scalar::all(0), Scalar::all(255));

1、【风格一】Opencv默认风格

cout << "r (OpenCV默认风格) = " << r << ";" << endl << endl;

这里写图片描述
2、【风格二】Python风格
此句为OpenCV2的代码:

cout << "r (Python风格) = " << format(r,"python") << ";" << endl << endl;

此句为OpenCV3的代码:

cout << "r (python风格) =" << format(r, Formatter::FMT_PYTHON) << ";" << endl << endl;

这里写图片描述
3、【风格三】Numpy风格
此句为OpenCV2的代码:

cout << "r (Numpy风格) = " << format(r,"numpy") << ";" << endl << endl;

此句为OpenCV3的代码:

cout << "r (numpy风格) = " << format(r, Formatter::FMT_NUMPY) << ";" << endl << endl;

这里写图片描述
4、【风格四】逗号分隔风格(CSV风格)
此句为OpenCV2的代码:

cout << "r (逗号分隔风格) = " << format(r,"csv") << ";" << endl<< endl;

此句为OpenCV3的代码:

cout << "r (逗号分隔风格) = \n" << format(r, Formatter::FMT_CSV) << ";" << endl << endl;

这里写图片描述
5、【风格五】C语言风格
此句为OpenCV2的代码:

cout << "r (C语言风格) = " << format(r,"C") << ";" << endl << endl;

此句为OpenCV3的代码:

cout << "r (C语言风格) = \n" << format(r, Formatter::FMT_C) << ";" << endl << endl;

这里写图片描述
6、【风格六】MATLAB风格
此句为OpenCV2的代码:

cout << "r (matlab风格) =" << format(r, "MATLAB") << ";" << endl << endl;

此句为OpenCV2的代码:

cout << "r (matlab风格) = " << format(r, Formatter::FMT_MATLAB) << ";" << endl << endl;

这里写图片描述

1.1.4输出其他常用数据结构

1、定义和输出二维点

Point2f p(6, 2);
cout << "【2维点】p = " << p << ";\n" << endl;

这里写图片描述
2、定义和输出三维点

Point3f p3f(8, 2, 0);
cout << "【3维点】p3f = " << p3f << ";\n" << endl;

这里写图片描述
3、定义输出基于cv::Mat的std::vector

vector<float> v;
v.push_back(3);
v.push_back(5);
v.push_back(7);
cout << "【基于Mat的vector】shortvec = " << Mat(v) << ";\n"<<endl;

这里写图片描述
4、定义输出std::vector 点

vector<Point2f> points(20);
for (size_t i = 0; i < points.size(); ++i)
    points[i] = Point2f((float)(i * 5), (float)(i % 7));
cout << "【二维点向量】points = " << points<<";";

这里写图片描述

1.1.5 OpenCV的命名空间

引入了一个显示图片的案例,在头文件引入之后和main函数之前,我们可以看到这样一句声明:

using namespace cv;  

这就是命名空间的声明,在OpenCV中,C++类和函数都是定义在cv命名空间内的,所以假如我们需要在代码中引用OpenCV的时,需要声明其命名空间,方法有两个:

  • 在main函数之后用“using namespace cv;”来声明命名空间
  • 在要调用的类或者函数前面加上“cv::”来声明命名空间
    显然,使用第一种方法的声明更加简洁方便。

1.1.6 Mat使用实例

【代码-参看附件demo1】
【代码注释】

  • randu

把image分成一个符合正太分布的随机数矩阵。
【函数原型】

    C++: void randu(InputOutputArray mtx, InputArray low, InputArray high)

【参数】
第一个参数,dst:输出随机数据的矩阵
第二个参数,low:产生随机数的下边界
第三个参数,high:产生随机数的上边界
dst的范围为:lowc ≤ dst(I)c < highc

  • vextor

vector容器是一个模板类,可以存放任何类型的对象(但必须是同一类对象)。vector对象可以在运行时高效地添加元素,并且vector中元素是连续存储的。
【函数原型】

template<typename T>  
    explicit vector();                                 // 默认构造函数,vector对象为空  
    explicit vector(size_type n, const T& v = T());    // 创建有n个元素的vector对象  
    vector(const vector& x);  
    vector(const_iterator first, const_iterator last);  

注:vector容器内存放的所有对象都是经过初始化的。如果没有指定存储对象的初始值,那么对于内置类型将用0初始化,对于类类型将调用其默认构造函数进行初始化(如果有其它构造函数而没有默认构造函数,那么此时必须提供元素初始值才能放入容器中)。
【举例】

vector<string> v1;         // 创建空容器,其对象类型为string类  
vector<string> v2(10);     // 创建有10个具有初始值(即空串)的string类对象的容器  
vector<string> v3(5, "hello"); // 创建有5个值为“hello”的string类对象的容器  
vector<string> v4(v3.begin(), v3.end());  // v4是与v3相同的容器(完全复制)
  • 本例程默认使用的是Linux操作系统和OPencv3的库

笔者在Linux上运行用的是Opencv3,因此要加以下宏定义。

 #define OPENCV3

在VS12上,也就是在Windows上要加宏

#define VS12

官方参考:
英文
中文

1.2图像的读取与显示

1.2.1图片载入、显示及输出

1.图片载入

  • 读取图片:imread()函数
C++: Mat imread(const string& filename, int flags=1 )

【参数】
第一个参数,filename- 用于填写图片存放的路径
第二个参数,flags-载入标志,用于指定载入图片的颜色类型。显然这是一个枚举类型的,而且其缺省值为1,具体的枚举表可以在higui_ch中找到。当然除了枚举也可以根据flag的赋值范围来判断颜色类型:
flags >0:返回一个3通道的彩色图像
flags =0:返回灰度图像
flags <0:返回包含Alpha通道的加载的图像
(需要注意的点:输出的图像默认情况下是不载入Alpha通道进来的。如果我们需要载入Alpha通道的话呢,这里就需要取负值。)
2.图片显示

  • 创建图片显示窗口:namedWindow()函数
C++: void namedWindow(const string& winname, int flags)

【参数】
第一个参数,string& winname- 用于标识作用的,窗口的名称,不作为显示名称
第二个参数,flags -int类型的窗口标识(跟窗口的大小有关)

  • 显示图片:imshow()函数
C++: void imshow(const string& winname, InputArray image)

【参数】
第一个参数,winname-窗口的名称,作为显示名称
第二个参数,InputArray - 图片矩阵类型数据
3.图片输出

  • 图片输出:imwrite()函数
C++: bool imwrite(const string& filename, InputArray image, const vector<int>& params=vector<int>())

【参数】
第一个参数,string& filename - 输出之后的文件名,需要加上后缀
第二个参数,InputArray - 输出的一个图片Mat数据
第三个参数,vector& params - 特定格式保存的参数编码,具有默认值,一般不填

1.2.2赋值与复制

如果我们想得到一个图像的副本并进行了下面的操作。

Mat newImage=Image;

如果我们对newImage进行修改或操作,则会直接影响Image图像,因为newImage与Image共用了数据内容。这也是图像的浅拷贝。
想要真正得到一个副本可以这样做,即图像的深拷贝。

Mat newImage;
Image.copyTo(newImage);//方法一
Mat newImage=image.clone();//方法二

copyTo还有一个重构函数copyTo(B,MASK)。意思是可以得到一个附加掩膜MASK的矩阵B。我们从图像的角度来看这个函数的作用。首先需要生成一张掩膜MASK,一般情况下这个膜和你需要操作的对象图像一样大。生成方法见下面例子:

Mat MASK(A.rows,A.cols,CV_8UC3,Scalar(0,0,0));//生成一个三通道的彩色掩膜,初始化为黑色。
Mat MASK(A.rows,A.cols,CV_8UC1,Scalar(0));//生成一个灰度的掩膜,初始化为黑色。
Mat MASK=Mat::zeros(A.size( ), CV_8UC3);//生成一个三通道的彩色掩膜,初始化为黑色。需要改成灰度的只需把CV_8UC3改为CV_8UC1。

对一幅图加一个掩膜顾名思义,就是想要盖住图片的某一部分。所以使用A.copyTo(B,MASK)之后得到的是A被MASK掩盖后的图像。因为初始化的掩膜时黑色的,如果直接加上去整个图片都会被掩盖了,所以需要把一部分你不想盖住的位置改成别的颜色。在后文会详细讲解掩膜的使用。
很多时候,我们并不想得到原图像的复制,但是要创建一个跟原图像大小相同的图像。下面的代码可以完成这样的功能。

Mat newImage;
newImage.create(Image.size(),Image.type());

前文也说了很多了,这里再总结一下,Mat包括头和数据指针,当使用Mat的构造函数初始化的时候,会将头和数据指针复制(注意:只是指针复制,指针指向的地址不会复制),若要将数据也复制,则必须使用copyTo或clone函数关于深拷贝和浅拷贝参考下图。
这里写图片描述
图6
官方参考:
掩码操作英文
掩码操作中文

1.2.3图片显示实例

【代码-参看附件demo2】

1.3常用数据结构和函数

1.3.1点的表示:Point类

 Point_类

/*【Point_源代码】*****************************************************************
 * @Version:OpenCV 3.0.0  
 * @源码路径:…\opencv\sources\modules\core\include\opencv2\core\types.hpp
 * @起始行数:147行   
********************************************************************************/
template<typename _Tp> class Point_
{
public:
    typedef _Tp value_type;

    // various constructors各种构造函数
    Point_();
    Point_(_Tp _x, _Tp _y);
    Point_(const Point_& pt);
    Point_(const Size_<_Tp>& sz);
    Point_(const Vec<_Tp, 2>& v);

    Point_& operator = (const Point_& pt);
    //! conversion to another data type转换为另一种类型
    template<typename _Tp2> operator Point_<_Tp2>() const;

    //! conversion to the old-style C structures转换为旧式风格的C的结构体
    operator Vec<_Tp, 2>() const;

    //! dot product点积运算
    _Tp dot(const Point_& pt) const;
    //! dot product computed in double-precision arithmetics
    double ddot(const Point_& pt) const;
    //! cross-product向量积运算
    double cross(const Point_& pt) const;
    //! checks whether the point is inside the specified rectangle判断当前这个点是否在指定的矩形之内
    bool inside(const Rect_<_Tp>& r) const;

    _Tp x, y; //< the point coordinates这是这个Point类模板最重要的两个信息------Point点的x和y坐标
};

typedef Point_<int> Point2i;// 二维单精度浮点型点类
typedef Point_<float> Point2f;// 二维双精度浮点型点类
typedef Point_<double> Point2d;// 维整形点类
typedef Point2i Point;

点与点、点与数值可以进行直接运算,如下:

pt1 = pt2 + pt3;
pt1 = pt2 - pt3;
pt1 = pt2 * a;
pt1 = a * pt2;
pt1 += pt2;
pt1 -= pt2;
pt1 *= a;
double value = norm(pt); // L2 norm
pt1 == pt2;
pt1 != pt2;

实例:

Point2f a(0.3f, 0.f), b(0.f, 0.4f);
Point pt = (a + b)*10.f;
cout << pt.x << ", " << pt.y << endl;  // 3,4

 Point3_
Point3_和Point2_类似,是三维点。

/*【Point_源代码】*****************************************************************
 * @Version:OpenCV 3.0.0  
 * @源码路径:…\opencv\sources\modules\core\include\opencv2\core\types.hpp
 * @起始行数:218行   
********************************************************************************/
template<typename _Tp> class Point3_
{
public:
    typedef _Tp value_type;

    // various constructors各种构造函数
    Point3_();
    Point3_(_Tp _x, _Tp _y, _Tp _z);
    Point3_(const Point3_& pt);
    explicit Point3_(const Point_<_Tp>& pt);
    Point3_(const Vec<_Tp, 3>& v);

    Point3_& operator = (const Point3_& pt);
    //! conversion to another data type转换为另一种类型
    template<typename _Tp2> operator Point3_<_Tp2>() const;
    //! conversion to cv::Vec<>
    operator Vec<_Tp, 3>() const;

    //! dot product点积运算
    _Tp dot(const Point3_& pt) const;
    //! dot product computed in double-precision arithmetics
    double ddot(const Point3_& pt) const;
    //! cross product of the 2 3D points
    Point3_ cross(const Point3_& pt) const;

    _Tp x, y, z; //< the point coordinates
};

typedef Point3_<int> Point3i;
typedef Point3_<float> Point3f;
typedef Point3_<double> Point3d;

1.3.2尺寸的表示:Size类

常用形式:Size(5,5);//表示长宽都为5。
二维尺寸,用来描述矩阵大小、矩形区域大小、宽高等。类型有,

/*【Size_源代码】*****************************************************************
 * @Version:OpenCV 3.0.0  
 * @源码路径:…\opencv\sources\modules\core\include\opencv2\core\types.hpp
 * @起始行数:284行   
********************************************************************************/
template<typename _Tp> class Size_
{
public:
    typedef _Tp value_type;

    //! various constructors
    Size_();
    Size_(_Tp _width, _Tp _height);
    Size_(const Size_& sz);
    Size_(const Point_<_Tp>& pt);

    Size_& operator = (const Size_& sz);
    //! the area (width*height)
    _Tp area() const;

    //! conversion of another data type.
    template<typename _Tp2> operator Size_<_Tp2>() const;

    _Tp width, height; // the width and the height
};

typedef Size_<int> Size2i;
typedef Size_<float> Size2f;
typedef Size_<double> Size2d;
typedef Size2i Size;

同Point2_一样,可以通过二维数据类型CvSize、CvSize2D32f等构造,成员变量有 width和height;还能计算面积area()=height*width;

1.3.3矩形的表示:Rect类

二维矩形,有效类型为 typedef Rect_ Rect; 该数据类型由顶点x,y和尺寸width、height定义。

/*【Rect _源代码】*****************************************************************
 * @Version:OpenCV 3.0.0  
 * @源码路径:…\opencv\sources\modules\core\include\opencv2\core\types.hpp
 * @起始行数:374行   
********************************************************************************/
template<typename _Tp> class Rect_
{
public:
    typedef _Tp value_type;

    //! various constructors
    Rect_();
    Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height);
    Rect_(const Rect_& r);
    Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz);
    Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2);

    Rect_& operator = ( const Rect_& r );
    //! the top-left corner
    Point_<_Tp> tl() const;
    //! the bottom-right corner
    Point_<_Tp> br() const;

    //! size (width, height) of the rectangle
    Size_<_Tp> size() const;
    //! area (width*height) of the rectangle
    _Tp area() const;

    //! conversion to another data type
    template<typename _Tp2> operator Rect_<_Tp2>() const;

    //! checks whether the rectangle contains the point
    bool contains(const Point_<_Tp>& pt) const;

    _Tp x, y, width, height; //< the top-left corner, as well as width and height of the rectangle
};

typedef Rect_<int> Rect2i;
typedef Rect_<float> Rect2f;
typedef Rect_<double> Rect2d;
typedef Rect2i Rect;

构造函数比较多,Rect(x,y,width,height)和Rect_( Rect )、Rect_( CvRect )结构上是一样的,4个参数;还可以用2个参数构造,顶点x,y这两个可以用Point表示,height和width可以用Point或Szie表示。
成员函数可以返回左上角tl()和右下角br()的二维点坐标,还有面积area(); 和Point对应,能够检查矩形内是是否有点contains()。
以下是rect的基本用法示例:

//如果创建一个Rect对象rect(100, 50, 50, 100),那么rect会有以下几个功能:  
rect.area();     //返回rect的面积 5000  
rect.size();     //返回rect的尺寸 [50 × 100]  
rect.tl();       //返回rect的左上顶点的坐标 [100, 50]  
rect.br();       //返回rect的右下顶点的坐标 [150, 150]  
rect.width();    //返回rect的宽度 50  
rect.height();   //返回rect的高度 100  
rect.contains(Point(x, y));  //返回布尔变量,判断rect是否包含Point(x, y)点  
//还可以求两个矩形的交集和并集  
rect = rect1 & rect2;  
rect = rect1 | rect2;  
//还可以对矩形进行平移和缩放    
rect = rect + Point(-100, 100); //平移,也就是左上顶点的x坐标-100,y坐标+100  
rect = rect + Size(-100, 100);  //缩放,左上顶点不变,宽度-100,高度+100  
//还可以对矩形进行对比,返回布尔变量  
rect1 == rect2;  
rect1 != rect2;  
//OpenCV里貌似没有判断rect1是否在rect2里面的功能,所以自己写一个吧  
bool isInside(Rect rect1, Rect rect2)  
{  
    return (rect1 == (rect1&rect2));  
}  
//OpenCV貌似也没有获取矩形中心点的功能,还是自己写一个  
Point getCenterPoint(Rect rect)  
{  
    Point cpt;  
    cpt.x = rect.x + cvRound(rect.width/2.0);  
    cpt.y = rect.y + cvRound(rect.height/2.0);  
    return cpt;  
}  
//围绕矩形中心缩放  
Rect rectCenterScale(Rect rect, Size size)  
{  
    rect = rect + size;   
    Point pt;  
    pt.x = cvRound(size.width/2.0);  
    pt.y = cvRound(size.height/2.0);  
    return (rect-pt);  
}

1.3.4标量的表示:Scalar类

标量,由Vec<_Tp, 4>模板类派生。在矩阵运算中,特别是图像矩阵涉及到多通道的运算中,对某个像素素的取值和赋值都是多个通道同时进行,因此Vec和Scalar的优势体现明显,被广泛用于图像像素赋值。

/*【Scalar_源代码】*****************************************************************
 * @Version:OpenCV 3.0.0  
 * @源码路径:…\opencv\sources\modules\core\include\opencv2\core\types.hpp
 * @起始行数:570行   
********************************************************************************/
template<typename _Tp> class Scalar_ : public Vec<_Tp, 4>
{
public:
    //! various constructors
    Scalar_();
    Scalar_(_Tp v0, _Tp v1, _Tp v2=0, _Tp v3=0);
    Scalar_(_Tp v0);

    template<typename _Tp2, int cn>
    Scalar_(const Vec<_Tp2, cn>& v);

    //! returns a scalar with all elements set to v0
    static Scalar_<_Tp> all(_Tp v0);

    //! conversion to another data type
    template<typename T2> operator Scalar_<T2>() const;

    //! per-element product
    Scalar_<_Tp> mul(const Scalar_<_Tp>& a, double scale=1 ) const;

    // returns (v0, -v1, -v2, -v3)
    Scalar_<_Tp> conj() const;

    // returns true iff v1 == v2 == v3 == 0
    bool isReal() const;
};

typedef Scalar_<double> Scalar;

既然Scalr_是从Vec派生,那么其元素的取值也是通过“[ ]”运算符操作的,例如,在 s = sum(Mat(m*m.t())) 运算中,sum返回值s是Scalar(693,0,0,0),是包含4个元素的向量,那么结果的取值方法为 double res= s[0]。
图像像素赋值,如 Mat(10, 10, CV_32FC2, Scalar(1,2) ),由于是2个通道,因此Scalar只需要2个数值;由于Scalar是一个4行1列的列向量,因此最多只能给4个通道的图像矩阵像素赋值。
常用的形式为:Scalar(a,b,c);

1.3.5颜色的空间转换:cvtColor()函数

C++: void cvtColor(InputArray src, 
                   OutputArray dst, 
                   int code, 
                   int dstCn=0 )

【参数】
第一个参数,src – Source image: 8-bit unsigned, 16-bit unsigned ( CV_16UC… ), or single-precision floating-point.
第二个参数,dst – Destination image of the same size and depth as src .
第三个参数,code – Color space conversion code. See the description below.
第四个参数,dstCn – Number of channels in the destination image. If the parameter is 0, the number of the channels is derived automatically from src and code .
The function converts an input image from one color space to another. In case of a transformation to-from RGB color space, the order of the channels should be specified explicitly (RGB or BGR). Note that the default color format in OpenCV is often referred to as RGB but it is actually BGR (the bytes are reversed). So the first byte in a standard (24-bit) color image will be an 8-bit Blue component, the second byte will be Green, and the third byte will be Red. The fourth, fifth, and sixth bytes would then be the second pixel (Blue, then Green, then Red), and so on.
The conventional ranges for R, G, and B channel values are:
0 to 255 for CV_8U images
0 to 65535 for CV_16U images
0 to 1 for CV_32F images
In case of linear transformations, the range does not matter. But in case of a non-linear transformation, an input RGB image should be normalized to the proper value range to get the correct results, for example, for RGB \rightarrow L*u*v* transformation. For example, if you have a 32-bit floating-point image directly converted from an 8-bit image without any scaling, then it will have the 0..255 value range instead of 0..1 assumed by the function. So, before calling cvtColor , you need first to scale the image down:
img *= 1./255;
cvtColor(img, img, COLOR_BGR2Luv);
If you use cvtColor with 8-bit images, the conversion will have some information lost. For many applications, this will not be noticeable but it is recommended to use 32-bit images in applications that need the full range of colors or that convert an image before an operation and then convert back.
If conversion adds the alpha channel, its value will set to the maximum of corresponding channel range: 255 for CV_8U, 65535 for CV_16U, 1 for CV_32F.

颜色转换:
http://docs.opencv.org/3.0.0/de/d25/imgproc_color_conversions.html
参考实例:edge.cpp, ffilldemo.cpp, houghcircles.cpp, houghlines.cpp, and watershed.cpp.

1.3.6常用数据结构和函数实例

【代码-参看附件demo3】

1.4 OpenCV访问图像像素、利用查找表和计时

1.4.1 OpenCV在内存中的存取方式

在前文,你或许已了解到,图像矩阵的大小取决于我们所用的颜色模型,确切地说,取决于所用通道数。如果是灰度图像,矩阵就会像这样:

这里写图片描述

图7

而对多通道图像来说,矩阵中的列会包含多个子列,其子列个数与通道数相等。例如,RGB颜色模型的矩阵:
这里写图片描述

图8

注意到,子列的通道顺序是反过来的:BGR而不是RGB。很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用 isContinuous() 来去判断矩阵是否是连续存储的. 相关示例会在接下来的内容中提供。

1.4.2颜色空间缩减

如果矩阵元素存储的是单通道像素,使用C或C++的无符号字符类型,那么像素可有256个不同值。但若是三通道图像,这种存储格式的颜色数就太多了(确切地说,有一千六百多万种)。用如此之多的颜色可能会对我们的算法性能造成严重影响。其实有时候,仅用这些颜色的一小部分,就足以达到同样效果。
这种情况下,常用的一种方法是 颜色空间缩减 。其做法是:将现有颜色空间值除以某个输入值,以获得较少的颜色数。例如,颜色值0到9可取为新值0,10到19可取为10,以此类推。
uchar (无符号字符,即0到255之间取值的数)类型的值除以 int 值,结果仍是 char 。因为结果是char类型的,所以求出来小数也要向下取整。利用这一点,刚才提到在 uchar 定义域中进行的颜色缩减运算就可以表达为下列形式:

Inew=(Iold10)10 I n e w = ( I o l d 10 ) ∗ 10

这样的话,简单的颜色空间缩减算法就可由下面两步组成:
第一步:遍历图像矩阵的每一个像素;
第二步:对像素应用上述公式。值得注意的是,我们这里用到了除法和乘法运算,而这两种运算又特别费时,所以,我们应尽可能用代价较低的加、减、赋值等运算替换它们。此外,还应注意到,上述运算的输入仅能在某个有限范围内取值,如 uchar 类型可取256个值。
由此可知,对于较大的图像,有效的方法是预先计算所有可能的值,然后需要这些值的时候,利用查找表直接赋值即可。查找表是一维或多维数组,存储了不同输入值所对应的输出值,其优势在于只需读取、无需计算。

1.4.3计时函数

 计算时钟周期:getTickCount()函数

C++: int64 getTickCount()

The function returns the number of ticks after the certain event (for example, when the machine was turned on). It can be used to initialize RNG() or to measure a function execution time by reading the tick count before and after the function call. See also the tick frequency.
 计算时钟频率:getTickFrequency()函数

C++: double getTickFrequency()

The function returns the number of ticks per second. That is, the following code computes the execution time in seconds:
double t = (double)getTickCount();
// do something …
t = ((double)getTickCount() - t)/getTickFrequency();

1.4.4核心函数LUT(The Core Function)

这是最被推荐的用于实现批量图像元素查找和更该操作图像方法。在图像处理中,对于一个给定的值,将其替换成其他的值是一个很常见的操作,OpenCV 提供里一个函数直接实现该操作,并不需要你自己扫描图像,就是:operationsOnArrays:LUT() ,一个包含于core module的函数. 首先我们建立一个mat型用于查表:

Mat lookUpTable(1, 256, CV_8U);
 uchar* p = lookUpTable.data; 
 for( int i = 0; i < 256; ++i)
     p[i] = table[i];

然后我们调用函数 (I 是输入 J 是输出):
LUT(I, lookUpTable, J);

1.4.5访问图像像素的三类方法

方法一:高效的方法 Efficient Way
说到性能,经典的C风格运算符[](指针)访问要更胜一筹. 因此,我们推荐的效率最高的查找表赋值方法,还是下面的这种:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     
    int channels = I.channels();
    int nRows = I.rows * channels; 
    int nCols = I.cols;
    if (I.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;         
    }
    int i,j;
    uchar* p; 
    for( i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];             
        }
    }
    return I; 
}

这里,我们获取了每一行开始处的指针,然后遍历至该行末尾。如果矩阵是以连续方式存储的,我们只需请求一次指针、然后一路遍历下去就行。彩色图像的情况有必要加以注意:因为三个通道的原因,我们需要遍历的元素数目也是3倍。
这里有另外一种方法来实现遍历功能,就是使用 data , data会从 Mat 中返回指向矩阵第一行第一列的指针。注意如果该指针为NULL则表明对象里面无输入,所以这是一种简单的检查图像是否被成功读入的方法。当矩阵是连续存储时,我们就可以通过遍历 data 来扫描整个图像。例如,一个灰度图像,其操作如下:

uchar* p = I.data;
for( unsigned int i =0; i < ncol*nrows; ++i)
    *p++ = table[*p];

这回得出和前面相同的结果。但是这种方法编写的代码可读性方面差,并且进一步操作困难。同时,我发现在实际应用中,该方法的性能表现上并不明显优于前一种(因为现在大多数编译器都会对这类操作做出优化)。
方法二:迭代法 The iterator (safe) method
在高性能法(the efficient way)中,我们可以通过遍历正确的 uchar 域并跳过行与行之间可能的空缺-你必须自己来确认是否有空缺,来实现图像扫描,迭代法则被认为是一种以更安全的方式来实现这一功能。在迭代法中,你所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。

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; 
}

对于彩色图像中的一行,每列中有3个uchar元素,这可以被认为是一个小的包含uchar元素的vector,在OpenCV中用 Vec3b 来命名。如果要访问第n个子列,我们只需要简单的利用[]来操作就可以。需要指出的是,OpenCV的迭代在扫描过一行中所有列后会自动跳至下一行,所以说如果在彩色图像中如果只使用一个简单的 uchar 而不是 Vec3b 迭代的话就只能获得蓝色通道(B)里的值。
方法三:通过相关返回值的On-the-fly地址计算
事实上这个方法并不推荐被用来进行图像扫描,它本来是被用于获取或更改图像中的随机元素。它的基本用途是要确定你试图访问的元素的所在行数与列数。在前面的扫描方法中,我们观察到知道所查询的图像数据类型是很重要的。这里同样的你得手动指定好你要查找的数据类型。下面的代码中是一个关于灰度图像的示例(运用 + at() 函数):

Mat& ScanImageAndReduceRandomAccess(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: 
        {
            for( int i = 0; i < I.rows; ++i)
                for( int j = 0; j < I.cols; ++j )
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
                break;
        }
        case 3: 
        {
            Mat_<Vec3b> _I = I;

            for( int i = 0; i < I.rows; ++i)
                for( int j = 0; j < I.cols; ++j )
                {
                    _I(i,j)[0] = table[_I(i,j)[0]];
                    _I(i,j)[1] = table[_I(i,j)[1]];
                    _I(i,j)[2] = table[_I(i,j)[2]];
                }
                I = _I;
                break;
        }
    }
    return I;
}

该函数输入为数据类型及需求元素的坐标,返回的是一个对应的值-如果用 get 则是constant,如果是用 set 、则为non-constant. 处于程序安全,当且仅当在 debug 模式下 它会检查你的输入坐标是否有效或者超出范围. 如果坐标有误,则会输出一个标准的错误信息. 和高性能法(the efficient way)相比, 在 release模式下,它们之间的区别仅仅是On-the-fly方法对于图像矩阵的每个元素,都会获取一个新的行指针,通过该指针和[]操作来获取列元素.
当你对一张图片进行多次查询操作时,为避免反复输入数据类型和at带来的麻烦和浪费的时间,OpenCV 提供了:basicstructures:Mat_ data type. 它同样可以被用于获知矩阵的数据类型,你可以简单利用()操作返回值来快速获取查询结果. 值得注意的是你可以利用 at() 函数来用同样速度完成相同操作. 它仅仅是为了让懒惰的程序员少写点
在附例中给出了三中访问像素的实例。
【参看附件demo4】用指针访问像素;
【参看附件demo5】用迭代器访问像素;
【参看附件demo6】用动态地址计算配合at访问像素;
另外,在【参看附件demo7】给出了遍历图像像素的14种方法实例。

1.5基本图像的绘制

1.5.1绘制直线

 绘制直线:line函数

C++: void line(Mat& img, 
               Point pt1, 
               Point pt2, 
               const Scalar& color, 
               int thickness=1, 
               int lineType=8, 
               int shift=0)

【参数】
第一个参数,img:输入图片;
第二个参数,pt1:起始点;
第三个参数,pt2:终点;
第四个参数,color:划线的颜色;
第五个参数,thickness:线的粗细程度;
第六个参数,lineType:线的类型,默认为8连通;
 8 (or omitted) - 8-connected line.
 4 - 4-connected line.
 CV_AA - antialiased line.
第七个参数,shift – Number of fractional bits in the point coordinates。

1.5.2绘制矩形

 画矩形:rectangle()函数

C++: void rectangle(Mat& img, 
                    Point pt1, 
                    Point pt2, 
                    const Scalar& color, 
                    int thickness=1, 
                    int lineType=8, 
                    int shift=0)

【参数】
第一个参数,img:输入图片;
第二个参数,pt1,矩形的顶点;
第三个参数,pt2:相对于pt1顶点;
第四个参数,color:颜色值;
第五个参数,thickness:线的粗细程度,如果为负数,则说明圆内被填充。
第六个参数,lineType:线的类型,默认为8;See the line() description;
第七个参数,shift – Number of fractional bits in the point coordinates。

1.5.3绘制圆

 画圆:circle()函数

C++: void circle(Mat& img,
                 Point center, 
                 int radius, 
                 const Scalar& color, 
                 int thickness=1, 
                 int lineType=8, 
                 int shift=0)

【参数】
第一个参数,img:输入图片;
第二个参数,center:圆点坐标;
第三个参数,radius:圆的半径;
第四个参数,color:颜色值;
第五个参数,thickness:线的粗细程度,如果为负数,则说明圆内被填充。
第六个参数,lineType:线的类型,默认为8;
第七个参数,shift – Number of fractional bits in the coordinates of the center and in the radius value。

1.5.4绘制椭圆

 画椭圆、弧线、扇形ellipse函数
功能:画椭圆、弧线、扇形

void ellipse(Mat& img, 
             Point center, 
             Size axes, 
             double startAngle, 
             const Scalar& color, 
             int lineType=8, 
             int shift=0)  
C++: void ellipse(Mat& img, 
                  Point center, 
                  Size axes, 
                  double angle, 
                  double startAngle, 
                  double endAngle, 
                  const Scalar& color, 
                  int thickness=1,
                  int lineType=8, 
                  int shift=0)

【参数】
第一个参数,img:输入图片;
第二个参数,center:椭圆中心
第三个参数,axes:椭圆轴的长度
第四个参数,angle:椭圆旋转角度
第五个参数,startAngle:椭圆弧度开始的角度
第六个参数,endAngle:椭圆弧度结束的角度
第七个参数,color:颜色值
第八个参数,thickness:线的粗细程度
第九个参数,lineType:线的类型,默认为8连通;
第十个参数,shift – Number of fractional bits in the coordinates of the center and values of axes。
【原理图】

这里写图片描述

图9

1.5.5绘制多边形

 填充多边形fillPoly()函数

C++: void fillPoly(Mat& img, 
                   const Point** pts, 
                   const int* npts, 
                   int ncontours, 
                   const Scalar& color, 
                   int lineType=8, 
                   int shift=0, 
                   Point offset=Point() )

【参数】
第一个参数,img:输入图片;
第二个参数,pts:多边形顶点集;
第三个参数,npts:要绘制的多边形顶点数目;
第四个参数,ncontours:要绘制的多边形数目;
第五个参数,color:颜色值;
第六个参数,lineType:线的类型,默认为8联通;
第七个参数,shift – Number of fractional bits in the vertex coordinates.
第八个参数,offset。
本节的参考实例在附件【参看附件demo8】,笔者在此就不列出了。
运行后的结果如下图所示。

这里写图片描述

图10

1.6随机数发生器&绘制文字

1.6.1随机数生成器

用OpenCV做算法的朋友们肯定为随机数烦恼过,新版本一直支持随机数产生器啦,而且还继续支持之前版本的c格式的函数,不过与时俱进,我这里介绍C++的RNG类。它可以压缩一个64位的i整数并可以得到scalar和array的随机数。目前的版本支持均匀分布随机数和Gaussian分布随机数。随机数的产生采用的是Multiply-With-Carry算法和Ziggurat算法。其构造函数的初始化可以传入一个64位的整型参数作为随机数产生器的初值。next可以取出下一个随机数,uniform函数可以返回指定范围的随机数,gaussian函数返回一个高斯随机数,fill则用随机数填充矩阵。
这里介绍一个uniform的使用事项,就是比如利用它产生0~1的随机数的问题,具体代码如下:

RNG rng;  
// always produces 0  
double a = rng.uniform(0, 1);  
// produces double from [0, 1)  
double a1 = rng.uniform((double)0, (double)1);  
// produces float from [0, 1)  
double b = rng.uniform(0.f, 1.f);  
// produces double from [0, 1)  
double c = rng.uniform(0., 1.);  
// may cause compiler error because of ambiguity:  
// RNG::uniform(0, (int)0.999999)? or RNG::uniform((double)0, 0.99999)?  
double d = rng.uniform(0, 0.999999);  

就是不能写成rng.uniform( 0 , 1),因为输入为int型参数,会调用uniform(int,int),只能产生0。还有一些随机数相关的函数,比如randu可以产生一个均匀分布的随机数或者矩阵,randn可以产生一个正态分布的随机数,randShuffle可以随机打乱矩阵元素
再简单介绍一下c版本的随机数产生器的相关函数,有cvRNG、cvRandArr、cvRandInt、cvRandReal

1.6.2绘制字符串:putText()函数

C++: void putText(Mat& img, 
                  const string& text, 
                  Point org, 
                  int fontFace, 
                  double fontScale, 
                  Scalar color, 
                  int thickness=1,
                  int lineType=8, 
                  bool bottomLeftOrigin=false )

【参数】
第一个参数,img–图像矩阵;
第一个参数,text–string型 文字内容;
第三个参数,org–文字坐标,以左下角为原点;
第四个参数,fontFace-字体类型;
FONT_HERSHEY_SIMPLEX, //正常大小无衬线字体.
FONT_HERSHEY_PLAIN, //小号无衬线字体.
//正常大小无衬线字体比 CV_FONT_HERSHEY_SIMPLEX 更复杂)
FONT_HERSHEY_DUPLEX,
FONT_HERSHEY_COMPLEX, //正常大小有衬线字体.
//正常大小有衬线字体 ( 比 CV_FONT_HERSHEY_COMPLEX更复杂)
FONT_HERSHEY_TRIPLEX,
FONT_HERSHEY_COMPLEX_SMALL, // CV_FONT_HERSHEY_COMPLEX 的小译本.
FONT_HERSHEY_SCRIPT_SIMPLEX, //手写风格字体.
FONT_HERSHEY_SCRIPT_COMPLEX, //比 CV_FONT_HERSHEY_SCRIPT_SIMPLEX 更复杂.
第五个参数,fontScale –字体大小
第六个参数,color – 字体颜色
第七个参数,thickness – 字体粗细
第八个参数,lineType – Line type. See the line for details.
第九个参数,bottomLeftOrigin – When true, the image data origin is at the bottom-left corner. Otherwise, it is at the top-left corner。
下面给出了字符显示的实例。

void put_string(string filename)  
{  
    Mat img = imread(“filename”);  
    putText(img, "OpenCV" , Point(0, int(img.rows*0.9)), CV_FONT_HERSHEY_COMPLEX,img.cols/400, Scalar(200, 200, 200, 0));  
    imwrite(“filename”,img);  
}

1.7创建Trackbar

<1>创建轨迹条——createTrackbar函数详解
createTrackbar这个函数我们以后会经常用到,它创建一个可以调整数值的轨迹条,并将轨迹条附加到指定的窗口上,使用起来很方便。首先大家要记住,它往往会和一个回调函数配合起来使用。先看下他的函数原型:

C++: int createTrackbar(conststring& trackbarname,  
                        conststring& winname,  
                        int* value, 
                        int count, 
                        TrackbarCallback onChange=0,
                        void* userdata=0);  

【参数】
第一个参数,const string&类型的trackbarname,表示轨迹条的名字,用来代表我们创建的轨迹条。
第二个参数,const string&类型的winname,填窗口的名字,表示这个轨迹条会依附到哪个窗口上,即对应namedWindow()创建窗口时填的某一个窗口名。
第三个参数,int* 类型的value,一个指向整型的指针,表示滑块的位置。并且在创建时,滑块的初始位置就是该变量当前的值。
第四个参数,int类型的count,表示滑块可以达到的最大位置的值。PS:滑块最小的位置的值始终为0。
第五个参数,TrackbarCallback类型的onChange,首先注意他有默认值0。这是一个指向回调函数的指针,每次滑块位置改变时,这个函数都会进行回调。并且这个函数的原型必须为void XXXX(int,void*);其中第一个参数是轨迹条的位置,第二个参数是用户数据(看下面的第六个参数)。如果回调是NULL指针,表示没有回调函数的调用,仅第三个参数value有变化。
第六个参数,void*类型的userdata,他也有默认值0。这个参数是用户传给回调函数的数据,用来处理轨迹条事件。如果使用的第三个参数value实参是全局变量的话,完全可以不去管这个userdata参数。
这个createTrackbar函数,为我们创建一个具有特定名称和范围的轨迹条(Trackbar,或者说是滑块范围控制工具),指定一个和轨迹条位置同步的变量。而且要指定回调函数onChange(第五个参数),在轨迹条位置改变的时候来调用这个回调函数。并且我们知道,创建的轨迹条显示在指定的winname(第二个参数)所代表的窗口上。
看完函数讲解,先给大家一个函数使用小示例:

//创建轨迹条  
createTrackbar("对比度:", "【效果图窗口】",&g_nContrastValue,  300,ContrastAndBright );
// g_nContrastValue为全局的整型变量,ContrastAndBright为回调函数的函数名(即指向函数地址的指针)  

然给大家一个完整的使用示例。这是OpenCV官方的sample示例程序,一个演示了用轨迹条来控制轮廓检测,轮廓填充的程序。笔者将其修改、代码简洁化和详细注释,放出来供大家消化研习。
【代码-参看附件demo9】。
运行效果图和原图。

这里写图片描述

图11

拖动滚动条,改变threshval(阈值)的值,得到效果迥异的图片:
这里写图片描述

图12

另外,在OpenCV路径/samples/cpp/connected_components.cpp下,可以找到原版的官方代码。接着顺便讲一个配合createTrackbar使用的函数,用于获取当前轨迹条的位置的getTrackbarPos函数吧。
<2>获取当前轨迹条的位置——getTrackbarPos函数
这个函数用于获取当前轨迹条的位置并返回。

C++: int getTrackbarPos(conststring& trackbarname, 
                        conststring& winname);  

【参数】
第一个参数,const string&类型的trackbarname,表示轨迹条的名字。
第二个参数,const string&类型的winname,表示轨迹条的父窗口的名称。

1.8鼠标操作

鼠标操作消息回调函数的函数为setMouseCallback(),它的原型为:

C++: void setTrackbarPos(const string& trackbarname,  
                         const string& winname, 
                         int pos)

【参数】
第一个参数,winname:窗口的名字 ;
第二个参数, onMouse:鼠标响应函数,回调函数。指定窗口里每次鼠标时间发生的时候,被调用的函数指针。
这个函数的原型应该为void on_Mouse(int event, int x, int y, int flags, void* param);
userdate:传给回调函数的参数。
第三个参数,pos – New position。
再来介绍MouseCallback onMouse的函数原型:

void on_Mouse( int event, 
               int x, 
               int y, 
               int flags, 
               void* param);  

【参数】
第一个参数,event是 CV_EVENT_*变量之一 ;
第二、三个参数,x和y是鼠标指针在图像坐标系的坐标(不是窗口坐标系)
第四个参数,flags是CV_EVENT_FLAG的组合;
第五个参数, param是用户定义的传递到setMouseCallback函数调用的参数。
【常用的event】

#defineCV_EVENT_MOUSEMOVE
#defineCV_EVENT_LBUTTONDOWN 
#defineCV_EVENT_RBUTTONDOWN   
#defineCV_EVENT_LBUTTONUP    
#defineCV_EVENT_RBUTTONUP  

和标志位flags有关的:

#defineCV_EVENT_FLAG_LBUTTON 

注意:flags & CV_EVENT_FLAG_LBUTTON 的意思是 提取flags的CV_EVENT_FLAG_LBUTTON 标志位,!()的意思是 标志位无效。
用户通过鼠标对图像视窗最常见的操作有:

  • 左键单击按下
  • 左键单击抬起
  • 左键按下拖动
  • 鼠标指针位置移动

单次单击操作响应事件及顺序:
OpenCV中setMouseCallback()创建了一个鼠标回调函数,每次在图像上单击鼠标左键再抬起的过程,都会分3次调用鼠标响应函数,并且响应顺序是:
第一步:左键单击按下;
第二步:左键单击抬起;
第三步:鼠标指针位置移动(即使原地单击,鼠标位置并没有移动);
接下来跟随笔者看看鼠标操作事件。
【代码-参看附件demo10】
程序运行后效果如下所示。

这里写图片描述

图13

接下来再跟着笔者看一个综合实例。
【代码-参见附件demo11】
这里写图片描述

图14

1.9 OpenCV 2、3与 OpenCV 1 同时使用(C与C++混合编程)

在用新版本之前,你首先需要学习一些新的图像数据结构: Mat - 基本图像容器 ,它取代了旧的 CvMat 和 IplImage 。转换到新函数非常容易,你仅需记住几条新的原则。
OpenCV 2 接受按需定制。所有函数不再装入一个单一的库中。我们会提供许多模块,每个模块都包含了与其功能相关的数据结构和函数。这样一来,如果你仅仅需要使用OpenCV的一部分功能,你就不需要把整个巨大的OpenCV库都装入你的程序中。使用时,你仅需要包含用到的头文件,比如:

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

所有OpenCV用到的东西都被放入名字空间 cv 中以避免与其他库的数据结构和函数名称的命名冲突。因此,在使用OpenCV库中的任何定义和函数时,你必须在名称之前冠以 cv:: ,或者在包含头文件后,加上以下指令:

using namespace cv;  // 新的C++接口API都在此名字空间中,需要导入。

因为所有库中函数都已在此名字空间中,所以无需加 cv 作为前缀。据此所有新的C++兼容函数都无此前缀,并且遵循驼峰命名准则。也就是第一个字母为小写(除非是单个单词作为函数名,如 Canny)并且后续单词首字母大写(如 copyMakeBorder ).
接下来,请记住你需要将所有用到的模块链接到你的程序中。如果你在Windows下开发且用到了 动态链接库(DLL) ,你还需要将OpenCV对应动态链接库的路径加入程序执行路径中。关于Windows下开发的更多信息请阅读 How to build applications with OpenCV inside the Microsoft Visual Studio ;对于Linux用户,可参考 Using OpenCV with Eclipse (plugin CDT) 中的实例及说明。
你可以使用 IplImage 或 CvMat 操作符来转换 Mat 对象。在C接口中,你习惯于使用指针,但此处将不再需要。在C++接口中,我们大多数情况下都是用 Mat 对象。此对象可通过简单的赋值操作转换为 IplImage 和 CvMat 。示例如下:

Mat I;
IplImage pI = I;
CvMat    mI = I;

现在,如果你想获取指针,转换就变得麻烦一点。编译器将不能自动识别你的意图,所以你需要明确指出你的目的。可以通过调用 IplImage 和 CvMat 操作符来获取他们的指针。我们可以用 & 符号获取其指针如下:

Mat I;
IplImage* pI     = &I.operator IplImage();
CvMat* mI        =  &I.operator CvMat();

来自C接口最大的抱怨是它将所有内存管理工作交给你来做。你需要知道何时可以安全释放不再使用的对象,并且确定在程序结束之前释放它,否则就会造成讨厌的内存泄露。为了绕开这一问题,OpenCV引进了一种智能指针。它将自动释放不再使用的对象。使用时,指针将被声明为 Ptr 模板的特化:

Ptr<IplImage> piI = &I.operator IplImage();

将C接口的数据结构转换为 Mat 时,可将其作为构造函数的参数传入,例如:

Mat K(piL), L;
L = Mat(pI);

【代码-参看附件demo12】

官方参考:
英文
中文

1.10 OpenCV算法优化

优化C++和OpenCV过程中,总结的若干技巧如下:
1. 访问Opencv的Mat格式时,需要注意访问方式,其中使用C语言的【】操作符访问最快,使用.At<>的方式最慢,效率相差20~30倍,具体可查看前文相应的解释。
2. 若在遍历矩阵过程中,需要求平方,使用pow函数效率并不高,所以尽可能用乘号(*)代替使用pow函数。
3. 若对若干矩阵进行大规模点运算,如A=B*C+D*E,则避免直接使用矩阵间的运算符,如B.mul(C)等,因为该操作符输出的并非最终结果,而是中间矩阵,因而会导致多余的运算。应直接遍历,访问矩阵中的元素,并计算结果。
4. 矩阵运算时,若矩阵较大,应使用GEMM函数,因为GEMM使用了BLAS进行加速。如普通的A=B*C可能需要20ms,而使用GEMM可能只需要2ms,效率相差还是挺大的。
5. Opencv虽然有DFT函数,但若最求更快的FFT,应使用fftw的库进行fft计算。
6. 若对性能要求非常高,可以考虑使用多核并行计算(OpenMP、TBB等)或添加GPU处理(CUDA)。
7. 关于C语言的优化,可参考《C语言代码优化方案》。
参考http://blog.csdn.net/china_video_expert/article/details/7231644

官方参考:
英文

本章附件:

请点击代码链接
【注意】博主在附件中的代码有两种使用方式,Windows版本使用的是Opencv3.0+VS2012,Linux使用的是Opencv3.3,请读者根据自己的情况自行选择,有问题联系博主。

这里写图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐