文章目录
前言
- 需要下载安装OpenCV工具包的朋友,请前往 此处 ;
- 系统要求:Windows系统,LabVIEW>=2018,兼容32位和64位。
Mat类的基本用法
Mat即矩阵Matrix,是OpenCV最重要的核心类,通常作为图像数据的容器。
本文将介绍如何在LabVIEW下使用Mat类进行矩阵运算和图像处理。
1. Mat初始化(从 LV 到 Mat)
在LabVIEW中初始化一个Mat类,可以使用函数选板>>Addons>>Molitec>>OpenCV>>core>>Mat>>new.vi ,位置如下图。
放置一个new到程序框图中,可以发现它是一个多态VI,通过下拉列表查看到,一共有12种初始化模式。
Mat() | 创建一个空矩阵 |
---|---|
Mat(mat,roi) | 创建子矩阵,指向另一个已知Mat的矩形ROI区域 ,适用于二维Mat。 |
Mat(mat,ranges) | 创建子矩阵,ranges代表每一维度的截取范围 ,适用于多维Mat。 |
Mat(rows,cols,type,scalar) | 通过行数、列数、类型、初始值来创建Mat,适用于二维Mat。 type包含数据类型+通道数,Mat中的每一个元素都等于scalar。下同 |
Mat(rows,cols,type,data,step=0) | 通过行数、列数、类型、数组来创建Mat,适用于二维Mat。 Mat将依次填入data数组的每一个值,不足时末尾补0。下同 目前限定数组内存连续,所以step永远等于0,直接忽略。下同 |
Mat(ndims,sizes,type,scalar) | 通过维数、尺寸、类型、初始值来创建Mat,适用于多维Mat。 sizes数组长度应等于ndims,代表每一维度的尺寸。下同 |
Mat(ndims,sizes,type,data,step=0) | 通过维数、尺寸、类型、数组来创建Mat,适用于多维Mat。 |
zeros(rows,cols,type) | 创建全0矩阵,参数为行数、列数、类型,适用于二维Mat。 |
zeros(ndims,sizes,type) | 创建全0矩阵,参数为维数、尺寸、类型,适用于多维Mat。 |
ones(rows,cols,type) | 创建全1矩阵,参数为行数、列数、类型,适用于二维Mat。 |
ones(ndims,sizes,type) | 创建全1矩阵,参数为维数、尺寸、类型,适用于多维Mat。 |
eye(rows,cols,type) | 创建单位矩阵(全1对角),参数为行数、列数、类型,只能是二维Mat。 |
例1-1:创建一个2行3列的1通道矩阵,元素类型为8U(8位无符号整数),初始值均为255。
- 使用 Mat(rows,cols,type,scalar) 模式【左图】与 使用 Mat(ndims,sizes,type,scalar) 模式【右图】
scalar 作为所有元素的公共初始值,其长度等于通道数。为了能涵盖所有数据类型,LabVIEW参数中的scalar采用最宽泛的double类型,但传入Mat对象时,将自动转换成type指定的数据类型。
例1-2:创建一个100行150列3通道矩阵,元素类型为8U,初始值均为[0,128,255] 。
- 使用 Mat(rows,cols,type,scalar) 模式【左图】与 使用 Mat(ndims,sizes,type,scalar) 模式【右图】
因为是3通道Mat,所以scalar长度也为3,当表示颜色时,通道顺序为 [B, G, R]
例1-3:创建一个2行3列1通道矩阵,元素类型为32F(单精度浮点数float),初始值为{{1.1, 2.2, 3.3}, {4.4, 5.5, 6.6}}。
- 使用 Mat(rows,cols,type,data,step=0) 模式【左图】与 使用 Mat(ndims,sizes,type,data,step=0) 模式【右图】
data数组作为整体的初始值,依次填入Mat对象。填入顺序是:先通道,再满行,后满列。
同样,data也采用最宽泛的double类型,传入Mat对象时,将自动转换成type指定的数据类型。
例1-4:创建一个2行3列3通道矩阵,元素类型8U,初始值{{[11,12,13], [21,22,23], [31,32,33]}, {[111,112,113], [121,122,123], [131,132,133]}}
-
使用 Mat(rows,cols,type,data,step=0) 模式实现如下。
此例Mat是3通道,data顺序正如上文提到:先通道,再满行,后满列。同一元素的3通道数据要彼此相邻,当作一个整体。
当你不想手动输入data时,就可以如下图那样,通过LabVIEW “捆绑簇” 的方式将data单独列出,然后用其他算法产生数据,连接到data上作为变量输入。
-
使用 2D_to_Mat.vi 实现如下。
由于二维矩阵最常用,所以Mat选板中额外提供一个VI,可以更加直观地将LabVIEW的2D数组,转化成二维Mat对象。2D数组的行,即是Mat的行;2D数组的列,即是Mat的列。多通道数据在2D数组中,同行相邻展开。
例1-5:创建一个三维1通道矩阵,尺寸4x3x2,元素类型32S(32位有符号整数),初始值:
{ { {1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24} } }
- 使用 Mat(ndims,sizes,type,data,step=0) 模式实现如下。
在多维矩阵的情况下,data的排列规律要更广义地理解:最低维度相邻排列,然后向次低纬度延申,再向更高维度延申,依此类推。这是因为矩阵遍历时,是从高纬度向低纬度,层层For循环剥开,最低维度在最后一层For循环。设元素的索引为(i,j,k),定义其“索引权重”为 (i * dim1 + j * dim2 + k),即:在读取它之前已经历的For循环次数。
从而data的排列规律为:按照“索引权重”从低到高排列。(多通道时,依然是通道最先相邻排列)
2. Mat可视化(从 Mat 到 LV)
将一个Mat对象的数据取出,并在LabVIEW中可视化地显示出来,可以使用相同选板下的 to_LV.vi ,位置如下图。
放置一个to_LV到程序框图中,可以发现它是一个多态VI,通过下拉列表查看到,一共有4种主模式,其中array模式又根据数据类型和维度的不同,细分为21种子模式。
raw | 读取原始buffer数据,返回数据类型为bytes数组,小端模式 (不必考虑Mat内存的不连续性,to_LV 已做处理,下同) |
---|---|
common | 根据Mat数据类型,对raw模式的buffer按照小端模式进行元素切割,排列成一维数组。 同样为了涵盖所有类型,该一维数组统一转化成double类型,相当于Mat初始化时的data数组。 |
image | 将Mat数据转化为LabVIEW的图像,仅适用于单通道和3通道,且数据类型为8U |
array | 根据Mat的通道、维数、尺寸,对common模式返回的一维数组进行再切割,转化成LabVIEW中相应的多维数组。 目前每种数据类型下,只提供3种维度(2D、3D、4D),其他维度请选common模式,然后自写算法来切割。 多通道数据,依然是同行相邻展开(沿最低维度展开) |
例2-1:把一个二维8U的Mat,读取成LabVIEW的2D数组
- 如下图,上方是1通道,下方是3通道。注意data数组长度小于Mat容量时,末尾自动补零。
最后调用release.vi 释放Mat的内存,该VI位于Mat选板最末。
例2-2:把一个未知数据类型的二维Mat,读取成LabVIEW的2D数组
-
使用common模式读取,然后根据返回的 properties 确定Mat的通道和尺寸,对 double_1d 进行reshape。
-
上例中出现了Mat属性(properties)这个变量,是伴随 to_LV 一起返回的。除此之外,我们还可以单独获取Mat的某些属性,作为后续算法处理的前提条件。方法是使用“属性节点”,如下图。属性节点是LabVIEW自带的,位于Application Control选板。
例2-3:创建一张纯色图片,并用LabVIEW的2D图片控件显示。然后修改ROI区域,变成另一种颜色,再显示。
- 注意,通过ROI创建的子矩阵,其内存直接指向父矩阵的某一块区域,而不是拷贝到新的内存空间(除非经过深拷贝,如clone)。所以当子矩阵被修改时,父矩阵也同样被修改。
- 父矩阵和子矩阵必须全部release之后,内存才会真正被释放。只release其中一个,另一个依然在使用中。
- setTo.vi 同样位于Mat选板,作用是将矩阵的元素统一重置为输入的scalar数值。
3. 矩阵运算
3.1 操作符
- 在Mat选板中,第一项是一个叫operator的子目录,里面包含多种运算符号,包括加、减、乘、除,逻辑与、或、非、异或,以及各种比较运算符。
- 利用这些VI,可以实现矩阵的基础运算。其逻辑相当于在C++编程中,直接用运算符号,连接两个对象。
- 连接的两个对象,可以都是Mat,也可以一个是Mat,另一个是元素(scalar),通过多态VI来切换模式。
3.2 内置函数
Mat选板下的所有VI,其图标设计与配色风格一致,都属于Mat类的内置函数。
这些函数功能包括:
函数 | 功能 |
---|---|
create | (对已有Mat)重新定义类型、尺寸,并分配存储空间 |
convertTo | 转换Mat数据类型,并可自定义线性变换 |
copyTo | (浅)拷贝 |
clone | (深)拷贝 |
col | 取源Mat的某一列,作为子矩阵输出(该子矩阵仍是二维,下同) |
colRange | 按照range范围,截取源Mat的某些列,作为子矩阵输出 |
row | 取源Mat的某一行,作为子矩阵输出 |
rowRange | 按照range范围,截取源Mat的某些行,作为子矩阵输出 |
diag | 创建对角矩阵,或获取矩阵对角向量 |
at | 按照坐标索引,读取或写入Mat中的某一元素 |
checkVector | 检查输入的Mat是否为向量(行数、列数,至少有一个等于1) |
dot | 计算两个向量的内积(数量积) |
cross | 计算两个向量的外积(向量积、叉积) |
mul | 两个尺寸相同的Mat,对应元素相乘,得到同样尺寸的乘积矩阵。并非“矩阵乘法” |
operator(*) | 顺便一提,上文提到的operator目录下的 * 符号,用于连接两个Mat时,才是真正的“矩阵乘法” 然而,下文中core目录下的multiply函数,其功能与内置函数mul一样,也不是“矩阵乘法” |
adjustROI | 调整ROI区域(适用于子矩阵) |
locateROI | 获取当前ROI位置(适用于子矩阵) |
push_back | 在向量末尾追加元素,或在Mat的末尾追加另一个列数相同的Mat,作为拓展的行 |
pop_back | 删除向量末尾的n个元素,或删除Mat末尾的n个行 |
resize | 修改Mat行数。行数减少时元素被释放,行数增加时新元素被写入指定的初始值 |
reshape | 在数据不变的前提下,重新切割Mat的通道、尺寸 |
reserve | 保留一定数量的行空间,确保不会在resize等操作时被删除,下同 |
reserveBuffer | 保留一定数量的buffer空间 |
inv | 求逆矩阵 |
t | 求转置矩阵 |
setTo | 重置Mat元素为统一初始值 |
具体用法不一一列举了。下图是一个范例,从创建的Mat中取出2列,求内积。然后用其中一列作为对角元素,生成对角阵,再求该对角阵的逆矩阵。(更多用法,请查找工具包附带的范例:examples\Molitec\OpenCV\core\Mat…)
3.3 外部函数
由于Mat是OpenCV最重要的核心类型,所以几乎所有模块下的函数,都需要Mat类型的变量作为输入输出。
其中,对矩阵计算支持最多,数量最庞大的,要数 core 模块。其函数选板如下图(节选):
注意,虽然core选板下也有加、减、乘、除、逻辑、比较运算符,但与上文operator目录下的运算符用法不尽相同。最主要的区别是,core下的运算函数,多了一个Mask输入量,用以定义掩膜区域。另外,multiply函数的功能与Mat内置的mul一样,与 operator(*) 不一样。
下面是一个范例,计算一个输入Mat的平方、开方、自然底数幂、自然对数。(更多范例请查找:examples\Molitec\OpenCV\core…)
4. 图像容器
4.1 读取图片
在OpenCV中,对图像的处理,都是基于对矩阵的运算来实现的。因此Mat类经常作为承载图像的容器。
下图中展示的是:读取彩色图片文件到Mat对象,并转为灰度图。imread.vi 位于imgcodes模块,cvtColor.vi 位于imgproc模块。
4.2 读取视频流
从摄像头、视频文件、URL等读取一帧图像,也是存放在Mat对象中。
下图中展示的是:连续读取摄像头画面,并连续显示。VideoCapture类位于videoio模块。
作为容器的Mat只需初始化一次(空矩阵),进入read.vi后将自动分配空间。每次循环读取的图像,都会写入同一个Mat对象中,避免占用过多内存。
不要忘记,LabVIEW本身也很强大
通过上文的 “Mat初始化” 和 “Mat可视化” ,我们已经打通了LabVIEW的数组与OpenCV的Mat之间互相转化的渠道。既然如此,很多关于矩阵的计算,我们就不必拘泥于非要用OpenCV的接口来实现。毕竟LabVIEW在矩阵(数组)计算方面也足够强大。
比如:通过 to_LV.vi 从Mat中获取到数组之后,接下来完全用LabVIEW来完成对该数组的自定义处理。如果有需要,再将LabVIEW计算之后的结果,重新转化成Mat对象。
下图中展示的是:读取一个二维Mat到2D数组,然后用LabVIEW做QR分解,得到一个正交矩阵(Q)和一个上三角矩阵(R),最后再分别用Q、R 新建Mat对象。
总结
- 本系列博文作为LabVIEW工具包—OpenCV的教程,将以专栏的形式陆续发布和更新。
- 对工具包感兴趣的朋友,欢迎下载试用:秣厉科技 - LabVIEW工具包 - OpenCV
- 各位看官有什么想法、建议、吐槽、批评,或新奇的需求,也欢迎留言讨论。