opencv Mat详解

这篇具有很好参考价值的文章主要介绍了opencv Mat详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.图像的存储

我们有多种方法从现实世界获取数字图像:数码相机、扫描仪、计算机断层扫描和磁共振成像等。在任何情况下,人类看到的都是图像。当将其转换为数字图像在设备中进行存储时,我们记录的是图像中每个点的数值。
opencv mat,opencv,opencv,计算机视觉,人工智能

例如,在上面的图像中,可以看到汽车的镜子只是一个包含像素点的所有强度值的矩阵。我们获取和存储像素值的方式可能会根据我们的需要而变化,但最终,世界中的所有图像都会被简化为数字矩阵和描述矩阵本身的其他信息存储在计算机中。OpenCV是一个计算机视觉库,其主要重点是处理和操纵这些信息。因此,首先需要熟悉OpenCV存储和处理图像的基本单元-Mat对象。

二.OpenCV中图像坐标系的定义

opencv mat,opencv,opencv,计算机视觉,人工智能
说明:

  1. Mat::Mat(int rows, int cols, int type)为创建行数为rows,列数为cols,类型为type的图像
  2. 坐标体系中的零点坐标为图片的左上角,X轴为图像矩形的上面那条水平线;Y轴为图像矩形左边的那条垂直线。该坐标体系在诸如结构体Mat,Rect,Point中都是适用的。
  3. 在使用image.at()来访问图像中点的值的时候,如果是以坐标方式进行访问,则坐标顺序为image.at(y, x)。但是,要是以坐标点(image图像中的Point(x, y)点)的方式进行访问,则为image.at(Point(x, y))
  4. 如果所画图像是多通道的,比如说image图像的通道数时n,则使用Mat::at(x, y)时,其x的范围依旧是0到image的height,而y的取值范围则是0到image的width乘以n,因为这个时候是有n个通道,所以每个像素需要占有n列。但是如果在同样的情况下,使用Mat::at(point)来访问的话,则这时候可以不用考虑通道的个数,因为Mat::at(x, y)返回的是一个数字,而,Mat::at(point)返回的是一个对应的n维向量。

三.OpenCV中的Mat

OpenCV从2001年就已经出现了。早期,OpenCV的库是围绕C接口构建的,为了将图像存储在内存中,使用了一个名为IplImage的C结构。这样做的问题是需要手动进行内存管理。IplImage是建立在用户负责处理内存分配和释放的假设之上的。虽然这对于较小的程序来说不是问题,但一旦代码库增长,处理内存管理将更加困难,而不是专注于解决开发目标。因此,OpenCV2.0引入了一个新的C++接口,它提供了一种新的工作方式-Mat对象,Mat的引入简化了用户的内存管理(不再需要手动分配其内存),使代码简洁(编写更少,实现更多)。C++接口的主要缺点是,目前许多嵌入式开发系统只支持C。

Mat基本上是一个包含两个数据部分的类:

  • 矩阵头(包含矩阵大小、用于存储的方法、存储矩阵的地址等信息)
  • 指向包含像素值的矩阵的指针(根据选择的存储方法采用任何维度)

OpenCV是一个图像处理库。它包含大量图像处理功能。为了解决计算难题,大多数时候将使用库的多个函数对同一个Mat对象进行操作。因此,将Mat对象传递给函数是一种常见的做法。但是对较大的图像进行不必要的复制会降低程序的速度。为了解决这个问题,OpenCV使用了参考计数系统。其思想是每个Mat对象都有自己的头,里面包含了一些图像的基本信息(图像大小,数据类型,通道数),然后,通过使两个Mat对象的矩阵指针指向同一地址,这样就可以在两个Mat对象之间共享矩阵,节省存储空间。另外,复制运算符也只是复制矩阵头和指向包含像素值的矩阵的指针,而不是数据本身。

Mat A, C;                          // 仅创建矩阵头
A = imread(argv[1], IMREAD_COLOR); // 分配矩阵内存
Mat B(A);                          // 使用拷贝构造函数进行Mat的复制
C = A;                             // 赋值运算符进行Mat的复制

上述例子中所有对象都指向同一个数据矩阵,对其中任何一个对象的像素值进行修改都会影响其他所有对象。实际上,不同的对象只是为相同的底层数据提供不同的访问方法。然而,它们的头部是不同的。我们可以创建仅引用完整数据的一个子部分的矩阵头。例如,要在图像中创建感兴趣区域(ROI),只需创建具有新矩阵头的Mat对象:

Mat D (A, Rect(10, 10, 100, 100) ); // 使用ROI区域来创建新的Mat对象
Mat E = A(Range::all(), Range(1,3)); // 使用ROI区域来创建新的Mat对象

