CV02-01:OpenCV之Mat与图像表示

??Mat是OpenCV中表示图像最基本的数据结构,本主题主要介绍这个类的使用。同时本主题使用的是C++11的语法标准,因篇幅缘故,所以对Mat相关的内容没有进一步展开,有兴趣可以参考OpenCV的官方文档。
  • 提示:
    • 本主题都是基于图像处理,实际上Mat表示的是矩阵,包括矩阵的运算,以及向量表示,多维矩阵表示等。
构造器的使用 基本构造器使用
  1. 构造器定义
cv::Mat::Mat( int rows, int cols, int type, void * data, size_t step=AUTO_STEP )

  1. 参数说明
    1. int rows:矩阵的行数(如果是图像,表示像素个数);
    2. int cols:矩阵的列数
    3. int type:矩阵元素的数据类型,数据类型是使用宏预定义的:
      • 宏定义可以使用通道,通道数1-4(用来对应颜色的RGBA),具体的宏的定义见下面的说明。
    4. void * data:用来填充矩阵的数据内存(使用指针指向数据缓冲的地址)
    5. size_t step=AUTO_STEP:矩阵行的字节数(默认AUTO_STEP就是元素类型长度*cols),单位:字节bytes
      • 默认参数AUTO_STEP的计算方式:cols*elemSize():简易采用默认参数。
      • AUTO_STEP是枚举类型,表示0。
  2. 矩阵数据类型的宏:
    1. 基本类型:
      • #define CV_8U 0
      • #define CV_8S 1
      • #define CV_16U 2
      • #define CV_16S 3
      • #define CV_32S 4
      • #define CV_32F 5
      • #define CV_64F 6
      • #define CV_16F 7
    2. 带通道的宏
      • 格式:类型Cn,其中的n表示1,2,3,4,例子:CV_8UC1CV_8UC2CV_8UC3CV_8UC4
      • 这样8个基本类型,每个可以带4个通道。一共可以使用32个,类型。
      • 带通道的类型定义如下:
        • #define CV_8UC1 CV_MAKETYPE(CV_8U,1)
  3. OpenCV自带的数据定义:
    • typedef uint32_t uint
    • typedef signed char schar
    • typedef unsigned char uchar
    • typedef unsigned short ushort
    • typedef int64_t int64
    • typedef uint64_t uint64
  4. 使用例子
  • GUI代码:main.cpp
#include #include #include // #include // 不需要引入 // #include //////////////////////////////// #include /////////////////////////////// #include "./m01_constructor/m01_constructor.h"#include int main(int argc, char* argv[]) { // 初始化QT应用 QApplication app(argc, argv); // 创建一个对话框 QDialogdlg; // 设置对话框大小 dlg.setWindowTitle("OpenCV之Mat图像表示"); dlg.setGeometry(100,100,400,300); // 显示图片的标签框 QLabel lbl_image("显示图片", &dlg); // 以dlg作为父窗体 // 图片框大小位置 lbl_image.setGeometry(0,0, 400,300); /* 图像显示 */ cv::Mat src_image; cv::Mat image; //////////////////////////////////// src_image = create_matrix(); // 调用矩阵构造函数 //////////////////////////////////// // 转换图像颜色空间 cv::cvtColor(src_image, image, cv::COLOR_BGRA2RGBA); // opencv读取数据的格式是BGRA,需要转换为QT能处理的格式 // 把图像转换: cv::Mat -> QImage -> QPixmap -> 设置到QLabel const uchar *u_image = image.data; QImage img_buffer(u_image, image.cols, image.rows, QImage::Format_RGBA8888); QPixmap img_map = QPixmap::fromImage(img_buffer); lbl_image.setPixmap(img_map); lbl_image.setScaledContents(true); // std::cout<< image.dims<

  • Mat对象创建头文件:m01_constructor.h,使用单独的目录存放,目录名也是:m01_constructor
#ifndef M01_CONSTRUCTOR_H #define M01_CONSTRUCTOR_H #include cv::Mat create_matrix(); #endif

  • Mat对象创建实现文件:m01_constructor.cpp,与对应头文件放在同一目录。
