OpenCV数据结构与基本绘图
1. 基础图像容器 Mat
1.1 数字图像存储概述
我们有多种方法从现实世界获取数字图像:数码相机、扫描仪、计算机断层扫描和磁共振成像等等。在每一种情况下,我们(人类)看到的都是图像。然而,当把它转化为我们的数字设备时,我们记录的是图像中每个点的数值。
例如,在上面的图像中,你可以看到汽车的镜子只不过是一个包含所有像素点强度值的矩阵。我们如何获得和存储像素值可能根据我们的需要而有所不同,但最终计算机世界里面的所有图像都可能被简化为数字矩阵和描述矩阵本身的其他信息。
1.2 Mat的使用
关于Mat类我们首先要知道:
- 不必在手动为其开辟空间
- 不必在不需要时立即将空间释放
大多数OpenCV函数会自动分配其输出数据所需的空间。如果你传递一个已经存在的Mat对象,它已经为矩阵分配了所需的空间,这将被重复使用。换句话说,我们在任何时候都只使用执行任务所需的内存。
Mat基本上是一个有两个数据部分的类:矩阵头(包含信息,如矩阵的大小,用于存储的方法,矩阵存储在哪个地址,等等)和一个指向包含像素值的矩阵的指针(根据选择的存储方法,采取任何尺寸)。矩阵头的大小是恒定的,但是矩阵本身的大小可能因图像的不同而不同,而且通常要大几个数量级。
为了解决这个问题,OpenCV使用了一个参考计数系统。这个想法是,每个Mat对象都有自己的头,然而一个矩阵可以在两个Mat对象之间共享,只要它们的矩阵指针指向同一个地址。此外,复制操作者将只复制头和指向大矩阵的指针,而不是数据本身。
问题:如果矩阵属于多个Mat对象,那么不再需要它时,谁来负责清理呢?
最后一个使用过它的对象。通过引用计数器机制来实现。我们无论什么时候复制一个Mat对象的信息头,都会增加矩阵的引用次数。反之,当一个头被释放之后,这个计数被减一;当计数值为0,矩阵就会被清理。
但某些时候你想复制矩阵本身,这个时候可以用clone()或者copyTo()函数
1.3 像素值的存储方法
- RGB颜色空是最常用的 颜色空间这归功于它也是人眼内部构成颜色的方式它的基色是红色、
绿色和蓝色有时为了表示透明颜色也会加入第四个元素 alpha(A)。 - HSV和HLS把颜色分解成色调、饱和度和亮度。这是描述颜色更自然的方式,比如可以通过抛弃最后一个元素,使算法对输入图像的光照条件不敏感。
- YCrCb在JPEG图像格式中广泛使用
- COE Lab*是一种在感知上均匀的颜色空间,它适用来度量两个颜色之间的距离。
1.4 显示创建Mat对象
- 使用Mat()构造函数
Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255));
cout << "M=" << endl << " " << M << endl << endl;
对于二维多通道图像,首先要定义其尺寸,即行和列。然后,需要指定存储元素的数据类型以及每个矩阵点的通道数。
CV_[位数[带符号与否][类型前缀]C[通道数]
比如人CV_8UC3表示使用8位的unsigned char型,每个像素由三个像素组成三通道。而预先定义的通道数可以多达四个。另外,Scalar是个short型的向量,能使用指定的定制化值来初始化矩阵,它还可以用于表示颜色。
- 使用C/C++数组并通过构造函数进行初始化
int sz[3] = {2,2,2};
Mat L(3,sz, CV_8UC(1), Scalar::all(0));
上面的例子演示了如何创建一个超过两维的矩阵: 指定维度,然后传递一个指向一个数组的指针,一个数组包含每个维度的尺寸。
- 利用create()函数
Mat M;
M.create(4, 4, CV_8UC(2));
cout << "M=" << endl<< M << endl;
该创建方法不能为矩阵设初值,只是在改变尺寸时重新为矩阵数据开辟内存而已。
- 采用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;
2.常用的数据结构和函数
2.1 点的表示:Point类
Point类数据结构表示了二维坐标系下的点,即由其图像坐标x和y指定的2D点。
Point point;
point.x=10;
point.y=8
//或者
Point point=Point(10,8);
OpenCV中有如下定义:
typedef Point_<int> Point2i;
typedef Point2i Point;
typedef Point_<float> Point2f;
所以,Point<int>、 Point2i、Point 互相等价 Point_<float>、Point2f 互相等价。
2.3 颜色的表示:Scalar类
Scalar()表示具有四个元素的数组,在OpenCV中被大量用于传递像素值,如RGB颜色值。而RGB颜色值为三个参数,其实对于Scalar函数来说,如果用不到第四个参数,则可以不用写出来;若只写三个参数,OpenCV会认为我们就像表示三个参数。
如果给出Scalar(a,b,c)
那么定义的RGB颜色值:红色分量为c,绿色分量为b,蓝色分量为a。
2.4 尺寸的表示:Size类
Size(width,heigth)
Size(5,5)
构造出Size宽度和高度都为5
即XXX.width=5,XXX.height=5
2.5 矩形的表示:Rect类
Rect类的成员变量有x、y、width、height,分别为左上角的坐标和矩形的宽和高。
常用的成员函数有:
Size()返回值为Size;
area(),返回矩形的面积;
contains(Point)判断点是否在矩形;
inside(Rect)函数判断矩形是否在矩形内
tl()返回左上角的点坐标
br()返回右下角的点坐标
求两个矩形的交集:Rect rect = rect1 & rect2;
求两个矩形的并集:Rect rect = rect1 | rect2;
平移操作:Rect rectShift = rect + point;
缩放操作:Rect rectSccale = rect + size;
2.5 颜色空间转换:cvtColor()函数
cvtColor()函数是OpenCV里的颜色空间转换函数,可以实现RGB颜色向HSV、HSI等颜色空间的转换,也可以转换为灰度图。
void cvtColor(InputArray src, OutputArray dst,int code, int dstCn=0)
- 第一个参数:输入图像
- 第二个参数:输出图像
- 第三个参数:颜色空间转换的标识符
- 第四个参数:目标图像的通道数,若该参数为0,表示目标图像取源图像的通道数。
例如:cvtColor(srcImage,dstImage,COLOR_GRAY2BGR)
3.基础图形的绘制
- 用于绘制直线的line函数
- 用于绘制椭圆的ellipse函数
- 用于绘制矩形的rectangle函数
- 用于绘制圆的circle函数
- 用于绘制填充的多边形的fillPoly函数
定义几个自定义的绘制函数,然后调用这些自定义函数绘制yi一幅图:化学原子图。
程序的宏定义
#define WINDOW_WIDTH 600 //定义窗口大小
3.1 DrawEllipse()函数的写法
//自定义绘制函数:实现了绘制不同角度、相同尺寸的椭圆
void DrawEllipse(Mat img, double angle) {
int thickness = 2;
int lineType = 8;
ellipse(img,
Point(WINDOW_WIDTH / 2, WINDOW_WIDTH / 2),
Size(WINDOW_WIDTH/4,WINDOW_WIDTH/16),
angle,
0,
360,
Scalar(255,129,0),
thickness,
lineType
);
}
ellipse函数的参数及其含义:
3.2 DrawFilledCircle()函数的写法
//自定义的绘制函数,实现了实心圆的绘制
void DrawFilledCircle(Mat img, Point center) {
int thickness = -1;
int lineType = 8;
circle(img,
center,
WINDOW_WIDTH / 32,
Scalar(0, 0, 255),
thickness,
lineType
);
}
3.3 main()函数
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
#define WINDOW_WIDTH 600
#define WINDOW_NAME "绘制图"
int main() {
Mat atomImage = Mat::zeros(WINDOW_WIDTH, WINDOW_WIDTH, CV_8UC3);
//1.1:绘制椭圆
DrawEllipse(atomImage, 90);
DrawEllipse(atomImage, 0);
DrawEllipse(atomImage, 45);
DrawEllipse(atomImage, -45);
//1.2:绘制圆心
DrawFilledCircle(atomImage, Point(WINDOW_WIDTH / 2, WINDOW_WIDTH / 2));
imshow(WINDOW_NAME, atomImage);
waitKey(0);
}