OpenCV-Mat的回顾与再学习的漫漫长路(一)

来源:互联网 发布:激战2呆萌夏尔捏脸数据 编辑:程序博客网 时间:2024/05/21 22:28

     接触OpenCV已经是漫漫长路,之前很多时候都没有进行定期的回顾与总结,于是决定好好再花时间稍微理理,可能并不是像初学那样有着递推的前后关系。

走到哪儿,看到哪儿,记到哪儿,这便是我的状态。

这篇文章主要参考博客,感谢这位牛人的无私奉献:

感谢你,点击here开始跳转(你们说我抄袭也罢,确实只是看了自己有感而发,总成此文,绝不商用,这是一个尊重版权的时代,也是一个共享的时代)

    PS:实验室周围面临装修,简直就是四面楚歌委屈

漫漫长路开篇(一)

相毕大多数人和我一样,刚接触OpenCV的时候,第一个接触到的就是Mat类型了,Mat是最基本的数据处理单元,既可以用来保存图像,又可以用来表示矩阵。

当时没太在意,大致知道怎么回事,于是乎就直接敲代码,后面接触多了,才慢慢总结出了它的一些特点,仍然是有些不全,我也没有可以系统的去了解它,这里稍微做一些总结。

相信大家都是数字图像处理过来的,在数字图像处理领域,图像就是离散的信号,说直接点,在计算机眼里,他就是一个大矩阵,矩阵的元素就是图像的像素值。

然后你就会疑惑,彩色的图像是咋回事呢?慢慢来

 

Mat本质上是有由一个矩阵头和一个指针组成的类。矩阵头包含了矩阵的大小,存储的方法,存储的地址等,指针指向了一个包含像素值的矩阵。

  我们来看看Mat的存储形式。Mat和Matlab里的数组格式有点像,但一般是二维向量,如果是灰度图,一般存放<uchar>类型;如果是RGB彩色图,存放<Vec3b>类型。

单通道灰度图数据存放格式:(这张图大家不陌生)


  我们知道色彩都是由RGB三原色构成的,那么彩色图像的构成也很简单,每一列由三个通道(R,G,B)组成,这里只需要注意一点,OpenCV的排列顺序是BGR,见下图:


比如我们读入一个100×100的灰度图像img1和一个100×100的RGB彩色图像img2。然后输出各自的矩阵,会发现img1输出了一个100×100的矩阵,而img2输出了一个300×100的矩阵。因为RGB图像每个像素点上有3个值,而灰度图像只有1个,尽管图像的size是一样的。

