【OpenCV3编程入门学习笔记】——第4章 OpenCV数据结构与基本绘图

本文介绍了OpenCV3中的基础图像容器Mat类,详细讲解了Mat结构的使用,包括创建、内存管理和输出方法。此外,还讨论了像素值的存储方式、常用数据结构如Point、Scalar、Size以及Rect,并介绍了颜色空间转换函数cvtColor()。最后,文章通过实例展示了基本图形的绘制,如绘制椭圆、圆、多边形和线条。
摘要由CSDN通过智能技术生成

第4章 OpenCV数据结构与基本绘图

前言

笔记系列

参考书籍:OpenCV3编程入门

作者:毛星云

版权方:电子工业出版社

出版日期:2015-02

笔记仅供本人参考使用,不具备共通性

笔记中代码均是OpenCV+Qt的代码,并非用vs开发,请勿混淆

本章核心函数/类清单

函数/类名称 说明 对应讲解章节
Mat::mat() Mat类的构造函数 4.1.4
Mat::Creat() Mat类的成员函数,可用于Mat类的初始化操作 4.1.1
Point类 用于表示点的数据结构 4.2.1
Scalar类 用于表示颜色的数据结构 4.2.2
Size类 用于表示尺寸的数据结构 4.2.3
Rect类 用于表示矩形的数据结构 4.2.4
cvtColor 用于颜色空间转换 4.2.5

4.1 基础图像容器Mat

4.1.1 数字图像存储概述

  • 图像在转化到数字设备中时,设备记录的是图像中每个点的数值

  • 在这里插入图片描述

  • 如上图所示,矩阵就是图像在数码设备中的表现形式

4.1.2 Mat结构的使用

  • Mat类的好处

    • 1.不必手动为其开辟空间
    • 2.不必在不需要时立即将其空间释放
    • 上述两点好处对比OpenCV1.0时代的代码来说,方便了太多
    • 注意:此处说的是手动开辟空间并非必须,但这种方式是依旧存在的----大多是OpenCV函数仍然会手动地为输入数据开辟空间
    • 当传递一个已经存在的Mat对象时,开辟好的矩阵空间会被重用.也就是说,我们每次都是使用大小正好的内存来完成任务
  • Mat是一个类,由两个数据部分组成

    • 矩阵头(包括矩阵尺寸,存储方法,存储地址等信息)
    • 指向存储所有像素值的矩阵的指针(根据所选存储方法的不同,该矩阵可以是不同的维数)
  • 矩阵头的尺寸是常数值,但存储像素值的矩阵本身会以图象的大小不同而不同(通常都比矩阵头大上数个数量级),因此,在程序中传递图像并创建副本时,大的内存开销是由矩阵造成的

  • OpenCV是一个图像处理库,囊括了大量的图像处理函数,为了解决问题通常要使用库中的多个函数,因此传递图像(并创建副本)是常有的事,但基于上一条的原因,所以不到万不得已,尽量不要进行大图像的复制,因为这会降低程序的运行速度

  • 当然OpenCV也设法解决了上面的问题-----引用计数机制

    • 其思路为:让每个Mat对象都有自己的信息头,但是共享一个矩阵
    • 这个方法通过让矩阵指针指向同一地址而实现
    • 拷贝构造函数时,程序只复制信息头和矩阵指针,而不复制矩阵
  • 来看下面这段代码

    • Mat A,C;//仅创建信息头部分
      A = imread("1.jpg",CV_LOAD_IMAGE_COLOR);//为矩阵开辟内存,并将图片存入
      Mat B(A);//使用拷贝构造函数
      C = A;//使用赋值运算符
      
    • 上面代码中的A、B、C三个对象最终都指向同一个数据矩阵

    • 虽然A、B、C三个对象的信息头不一样,但是通过任何一个对象所作的改变也会影响到其他对象

  • 矩阵指针指向同一个数据矩阵的不同Mat对象只是访问相同数据的不同途径而已

  • OpenCV还可以创建只引用一部分数据的信息头,如下

    • //创建一个感兴趣区域(ROI),它是只包含边界信息的信息头
      Mat D(A,Rect(10,10,100,100));//使用矩形界定
      Mat E = A(Range::all(),Range(1,3));//使用行和列来界定
      
  • 当矩阵属于多个Mat对象时,由最后使用它的对象来清理

    • 通过上面的说的计数机制
      • 无论什么时候复制一个Mat对象的信息头,都会增加矩阵的引用次数
      • 反之,当一个信息头被释放后,引用次数就会减一
      • 当引用次数归零时,就说明没有对象引用该矩阵了,矩阵就会被执行清理程序
  • 复制矩阵本身的方法

    • 可以用clone()函数或者copyTo()函数

      • Mat F = A.clone();
        Mat G;
        A.copyTo(G);
        
      • 通过上面的操作,现在对F和G做出改变就不会影响到A,B,C三个对象所指向的矩阵了

  • 总结:

    • OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)
    • 使用OpenCV的C++接口时不需要考虑内存释放的问题
    • 赋值运算符和拷贝构造函数只复制信息头
    • 函数clone()或者函数copyTo()会把图像的矩阵也复制一份

