本文共 6081 字,大约阅读时间需要 20 分钟。
Mat类是OpenCV表达二维图片的基础。
经过简单阅读有关Mat的,记录其中提出的几个关键要点。
OpenCV使用BGR格式存储三原色。
对于二维的Mat,可以使用 << 操作符将矩阵内容显示在屏幕上(插入流)。
Mat的数据格式使用预定义的类似一样的宏。其中8U代表8bit的无符号整数,C3代表每个pixel有三个通道,8UC3即表示每个pixcel共需要24 bit,3 byte的数据进行存储。注意,在C++代码中,若没有申明using namespace cv; 那么在使用CV_8UC3一类的宏时,不能在前面加 cv:: 作用域修饰,这会导致预编译器在做宏展开时报错。
创建一个Mat可以使用如下形式的语句。
cv::Mat M(2,2, cv::CV_8UC3, cv::Scalar(0,0,255));
其中cv::Scalar是一个4元标量,这里仅使用了其前3个元素。
OpenCV提供类似于MATLAB的eye(), ones(), zeros()函数,但需要指定数据格式。
Mat的 << 操作符支持使用不同的格式输出,输出的格式涵盖python默认,numpy和c语言的格式。
经过学习Mat的一些特性,了解到Mat实际是为了处理图像而设计的一种矩阵表示方法,是图像表示成了矩阵,而不是用矩阵表示图像。这里我们不能假定Mat的所有特性与一般意义下的矩阵是一致的,这充分体现在Mat的数据格式和Mat的算术运算上。
像std::vector那样提供了at()函数,但at()是一个模板函数,需要在使用是制定数据类型。
对于一个提供矩阵表达和运算的库,应该测试一下某些关键函数的行为。这里测试了转置函数,加法,减法,数乘和矩阵乘法,确实发现了一些OpenCV的特性。
与预想基本一致,Mat的转置函数t(), 返回一个类型的对象,可以参与其他算数运算或者赋值给另外一个Mat对象。当进行赋值时,例如B = A.t(); B将不共享A的存储,B与A的存储是独立的,互不影响。
测试了标量数值与矩阵的加减法,也测试了矩阵之间的加减法。OpenCV重载了诸如 +, -, *, +=, -=, *=这类运算符,但仅限于数据类型一致的情况。数据类型不一致时需要使用函数接口来完成特定的运算。例如加法使用函数。
另外,对于矩阵加减法,数与矩阵加减法,在使用+, += 或者相应的减法运算符时,OpenCV会根据当前的数据格式进行自动截断。例如CV_8UC1类型的数据,最大值为255。所有加法运算结果超过255的元素,都将截断为255。这正是处理图像数据时需要的特性,而不能用于一般情况下的矩阵运算。当需要进行更一般的运算时,需要使用更大的数据深度,或者使用浮点数据类型。上述关于数据自动截断的特性,cv::add()函数同样具有。
当前本机OpenCV版本已更新至4.1+.
关于 函数,如下C++代码的和执行结果是合理的。
#include#include int main( int argc, char** argv ) { std::cout << "Hello, TryOpenCV! " << std::endl; cv::Mat img( 2, 2, CV_8UC3 ); std::cout << "img: " << img << std::endl; cv::Mat img1; cv::add( img, cv::Scalar::all(255), img1 ); std::cout << "img1: " << img1 << std::endl; return 0;}
执行结果
Hello, TryOpenCV! img: [ 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0]img1: [255, 255, 255, 255, 255, 255; 255, 255, 255, 255, 255, 255]
在使用Python的cv2进行类似操作时会有意想不到的效果。
import cv2import numpy as npimg = np.zeros((2, 2, 3), dtype=np.uint8)img1 = cv2.add(img, 255)
结果将是
array([[[255, 0, 0], [255, 0, 0]], [[255, 0, 0], [255, 0, 0]]], dtype=uint8)
这里需要借鉴C++中cv::Scalar的概念。根据的描述(arithm_op函数),当进行Mat和实数的加法时,实数必须已CV_64F表示。在python环境中,这可以利用一个只有单个元素的NumPy array实现。
import cv2import numpy as npimg = np.zeros((2, 2, 3), dtype=np.uint8)s = np.array([255], dtype=np.float64)img1 = cv2.add(img, s)
运行将得到正确的结果。
array([[[255, 255, 255], [255, 255, 255]], [[255, 255, 255], [255, 255, 255]]], dtype=uint8)
更加关心的是矩阵乘法。这里特指与通常情况下数学上定义的乘法一致的矩阵乘法,而非element-wise multiplication。类比于MATLAB,也就是 * 而不是 .* 。
OpenCV强制规定,Mat之间的乘法只能在如下4种数据格式上进行:CV_32FC1, CV_32FC2, CV_64FC1 以及 CF_64FC2。并且两个Mat必须具有相同的数据格式。这个规定体现在OpenCV的源码上,以现在手上的版本(3.4.1)为例,该限制出现在 modules/core/src/matmul.cpp: 1558位置,如图所示。
在进行矩阵乘法前,可能需要对现有Mat对象进行数据格式的转换,这通过Mat的convertTo()成员函数实现。converTo()函数可以用于转换自身的数据格式,例如一个Mat对象A,可以这样:
A.convertTo(A, CV_64FC1);
另外,在运用convertTo()时,像进行加法和数乘一样,OpenCV也会进行自动数据截断。
矩阵乘法也重载了 *= 运算符。可以 A *= A; 这样使用。
今天先到这里,上一波简单代码。
cv::Mat A = ( cv::Mat_(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9 ); std::cout << "A = " << std::endl << A << std::endl << std::endl; // Test the t() function. std::cout << "A.t() = " << std::endl << A.t() << std::endl << std::endl; std::cout << "After transposition, A = " << std::endl << A << std::endl << std::endl; // Test the t() function with assignment. cv::Mat B = A.t(); std::cout << "B = " << std::endl << B << std::endl << std::endl; B.at (1,2) = 10; std::cout << "After assigning value to B." << std::endl; std::cout << "A = " << std::endl << A << std::endl; std::cout << "B = " << std::endl << B << std::endl; // Test arithmetic operations. cv::Mat C = A.clone(); C += 1; std::cout << "C += 1 -> " << std::endl << C << std::endl << std::endl; C -= 1; std::cout << "C -= 1 -> " << std::endl << C << std::endl << std::endl; C *= 10; std::cout << "C *= 10 -> " << std::endl << C << std::endl << std::endl; C = A.clone(); C += A; std::cout << "C += A -> " << std::endl << C << std::endl << std::endl; C -= A; std::cout << "C -= A -> " << std::endl << C << std::endl << std::endl; C = A.clone(); C += 250; std::cout << "C += 250 -> " << std::endl << C << std::endl << std::endl; C = A.clone(); C -= 250; std::cout << "C -= 250 -> " << std::endl << C << std::endl << std::endl; C = A.clone(); C *= 100; std::cout << "C *= 100 -> " << std::endl << C << std::endl << std::endl; cv::Mat D; A.convertTo(D, CV_32FC1); // Only CV_32FC1, CV_32FC2, CV_64FC1 and, CV_64FC2 could be used to do matrix multiplication. std::cout << "D = " << std::endl << D << std::endl << std::endl; D *= D; std::cout << "D *= D -> " << std::endl << D << std::endl << std::endl; cv::Mat DD = A.clone(); std::cout << "DD.type() = " << DD.type() << std::endl; std::cout << "DD = " << std::endl << DD << std::endl << std::endl; DD.convertTo(DD, CV_64FC1); std::cout << "DD.type() = " << DD.type() << std::endl; std::cout << "DD = " << std::endl << DD << std::endl << std::endl; DD *= 100; std::cout << "DD *= 100 -> " << std::endl << DD << std::endl << std::endl; DD.convertTo(DD, CV_8UC1); std::cout << "DD convert to CV_8UC1 -> "<< std::endl << DD << std::endl << std::endl; DD = A.clone(); DD.convertTo(DD, CV_64FC1); DD -= 250; std::cout << "DD convert to CV_64FC1, -= 250 -> " << std::endl << DD << std::endl << std::endl; DD.convertTo(DD, CV_8UC1); std::cout << "DD convert to CV_8UC1 -> " << std::endl << DD << std::endl << std::endl; cv::Mat E;// E = D + A; // This will cause a runtime exception.// std::cout << "E.type() = " << E.type() << std::endl << std::endl; cv::add( A, D, E, cv::Mat(), CV_32SC1 ); std::cout << "E = A + D -> " << E << std::endl << std::endl; std::cout << "E.type() = " << E.type() << std::endl << std::endl; B = 250; cv::add(B, D, E, cv::Mat(), CV_32SC1); std::cout << "B = 250, E = B + D with CV_32SC1 -> " << std::endl << E << std::endl << std::endl; cv::add(B, D, E, cv::Mat(), CV_8UC1); std::cout << "B = 250, E = B + D with CV_8UC1 -> " << std::endl << E << std::endl << std::endl;