#include "m01_constructor.h" cv::Mat create_matrix(){ int rows = 300; // 图像高度 int cols = 400; // 图像宽度 int type = CV_8UC4; // 像素类型(宏没有命名空间) uchar *data = https://www.it610.com/article/new uchar[rows * cols * 4]; // 像素4通道unsigned char(0-255之间); /* data暂时不初始化,使用分配时的脏数据 */ /* data初始化 */ bzero(data, rows * cols * 4); // 初始化为0(透明的黑色) // memset(data, 255, rows * cols * 4); // 全部初始化为255,不透明的白色 // 循环初始化(4通道中第1通道与第4通道为255,其他为0) for(int idx=0; idx

  1. 数据产生的图像
    • 注意:Mat表示图像,颜色顺序式B G R A
CV02-01:OpenCV之Mat与图像表示
文章图片
使用Mat创建的图像 重载构造器
  • 其他的构造器除了拷贝构造器外,都是上面基本构造器的重载类型:
    • 重载的方式主要是使用不同的方式,给用户提供更多的方便。比如:
      1. 高宽使用一个Size替代。
      2. 数据使用Scalar替代。(Scalar是4个元素的double数组,从vector继承)
      3. data使用默认值;
      4. 高宽使用std::vector替代;
      5. 数据使用std::vector替代;
      6. 数据使用std::array替代;(简单数据直接使用Point_与Point3_替代)
    • 类似功能的重载构造器如下(只列出部分,其他参考官方文档):
      • Mat ()
      • Mat (int rows, int cols, int type)
      • Mat (Size size, int type)
      • Mat (int rows, int cols, int type, const Scalar &s)
      • Mat (Size size, int type, const Scalar &s)
      • Mat (int ndims, const int *sizes, int type)
      • Mat (const std::vector< int > &sizes, int type)
      • Mat (int ndims, const int *sizes, int type, const Scalar &s)
      • Mat (const std::vector< int > &sizes, int type, const Scalar &s)
      • Mat (const Mat &m)
      • 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 (const std::vector< int > &sizes, int type, void *data, const size_t *steps=0)
  • 重载构造器使用例子
#include "m01_constructor.h" cv::Mat create_matrix(){ int rows = 300; // 图像高度 int cols = 400; // 图像宽度 int type = CV_8UC4; // 像素类型(宏没有命名空间) cv::Scalar pixel(0,255,0, 255); cv::Matimg(rows, cols, type, pixel); return img; /* data不用释放,由cv::Mat管理; */ }

数据属性 行列
  1. 属性:
    1. int cols
    2. int rows
  2. 相关函数
数据
  1. 属性:
    • uchar * data
  2. 相关函数:
    • uchar * ptr (int i0=0)
      • 参数使用默认的0,表示第一行的数据位置。
维度
  • int dims
标记位
  • int flags:标记位,可以使用位运算获取,标记位包含如下信息:
    • the magic signature:魔法签名
    • continuity flag:连续性标记
    • depth:深度
    • number of channels:通道数
其他属性
  1. MatSize size
  2. MatStep step
  3. UMatData * u
使用函数可以访问的属性
  1. int channels () const
    • 通道数
  2. int depth () const
    • 深度原色基本类型,如下几种,不包含通道信息:
      • 0:CV_8U - 8-bit unsigned integers ( 0..255 )
      • 1:CV_8S - 8-bit signed integers ( -128..127 )
      • 2:CV_16U - 16-bit unsigned integers ( 0..65535 )
      • 3:CV_16S - 16-bit signed integers ( -32768..32767 )
      • 4:CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
      • 5:CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
      • 6:CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
  3. size_t elemSize () const
    • 每个元素的字节数。
  4. size_t elemSize1 () const
    • 每个通道的字节数;
  5. bool empty () const
    • 判定数据是否为空;
  6. size_t total () const
    • 返回数组的元素个数;
  7. int type () const
    • 返回矩阵元素的类型;该类型不是C++的数据类型,是上面讲述的带同到信息的类型。
  • 下面是属性的例子,可以直观体会。
// 测试Mat的属性: std::cout<<"行列:(" << src_image.rows << ","<< src_image.cols <<")"<

  • 输出结果如下:
localhost:05Mat yangqiang$ ./main 行列:(300,400) 维度,通道与深度:(2,4,0) 类型:24, 是否是CV_8UC4?1 元素字节数,通道字节数,总的元素个数:4,1,120000 localhost:05Mat yangqiang$