矩阵的英文单词(Matrix),Mat估计很多人像我一样,直接就是用它来读图,其实不然,因为图像本身就是一个矩阵,所以我们只有了解它用来创建矩阵的特性才能在今后的应用中更熟练的使用它。(感兴趣可以深入源代码了解

Mat-用来创建矩阵

1.只构造矩阵头,无数据

Mat();//构造2维矩阵头    Mat(int rows, int cols, int type);Mat(Size size, int type);//构造n维矩阵头 Mat(int ndims, const int* sizes, int type);//创建矩阵头void create(int rows, int cols, int type);void create(Size size, int type);void create(int ndims, const int* sizes, int type);//创建一个指定行数的矩阵头     Mat row(int y) const;//创建一个指定列数的矩阵头Mat col(int x) const;//创建一个指定行数范围的矩阵头Mat rowRange(int startrow, int endrow) const;Mat rowRange(const Range& r) const;//创建一个指定列数范围的矩阵头Mat colRange(int startcol, int endcol) const;Mat colRange(const Range& r) const;

2.创建矩阵并初始化

//构造2维矩阵并赋值Mat(int rows, int cols, int type, const Scalar& s);Mat(Size size, int type, const Scalar& s);//构造n维矩阵并赋值Mat(int ndims, const int* sizes, int type, const Scalar& s);//复制矩阵Mat(const Mat& m);  //Mat m3(m2);将m2复制到m3中,m2和m3共用同一内存位置的数据,也就是说改变m2,m3会根根变,同理//根据data构造矩阵Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);Mat(Size size, int type, void* data, size_t step=AUTO_STEP);Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0);//从Mat中提取一个区域来构造矩阵Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());Mat(const Mat& m, const Rect& roi);Mat(const Mat& m, const Range* ranges);//根据特定对象来构造矩阵,其实是其他数据类型转Mat,false表示不包含数据,true表示包含数据//将CvMat转换成Mat,默认不复制数据。Mat(const CvMat* m, bool copyData=false);//将CvMatND转换成Mat,默认不复制数据。Mat(const CvMatND* m, bool copyData=false);//将IplImage转换成Mat,默认不复制数据。 Mat(const IplImage* img, bool copyData=false); //根据std::vector构造矩阵,默认不复制数据template<typename _Tp> explicit Mat(const vector<_Tp>& vec, bool copyData=false);//根据cv::Vec构造矩阵,默认不复制数据template<typename _Tp, int n> explicit Mat(const Vec<_Tp, n>& vec, bool copyData=true);//根据cv::Matx构造矩阵,默认不复制数据template<typename _Tp, int m, int n> explicit Mat(const Matx<_Tp, m, n>& mtx, bool copyData=true);//根据2维点构造矩阵template<typename _Tp> explicit Mat(const Point_<_Tp>& pt, bool copyData=true);//根据3维点构造矩阵template<typename _Tp> explicit Mat(const Point3_<_Tp>& pt, bool copyData=true);//根据comma initializer构造矩阵,默认不复制矩阵template<typename _Tp> explicit Mat(const MatCommaInitializer_<_Tp>& commaInitializer);//构造特殊矩阵static MatExpr zeros(int rows, int cols, int type);static MatExpr zeros(Size size, int type);static MatExpr zeros(int ndims, const int* sz, int type);static MatExpr ones(int rows, int cols, int type);static MatExpr ones(Size size, int type);static MatExpr ones(int ndims, const int* sz, int type);static MatExpr eye(int rows, int cols, int type);static MatExpr eye(Size size, int type);//Mat m1 = Mat::eye(5,5,CV_8UC1);//复制矩阵,包括数据Mat clone() const;void copyTo( OutputArray m ) const;   void copyTo( OutputArray m, InputArray mask ) const;   void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;void assignTo( Mat& m, int type=-1 ) const;//从GpuMat下载数据explicit Mat(const gpu::GpuMat& m);

小结一下:

1.我们主要还是用来创建二维矩阵,高维的东西脑容量不够,哈哈

2.IplImage转换为Mat,不建议使用,因为程序中还有其他一些相应的东西要改,不单单这一句简单的代码,所以碰到还使  用IplImage的老资源,干脆别看了,相信我,肯定有更好的Mat资源,再找找
3.zeros(全零矩阵)  ones(全1矩阵)  eye(单位矩阵)

3.释放矩阵及其元素(其实我还没有达到这一层境界,每次基本都没有释放奋斗

//析构函数~Mat();   //同Mat::release()。//释放矩阵void release();//释放矩阵数据void deallocate();

4.改变矩阵的结构和元素(这一步确实用的比较多)

//将矩阵所有元素设置为sMat& operator = (const Scalar& s);//根据mask将矩阵部分元素设置为sMat& setTo(InputArray value, InputArray mask=noArray());//改变矩阵的通道数cn、维数dims和行数rowsMat reshape(int cn, int rows=0) const;Mat reshape(int cn, int newndims, const int* newsz) const;


5.返回矩阵头的信息和指向矩阵的指针

很重要,必须熟悉

//判断矩阵在内存是否是连续存放bool isContinuous() const;//判断一个矩阵是否是另一个矩阵的子矩阵bool isSubmatrix() const;//以字节为单位返回元素大小,比如CV_8U返回1,CV_8U3返回3,CV_16U返回2,CV_64FC3返回24    size_t elemSize() const;//以字节形式返回元素通道的尺寸,<span style="font-family: Arial, Helvetica, sans-serif;">也就是elemSize()/channels()</span>size_t elemSize1() const;//返回矩阵数据类型int type() const;//返回矩阵数据深度,<span style="color: rgb(17, 17, 17); font-size: 13px; line-height: 16.848px; white-space: pre-wrap;">CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6</span>int depth() const;   //返回矩阵通道int channels() const;//返回step/elemSize1()size_t step1(int i=0) const;//判断矩阵是否为空,是返回truebool empty() const;//返回矩阵所有元素的和size_t total() const;//检查Mat某通道元素矩阵是否是向量,是就返回Nint checkVector(int elemChannels, int depth=-1, bool requireContinuous=true) const;//返回指向Mat的行指针uchar* ptr(int i0=0);const uchar* ptr(int i0=0) const;//返回指向Mat像素点的指针uchar* ptr(int i0, int i1);const uchar* ptr(int i0, int i1) const;//返回指向第#0行#1列#2通道的指针uchar* ptr(int i0, int i1, int i2);const uchar* ptr(int i0, int i1, int i2) const;//返回指向索引为idx的某个数(不是像素点)的指针uchar* ptr(const int* idx);//返回只读型的指向索引为idx的某个数(不是像素点)的指针const uchar* ptr(const int* idx) const;

6.矩阵的数学运算

//返回矩阵对角线元素,d=0,对角线,d>0,对角线下移d单位的斜线上元素,d<0,对角线上移d单位的斜线上元素Mat diag(int d=0) const;static Mat diag(const Mat& d);//计算矩阵的转置MatExpr t() const;//计算矩阵的逆,/*DECOMP_LU:采用的是高斯消去法(Gaussian elimination),只能计算非奇异矩阵的逆,如果矩阵可逆,则函数返回非零值,否则返回0.DECOMP_SVD:采用的是奇异值分解法(singular value decomposition)。对于奇异矩阵,计算伪逆矩阵,非奇异矩阵则计算逆矩阵,如果矩阵是非奇异的,则返回最小奇异值和最大奇异值的比例,否则返回0. DECOMP_CHOLESKY:采用的是Cholesky分解*/MatExpr inv(int method=DECOMP_LU) const;//矩阵按元素相乘或相除。比如,Mat C = A.mul(5/B); //等价于divide(A,B,C,5)MatExpr mul(InputArray m, double scale=1) const;//计算两向量的叉积(外积,向量积)。Mat cross(InputArray m) const;//计算两向量的点积(内积,数量积)。double dot(InputArray m) const;

接下来看看实际读入一幅图像,看看内存里放了哪些东西(这才是这篇博客的重点

   (像素大小200*134)          

Mat img = imread("girl.jpg");

关于flags有一篇博客讲解的很好,你好!戳我开始跳转下面给出的这张图也是截图他的博客


dims = 2 .  表明img是矩阵,等于1就是向量了;

rows = 0x00000086 = 8*16 + 6 = 134;    cols = 0x000000c8 = 12*16 + 8 = 200; 图像尺寸是200×134,也可以用img.size返回图像尺寸;

我们继续:

Mat的数据都存在了data中,注意到data是指针型的,所以我们访问数据都要用到指针,也就是地址;

图像在内存的地址从datastart到dataend,即0x02968aa0 - 0x02955090 = 0x00013a10 = 80400 = 200 * 134* 3 = cols*rows*channels;

重点是step,step定义了矩阵的布局,img.step[0] = 0x00000258 = 600 = 200*3,所以step[0]是矩阵一行的大小。

step[1]=3, step[1]表示每一列由3组元素组成,其实img.channels()也等于3,step[1]也可以看做通道数。知道了数据的起始地、矩阵一行的大小和通道数,那么图像上(m,n)点的地址,就可以表示为:

ptr = datastart + step[0]*m + step[1]*n

对应的各通道数值的地址就分别为ptr[0]、ptr[1]、ptr[2]。

下面这张图片很精髓


上面是一个 3X4的矩阵,假设其数据类型为 CV_8UC1, 则

M.dims = 2,

M.rows = 3;

M.cols = 4.

因为是二维矩阵,那么step数组只有两个值,step[0] 和 step[1]分别代表一行的数据大小和一个元素的数据大小,则M.step[0] ==4, M.step[1] == 1.点(1,2)的地址

ptr12 = addr(M(1,2)) = M.data + M.step[0] *1 + M.step[1]*2

假设上面的矩阵数据类型是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;

点(1,2)处各通道数值的地址分别为ptr12[0] , ptr12[1] , ptr12[2]


再来看看三维情况(这张图很惊艳,诠释的太好,感觉根本不需要任何解释)



上面是一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型,在内存中是存成3个4×6矩阵的。


M.dims = 3 ; 
M.channels() = 4 ; 
M.elemSize1() = sizeof(short) = 2 ;
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(第三维度(即元素) * 通道数);
那么点(0,1,2)的地址为
ptr = addr(M0,1,2) = M.data +M.step[0]*0 + M.step[1]*1+ M.step[2]*2
对应的各通道的地址为ptr[0]、ptr[1]、ptr[2]、ptr[3]



知道了地址,元素的值就用取值运算符“
 * ”,比如上面,*ptr[0] 表示M在点(0,1,2)处第1通道的值。


Mat 很重要,接下来还有访问Mat的总结

工欲善其事,必先利其器!

感谢!参考博客链接

jie

原创粉丝点击