opencv访问mat
① 2019-10-01 opencv图像数据结构之Mat
Mat的本质是具有两个数据部分的类:矩阵头(包含诸如矩阵大小,用于存储的方法,用于存储矩阵的地址等信息)以及指向真正图像数据矩阵的指针。矩阵头的大小是恒定的,但真正的图像矩阵的大小是因实际图像的大小而异。
opencv是图像处理库,包含有大量的图像处理功能。这些功能在程序中表现为一个一个函数的形式,那么为了处理图像,一定少不了在函数间传递图像。如果每传递一次图像就为其新分配一次内存空间,必然会导致图像处理速度的大大下降,同时也会占用过高的内存空间团闷。为了解决这个问题,opencv是这样做的:每个Mat对象都有其唯一的矩阵头,但是实质性的图像矩阵地址可以共享,访问方式可以通过指针。在这种机制下,在程序中如果需要传递图像时,仅传递图像的搏或中矩阵头信息和指针,而不会去传递实质性的图像数据。
例子1: Mat A, C;// creates just the header parts
A = imread (argv[1], IMREAD_COLOR );// here we'll know the method used (allocate matrix)
Mat B(A);// Use the constructor
C = A;// Assignment operator
例子1中,3个Mat对象 A,B,C所指向的实质性的图像数据矩阵是同一个,但是它们的矩阵头信息(身份证号)是不同的。由于实质性的图像数据矩阵是同一个,所以改变A,B,C中的任何一个,另外两个也会改变。这种改变表现在它们的矩阵头信息的变化上。例如,通过对A操作把图像大小减小了一半,那么B,C的矩阵头信息中表征图像大小的信息部分一定也会变。在这种机制基础之上会有一个有意思的事情发生,你完全可以创建一个只引用实质图像矩阵的子部分信息的矩阵头。这种应用有一个很好的例子,它是:ROI,只需创建具有新边界的新标题即可,如下面例子2,其中 Mat 对象D,E只引用了A所引用的图像的一部分。
例子2:Mat D (A, Rect (10, 10, 100, 100) );// using a rectangle
Mat E = A(Range::all(), Range(1,3));// using row and column boundaries
图像在内存中的整个生命周期中会有一个计数变量与其相关,这个变量有什么用呢?它用来表征,有多少个Mat 对象,引用这个图像,当这个计数变量值为0时,系统会释放掉图像 所占内存。当然有时也许要真正完全传递整个图像而不是指针,因此opencv提供了 cv :: Mat :: clone() 和 cv :: Mat :: To() 函数来解决这个问题,如下例子3。
例子3:Mat F = A.clone();
Mat G;
A.To(G);
例子3中,A,F,G指针所指的实质性图像矩阵不是同一个,因此通过A,F,G对图像操作,相互之间不会有影响 。
存放方法的描述是关于如何存基山储像素值的(说明存放像素有多种方法可以选择)。这种选择,体现在色彩空间和数据类型的选择上。色彩空间不同,也就是对颜色编码的方式不同。最简单的一种是灰度空间,在灰度空间下我们可以使用白色和黑色的不同组合来创建许多灰色阴影。
对应颜色,我们有多种编码方式选择,比较主流的是RGB。RGB主流的原因和我们人眼编码颜色的方式有关系。它的基色是
红色,绿色和蓝色。为了编码颜色的透明度,有时会添加第4个量:alpha(A)。
但是还有很多其他色彩空间系统,并且各有优点:
1、 RGB是最常见的一种,因为我们的眼睛使用的,就是类似的系统。但是opencv标准显示系统使用BGR色彩空间(红、蓝通道切换)来编码颜色。
2、 HSV和HLS将颜色分解为它们的色相,饱和度和值/亮度分量,这是我们描述颜色更自然的方法。例如,您可以忽略最后一个变量,从而使算法对输入图像的光照条件不太敏感。
3、 流行的JPEG图像格式使用的YCrCb。
4、CIE L*a*b*是一个感知上统一的色彩空间,如果您需要测量给定颜色与另一种颜色空间的差距,它会派上用场。
色彩空间解决的是一信号有多少通道的问题(多少变量),那么数据类型解决的是,一个变量几位数据位(是否有符号,是整数还是实数)度量它的问题。
② opencv怎么给mat赋值
在OpenCV中有三种方式访问矩阵中的数据元素:容易的方式,困难的方式,以及正确的方式。今天主要讲容易方式:
最容易的方式是使用宏CV_MAT_ELEM( matrix, elemtype, row, col ),输入参数是矩阵,不是指针,网上有很多人说是指针,矩阵元素类型,行,列,返回值是相应行,列的矩阵元素。CV_MAT_ELEM可以给矩阵赋值,也可以访问矩阵元素。
CV_MAT_ELEM宏实际上会调用CV_MAT_ELEM_PTR(matrix,row,col)宏来完成任务。 CV_MAT_ELEM_PTR()宏的参数是矩阵,行,列。CV_MAT_ELEM()宏和CV_MAT_ELEM_PTR()宏的区别是,在调用CV_MAT_ELEM时,指向矩阵元素的指针的数据类型已经依据输入参数中的元素类型而做了强制转换:
如下程序:
CvMat* mat = cvCreateMat(3,3,CV_32FC1);//创建矩阵
cvZero(mat);//将矩阵置0
//为矩阵元素赋值
CV_MAT_ELEM( *mat, float, 0, 0 ) = 1.f; CV_MAT_ELEM( *mat, float, 0, 1 ) = 2.f;
CV_MAT_ELEM( *mat, float, 0, 2 ) = 3.f;
CV_MAT_ELEM( *mat, float, 1, 0 ) = 4.f;
CV_MAT_ELEM( *mat, float, 1, 1 ) = 5.f;
CV_MAT_ELEM( *mat, float, 1, 2 ) = 6.f;
CV_MAT_ELEM( *mat, float, 2, 0 ) = 7.f;
CV_MAT_ELEM( *mat, float, 2, 1 ) = 8.f;
CV_MAT_ELEM( *mat, float, 2, 2 ) = 9.f;
//获得矩阵元素的值
float element = CV_MAT_ELEM(*mat,float,2,2);
float element_1_1 = 7.7f;
*((float*)CV_MAT_ELEM_PTR(m, 1, 1)) = element_1_1;
float element = CV_MAT_ELEM(m,float, 1,1 );
cout<<element<<endl;
以上使用矩阵中元素的方式很方便,但不幸的是,该宏在每次调用时,都会重新计算指针的位置。这意味着,先查找矩阵数据区中第0个元素的位置,然后,根据参数中的行和列,计算所需要的元素的地址偏移量,然后将地址偏移量与第0个元素的地址相加,获得所需要的元素的地址。
所以,以上的方式虽然很容易使用,但是却不是获得矩阵元素的最好方式。特别是当你要顺序遍历整个矩阵中所有元素时,这种每次对地址的重复计算就更加显得不合理。
③ Opencv中数据结构Mat的相关属性
搬运自本人 CSDN 博客: 《Opencv中数据结构Mat的相关属性》
以上摘自OpenCV 2.4.9的官方文档opencv2refman.pdf。
以前虽然能够比较熟练的使用OpenCV,但是最近感觉其实笔者自己对OpenCV的最底层数据结构Mat与IplImage都不怎么熟悉…… 由于笔者比较反感总是需要管理内存的IplImage,所以对Mat数据结构做一下学习工作还是有必要的。
官方说明文档opencv2refman.pdf中,写出了Mat的定义如下:
下面笔者将从几个方面总结Mat数据结构的主要组成。
参考网址:
《OpenCV中对Mat里面depth,dims,channels,step,data,elemSize和数据地址计算的理解 》
《OpenCV Mat的常见属性》
《OpenCV学世拿早习笔记(四十)——再谈OpenCV数据结构Mat详解》
参考文档:
《opencv2refman.pdf》
如上面的Mat定义源码,Mat类中有很多重要的数据类型成员。
下面进行简单的列举。
把这四个数据成员放在一起,是因为这四个数据成员相互之间有关系。
数据的存储一直都是个值得关注的问题,所以数据元素存储的位数和范围就十分重要了。depth就体现了每一个像素的位数,即深度。
Mat中包含的图像深度如下所示:
另外还需要注意:大部分OpenCV的函数支持的数据深度只有8位和32位,所以尽量使用CV_64F。
channels表示了矩阵拥有的通道数量,这个比较容易理解:
type表示矩阵中元素的类型(depth)与矩阵的通道个数(channels),可以理解成上面的depth与channels的综合说明。type是一系列预定义的常量,命名规则如下:
<code>CV_+位数+数据类型+通敏简道数</code>
具体有如下值:
表格中,行代表了通道数量channels,列代表了图像深度depth。
例如CV_8UC3,可以拆分为:
注:type一般是在创建Mat对象时设定,若要去搜雀的Mat的元素类型,可以不使用type,使用depth。
elemSize表示了矩阵中每一个元素的数据大小,单位是字节。公式如下:
<code>elemSize = channels * depth / 8</code>
例如type == CV_16SC3,则elemSize = 3 * 16 / 8 = 6 Bytes。
elemSize1表示了矩阵元素的一个通道占用的数据大小,单位是字节。公式如下:
<code>elemSize = depth / 8</code>
例如type == CV_16SC3,则elemSize1 = 16 / 8 = 2 Bytes。
使用OpenCV处理图像时,最普遍的处理方式便是遍历图像,即访问所有的图像像素点。但有的算法还需要访问目标像素的邻域,所以这时候就需要了解访问Mat数据元素地址的方式。
假设有矩阵M,则数据元素的地址计算公式如下:
$$ addr(M_{i_{0}, i_{1}, ... i_{m-1}}) = M.data + M.step[0] * i_{0} + M.step[1] * i_{1} + ... + M.step[M.dims - 1] * i_{M_{dims-1}} $$
如果是二维数组,则上述公式就简化成:
$$ addr(M_{i,j}) = M.data + M.step[0] * i + M.step[1] * j $$
注:式中m = M.dims,即矩阵的维度。
假设存在一个二维矩阵如下图所示:
上面是一个3 × 4的矩阵。此时我们按照数据类型为CV_8U, CV_8UC3的情况,分别对其进行讨论。
首先假设其数据类型为CV_8U,也就是单通道的uchar类型,则可以得出上面的数据成员情况分别为:
若假设其数据类型为CV_8UC3,也就是三通道的uchar类型,则可以得出上面的数据成员情况分别为:
假设存在一个三维矩阵如下图所示:
上面是一个3 × 4 × 6的矩阵。假设其数据类型为CV_16SC4,此时对其进行讨论。
关于OpenCV地址访问方法及效率的部分,请见笔者的博文 《OpenCV像素点邻域遍历效率比较,以及访问像素点的几种方法 》 。
④ 【OPENCV】cv::Mat像素遍历方法比较
像素级别遍历是我们在图像任务中经常遇到的问题,在实时的图像处理中,能轿竖够高效物搭的访问像素数据是很重要的。OpenCV中的数据容器是cv::Mat,cv::Mat提供了三种数据访问的方式分别是下标寻址,指针访问,迭代器访问。下面我们对比下这几种不同方式的访问速度。
对比这几种方式我们可以发现,最为高效的还是直接使用指针闭蚂大计算地址偏移量, 然而这种方式必须保证Mat在内存的存储是连续的,可以通过cv::Mat::isContinous()函数检测,如果是连续的则可以处理为单行向量,使用最为高效的方式访问。如果不想这么麻烦,其实method5是一种较为可取的方式,通过从cv::Mat::ptr()得到每一行的首地址,这样就不需要保证连续存储,速度和纯粹使用指针也差不了多少。
实际上对于method5,不使用中间指针进行改写的话:
重新测试下:
时间上已经十分接近method6,实际操作的时候直接使用method5,不使用中间指针即可。
⑤ OpenCV (一)Mat基本操作以及灰度图转化
开始写OpenCV这篇文章的时候,不由想到,我的大学计算机图形学的第一门实操课程就是灰度转化,拉普拉斯锐化等。其中灰度图的转化,是计算机图形学基础中基础,这里就顺着OpenCV的灰度的转化,来看看OpenCV一些基础的api。
本文地址: https://www.jianshu.com/p/7963c7dbaf92
先来看看OpenCV,基础对象Mat,矩阵。什么是矩阵,实际上没有必要解释,一般人都能够明白数学意义上矩阵的含义。
OpenCV把每一个M * N的宽高图像,看成M*N的矩阵。矩阵的每一个单元就对应着图像中像素的每一个点。
我们如果放大图中某个部分,就会发现如下情况
图像实际上就如同矩阵一样每个单元由一个像素点构成。
因为OpenCV的Mat每一个像素点,包含的数据不仅仅只有一个单纯的数字。每一个像素点中包含着颜色通道数据。
稍微解释一下颜色通道,我们可以把世间万物肉眼能识别的颜色由3种颜色(R 红色,G 绿色,B 蓝色)经过调节其色彩饱和度组成的。也就是说通过控制RGB三种的色值大小(0~255)来调配新的颜色。
当我们常见的灰度图,一般是单个颜色通道,因为只用黑白两种颜色。我们常见的图片,至少是三色通道,因为需要RGB三种颜色通道。
我们常见Android的Bitmap能够设置ARGB_8888的标志位就是指能够通过A(透明通道),R,G,B来控制图片加载的颜色通道。
OpenCV为了更好的控制这些数据。因此采用了数学上的矩阵的概念。当OpenCV要控制如RGB三色通道的Mat,本质上是一个M * N * 3的三维矩阵。
但是实际上,我们在使用OpenCV的Mat的时候,我们只需要关注每个图片的像素,而每个像素的颜色通道则是看成Mat中每个单元数据的内容即可
我们先来看看Mat的构造方法
现阶段,实际上我们值得我们注意的是构造函数:
举个例子:
这个mat矩阵将会制造一个高20,宽30,一个1字节的颜色通道(也是Mat中每一个像素数据都是1字节的unchar类型的数据),同时颜色是白色的图片。
在这里面我们能够看到一个特殊的宏CV_8UC1。实际上这是指代OpenCV中图片带的是多少颜色通道的意思。
这4个宏十分重要,要时刻记住。
当我们需要把Mat 中的数据拷贝一份出来,我们应该调用下面这个api:
这样就能拷贝一份像素数据到新的Mat中。之后操作新的Mat就不会影响原图。
实际上,在本文中,我们能够看到OpenCV是这么调用api读取图片的数据转化为Mat矩阵。
OpenCV会通过imread去读图片文件,并且转化为Mat矩阵。
能看见imread,是调用imread_把图片中的数据拷贝的img这个Mat对象中。接着会做一次图片的颠倒。这个方面倒是和Glide很相似。
文件:moles/imgcodecs/src/loadsave.cpp
这里面做了几个事情,实际上和FFmpge的设计十分相似。
其核心也是操作Mat中的像素指针,找到颜色通道,确定指针移动的步长,赋值图片的数据到Mat矩阵中。核心如下:
其中还涉及到jpeg的哈夫曼算法之类的东西,这里就不深入源码。毕竟这是基础学习。
什么是灰度图,灰度度图实际上我们经常见到那些灰白的也可以纳入灰度图的范畴。实际上在计算机图形学有这么一个公式:
将RGB的多颜色图,通过 的算法,将每一个像素的图像的三颜色通道全部转化为为一种色彩,通过上面的公式转为为一种灰色的颜色。
一旦了解了,我们可以尝试编写灰度图的转化。我们通过矩阵的at方法访问每一个像素中的数据。
为了形象表示矩阵指针,指向问题,可以把RGB在OpenCV的Mat看成如下分布:
记住OpenCV的RGB的顺序和Android的不一样,是BGRA的顺序。和我们Android开发颠倒过来。
因此,我们可以得到如下的例子
我们经过尝试之后,确实能够把一个彩色的图片转化一个灰色图片。但是这就是
这里介绍一下Mat的一个api:
实际上OpenCV,内置了一些操作,可以把RGB的图像数据转化灰度图。
我们看看OpenCV实际上的转化出来的灰度图大小。我们通过自己写的方法,转化出来的灰度图是119kb,而通过cvtColor转化出来的是44kb。
问题出在哪里?还记得吗?因为只有灰白两种颜色,实际上只需要一种颜色通道即可,而这边还保留了3个颜色通道,也就说图片的每一个像素点中的数据出现了没必要的冗余。
这样就是44kb的大小。把三颜色通道的数据都设置到单颜色通道之后,就能进一步缩小其大小。
实际上在Android中的ColorMatrix中也有灰度图转化的api。
对画笔矩阵进行一次,矩阵变化操作。
实际上就是做了一次矩阵运算。绘制灰度的时候相当于构建了这么一个矩阵
接着通过矩阵之间的相乘,每一行的 0.213f,0.715f,0.072f控制像素的每个通道的色值。
对于Java来说,灰度转化的算法是: ,把绿色通道的比例调大了。
在OpenCV中有这么两个API,add和addWidget。两者都是可以把图像混合起来。
add和addWidget都是将像素合并起来。但是由于是像素直接相加的,所以容易造成像素接近255,让整个像素泛白。
而权重addWeighted,稍微能减轻一点这种问题,本质上还是像素相加,因此打水印一般不是使用这种办法。
等价于
saturate_cast这个是为了保证计算的值在0~255之间,防止越界。
饱和度,图片中色值更加大,如红色,淡红,鲜红
对比度:是指图像灰度反差。相当于图像中最暗和最亮的对比
亮度:暗亮度
控制对比度,饱和度的公式: , ,
因此当我们想要控制三通道的饱和度时候,可以通过alpha来控制色值成比例增加,beta控制一个色值线性增加。
如下:
在这里,看到了OpenCV会把所有的图片看成Mat矩阵。从本文中,能看到Mat的像素操作可以能看到有两种,一种是ptr像素指针,一种是at。ptr是OpenCV推荐的更加效率的访问速度。
当然还有一种LUT的核心函数,用来极速访问Mat矩阵中的像素。其原理是对着原来的色值进行预先的变换对应(设置一个颜色通道)。用来应对设置阈值等情况。