flags属性的使用
  1. flags属性包含的数据:
    • the magic signature
    • continuity flag
    • depth
    • number of channels
      • 就是type标记位,需要处理下才能得到通道数(位运算)。
  2. 位遮罩
    • MAGIC_MASK = 0xFFFF0000,
    • TYPE_MASK = 0x00000FFF,
      • int type () const
    • DEPTH_MASK = 7
      • int depth () const
    • CONTINUOUS_FLAG = CV_MAT_CONT_FLAG
      • bool isContinuous () const
  3. 使用例子
int flags = src_image.flags; std::cout << "标记值:" << flags << std::endl; std::cout << "魔法值:" << (flags & cv::Mat::MAGIC_MASK) << "," << cv::Mat::MAGIC_VAL << std::endl; std::cout << "深度值:" << (flags & cv::Mat::DEPTH_MASK) << "," << CV_8U << std::endl; std::cout << "类型值:" << (flags & cv::Mat::TYPE_MASK) << "," << CV_8UC4 << std::endl; // 矩阵是否连续,flags & cv::Mat::CONTINUOUS_FLA 不等于0都是连续的。 std::cout << "连续值:" << (flags & cv::Mat::CONTINUOUS_FLAG) << "," << cv::Mat::CONTINU

数据访问 at函数
  • at函数返回Mat矩阵中数据的引用(这意味着可以直接修改,并影响到矩阵中的数据元素)。
    • 根据矩阵的维数,提供了常用的向量(一维)矩阵(一个参数),二维矩阵(两个参数),三维矩阵支持(三个参数)。
    • 多维矩阵的访问,直接使用int *Vec参数来访问多维。
    • 因为Mat经常用于图像,所以提供了Point参数访问元素。
  1. at函数的基本声明
template _Tp & at (int row, int col)

  1. at函数的重载声明
    • template _Tp & at (int i0=0)
    • template const _Tp & at (int i0=0) const
    • template _Tp & at (int row, int col)
    • template const _Tp & at (int row, int col) const
    • template _Tp & at (int i0, int i1, int i2)
    • template const _Tp & at (int i0, int i1, int i2) const
    • template _Tp & at (const int *idx)
    • template const _Tp & at (const int *idx) const
    • template_Tp & at (const Vec< int, n > &idx)
    • templateconst _Tp & at (const Vec< int, n > &idx) const
    • template_Tp & at (Point pt)
    • templateconst _Tp & at (Point pt) const
  2. 关于元访问的封装
    • 在opencv中提供了Vec模板类来封装矩阵元素的访问,尤其是3这种无法对齐的数据,访问起来特别方便。
      • Vec从cv::Matx类。
      • 模板:Vec< T, cn > () const
        • T :数据类型
        • cn : 元素个数。
      • 提供[ ]运算符,用来访问Vec中的元素。
  3. at函数的使用例子
    • 把蓝色通道设置为0;