4.1.3 像素值的存储方式

  • 存储像素值需要制定颜色空间和数据类型
    • 颜色空间
      • 是指针对一个给定的颜色,如何组合颜色原色以对其编码
      • 最简单的颜色空间是灰度级空间,它只处理白色和黑色,对他们进行组合便可以产生不同程度的灰色
      • 对于彩色方式有更多种类的颜色空间,无论哪种方式都是把颜色分成三个或者四个基本元素,通过组合基本元素可以产生所有的颜色
      • RGB颜色空间是最常用的彩色空间
        • 它的基色是红(Red)绿(Green)蓝(Blue)
        • 有时为了控制透明度还会加入第四个元素Alpha(A)
      • 颜色系统有很多
        • RGB是最常见的,因为其基色是人眼内部构成颜色的方式
        • HSVHLS把颜色分解成色调、饱和度和亮度/透明的
          • 这是描述颜色更自然的方式,比如可以通过抛弃最后一个元素,是算法对输入图像的光照条件不敏感
        • YCrCB在JPEG图像格式中广泛使用
        • CIE L*a*b*是一种在感知上均匀的颜色空间,它适合用来度量两个颜色之间的距离
    • 数据类型
      • 每个组成元素(即元素空间的每一个颜色)都有自己的定义域,而定义域取决于其数据类型,如何存储一个元素决定了我们在其定义域上能够控制的精度
      • 最小的数据类型是char
        • 占一个字节
        • 可以是有符号型(0255之间),也可以是无符号型(-127+128之间)
        • 使用三个char型已经可以表示1600万种可能的颜色(RGB的颜色空间)
      • 如果使用float型(4字节)或double型(8字节)可以给出更加精细的颜色分辨能力
      • 增加元素的尺寸的同时也会增加所占内存空间的大小

4.1.4 显示创建Mat对象的七种方法

  • 可以通过Mat的运算符"<<"来查看图像矩阵的实际值,但是该方法只对二维矩阵有效果

  • Mat不单是一个非常有用的图像类容器,同时也是一个通用的矩阵类,我们可以用它来创建和操作多为矩阵

  • 创建Mat对象的方法有:

    【方法一】使用Mat()构造函数
    • 最简单的方法

    •     Mat M(2,2,CV_8UC3,Scalar(0,0,255));
          cout<<"M = "<<endl<<""<<M<<endl<<endl;
      	//Qt Creator中如果要使用cout,需要像VS里面那样,#includ <iostream>,同时using namespace std;
      
    • 上述代码运行效果(Qt Creator中)

      • 在这里插入图片描述
    • 对于二维通道图像

      • 首先要定义其尺寸,即行数和列数

      • 然后需要指定存储元素的数据类型和每个矩阵点的通道数(CV_8UC3)

        • 对此,可以依据下面的规则来定义

        • CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
          
        • 即:

        • CV_[位数][带符号与否][类型前缀]C[通道数]
          
        • 比如

          • CV_8UC3表示使用8位的unsignerd char型,每个像素由三个元素组成三通道
          • CV_32FC2表示使用32位的单精度浮点型,每个像素由两个元素组成双通道
        • CV_32FC3参数的含义

【方法二】在C/C++中通过构造函数进行初始化
  •     int sz[3] = {
         2,2,2};
        Mat M(3,sz,CV_8UC(2),Scalar::all(0));
        //cout<<"M = "<<endl<<""<<M<<endl<<endl;
    
  • 上面的例子演示了如何创建一个超过两维的矩阵

    • 首先指定位数3(Mat构造函数的第一个参数)

    • 然后,传递一个指向一个数组的指针sz(Mat构造函数的第二个参数)

      • 这个数组里面包含了每个维度的尺寸
      • 这个参数不能写成&sz,因为在C语言中,数组名就是指向该数组的第一个数组元素的地址
    • 后面两个参数与方法一中的相同,不再赘叙

    • cout<<"M = "<<endl<<""<<M<<endl<<endl;

      • 这句代码是不能写在此处的,因为用Mat的"<<"字符来查看矩阵元素只对二维矩阵有效果

      • 如果写了这句代码,则系统会报错

      • OpenCV: terminate handler is called! The last OpenCV error is:
        OpenCV(3.4.13) Error: Assertion failed (m.dims <= 2) in FormattedImpl, file D:\opencv\opencv-3.4.13\modules\core\src\out.cpp, line 86
        
      • 上述错误信息中,有这么一句:(m.dims <= 2)就是在提醒开发者,只有维度小于等于2的矩阵才可以cout出来