矩阵是由最后一个使用它的对象来进行内存释放的。这是通过使用引用计数机制来处理的。每当有人复制Mat对象的矩阵头时,矩阵的计数器就会增加。每个指向该矩阵的Mat对象被释放,此计数器都会减少。当计数器达到零时,该矩阵被释放。要想要复制矩阵本身,使用cv::Mat::clone()和cv::Mat::copyTo()函数。

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

四.OpenCV中的图像存储方式

除了灰度图像外,最流行图像存储方式是RGB。它的底色是红色、绿色和蓝色。为了编码颜色的透明度,有时会添加第四个元素alpha(a)。
对于灰度图,OpenCV中的数据存储方式为:
opencv mat,opencv,opencv,计算机视觉,人工智能
对于彩色图,OpenCV中的数据存储方式为:
opencv mat,opencv,opencv,计算机视觉,人工智能
还有许多其他颜色系统,每个都有自己的优势:

  • RGB是最常见的,因为我们的眼睛使用类似的颜色,但是OpenCV标准显示系统使用的是BGR颜色空间合成颜色(红色和蓝色通道交换位置)。
  • HSV和HLS将颜色分解为色调、饱和度和值/亮度分量,这是描述颜色会更加自然。例如,我们可以忽略最后一个分量(亮度),从而使算法对输入图像的光线条件不太敏感。
  • 流行的JPEG图像格式使用YCrCb。
  • CIE Lab*是一个感知上均匀的颜色空间,如果需要测量给定颜色与另一种颜色的距离,它很有用。

我们如何存储像素定义了我们对其域的控制。最小数据类型是char,表示一个字节或8位。可以是无符号的(可以存储0到255的值)或有符号的(-127到+127的值)。虽然在三个分量(如RGB)的情况下,这个颜色宽度已经达到了(256x256x256=1638400)1600万种可能的颜色,也可以通过为每个分量使用浮点(4字节=32位)或双(8字节=64位)数据类型来获得更精细的颜色。但是,增加每个像素的大小也会增加内存中整个图片的大小。

五.显式创建Mat对象

1.使用Mat构造函数

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

opencv mat,opencv,opencv,计算机视觉,人工智能

对于二维和多通道图像,首先定义它们的大小:按行(rols)和列(cols)计数。然后,我们需要指定用于存储元素的数据类型以及每个矩阵点的通道数。为此,OpenCV根据以下约定构建了多个Mat数据类型的定义:

CV_[比特位数][有无符号][数据类型]C[通道数]

例如,CV_8UC3表示使用8位长的无符号字符类型,每个像素有三个这样的字符类型来形成三个通道。最多可为四个通道预定义类型。

2.使用C/C++数组初始化Mat

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

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

3.使用cv::Mat::create函数

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

opencv mat,opencv,opencv,计算机视觉,人工智能

无法使用此构造初始化矩阵值。只有当新的大小与旧的大小不匹配时,它才会重新分配矩阵数据内存。

4.使用cv::Mat::zeros , cv::Mat::ones , cv::Mat::eye 函数.

 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;

opencv mat,opencv,opencv,计算机视觉,人工智能

5.使用逗号分隔的初始化器或初始化器列表

 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;

opencv mat,opencv,opencv,计算机视觉,人工智能

6.为现有Mat对象和cv::Mat::clone或cv::Mat::copyTo创建新矩阵头

Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl;

opencv mat,opencv,opencv,计算机视觉,人工智能

7.使用randu()函数为现有矩阵头填充随机数

可以使用cv::randu()函数用随机值填充矩阵。这时需要为随机值提供下限和上限:

Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));

六.访问Mat元素的方式

1.直接指针访问(最高效的方法)

在性能方面,最有效的访问方式为经典的C风格运算符[](指针)访问方式。因此,推荐的最有效的访问方法是:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // 只能接收char类型数组
    CV_Assert(I.depth() == CV_8U);
    
    //获取数组基本信息
    int channels = I.channels();
    int nRows = I.rows;
    int nCols = I.cols * channels;
    
    //判断数组内容在内存中是否为连续存储
    if (I.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;
    }
	
	//通过指针的方式访问数组中指定位置的元素
    int i,j;
    uchar* p;
    for( i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];
        }
    }
    return I;
}

这里,我们基本上只需要获取一个指向每行数据开头的指针,然后遍历它直到每行的结尾。在矩阵以连续方式存储的特殊情况下,我们只需要一次请求指针并一直到最后。如果需要寻找彩色图像:那么每行有三个通道(BGRBGRBGR…),所以我们需要在每行中访问不同通道的值。
还有另一种方式。Mat对象的数据数据成员返回指向第一行、第一列的指针。如果此指针为空,则该对象中没有有效输入。如果存储是连续的(Mat::isContinuous()),我们可以使用它来遍历整个数据指针。如果是灰度图像,则如下所示:

uchar* p = I.data;
for( unsigned int i = 0; i < ncol*nrows; ++i)
    *p++ = table[*p];

2.迭代器方法

使用迭代器,程序员就不用考虑上面的问题了。只需要获得图像矩阵的begin和end迭代器,传统方式一直 ++ 即可。迭代器会自动跳到下一行并跳过内存中不连续的地方。使用解引用运算符 * 获得访问值。但是这种方法会损失效率

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // 只能接收char类型数组
    CV_Assert(I.depth() == CV_8U);
	
	// 获取数组基本信息
    const int channels = I.channels();
	
	// 分通道数进行数据的访问
    switch(channels)
    {
    case 1:
        {
        	//使用迭代器进行Mat元素的访问
            MatIterator_<uchar> it, end;
            for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = table[*it];
            break;
        }
    case 3:
        {
        //使用迭代器进行Mat元素的访问
            MatIterator_<Vec3b> it, end;
            for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
        }
    }
    return I;
}

上述代码中,彩色图像对应的三通道情况下,使用vector存储每个像素三个通道的值,OpenCV中专门有一个Vec3b数据类型,相当于 vector。

3.带引用返回的动态地址计算访问方式

它只是用来随机访问图像中任意一个像素,可以读取或修改该像素,这里用到 at() 函数。无论用何用方法访问图像矩阵,都需要指定数据类型。因此,需要给函数提供访问数据类型,数据所在的行号和列号

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    // 只能接收char类型数组
    CV_Assert(I.depth() == CV_8U);
    
	// 获取数组基本信息
    const int channels = I.channels();

	// 分通道数进行数据的访问
    switch(channels)
    {
    case 1:
        {
        	//通过cv::Mat::at()进行元素的访问
            for( int i = 0; i < I.rows; ++i)
                for( int j = 0; j < I.cols; ++j )
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
            break;
        }
    case 3:
        {
         Mat_<Vec3b> _I = I;
         //通过cv::Mat::at()进行元素的访问
         for( int i = 0; i < I.rows; ++i)
            for( int j = 0; j < I.cols; ++j )
               {
                   _I(i,j)[0] = table[_I(i,j)[0]];
                   _I(i,j)[1] = table[_I(i,j)[1]];
                   _I(i,j)[2] = table[_I(i,j)[2]];
            }
         I = _I;
         break;
        }
    }
    return I;
}

at() 函数输入数据类型和坐标,返回相应像素值的 引用。读操作是一个const函数,写操作是非const函数。 仅在debug模式下,函数会检查输入的坐标是否存在和有效,无效则通过标准错误输出流给出错误提示信息。相比于在release模式的高效率(第一种)方法,使用该函数的唯一的区别是,对图像中的每一个元素,你会得到一个行指针,以便于我们用 C风格的 [ ] 运算符获得列元素。
因为每次查找都要输入数据类型和坐标,用这种方法在一幅图像中多次执行查找操作是麻烦和费时的。为了解决这个问题,OpenCV提供了一个 Mat_ 的数据类型。它和 Mat 数据类型唯一的不同是需要额外指定 Mat_ 对象存储的数据矩阵的数据类型。它可以使用 () 运算符来快速访问元素,也可以容易地与 Mat 对象相互转换类型。上述代码中对彩色图像的处理(case 3)就是使用的 Mat_ 对象。

4.使用核心函数(The Core Function)

图像处理中经常需要将一幅图像中的所有值都替换成其他值,通过查找表可以高效实现这种替换。OpenCV提供了一个函数,使程序员不用手动地遍历图像,查表,写入替换值,而是直接利用 LUT() 实现 。实现代码如下:

Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for( int i = 0; i < 256; ++i)
p[i] = table[i];
 
LUT(I, lookUpTable, J);

首先,创建一个1行256列的Mat对象用来存储查找表,然后调用 LUT() 函数实现转换。函数的参数中I是输入图像,J是输出图像。

5.不同访问方式的性能比较

教程中对上述四种方法进行了性能比较,采用了大小为 2560×1600 的彩色图像进行遍历替换,每种方法运行了上百次取平均值,得到如下结果:
opencv mat,opencv,opencv,计算机视觉,人工智能
总结: OpenCV自身提供的函数 LUT() 速度最快,这主要是因为OpenCV的库是通过Intel Threaded Building Blocks实现了多线程。如果是简单的图像遍历,可以使用第一种方法。迭代器方法损失了速度获得了安全性。Debug模式下的随机引用访问是最耗时的,Release模式下的随机引用访问效率和迭代器方法相当,但是它牺牲了迭代器具有的安全性。文章来源地址https://www.toymoban.com/news/detail-734525.html