void access_at(cv::Mat &mat){ int rows = mat.rows; int cols = mat.cols; int channels = mat.channels(); // 三通道 int depth = mat.depth(); std::cout<< depth << "," << channels << std::endl; for(int y = 0; y < rows; y++){ for(int x = 0; x < cols; x++){ cv::Vec& pixel = mat.at >(y, x); // std::cout<<(unsigned int)pixel[0]<< ","; // std::cout<<(unsigned int)pixel[1]<< ","; // std::cout<<(unsigned int)pixel[2]<< ","; // std::cout<

CV02-01:OpenCV之Mat与图像表示
文章图片
使用at修改图像所有像素的蓝色通道为0的效果 ptr函数
  • ptr与at函数一样,唯一的差别返回元素的地址。
    • uchar * ptr (int row, int col)
  • ptr使用例子:
    • 该例子与上面例子完全一样。
void access_ptr(cv::Mat &mat){ int rows = mat.rows; int cols = mat.cols; for(int y = 0; y < rows; y++){ for(int x = 0; x < cols; x++){ cv::Vec* pixel = mat.ptr >(y, x); (*pixel)[0] = 0; // B把蓝色通道设置为0 } } }

row/col与rowRange/colRange函数
  • row返回矩阵中的指定一行
  • rowRangle返回矩阵中的指定多行(开始行,结束行)
    • 不包含结束行。
  1. row函数使用例子
void access_row(cv::Mat &mat){ int rows = mat.rows; int cols = mat.cols; for(int y = 0; y < rows; y++){ cv::Mat row = mat.row(y); for(int x = 0; x < cols; x++){ cv::Vec& pixel = row.at >(x); pixel[0] = 0; // B把蓝色通道设置为0 } } }

  1. rowRange使用例子
void access_row(cv::Mat &mat){ int rows = mat.rows; int cols = mat.cols; // 取部分矩阵 cv::Mat part_mat = mat.rowRange(100,200); for(int y = 0; y < 100; y++){// 注意不要越界 cv::Mat row = part_mat.row(y); for(int x = 0; x < cols; x++){ cv::Vec& pixel = row.at >(x); pixel[0] = 0; // B把蓝色通道设置为0 pixel[1] = 0; // B把蓝色通道设置为0 } } }

CV02-01:OpenCV之Mat与图像表示
文章图片
使用row函数修改图像局部区域的蓝色与绿色通道为0的效果 begin与end函数
  • 这里需要迭代器相关知识。
  1. begin函数定义:
    • 模板函数。
    • end函数类似。
template MatConstIterator_< _Tp >begin () const

  1. begin与end函数返回的是迭代器。迭代器的最大好处就是提供了位置移动的运算符:
    • ++:分前后
    • --:分前后
    • +=
    • -=
    • []:也无法修改:
      • const _Tp & operator[] (ptrdiff_t i) const
    • *:取值运算符;这个取值返回的数据不能修改:
      • const _Tp & operator* () const
    • 返回位置:
      • Point pos () const
  2. 使用cv::Mat_的内部迭代器
    • 可以修改访问数据
  3. 使用例子
void access_begin_end(cv::Mat &mat){ // 获取元素开始的迭代器 // cv::MatConstIterator_ > pixel = mat.begin >(); // 使用外部迭代器,使用[]无法修改。 cv::Mat_ >::iterator pixel = mat.begin >(); // 使用内部迭代器,可以访问成员 // 循环迭代 do{ (*pixel)[0] = 0; // B把蓝色通道设置为0 (*pixel)[1] = 0; // B把蓝色通道设置为0 pixel++; } while(pixel != mat.end >()); }

forEach函数
  1. 函数原型定义
    • 其中的函数对象就是回调函数(C++11增加了箭头函数)。
template voidforEach (const Functor &operation) template voidforEach (const Functor &operation) const

  1. 关于函数对象Functor
    • 包含特定的运算符:
      • void operator ()(.....) const {
  2. functor使用例子:
    • 位置根据Mat的维数来决定。
    • 使用struct与class注意下区分就行。
// Functor(这个是并行调用的) struct PixelFunctor{ void operator ()(cv::Vec& pixel, const int *pos) const{ // 传递像素与像素在矩阵中位置 // std::cout << pos[0] << "," << pos[1] << std::endl; (位置)// 因为并行,打印可能是错乱的。 pixel[0] = 0; } }; // 记得这个分号void access_foreach(cv::Mat &mat){ struct PixelFunctor functor; mat.forEach >(functor); }

  1. C++11的lambda表达式使用例子(怪异的箭头函数)
    • 其中[ ] 表示闭包访问的本地局部变量。简易使用&按引用传递,=按值传递不建议。
void access_foreach(cv::Mat &mat){ int a = 20; mat.forEach >([&a](cv::Vec& pixel, const int *pos)->void{ pixel[1] = 0; a = 30; // [&]知名本地局部变量按照引用访问 }); std::cout<

  1. 函数指针的例子
void function_ptr(cv::Vec& pixel, const int *pos){ pixel[0] = 0; } void access_foreach(cv::Mat &mat){ mat.forEach >(&function_ptr); }

运算符 类型转换运算符 常规类型转换运算符
template operator Matx< _Tp, m, n > () const template operator std::array< _Tp, _Nm > () const template operator std::vector< _Tp > () const templateoperator Vec< _Tp, n > () const

带参类型转换运算符
Mat operator() (Range rowRange, Range colRange) const Matoperator() (const Rect &roi) const Matoperator() (const Range *ranges) const Matoperator() (const std::vector< Range > &ranges) const

赋值运算符
Mat &operator= (const Mat &m) Mat &operator= (const MatExpr &expr) Mat &operator= (const Scalar &s) Mat &operator= (Mat &&m)

运算符使用例子
  • 修改图像局部像素;
///////////////////////////////////////////////////// // ()类型转换运算符 cv::Mat sub_image = src_image(cv::Range(100, 200), cv::Range(100, 200)); // 返回的是引用 // 赋值运算符 sub_image = cv::Scalar(255,0,0); /////////////////////////////////////////////////////

CV02-01:OpenCV之Mat与图像表示
文章图片
使用重载运算符()访问局部区域,并修改为单一颜色的效果 矩阵操作 数据操作 拷贝与克隆
voidcopyTo (OutputArray m) const voidcopyTo (OutputArray m, InputArray mask) constMatclone () const CV_NODISCARD

修改值
Mat &setTo (InputArray value, InputArray mask=noArray())

添加与删除值
voidpop_back (size_t nelems=1) voidpush_back (const _Tp &elem)template voidpush_back (const Mat_< _Tp > &elem) template voidpush_back (const std::vector< _Tp > &elem) voidpush_back (const Mat &m) voidpush_back_ (const void *elem)

变形
Matreshape (int cn, int rows=0) const Matreshape (int cn, int newndims, const int *newsz) const Matreshape (int cn, const std::vector< int > &newshape) const

改变大小
voidresize (size_t sz) voidresize (size_t sz, const Scalar &s) // const Scalar &s是变大后,新空间的填充值

矩阵运算操作
  • 下面这些运算,属于基本常识,不具体介绍。
矩阵乘法
MatExprmul (InputArray m, double scale=1) const

向量点积
doubledot (InputArray m) const

叉积
Matcross (InputArray m) const

逆矩阵
MatExprinv (int method=DECOMP_LU) const

矩阵转置
MatExprt () const

关于MatExpr
  • 这是一个正经的矩阵表示。
    • Mat不太正经是因为Mat表示是数组,包含向量,多维数组,矩阵等。
CV02-01:OpenCV之Mat与图像表示
文章图片
正经的矩阵类MatExpr 初始化与释放操作 分配
voidcreate (Size size, int type) voidcreate (int ndims, const int *sizes, int type) voidcreate (const std::vector< int > &sizes, int type)

释放
voiddeallocate ()// 内部调用,一般使用release voidrelease ()

工具函数
  • 从来创建单位矩阵,零矩阵什么的。
创建单位矩阵
static MatExpreye (int rows, int cols, int type) static MatExpreye (Size size, int type)

创建对角矩阵
static Matdiag (const Mat &d)

创建1矩阵
static MatExprones (int rows, int cols, int type) static MatExprones (Size size, int type) static MatExprones (int ndims, const int *sz, int type)

创建零矩阵
static MatExprzeros (int rows, int cols, int type) static MatExprzeros (Size size, int type) static MatExprzeros (int ndims, const int *sz, int type)

附录:
  • 参考文档:
    • https://docs.opencv.org/4.1.2/d3/d63/classcv_1_1Mat.html
  • Mat的支持的数据类型:

    CV02-01:OpenCV之Mat与图像表示
    文章图片
    Mat支持的深度数据类型
  • 与Mat相关的还有:
    1. 关联的基本结构,比如cv::Vec,cv::Point3等。
    2. 关联的工具函数,比如求解方程等,特征值求解,奇异值分解等。
    3. 硬件加速函数;
    4. 与OpenGL与DirectX的数据结构转换函数;
  • 两个独特的构造器
    • 列表初始化器:
      1. Mat (const std::initializer_list< _Tp > list)
      2. Mat (const std::initializer_list< int > sizes, const std::initializer_list< _Tp > list)
    • 逗号初始化器:
      1. Mat (const MatCommaInitializer_< _Tp > &commaInitializer)
  • 【CV02-01:OpenCV之Mat与图像表示】独特构造器使用例子:
#include int main(int argc, char* argv[]) { // 初始化列表 std::initializer_list v1 = {1,2,3,4}; cv::Mat m1(v1); cv::Mat m2({1,2,3,4,5}); cv::Mat_ m3={1,2,3,4}; // 这种方式对Mat错误(语法没有问题),但是要求显式选择构造器(模板类型歧义造成)// MatCommaInitializer_ 必须由矩阵产生 cv::Mat M = (cv::Mat_(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1); return 0; }

    推荐阅读