Opencv core module - Mat学习

Mat - 基本图像容器

目标

我们有多种方法从现实世界中获取数字图像:数码相机,扫描仪,计算机断层扫描和磁共振成像等等。
在任何情况下,我们(人类)看到的都是图像。然而,当将其转换到数字设备时,我们记录的是图像中每个点的数值,即灰度值。

Mat-Basic Image For Computer

例如在上述图像中,可以看到的汽车图像只不过是包含像素点所有强度值的矩阵。

OpenCV 是一个计算机视觉库,其主要重点是处理和操纵这些矩阵信息。 因此,需要熟悉的第一件事是OpenCV如何存储和处理图像。

Mat

OpenCV自2001年以来发展。刚开始,库围绕着 C接口构建,并将图像存储在内存,它们使用了一个称为IplImage的C结构。在大部分旧版教程和教材中可以看到它的存在。IplImageC结构的问题是它带来了C语言的缺点。最大的问题是手动内存管理。它建立在假设用户负责处理内存分配和释放的基础上。
虽然对于小的程序,这不是一个问题,一旦代码增长,我们将更多地处理内存分配和释放的问腿,而不是专注于解决图像处理问题。

幸运的是C++来了,并介绍了的概念,使用户或多或少更容易通过自动内存管理。好消息是C ++与C完全兼容,因此不会出现兼容性问题。因此,OpenCV2.0引入了一个新的C++接口,提供了一种新的处理方式,这意味着您不需要调节内存管理,从而使您的代码简洁。C ++接口的主要缺点是,目前许多嵌入式开发系统只支持C。因此,除非必须使用嵌入式平台,否则无需使用旧的C接口。

了解Mat的第一件事是不再需要手动分配内存,并在不需要它时立即释放它,并且大多数OpenCV函数将自动分配其输出数据。如果使用已经创建的Mat对象(已经为矩阵分配了所需的空间),那么这可以被重新使用。

Mat是一个具有两个数据部分的类:矩阵头(包含矩阵的大小,用于存储的方法,存储地址的信息等等)和指向包含像素值矩阵的指针(维度取决于所选存储方法)。矩阵头大小是恒定的,然而矩阵本身的大小可能会随着图像的不同而变化。

OpenCV是一个图像处理库。它包含大量的图像处理功能。为了解决计算量巨大这个挑战,大多数时候你最终会使用库的多个功能。因此,将图像传递给函数是常见的做法。

每个Mat对象都有自己的头,但是矩阵可以通过使它们的矩阵指针指向相同的地址而共享。因此,复制操作符只会将指针复制到大的矩阵,而不是数据本身。例如:

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 copy constructor

C = A; // Assignment operator

所有上述操作,最后都指向相同的单一数据矩阵。然而,他们的头是不同的,对它们中的任何一个进行修改也会影响其他。实际上,不同的对象只是为相同的底层数据提供不同的访问方法。然而,它们的头部分是不同的。真正有趣的部分是可以创建仅引用一部分完整数据的头。例如,要在图像中创建感兴趣的区域(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

我们可能好奇,当一个矩阵本身属于多个Mat对象,不再需要时怎么负责清理它。答案是:使用它的最后一个对象。这是通过使用引用计数机制来处理。每当复制Mat对象的头部时,矩阵的计数器就会增加。
每当头部被清空时,这个计数器就会减少。当计数器达到零时,矩阵也被释放。

那我们想要复制矩阵本身,该怎么办呢?贴心的OpenCV提供了cv :: Mat :: clone()cv :: Mat :: copyTo()函数。

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

现在修改FG不会影响Mat头指向的矩阵。Note:

  • OpenCV函数的输出图像内存分配是自动的(除非另有说明)。

  • 不需要考虑使用OpenCV C++的内存管理。

  • 赋值运算符和拷贝构造函数仅复制矩阵头。

  • 可以使用cv :: Mat :: clone()和cv :: Mat :: copyTo()函数复制图像的基础矩阵本身。

存储方法

如何存储像素值? 可以自由选择使用的颜色空间和数据类型。颜色空间是指我们如何组合颜色分量以编码给定的颜色。最简单的一个是我们所掌握的颜色是黑色和白色的灰度级。这些组合使我们能够创建许多灰色阴影。

对于彩色空间表达,有更多的选择方法。每个都将它们分解为三到四个基本颜色变量,我们可以使用这些组合来创建其他颜色。最流行的是RGB,主要是因为这与我们的眼睛如何识别颜色有关。其基色为红,绿,蓝。为了对颜色的透明度进行编码,有时会添加第四个元素:即alpha(A)。

其他颜色空间系统也都有自己的优势:

  • RGB 是最常见的,因为我们的眼睛使用类似的东西,但请记住,OpenCV标准显示系统使用BGR颜色空间
  • HSV 和 HLS 将颜色分解为色调,饱和度和值/亮度分量,这是我们描述颜色更自然的方式。例如,如果忽略最后一个分量,则算法对输入图像的光线条件不太敏感。
  • YCrCb 被流行的JPEG图像格式使用。
  • CIE L * a * b *是一种感知统一的色彩空间,如果您需要测量给定颜色与另一种颜色的距离,则可方便使用。

每个颜色空间分量都有自己的有效域。这导致使用不同的数据类型。我们如何存储颜色变量是关键。最小数据类型可能是char,这意味着一个字节或8位。这可能是无符号的(可以存储从0到255的值)或带符号(从-127到+127的值)。如果用三个颜色分量(例如RGB)表示,则已经有1600万个可能的颜色来表示。为了更好的表示,也可以使用float(4字节= 32位)或双(8字节= 64位)数据来获得更精细的控制。然而,增加颜色变量的大小会增加内存中整个画面的大小。

创建一个 Mat 对象

虽然Mat作为一个图像容器非常好,但它也是一个通用的矩阵类。因此,可以创建和操纵多维矩阵。可以通过多种方式创建Mat对象:

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

对于二维多通道图像,我们首先定义它们的大小:行和列数。

然后,我们需要指定用于存储元素的数据类型和每个矩阵点的通道数。为此,我们根据以下约定构造了多个定义:

CV_ [每个项目的位数] [有符号或无符号] [类型前缀] C [通道号]

例如,CV_8UC3 意味着我们使用8位长的无符号字符类型,并且每个像素都有三个通道。这是最多可以有四个预定义的通道。
cv::Scalar是使用自定义值初始化所有矩阵点。
如果需要更多功能,您可以使用上部宏来创建类型,在括号中设置通道号,如下所示。
使用C / C ++数组并通过构造函数进行初始化:

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

上面的例子显示了如何创建一个具有两维以上的矩阵。指定其维度,然后传递包含每个维度的大小的指针,其余的保持不变。

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

无法使用此结构初始化矩阵值。如果新的大小不适合旧的,它将仅重新分配其矩阵数据存储器。

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::zeros,Mat::ones,Mat::eye
  • 对于小矩阵,可以使用逗号分隔的初始化器或初始化器列表(在最后一种情况下需要C ++ 11支持):
Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "C = " << endl << " " << C << endl << endl;
C = (Mat_<double>({ 0, -1, 0, -1, 5, -1, 0, -1, 0 })).reshape(3);
cout << "C = " << endl << " " << C << endl << endl;
小矩阵using逗号
Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl;
RowClone
  • 注意: 可以使用 cv::randu()函数填写带有随机值的矩阵。需要给出随机值的较低和较高值:
Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容