OpenCV中的矩阵操作
OpenCV中的矩阵操作主要围绕Mat
类展开,涵盖创建、访问、运算及变换等。
1. 创建矩阵
- 零矩阵/单位矩阵:
Mat zeros = Mat::zeros(3, 3, CV_32F); // 3x3浮点零矩阵 Mat eye = Mat::eye(3, 3, CV_32F); // 3x3单位矩阵
- 自定义初始化:
Mat A = (Mat_<float>(3,3) << 1,2,3,4,5,6,7,8,9);
2. 访问与修改元素
- 使用
at
方法(注意数据类型):float elem = A.at<float>(0,0); // 访问(0,0)元素 A.at<float>(1,1) = 10; // 修改(1,1)元素
- 多通道矩阵(如RGB图像):
Vec3b pixel = img.at<Vec3b>(i,j); // 获取三通道像素值 pixel = 255; // 修改蓝色通道,实际上取的pixel[0] pixel[0] = 255; // 修改蓝色通道 pixel[1] = 255; // 修改绿色通道 pixel[2] = 255; // 修改红色通道 img.at<Vec3b>(14,25) [0]= 25;//B img.at< Vec3b >(14,25) [1]= 25;//G img.at< Vec3b >(14,25 [2]= 25;//R
- 使用ptr方法:
Mat a(Size(720,1024),CV_8UC3); for(int i=0;i<a.rows;i++){ for(int j=0;j<a.cols;j++){ a.ptr(i,j)[0]=0; a.ptr(i,j)[1]=0; a.ptr(i,j)[2]=255; } } for(int i=0;i<a.rows;i++){ for(int j=0;j<a.cols;j++){ a.ptr<Vec3b>(i,j)[0]=0; a.ptr<Vec3b>(i,j)[1]=0; a.ptr<Vec3b>(i,j)[2]=255; } }
- 迭代器访问元素:
Mat a(Size(720,1024),CV_8UC3); for(auto iter=a.begin<Vec3b>();iter!=a.end<Vec3b>();iter++){ iter[0]=255; iter[1]=0; iter[2]=0; }
3. 矩阵运算
-
基本运算(需尺寸/类型一致):
使用"+"和"-"符号进行矩阵加减运算。 -
cv::Mat a= Mat::eye(Size(3,2), CV_32F); cv::Mat b= Mat::ones(Size(3,2), CV_32F); cv::Mat c= a+b; cv::Mat d= a-b;
- 矩阵乘法(A*B):
使用"*"号计算矩阵与标量相乘,矩阵与矩阵相乘。 A*B是以数学运算中矩阵相乘的方式实现的,即Mat矩阵A和B被当做纯粹的矩阵做乘法运算,这就要求A的列数等 于B的行数时,才能定义两个矩阵相乘。如A是m×n矩阵,B是n×p矩阵,它们的乘积AB是一个m×p矩阵。
另外:参与点乘的两个Mat矩阵的数据类型(type)只能是 CV_32F、 CV_64FC1、 CV_32FC2、 CV_64FC2 这4种类 型中的一种。若选用其他类型,比如CV_8UC1,编译器会报错。#include "core/core.hpp" #include "iostream" using namespace std; using namespace cv; int main(int argc,char *argv[]) { Mat A=Mat::ones(2,3,CV_32FC1); Mat B=Mat::ones(3,2,CV_32FC1); Mat AB; A.at<float>(0,0)=1; A.at<float>(0,1)=2; A.at<float>(0,2)=3; A.at<float>(1,0)=4; A.at<float>(1,1)=5; A.at<float>(1,2)=6; B.at<float>(0,0)=1; B.at<float>(0,1)=2; B.at<float>(1,0)=3; B.at<float>(1,1)=4; B.at<float>(2,0)=5; B.at<float>(2,1)=6; AB=A*B; cout<<"A=\n"<<A<<endl<<endl; cout<<"B=\n"<<B<<endl<<endl; cout<<"AB=\n"<<AB<<endl<<endl; system("pause"); }
- 矩阵dot
Opencv中.dot操作才算得上是真正的“点乘”,A.dot(B)操作相当于数学向量运算中的点乘,也叫向量的内积、数量积。double dot(InputArray m) const;
dot说明:
1). 对两个向量执行点乘运算,就是对这两个向量对应位一一相乘之后求和的操作,点乘的结果是一个标量。
对于向量a和向量b:
a和b的点积公式为:
要求向量a和向量b的行列数相同。
Mat矩阵的dot方法扩展了一维向量的点乘操作,把整个Mat矩阵扩展成一个行(列)向量,之后执行向量的点乘运算,仍然要求参与dot运算的两个Mat矩阵的行列数完全一致。
2). dot方法声明中显示返回值是double,所以A.dot(B)结果是一个double类型数据,不是Mat矩阵,不能把A.dot(B)结果赋值给Mat矩阵。
#include "core/core.hpp" #include "iostream" using namespace std; using namespace cv; int main(int argc,char *argv[]) { Mat A=Mat::ones(2,3,CV_8UC1); Mat B=Mat::ones(2,3,CV_8UC1); A.at<uchar>(0,0)=1; A.at<uchar>(0,1)=2; A.at<uchar>(0,2)=3; A.at<uchar>(1,0)=4; A.at<uchar>(1,1)=5; A.at<uchar>(1,2)=6; B.at<uchar>(0,0)=1; B.at<uchar>(0,1)=2; B.at<uchar>(0,2)=3; B.at<uchar>(1,0)=4; B.at<uchar>(1,1)=5; B.at<uchar>(1,2)=6; double AB=A.dot(B); cout<<"A=\n"<<A<<endl<<endl; cout<<"B=\n"<<B<<endl<<endl; cout<<"double类型的AB=\n"<<AB<<endl<<endl; system("pause"); }
- 矩阵mul(元素乘法)
Opencv中mul会计算两个Mat矩阵对应位的乘积,所以要求参与运算的矩阵A的行列和B的行列数一致。计算结果是跟A或B行列数一致的一个Mat矩阵。
以简单的情况为例,对于2*2大小的Mat矩阵A和B:MatExpr mul(InputArray m, double scale=1) const;
-
对A和B执行mul运算:
mul说明:
1). mul操作不对参与运算的两个矩阵A、B有数据类型上的要求,但要求A,B类型一致,不然报错。2). Mat AB=A.mul(B),若声明AB时没有定义AB的数据类型,则默认AB的数据类型跟A和B保存一致。
3). 若AB精度不够,可能产生溢出,溢出的值被置为当前精度下的最大值。
#include "core/core.hpp" #include "iostream" using namespace std; using namespace cv; int main(int argc,char *argv[]) { Mat A=Mat::ones(2,3,CV_8UC1); Mat B=Mat::ones(2,3,CV_8UC1); A.at<uchar>(0,0)=60; A.at<uchar>(0,1)=2; A.at<uchar>(0,2)=3; A.at<uchar>(1,0)=4; A.at<uchar>(1,1)=5; A.at<uchar>(1,2)=6; B.at<uchar>(0,0)=60; B.at<uchar>(0,1)=2; B.at<uchar>(0,2)=3; B.at<uchar>(1,0)=4; B.at<uchar>(1,1)=5; B.at<uchar>(1,2)=6; Mat AB=A.mul(B); cout<<"A=\n"<<A<<endl<<endl; cout<<"B=\n"<<B<<endl<<endl; cout<<"AB=\n"<<AB<<endl<<endl; system("pause"); }
- 矩阵转置
矩阵转置是将矩阵的行与列顺序对调(第i行转变为第i列)形成一个新的矩阵。OpenCV通过Mat类的t()函数实现。/ 转置 Mat m1= Mat::eye(2,3, CV_32F); Mat m1t = m1.t(); cout<<"m1 = "<<endl<<m1<<endl<<endl; cout<<"m1t = "<<endl<<m1t<<endl<<endl;
- 矩阵求逆
逆矩阵在某些算法中经常出现,在OpenCV中通过Mat类的inv()方法实现。// 求逆 Mat meinv = me.inv(); cout<<"me = "<<endl<<me<<endl<<endl; cout<<"meinv = "<<endl<<meinv<<endl<<endl;
4. 矩阵扩展方法
- 计算矩阵非零元素个数
计算物体的像素或面积常需要用到计算矩阵中的非零元素个数,OpenCV中使用countNonZero()函数实现。// 非零元素个数 int nonZerosNum = countNonZero(me); // me为输入矩阵或图像 cout<<"me = "<<endl<<me<<endl; cout<<"me中非零元素个数 = "<<nonZerosNum<<endl<<endl;
- 归一化
函数:
参数:void cv::normalize( InputArray src, OutputArray dst, double alpha = 1, double beta = 0, int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray() );
src (InputArray): 输入数组或图像,通常为单通道或多通道的矩阵(如 cv::Mat 类型)。这是需要被标准化的源数据。
dst (OutputArray): 输出标准化后的数组或图像。大小和类型取决于输入和参数设置。
alpha (double): 归一化后的最小值或缩放系数,具体含义取决于 norm_type。如果选择了 NORM_MINMAX,alpha 表示归一化后数据的最小值。如果是 NORM_L2、NORM_L1 或 NORM_INF,alpha 是缩放的目标值。
beta (double): 归一化后的最大值,当 norm_type 为 NORM_MINMAX 时有效。这个参数只会在该类型下使用,以设置归一化后的最大值。
norm_type (int): 归一化的类型。常见的有以下几种:
NORM_INF: 将数组的每个元素除以绝对值的最大值。
NORM_L1: 将数组的每个元素除以元素绝对值之和。
NORM_L2: 将数组的每个元素除以元素平方和的平方根(欧几里得范数)。
NORM_MINMAX: 线性缩放,使得数组的最小值和最大值分别为 alpha 和 beta。
dtype (int): 输出数据类型。如果设置为 -1,则输出与输入类型相同。否则,可以显式指定输出类型(如 CV_32F、CV_8U 等)。
mask (InputArray): 可选的掩码数组(通常是二值图像)。只对掩码中为非零的像素进行标准化处理。
返回值:
该函数没有返回值,但它会通过 dst 输出标准化后的结果。cv::Mat src = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE); cv::Mat dst; // 将图像归一化到 [0, 255] 范围内 cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX);
- 变形
函数:
参数:C++: Mat Mat::reshape(int cn, int rows=0) const
cn: 表示通道数(channels), 如果设为0,则表示保持通道数不变,否则则变为设置的通道数。rows: 表示矩阵行数。 如果设为0,则表示保持原有的行数不变,否则则变为设置的行数。
int main() { Mat data = Mat(20, 30, CV_32F); //设置一个20行30列1通道的一个矩阵 cout << "行数: " << data.rows << endl; cout << "列数: " << data.cols << endl; cout << "通道: " << data.channels() << endl; cout << endl; Mat dst = data.reshape(0, 1);//通道数不变,将矩阵序列化1行N列的行向量 cout << "行数: " << dst.rows << endl; cout << "列数: " << dst.cols << endl; cout << "通道: " << dst.channels() << endl; system("pause"); return 0; }
- 计算两个矩阵之间的差异或误差
函数:
参数:// 单数组范数 double cv::norm(InputArray src, int normType = NORM_L2, InputArray mask = noArray()); // 两数组差异范数 double cv::norm(InputArray src1, InputArray src2, int normType = NORM_L2, InputArray mask = noArray());
src1和src2是输入的数组,可以是 cv::Mat 或 cv::Mat_<T> 类型的对象。它可以是一维或多维数组。
normType 是一个可选参数,用于指定计算范数的类型。常见的取值为 NORM_L1、NORM_L2、NORM_INF,分别表示计算 L1 范数、L2 范数和无穷范数。默认值为 NORM_L2。
对不同范数类型的解释:
NORM_L1:计算 L1 范数,也称为曼哈顿范数。对于一维数组,它表示数组中所有元素的绝对值之和。对于多维数组,它表示所有元素绝对值之和的最大值。
NORM_L2:计算 L2 范数,也称为欧几里得范数。对于一维数组,它表示数组中所有元素的平方和的平方根。对于多维数组,它表示所有元素平方和的平方根。
NORM_INF:计算无穷范数,也称为最大绝对值范数。对于一维数组,它表示数组中绝对值最大的元素的绝对值。对于多维数组,它表示所有元素绝对值的最大值。
mask 是一个可选的掩码数组,用于指定哪些元素应该包含在范数计算中。它必须与 src 的尺寸相同,且类型为 CV_8UC1 或 CV_8SC1。 - 矩阵的全局最大最小值
求输入矩阵的全局最大最小值及其位置,可使用函数:void minMaxLoc(InputArray src, CV_OUT double* minVal, CV_OUT double* maxVal=0, CV_OUT Point* minLoc=0, CV_OUT Point* maxLoc=0, InputArray mask=noArray());
参数:
src – 输入单通道矩阵(图像).
minVal – 指向最小值的指针, 如果未指定则使用NULL
maxVal – 指向最大值的指针, 如果未指定则使用NULL
minLoc – 指向最小值位置(2维情况)的指针, 如果未指定则使用NULL
maxLoc – 指向最大值位置(2维情况)的指针, 如果未指定则使用NULL
mask – 可选的蒙版,用于选择待处理子区域// 求极值 最大、最小值及其位置 Mat img = imread("Lena.jpg",0); imshow("original image",img); double minVal=0,maxVal=0; cv::Point minPt, maxPt; minMaxLoc(img,&minVal,&maxVal,&minPt,&maxPt); cout<<"min value = "<<minVal<<endl; cout<<"max value = "<<maxVal<<endl; cout<<"minPt = ("<<minPt.x<<","<<minPt.y<<")"<<endl; cout<<"maxPt = ("<<maxPt.x<<","<<maxPt.y<<")"<<endl; cout<<endl; cv::Rect rectMin(minPt.x-10,minPt.y-10,20,20); cv::Rect rectMax(maxPt.x-10,maxPt.y-10,20,20); cv::rectangle(img,rectMin,cv::Scalar(200),2); cv::rectangle(img,rectMax,cv::Scalar(255),2); imshow("image with min max location",img); cv::waitKey();
5. 矩阵切片与ROI
- 获取子矩阵
Mat roi = A(Rect(0,0,2,2)); // 左上角2x2区域 Mat rows = A.rowRange(0,2); // 前两行
-
矩阵的ROI(感兴趣区域)
在OpenCV中,矩阵的ROI(Region of Interest,感兴趣区域)指的是在原矩阵中指定的一块区域。你可以从这个区域中提取数据,或者在不影响原矩阵的情况下对这个区域进行操作。
要设置ROI,你可以使用cv::Rect
对象来定义感兴趣区域的坐标和大小,然后使用Mat::operator()
来访问这个区域。// 定义ROI cv::Rect roi(x, y, width, height); // x, y 是左上角的坐标,width 和 height 是区域的宽度和高度 // 使用ROI cv::Mat submat = image(roi);
一旦你有了ROI的引用(如
submat
),你就可以像操作普通矩阵一样操作它了。任何对submat
的修改都不会影响原始的image
矩阵,除非你明确地复制了这些修改。// 例如,将ROI区域内的所有像素值设置为0 submat = cv::Scalar(0, 0, 0); // 对于彩色图像,三个通道都需要设置为0
如果你希望保留原始图像不变,同时又想在其他地方使用修改后的ROI,你可以将
submat
复制到一个新的矩阵。cv::Mat newImage = image.clone(); // 克隆原始图像以保留原始数据 newImage(roi) = submat.clone(); // 将修改后的ROI复制到新图像的相应位置
6. Mat深拷贝
在OpenCV中,cv::Mat对象通常用于存储图像数据。当你创建一个cv::Mat对象并将其赋值给另一个cv::Mat对象时,默认情况下是浅拷贝(shallow copy),这意味着两个对象指向相同的内存地址。如果你修改了任一对象的内存内容,另一个对象的内存内容也会相应改变,这可能导致不可预期的行为或错误。
如何进行深拷贝?
方法1:使用cv::Mat::clone()
这是进行深拷贝的最直接方法。clone()方法会创建一个新的cv::Mat对象,并复制原始矩阵的数据到一个全新的内存区域。
cv::Mat original = cv::imread("path_to_image.jpg");
cv::Mat copy = original.clone();
// 现在,original和copy指向不同的内存区域,修改它们不会相互影响。
方法2:使用cv::Mat::copyTo()
另一个方法是使用copyTo()方法,这也可以用来创建数据的深拷贝。
cv::Mat original = cv::imread("path_to_image.jpg");
cv::Mat copy;
original.copyTo(copy);
// 现在,original和copy也是独立的,修改它们不会相互影响。