当前位置: 首页 > article >正文

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矩阵。
        MatExpr mul(InputArray m, double scale=1) const;
    以简单的情况为例,对于2*2大小的Mat矩阵A和B:
  • 对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也是独立的,修改它们不会相互影响。


http://www.kler.cn/a/596814.html

相关文章:

  • OSASIS(One-Shot Structure-Aware Stylized Image Synthesis)
  • 计算机网络性能优化相关内容详解
  • JavaScript基础-API 和 Web API
  • QT笔记----QCheckBox
  • 零、ubuntu20.04 安装 anaconda
  • 100道C#高频经典面试题及答案解析:C#程序员面试题库分类总结
  • 《通用去条纹算法:兼容自然图像与荧光图像的频域滤波方法》
  • 适配器模式 (Adapter Pattern)
  • 通俗一点介绍什么是场外期权交易 ?
  • 自动化测试框架pytest+requests+allure
  • 9.使用库
  • Android开源库——Glide
  • Eclipse 快捷键
  • linux 脚本题
  • GO语言 单元测试
  • 计算机基础:编码04,认识反码和补码
  • html转png完美方案
  • Java进阶 面试速记
  • 第二十八篇 数据获取与数据分析:数仓体系下的专业化分工与协同
  • Docker与K8S是什么该怎么选?