【方法三】为已存在的IplImage指针创建信息头
  • (1) IplImage 转 Mat:
    IplImage* image = cvLoadImage( "1.jpg");  
    Mat mat=cvarrToMat(image);
     
    (2)Mat转IplImage:
    Mat img = imread("test.jpg");
    IplImage* ipl_img = cvCreateImage(cvSize(img.cols, img.rows),IPL_DEPTH_8U,img.channels());
    memcpy(ipl_img->imageData, img.data, ipl_img->height * ipl_img->widthStep);
    
  • 书上写的方法如下,但因为我装的OpenCV版本过高(3.4.13),这些方法都失效了

  • IplImage* image = cvLoadImage( "1.jpg");  
    Mat mat(image);
    
  • OPENCV 3.4.*版本 mat转IplIMage报错问题的解决方案

【方法四】利用Create()函数
  •     Mat M(2,2,CV_32FC3,Scalar(0,0,255));
        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;
    
  • 结果如下:

    E =

    [1, 0, 0, 0;

    0, 1, 0, 0;

    0, 0, 1, 0;

    0, 0, 0, 1]

    O =

    [1, 1;

    1, 1]

    Z =

    [ 0, 0, 0;

    0, 0, 0;

    0, 0, 0]

  • Mat::ones() 和 Mat::zeros()

【方法六】对小矩阵使用逗号分隔式初始化函数
  •     Mat C = (Mat_<double>(3,3)<<0,-1,0,-1,5,-1,0,-1,0);
        cout<<"C = "<<endl<<""<<C<<endl<<endl;
    
  • 结果如下:

    C =

    [0, -1, 0;

    -1, 5, -1;

    0, -1, 0]

    如过输入的元素不够矩阵的总元素,则会如下

    C =

    [0, -1, 0;

    -1, 5, -1;

    2.143248128504563e-312, 1.958064995486337e-306, 0]

    如上:未输入元素会变成不可预知的值

【方法七】为已存在的对象创建信息头
  • 使用成员函数clone()或者copyTo()为一个已经存在的Mat对象创建一个新的信息头

  •     Mat C = (Mat_<double>(3,3)<<0,-1,0,-1,5,-1,0,0,0);
        cout<<"C = "<<endl<<""<<C<<endl<<endl;
    
        Mat RowClone = C.row(1).clone();
        cout<<"RowClone = "<<endl<<""<<RowClone<<endl<<endl;
    
  • 结果如下:

    C =

    [0, -1, 0;

    -1, 5, -1;

    0, 0, 0]

    RowClone =

    [-1, 5, -1]

4.1.5 OpenCV中的格式化输出方法

  • 首先先定义一个R矩阵,使用Randu()函数产生的随机数来填充矩阵,当然需要给定一个上限和下限来确保随机值在期望的范围内

  •     Mat r = Mat(10,3,CV_8UC3);
        randu(r,Scalar::all(0),Scalar::all(255));
    
【风格一】OpenCV默认风格
  • cout<<"r (OpenCV default style)="<<endl<<r<<";"<<endl<<endl;
    
  • 效果如下

  • r (OpenCV default style)=

    [ 91, 2, 79, 179, 52, 205, 236, 8, 181;

    239, 26, 248, 207, 218, 45, 183, 158, 101;

    102, 18, 118, 68, 210, 139, 198, 207, 211;

    181, 162, 197, 191, 196, 40, 7, 243, 230;

    45, 6, 48, 173, 242, 125, 175, 90, 63;

    90, 22, 112, 221, 167, 224, 113, 208, 123;

    214, 35, 229, 6, 143, 138, 98, 81, 118;

    187, 167, 140, 218, 178, 23, 43, 133, 154;

    150, 76, 101, 8, 38, 238, 84, 47, 7;

    117, 246, 163, 237, 69, 129, 60, 101, 41];

【风格二】python风格
  • cout<<"r (python style) ="<<endl<<format(r,Formatter::FMT_PYTHON)<<";"<<endl<<endl;
    
  • 效果如下

  • r (python style) =

    [[[ 91, 2, 79], [179, 52, 205], [236, 8, 181]],

    [[239, 26, 248], [207, 218, 45], [183, 158, 101]],

    [[102, 18, 118], [ 68, 210, 139], [198, 207, 211]],

    [[181, 162, 197], [191, 196, 40], [ 7, 243, 230]],

    [[ 45, 6, 48], [173, 242, 125], [175, 90, 63]],

    [[ 90, 22, 112], [221, 167, 224], [113, 208, 123]],

    [[214, 35, 229], [ 6, 143, 138], [ 98, 81, 118]],

    [[187, 167, 140], [218, 178, 23], [ 43, 133, 154]],

    [[150, 76, 101], [ 8, 38, 238], [ 84, 47, 7]],

    [[117, 246, 163], [237, 69, 129], [ 60, 101, 41]]];

【风格三】逗号分隔风格(Comma separated values,CSV)
  • cout<<"r (CSV) ="<<endl<<format(r,Formatter::FMT_CSV
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值