到了这里,关于opencv Mat详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入探索人工智能与计算机视觉

    在当今数字化时代,人工智能(AI)和计算机视觉(CV)作为两大前沿技术,正以惊人的速度改变着我们的生活。本文将深入探讨人工智能与计算机视觉的关系、应用以及未来发展方向。 1. 人工智能与计算机视觉的关系 人工智能是一门涵盖众多技术领域的学科,旨在使计算机

    2024年04月14日
    浏览(57)
  • 人工智能在计算机视觉中的应用与挑战

    引言 计算机视觉是人工智能领域的一个重要分支,旨在让计算机能够像人一样理解和解释视觉信息,实现图像和视频的自动识别、理解和分析。计算机视觉技术已经在许多领域产生了深远的影响,如人脸识别、自动驾驶、医学影像分析等。本篇博客将深入探讨人工智能在计算

    2024年02月14日
    浏览(56)
  • opencv Mat详解

    我们有多种方法从现实世界获取数字图像:数码相机、扫描仪、计算机断层扫描和磁共振成像等。在任何情况下,人类看到的都是图像。当将其转换为数字图像在设备中进行存储时,我们记录的是图像中每个点的数值。 例如,在上面的图像中,可以看到汽车的镜子只是一个包

    2024年02月07日
    浏览(35)
  • 【计算机视觉】基于OpenCV计算机视觉的摄像头测距技术设计与实现

    在当今技术日益进步的时代,计算机视觉已成为我们生活中不可或缺的一部分。从智能监控到虚拟现实,计算机视觉技术的应用范围日益广泛。在这篇博客中,我们将探索一个特别实用的计算机视觉案例:使用OpenCV实现摄像头测距。这一技术不仅对专业人士有用,也为编程爱

    2024年02月04日
    浏览(45)
  • 读十堂极简人工智能课笔记04_计算机视觉

    3.2.3.1. 应该发现真正的边缘,而尽量避免错报 3.2.4.1. 应该正确地找出边缘的确切位置 3.2.5.1. 每条实际的边缘应该检测为一条边缘,而不是多条边缘 4.7.5.1. 有数以百万计的几乎任何种类的图像例子 4.7.7.1. 神经网络自己就能完成这一切

    2024年02月19日
    浏览(46)
  • OpenCV Mat实例详解 一

              OpenCV中的Mat是一个类,它用存储图像信息。由两部分数据组成:矩阵头和像素值矩阵。矩阵头包含矩阵尺寸、存储方法、存储地址等信息,而像素值矩阵则存储实际的像素值数据。         Mat类在OpenCV中有十分重要的作用,图像信息的载入、保存、传递都离不

    2024年02月19日
    浏览(36)
  • OpenCV Mat实例详解 四

            OpenCV Mat实例详解三中详细介绍来了OpenCV Mat类的公有静态成员函数,下面介绍OpenCV Mat类的其他常用成员函数。          Mat  adjustROI (int dtop, int dbottom, int dleft, int dright);         dtop ROI 上边界移动值,如果为正,上边界向上移动,如果值为负,则向下

    2024年02月21日
    浏览(37)
  • OpenCV Mat实例详解 三

            OpenCV Mat实例详解 一、二介绍了,OpenCV Mat类构造函数及其公共属性。下面继续介绍OpenCV Mat类公有静态成员函数         static CV_NODISCARD_STD Mat    diag (const Mat d);         该函数用已有的Mat对象的数据矩阵对角线上的数据填充新创建Mat对象数据矩阵。

    2024年03月14日
    浏览(32)
  • 计算机视觉(OpenCV+TensorFlow)

    本系列文章是OpenCV系列文章的第三篇,仍然跟随上篇内容主要聚焦于图像的一些操作 在通常情况下我们使用大小恒定的图像。但在某些情况下,我们需要使用不同分辨率的同幅图像,例如,在搜索图像中的某些内容比如脸部信息时,并不确定该内容在图像中占据的大小。这种

    2024年02月05日
    浏览(47)
  • 计算机视觉:OpenCV相机标定

    针孔照相机模型是一种经典的相机模型,它将相机视为一个针孔,将场景中的点投影到成像平面上。在这个模型中,相机的 内参和外参 描述了相机的几何形状和相机的姿态。 相机的 内参矩阵 描述了相机的内部几何形状,包括相机的焦距、像素尺寸和像素坐标原点。相机的

    2024年01月19日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包