《Opencv3编程入门》学习笔记
记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。
第四章 OpenCV数据结构与基本绘图
四、基础图像容器Mat
(一)数字图像存储概述
图像在数码设备中的表现形式为包含众多强度值的像素点矩阵。
(二)Mat结构的使用
- Mat是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不同的维数)的指针。
- Opencv使用了引用计数机制。通过拷贝构造函数只复制信息头和矩阵指针,矩阵指针指向矩阵地址。
- opencv函数中输出图像的内存分配是自动完成的,且不需要考虑内存释放问题。
- 一般不复制矩阵本身,如何非要复制,可以使用函数clone()或者copyTo()来复制一幅图像的矩阵。
Mat F = A.clone();
Mat G;
A.copyTo(G);
- 一个很棒的功能:可以创建只引用部分数据的信息头
imageROI=image(Rect(500,250,logo.cols,logo.rows)); //使用矩阵界定
imageROI=image(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols)); //使用行和列来界定
(三)像素值的存储方法
颜色系统有很多,具体如下:
- RGB是最常见的,这是因为人眼采用相似的工作机制,它也被显示设备所采用。
- HSV和HLS把颜色分解为色调、饱和度和亮度/明度。这是描述颜色更自然的方式,比如可以通过抛弃最后一个元素,使算法对输入图像的光照条件不敏感。
- YCrCb在JPEG图像格式中广泛使用。
- CIE Lab是一种在感知上均匀的颜色空间,它适合用来度量两个颜色之间的距离。
(四)显式创建Mat对象的七种方法
【方法一】使用Mat()构造函数
针对二维多通道图像
Mat M(2,2,CV_8UC3,Scalar(0,0,255));
cout << "M = " << endl << " " << M << endl << endl;
1、参数1:行数
2、参数2:列数
3、参数3:存储元素的数据类型以及每个矩阵点的通道数
CV_[位数][带符号与否][类型前缀]C[通道数]
CV_8UC3:表示使用8位的unsigned char型,每个像素由三个元素组成三通道
4、参数4:Scalar:short型的向量,能使用指定的定制化值来初始化矩阵,它还可以用于表示颜色。
【方法二】在C\C++中通过构造函数进行初始化
创建超过二维的矩阵
int sz[3] = {2,2,2}
Mat L(3,sz,CV_8UC,Scalar::all(0));
1、参数1:指定维数
2、参数2:指向一个数组的指针,这个数组包含每个维度的尺寸
3、其他参数与上面一致
【方法三】为已存在的IpIImage指针创建信息头
IpIImage已经被废弃了
【方法四】利用Create()函数
此创建方法不能为矩阵设初值,只是在改变尺寸时重新为矩阵数据开辟内存而已。
M.create(4,4,CV_8UC(2));
cout << "M = " << endl << " " << M << endl << 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;
【方法六】对小矩阵使用都好分隔式初始化函数
Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "C = " << endl << " " << C << endl << endl;
【方法七】为已存在的对象创建新信息头
Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl;
(五)OpenCV中的格式化输出方法
r矩阵的定义和初始化
Mat r = Mat(10,3,CV_8UC3);
//randu:产生随机值来填充矩阵,需要给定一个上限和下限来确保随机值在期望的范围内。
randu(r,Scalar::all(0),Scalar::all(255));
【风格一】OpenCV默认风格
cout << "r (OpenCV默认风格) = " << r << ";" << endl << endl;
【风格二】Python风格
//opencv2
cout << "r (Python风格) = " << format(r,"python") << ";" << endl << endl;
//opencv3
cout << "r (Python风格) = " << format(r,Formatter::FMT_PYTHON) << ";" << endl << endl;
【风格三】逗号分隔风格
//opencv2
cout << "r (逗号分隔风格) = " << format(r,"csv") << ":" << endl << endl;
//opencv3
cout << "r (逗号分隔风格) = " << format(r,"Formatter::FMT_CSV") << ":" << endl << endl;
【风格四】Numpy风格
//opencv2
cout << "r (Numpy风格) = " << format(r,"numpy") << ":" << endl << endl;
//opencv3
cout << "r (Numpy风格) = " << format(r,"Formatter::FMT_NUMPY") << ":" << endl << endl;
【风格五】C语言风格
//opencv2
cout << "r (Numpy风格) = " << format(r,"C") << ":" << endl << endl;
//opencv3
cout << "r (Numpy风格) = " << format(r,"Formatter::FMT_C") << ":" << endl << endl;
(六)输出其他常用数据结构
使用运算符"<<"
来打印其他常用的OpenCV数据结构。
1、定义和输出二维点
二维点:point2f(x,y)中的x代表在图像中的列,y代表图像中的行
Point2f p(6,2);
cout << "【二维点】p = " << p << ";\n" <<endl;
2、定义和输出三维点
二维点:point3f(x,y,z)
Point3f p3f(8,2,0);
cout << "【三维点】p3f = " << p3f << ";\n" <<endl;
3、定义和输出基于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);
//size_t类型表示C中任何对象所能达到的最大长度,它是无符号整数
for(size_t i = 0;i<points.size();++i)
points[i] = Point2f((float)(i*5),(float)(i%7));
cout << "【二维点向量】points = " <<points<< ";";
(七)示例程序:基础图像容器Mat类的使用
书本配套示例程序包中有一个短小的示例程序。
二、常用数据结构和函数
(一)点的表示:point类
Point类数据结构表示了二维坐标系下的点。
Point point;
point.x = 10;
point.y = 8;
或
Point point = Point(10,8);
(二)颜色的标识:Scalar类
Scalar()表示具有4个元素的数组,在OpenCV中被大量用于传递像素值,如RGB颜色值。第四个参数可不写。
Scalar(a,b,c)
定义的RGB颜色值:红色分量c,绿色分量c,蓝色分量c
(三)尺寸的表示:Size类
Size(5,5);//构造出的Size宽度和高度都为5,即XXX.width和XXX.height都为5
(四)矩形的表示:Rect类
Rect类的成员变量有x,y,width,height,分别为左上角点的坐标和矩形的宽和高。常用的成员函数有:
- Size()返回值为Size。
- area()返回矩形的面积。
- contains(Point)判断点是否在矩形内。
- inside(Rect)函数判断矩形是否在该矩形内。
- tl()返回左上角点坐标
- br()返回右下角点坐标。
求两个矩形的交集和并集
Rect rect =rect1 & rect2;
Rect rect = rect | rect2;
矩阵执行平移操作和缩放操作
Rect rectShift = rect + point;
Rect rectScale = rect + size;
(五)颜色空间转换:cvtColor()函数
cvtColor()函数是OpenCV里的颜色空间转换函数,可以实现RGB颜色像HSV、HSI等颜色空间的转换,也可以转换为灰度图像。
void cvtColor(InputArray src,OutputArray dst,int code,int dstCn=0)
1、参数1:输入图像
2、参数2:输出图像
3、参数3:颜色空间转换的标识符
4、参数4:目标图像的通道数。若该参数是0,表示目标图像取源图像的通道数。
//opencv2
cvtColor(srcImage,dstImage,CV_GRAY2BGR); //转换原始图为灰度图
//opencv3
cvtColor(srcImage,dstImage,COLOR_GRAY2BGR); //转换原始图为灰度图
标识符列举:
颜色空间转换的最简化版代码:
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
using namespace cv;
void main(){
//1、载入图片
Mat srcImage = imread("D://lili/Desktop/jpg/opencv/1.jpg",1),dstImage;
//2、转换颜色空间
cvtColor(srcImage,dstImage,COLOR_BGR2Lab);
//3、显示效果图
imshow("效果图",dstImage);
//4、保持窗口显示
waitKey();
}
(六)其他常用的知识点
- Matx是个轻量级的Mat,必须在使用前规定好大小,比如一个2*3的float型的Matx,可以声明为Matx23f。
- Vec是Matx的一个派生类,是一个一维的Matx,跟vector很相似。在OpenCV源码中有如下定义。
template<typename _Tp,int n> class vec:public Matx<_Tp,n,1>{…);
typedef Vec<uchar, 2> Vec2b;
- Range类其实就是为了使OpenCV的使用更像MATLAB而产生的。比如Range:all()其实就是MATLAB里的符号。而Range(a,b)其实就是MATLAB中的a:b,注意这里的a和b都应为整型。
- OpenCV中防止内存溢出的函数有alignPtr、alignSize、allocate、deallocate、fastMalloc、fastFree等。
- <math.h>里的一些函数使用起来很方便,有计算向量角度的函数fastAtan2、计算立方根的函数cubeRoot、向上取整函数cvCeil、向下取整函数cvFloor、四舍五入函数cVRound等。还有一些类似MATLAB里面的函数,比如cvlslnf判断自变量是否无穷大,cvlsNaN判断自变量是否不是一个数。
- 显示文字相关的函数有 getTextSize、cvInitFont、putText。
- 作图相关的函数有 circle、clipLine、ellipse、ellipse2Poly、line、rectangle、polylines、类Linelterator。
- 填充相关的函数有fillConvexPoly、fillPoly。
- OpenCV中RNG()函数的作用为初始化随机数状态的生成器
其他数据结构相关的知识由于使用较少,在需要用到的时候可以配合OpenCV文档,并查阅OpenCV的源码进行学习。
三、基本图形的绘制
绘制函数
- 用于绘制直线的line函数;
- 用于绘制椭圆的ellipse函数;
- 用于绘制矩形的rectangle函数;
void rectangle(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);
第一个参数:表示要绘制矩形的图像。
第二个参数:表示矩形左上角顶点。
第三个参数:表示矩形右下角顶点。
第四个参数:表示矩形的颜色。
第五个参数:表示线条的粗细程度。 FILLED = -1, //取负值时(如 CV_FILLED)函数绘制填充了色彩的矩形
第六个参数:表示线条的类型。其中: LINE_4 = 4, LINE_8 = 8, LINE_AA = 16,
- 用于绘制圆的circle函数;
- 用于绘制填充的多边形的fillPoly函数。
示例程序:绘制一幅化学原子示例图和一幅组合图
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
#define WINDOW_WIDTH 600 // 定义窗口大小的宏
#define WINDOW_NAME1 "原子图" //为窗口标题定义的宏
#define WINDOW_NAME2 "多边形图以及画线" //为窗口标题定义的宏
/*绘制椭圆
void ellipse( CvArr* img, CvPoint center, CvSize axes, double angle, double start_angle, double end_angle, CvScalar color, int thickness=1, int line_type=8, int shift=0 );
第一个参数:表示要绘制椭圆的图像。
第二个参数:表示椭圆圆心坐标。(Point)
第三个参数:表示轴的长度。(Size)
第四个参数:表示偏转的角度。
第五个参数:表示圆弧起始角的角度。.
第六个参数:表示圆弧终结角的角度。
第七个参数:表示线条的颜色。(Scalar)
第八个参数:表示线条的粗细程度。
第九个参数:表示线条的类型。
第十个参数:表示圆心坐标点和数轴的精度。
*/
void DrawEllipse(Mat img, double angle)
{
int thickness = 2; // 线宽
int lineType = 8; // 线性,8 代表联通线性
ellipse(img,
Point(WINDOW_WIDTH / 2, WINDOW_WIDTH / 2),
Size(WINDOW_WIDTH / 4, WINDOW_WIDTH / 16),
angle, // 角度,0~360 度
0,
360,
Scalar(255, 129, 0),
thickness,
lineType);
}
/*绘制实心圆
void circle(InputOutputArray img, Point center, int radius, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);
第一个参数:表示要绘制圆的图像。
第二个参数:表示圆心坐标。
第三个参数:表示圆的半径。
第四个参数:表示线条的颜色。
第五个参数:表示线条的粗细程度。
第六个参数:表示线条的类型。
第七个参数:表示圆心坐标点和数轴的精度。
*/
void DrawFilledCircle(Mat img, Point center)
{
int thickness = -1; // 线粗-1,代表实心
int lineType = 8; // 线性,8 代表联通线性
circle(img,
center,
WINDOW_WIDTH / 32,
Scalar(0, 0, 255),
thickness,
lineType);
}
/*凹多边形绘制
void fillPoly(Mat& img, const Point** pts, const int* npts, int ncontours, const Scalar& color, int lineType=8, int shift=0, Point offset=Point());
第一个参数:表示要绘制填充多边形的图像。
第二个参数:表示指向多边形的指针数组。(const)
第三个参数:表示多边形的顶点个数。(数组)
第四个参数:表示多边形的个数。
第五个参数:表示填充的颜色。
第六个参数:表示线条的粗细程度。
第七个参数:顶点坐标的小数点位数。
*/
void DrawPolygon(Mat img)
{
int lineType = 8;
// 创建一些点
Point rookPoints[1][20];
rookPoints[0][0] = Point(WINDOW_WIDTH / 4, 7 * WINDOW_WIDTH / 8);
rookPoints[0][1] = Point(3 * WINDOW_WIDTH / 4, 7 * WINDOW_WIDTH / 8);
rookPoints[0][2] = Point(3 * WINDOW_WIDTH / 4, 13 * WINDOW_WIDTH / 16);
rookPoints[0][3] = Point(11 * WINDOW_WIDTH / 16, 13 * WINDOW_WIDTH / 16);
rookPoints[0][4] = Point(19 * WINDOW_WIDTH / 32, 3 * WINDOW_WIDTH / 8);
rookPoints[0][5] = Point(3 * WINDOW_WIDTH / 4, 3 * WINDOW_WIDTH / 8);
rookPoints[0][6] = Point(3 * WINDOW_WIDTH / 4, WINDOW_WIDTH / 8);
rookPoints[0][7] = Point(26 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 8);
rookPoints[0][8] = Point(26 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 4);
rookPoints[0][9] = Point(22 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 4);
rookPoints[0][10] = Point(22 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 8);
rookPoints[0][11] = Point(18 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 8);
rookPoints[0][12] = Point(18 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 4);
rookPoints[0][13] = Point(14 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 4);
rookPoints[0][14] = Point(14 * WINDOW_WIDTH / 40, WINDOW_WIDTH / 8);
rookPoints[0][15] = Point(WINDOW_WIDTH / 4, WINDOW_WIDTH / 8);
rookPoints[0][16] = Point(WINDOW_WIDTH / 4, 3 * WINDOW_WIDTH / 8);
rookPoints[0][17] = Point(13 * WINDOW_WIDTH / 32, 3 * WINDOW_WIDTH / 8);
rookPoints[0][18] = Point(5 * WINDOW_WIDTH / 16, 13 * WINDOW_WIDTH / 16);
rookPoints[0][19] = Point(WINDOW_WIDTH / 4, 13 * WINDOW_WIDTH / 16);
const Point* ppt[1] = { rookPoints[0] }; // 多边形定点集
int npt[] = { 20 }; // 多边形定点数目
fillPoly(img,
ppt,
npt,
1,
Scalar(255, 255, 255), // 多边形颜色:白色
lineType);
}
/*线绘制
void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0);
第一个参数:表示要绘制线段的图像。
第二个参数:表示线段的起点。
第三个参数:表示线段的终点。
第四个参数:表示线段的颜色。
第五个参数:表示线段的粗细。
第六个参数:表示线段的类型。可以取值8,4,和CV_AA,分别代表8邻接连接线,4邻接连接线和反锯齿连接线。默认值为8。
第七个参数:表示坐标点小数点位数。
*/
void DrawLine(Mat img, Point start, Point end)
{
int thickness = 2;
int lineType = 8;
line(img,
start,
end,
Scalar(0,0,0), // 黑色
thickness,
lin eType);
}
int main(int argc, char ** argv)
{
Mat atomImage = Mat::zeros(WINDOW_WIDTH, WINDOW_WIDTH, CV_8UC3);
Mat rookImage = Mat::zeros(WINDOW_WIDTH, WINDOW_WIDTH, CV_8UC3);
//---------1、绘制化学中的原子示例图--------------
// 椭圆绘制
DrawEllipse(atomImage, 0);
DrawEllipse(atomImage, 90);
DrawEllipse(atomImage, 45);
DrawEllipse(atomImage, -45);
// 绘制圆心
DrawFilledCircle(atomImage, Point(WINDOW_WIDTH / 2, WINDOW_WIDTH / 2));
//---------2、绘制组合图--------------
// 绘制多边形
DrawPolygon(rookImage);
// 绘制矩形
rectangle(rookImage,
Point(0, 7 * WINDOW_WIDTH / 8),
Point(WINDOW_WIDTH, WINDOW_WIDTH),
Scalar(0, 255, 255),
-1,
8);
// 绘制一些线段
DrawLine(rookImage, Point(0, 15 * WINDOW_WIDTH / 16), Point(WINDOW_WIDTH, 15 * WINDOW_WIDTH / 16));
DrawLine(rookImage, Point(WINDOW_WIDTH / 4, 7 * WINDOW_WIDTH / 8), Point(WINDOW_WIDTH / 4, WINDOW_WIDTH));
DrawLine(rookImage, Point(WINDOW_WIDTH / 2, 7 * WINDOW_WIDTH / 8), Point(WINDOW_WIDTH / 2, WINDOW_WIDTH));
DrawLine(rookImage, Point(3 * WINDOW_WIDTH / 4, 7 * WINDOW_WIDTH / 8), Point(3 * WINDOW_WIDTH / 4, WINDOW_WIDTH));
// ---------------------------<3>显示绘制出的图像------------------------
imshow(WINDOW_NAME1, atomImage);
moveWindow(WINDOW_NAME1, 0, 200);
imshow(WINDOW_NAME2, rookImage);
moveWindow(WINDOW_NAME2, WINDOW_WIDTH, 200);
waitKey(0);
return(0);
}
运行效果