CV02-01:OpenCV之Mat与图像表示
??Mat是OpenCV中表示图像最基本的数据结构,本主题主要介绍这个类的使用。同时本主题使用的是C++11的语法标准,因篇幅缘故,所以对Mat相关的内容没有进一步展开,有兴趣可以参考OpenCV的官方文档。
- 提示:
- 本主题都是基于图像处理,实际上Mat表示的是矩阵,包括矩阵的运算,以及向量表示,多维矩阵表示等。
- 构造器定义
cv::Mat::Mat(
int rows,
int cols,
int type,
void * data,
size_t step=AUTO_STEP
)
- 参数说明
-
int rows
:矩阵的行数(如果是图像,表示像素个数); -
int cols
:矩阵的列数 -
int type
:矩阵元素的数据类型,数据类型是使用宏预定义的:- 宏定义可以使用通道,通道数1-4(用来对应颜色的RGBA),具体的宏的定义见下面的说明。
-
void * data
:用来填充矩阵的数据内存(使用指针指向数据缓冲的地址) -
size_t step=AUTO_STEP
:矩阵行的字节数(默认AUTO_STEP就是元素类型长度*
cols),单位:字节bytes- 默认参数AUTO_STEP的计算方式:
cols*elemSize()
:简易采用默认参数。 - AUTO_STEP是枚举类型,表示0。
- 默认参数AUTO_STEP的计算方式:
-
- 矩阵数据类型的宏:
- 基本类型:
#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
- 带通道的宏
- 格式:类型Cn,其中的n表示1,2,3,4,例子:
CV_8UC1
,CV_8UC2
,CV_8UC3
,CV_8UC4
- 这样8个基本类型,每个可以带4个通道。一共可以使用32个,类型。
- 带通道的类型定义如下:
#define CV_8UC1 CV_MAKETYPE(CV_8U,1)
- 格式:类型Cn,其中的n表示1,2,3,4,例子:
- 基本类型:
- 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
- 使用例子
- 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
- 数据产生的图像
- 注意:Mat表示图像,颜色顺序式
B G R A
- 注意:Mat表示图像,颜色顺序式
文章图片
使用Mat创建的图像 重载构造器
- 其他的构造器除了拷贝构造器外,都是上面基本构造器的重载类型:
- 重载的方式主要是使用不同的方式,给用户提供更多的方便。比如:
- 高宽使用一个Size替代。
- 数据使用Scalar替代。(Scalar是4个元素的double数组,从vector继承)
- data使用默认值;
- 高宽使用std::vector替代;
- 数据使用std::vector替代;
- 数据使用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管理;
*/
}
数据属性 行列
- 属性:
int cols
int rows
- 相关函数
- 无
- 属性:
uchar * data
- 相关函数:
-
uchar * ptr (int i0=0)
- 参数使用默认的0,表示第一行的数据位置。
-
int dims
-
int flags
:标记位,可以使用位运算获取,标记位包含如下信息:- the magic signature:魔法签名
- continuity flag:连续性标记
- depth:深度
- number of channels:通道数
MatSize size
MatStep step
UMatData * u
-
int channels () const
- 通道数
-
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 )
- 0:
- 深度原色基本类型,如下几种,不包含通道信息:
-
size_t elemSize () const
- 每个元素的字节数。
-
size_t elemSize1 () const
- 每个通道的字节数;
-
bool empty () const
- 判定数据是否为空;
-
size_t total () const
- 返回数组的元素个数;
-
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属性的使用
- flags属性包含的数据:
the magic signature
continuity flag
depth
-
number of channels
- 就是type标记位,需要处理下才能得到通道数(位运算)。
- 位遮罩
MAGIC_MASK = 0xFFFF0000,
-
TYPE_MASK = 0x00000FFF,
int type () const
-
DEPTH_MASK = 7
int depth () const
-
CONTINUOUS_FLAG = CV_MAT_CONT_FLAG
bool isContinuous () const
- 使用例子
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参数访问元素。
- at函数的基本声明
template _Tp & at (int row, int col)
- 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) template
const _Tp & at (const Vec< int, n > &idx) const template
_Tp & at (Point pt) template
const _Tp & at (Point pt) const
- 关于元访问的封装
- 在opencv中提供了Vec模板类来封装矩阵元素的访问,尤其是3这种无法对齐的数据,访问起来特别方便。
- Vec从cv::Matx类。
- 模板:
Vec< T, cn > () const
- T :数据类型
- cn : 元素个数。
- 提供
[ ]
运算符,用来访问Vec中的元素。
- 在opencv中提供了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<
文章图片
使用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返回矩阵中的指定多行(开始行,结束行)
- 不包含结束行。
- 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
}
}
}
- 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
}
}
}
文章图片
使用row函数修改图像局部区域的蓝色与绿色通道为0的效果 begin与end函数
- 这里需要迭代器相关知识。
- begin函数定义:
- 模板函数。
- end函数类似。
template MatConstIterator_< _Tp >begin () const
- begin与end函数返回的是迭代器。迭代器的最大好处就是提供了位置移动的运算符:
-
++
:分前后 -
--
:分前后 +=
-=
-
[]
:也无法修改:const _Tp & operator[] (ptrdiff_t i) const
-
*
:取值运算符;这个取值返回的数据不能修改:const _Tp & operator* () const
- 返回位置:
Point pos () const
-
- 使用cv::Mat_的内部迭代器
- 可以修改访问数据
- 使用例子
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函数
- 函数原型定义
- 其中的函数对象就是回调函数(C++11增加了箭头函数)。
template voidforEach (const Functor &operation)
template voidforEach (const Functor &operation) const
- 关于函数对象Functor
- 包含特定的运算符:
void operator ()(.....) const {
- 包含特定的运算符:
- 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);
}
- 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<
- 函数指针的例子
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);
/////////////////////////////////////////////////////
文章图片
使用重载运算符()访问局部区域,并修改为单一颜色的效果 矩阵操作 数据操作 拷贝与克隆
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表示是数组,包含向量,多维数组,矩阵等。
文章图片
正经的矩阵类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的支持的数据类型:
文章图片
Mat支持的深度数据类型
- 与Mat相关的还有:
- 关联的基本结构,比如cv::Vec,cv::Point3等。
- 关联的工具函数,比如求解方程等,特征值求解,奇异值分解等。
- 硬件加速函数;
- 与OpenGL与DirectX的数据结构转换函数;
- 两个独特的构造器
- 列表初始化器:
Mat (const std::initializer_list< _Tp > list)
Mat (const std::initializer_list< int > sizes, const std::initializer_list< _Tp > list)
- 逗号初始化器:
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;
}
推荐阅读
- PMSJ寻平面设计师之现代(Hyundai)
- 太平之莲
- 闲杂“细雨”
- 七年之痒之后
- 深入理解Go之generate
- 由浅入深理解AOP
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 生活随笔|好天气下的意外之喜
- 感恩之旅第75天
- python学习之|python学习之 实现QQ自动发送消息