Opencv C++图像处理:矩阵Mat + 随机数RNG + 计算耗时 + 鼠标事件

文章目录
1、C++的数据类型+字节数+取值范围
2、Mat对象:n 维单/多通道的密集矩阵
2.1、创建 Mat 矩阵
2.2、获取像素1:img.at<uchar>(y,x)
2.3、获取像素2(防止颜色溢出):saturate_cast<uchar>(y,x)
2.4、Mat矩阵常用属性
3、基本数据类型
3.1、Point类:cv::Point()
3.2、Scalar类:cv::Scalar()
3.3、Size类:cv::Size()
3.4、Rect类:cv::Rect()
3.5、Matx类:cv::Matx()
3.6、Vec类:cv::Vec()
3.7、Range类:cv::Range()
4、随机数:cv::RNG
4.1、生成一个随机数:cv::RNG::uniform() + cv::RNG::gaussian()
4.2、获取下一个随机数:next + operator
4.3、用随机数填充矩阵:cv::RNG::fill()
5、计算消费时间函数
5.1、耗时:cv::getTickCount()
5.2、频率:cv::getTickFrequency()
5.3、实战案例
6、鼠标与轨迹条操作
6.1、鼠标事件的回调函数:cv::MouseCallback
6.2、设置鼠标事件的回调函数:cv::setMouseCallback()
6.3、创建轨迹条,并将其附加到指定的窗口:cv::createTrackbar()
6.4、获取指定轨迹条的当前位置:cv::getTrackbarPos()
6.5、设置指定轨迹条在指定窗口中的位置:cv::setTrackbarPos()
1、C++的数据类型+字节数+取值范围
数据类型    字节数    取值范围
bool型(布尔型)    1    [0 or 1]
BOOL型(int型)    4    [TRUE or FALSE]
sbyte型(有符号8位整数)    1    [128 ~ 127]
bytet型(无符号8位整数)8U    2    [0 ~ 255]
short型(有符号16位整数)16S    2    [-32,768 ~ 32,767]
ushort型(无符号16位整数)16U    2    [0 ~ 65,535]
int型(有符号32位整数)32S    4    [-2,147,483 ~ 2,147,483,647]
uint型(无符号32位整数)    4    [0 ~ 4,294,967,295]
long型(64位有符号整数)    8    [9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807]
ulong型(64位无符号整数)    8    [0 ~ 18,446,744,073,709,551,615]
float型(32位单精度实数)32F    4    [3.4E+10的负38次方 ~ 3.4E+10的38次方]
double型(64位双精度实数)64F    8    [1.7E+10的负308次方 ~ 1.7E+10的正308次方]
指针    4    
注意:int,float,double占多少个字节是由编译器决定的,不同的编译器,规定也不一样。ANSI标准定义int是占2个字节。

2、Mat对象:n 维单/多通道的密集矩阵
Mat(n-dimensional dense array class): 用于存储数值、向量、矩阵、灰度或彩色图像、体素体积、向量场、点云、张量、直方图。
Mat类:创建并访问二维、三维、四维、五维矩阵
Mat类:矩阵相乘——点乘、dot、mul运算详解

OpenCV C++使用 Mat 来存储图像数据,每个像素点的数据格式由存储格式和通道数组成。

> 表达式:CV_{depth}C{channels}
>         depth表示每个像素点的存储格式
>         channels表示每个像素点的通道数。
> 即CV_{8U, 16S, 16U, 32S, 32F, 64F}C{1, 2, 3, 4}。

    (1)存储格式类型:8U, 16S, 16U, 32S, 32F, 64F
                8U(unsigned char), 
                16S(short int), 16U(unsigned short int), 
                32S(signed int), 32F(float), 
                64F(double), 
                
        备注:    8U取值范围 =[0, 255],
                 16U取值范围=[0, 65535], 
                 32f取值范围=[3.4E+10的负38次方 ~ 3.4E+10的38次方].
    (2)通道数类型:C{1, 2, 3, 4}
                C1(单通道图像)。如:gray灰度图
                C3(3 通道图像)。如:RGB 彩色图
                C4(4 通道图像)。如:带有透明度通道的RGB图像

常规RGB图像的存储格式CV_8UC3:表示8位无符号整型3通道矩阵.
        
        假设矩阵Mat = (100, 100, 3)
        11、像素点个数:100 x 100 x 3 = 30000
        22、存储格式:8U。表示每个像素点的值是8位无符号整数,即占用1个字节空间。
        33、通道数:C3。表示每个像素点有3个通道,即存放3个像素值。


在图像计算时,需要将读取图像格式CV_8UC3(Vec3b)转换为CV_32F(Vec3F),计算之后再转回CV_8UC3(Vec3b)。

(1)图像处理:直接处理,无需转换。
(2)图像计算:为了保持高精度,避免信息损失,需要转换。
CV_8UC1(uchar):8位无符号整型,单通道矩阵
CV_8UC3(Vec3b):8位无符号整型,三通道矩阵
CV_32FC3(Vec3F):32位浮点型,三通道矩阵
2.1、创建 Mat 矩阵
函数说明:cv::Mat M_temp(row, column, data_type, cv::Scalar(0, 0, 255))
输入参数:        
                row                    矩阵的行
                column                矩阵的列
                data_type            图像的数据格式
                Scalar(0, 0, 255)    初始化RGB的像素值。
                                    (0, 0, 255)表示R/G全为0,B全为255
                
//
cv::Mat img1(480, 640, CV_8UC3);        //新建矩阵
cv::Mat img2(img1);                        //复制矩阵

cv::Mat img7 = cv::Mat::zeros(rows, cols, CV_8UC3);         //值全0矩阵。
cv::Mat img8 = cv::Mat::ones(rows, cols, CV_64FC1);         //值全1矩阵。
cv::Mat img9 = cv::Mat::eye(rows, cols, CV_16SC2);             //单位矩阵。        

2.2、获取像素1:img.at(y,x)
cv::Mat img = cv::Mat::ones(240, 320, CV_8UC1);                //创建单通道Mat矩阵
cv::Mat img1 = cv::Mat::ones(480, 640, CV_8UC3);            //创建多通道Mat矩阵

float elem = img.at<uchar>(10, 10);                            //获取(单通道)像素点的像素值:img.at<uchar>(y,x)
cv::Vec3b elem = img1.at<cv::Vec3b>(10, 10);                //获取(多通道)像素点的像素值:img.at<cv::Vec3b>(y,x)
elem_B = elem[0];             //蓝色通道数值(全255)
elem_G = elem[1];             //绿色通道数值(全255)
elem_R = elem[2];             //红色通道数值(全0)

2.3、获取像素2(防止颜色溢出):saturate_cast(y,x)
避免边界溢出(截断):

若像素值大于255,则赋值255;
若像素值小于0,则赋值0。

//原理如下
if (data_value < 0) 
    data_value = 0; 
else if (data_value > 255) 
    data_value = 255;

#include <opencv2/opencv.hpp>
//using namespace cv;
//using namespace std;

int main(int argc, const char* argv[]) 
{
    //(1)读取图像
    std::string img_path = "test.jpg";
    cv::Mat src = cv::imread(img_path, 1);

    //(2)判断图像是否读取成功
    if (!src.data)
    {
        std::cout << "can't read image!" << std::endl;
        return -1;
    }
    
    //(3)获取元素
    cv::Mat dst1, dst2;
    dst1 = cv::Mat::zeros(src.size(), src.type());    
    dst2 = cv::Mat::zeros(src.size(), src.type());

    //三个for循环:dst(i,j) =a * src(i,j) + b
    for (int y = 0; y < src.rows; y++)
    {
        for (int x = 0; x < src.cols; x++)
        {
            for (int c = 0; c < 3; c++)        //三个通道
            {
                dst1.at<cv::Vec3b>(y, x)[c] = src.at<cv::Vec3b>(y, x)[c] * 2;                                //不饱和滤除
                dst2.at<cv::Vec3b>(y, x)[c] = cv::saturate_cast<uchar>(src.at<cv::Vec3b>(y, x)[c] * 2);        //饱和滤除
            }
        }
    }
    
    //(4)显示图像
    cv::imshow("src", src);
    cv::imshow("dst1", dst1);
    cv::imshow("dst2", dst2);

    cv::waitKey(0);
    return 0;
}


2.4、Mat矩阵常用属性
cv::Mat img(320, 320, CV_8UC3, cv::Scalar(255, 255, 0));        //创建320×320的3通道彩色图像。

Mat.rows                 获取矩阵:行数
Mat.cols                 获取矩阵:列数
Mat.dims                 获取矩阵:维数(单通道是二维,多通道是三维)
Mat.channels()             获取矩阵:通道数
Mat.size();                获取矩阵:大小
Mat.total()             获取矩阵:面积=[行数*列数](与通道数无关)
Mat.depth()                获取矩阵:存储格式(返回0~6):enum{CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6}  

Mat.row(0)                 获取矩阵:第一行元素组成的数组。
Mat.col(0)                 获取矩阵:第一列元素组成的数组。
Mat.rowRange(0, 10)        获取矩阵:第0-10行元素组成的数组。
Mat.colRange(0, 10)        获取矩阵:第0-10列元素组成的数组。

Mat.empty();            判断矩阵是否为空,若为空,则返回true。
Mat.clone()                复制矩阵并赋值给新矩阵。

Mat.setTo(0);                        将矩阵全部设置为指定值(0)。如:src.setTo(0, src<10);当src中的某个像素值小于10,就将该值设置成0。
MatA.copyTo(MatB)                    将矩阵A复制到矩阵B中。
Mat.convertTo(img, CV_32F)            将img的数据格式转换为CV_32F。
Mat.push_back()                        将一个或多个元素添加到矩阵底部。其类型和列数必须与Mat矩阵中的相同。


3、基本数据类型
3.1、Point类:cv::Point()
cv::Point类由两个部分组成:cv::Point{2, 3}{b, s, i, f, d}。
        (1)维度            2、3代表维度(分别代表2个点、3个点)
        (2)存储类型        b: 表示无符号字符
                            s: 表示短整型
                            i: 表示32位整型
                            f: 表示32位浮点数
                            d: 表示64位浮点数
                            
Point类支持的操作:
        默认构造函数:        cv::Point2i p;    
                            cv::Point3i p;
        复制构造函数:        cv::Point3f p2(p1);
        值构造函数:            cv::Point2i(x0,x1);
                            cv::Point3d p(x0,x1,x2);
        构造固定向量类:        cv::Vec3f p;
        成员访问:            p.x, p.y;
        点乘:                float x = p1.dot(p2);
        双精度点乘:            double x =p1.ddot(p2);
        叉乘:                p1.cross(p2);


3.2、Scalar类:cv::Scalar()
cv::Scalar本质上是一个四维向量,其可以产生任意四元素的向量,一般是double类型。

Scalar类支持的操作:
        默认构造函数:                cv::Scalar s;
        复制构造函数:                cv::Scalar s2(s1);
        值构造函数:                    cv::Scalar s(x0);
                                    cv::Scalar s(x0, x1, x2, x3);
        返回所有元素都为标量k:        cv::Scalar::all(k)        
        
        元素相乘:                    s1.mul(s2);
        (四元数)共轭:                s.conj();        //return cv::Scalar(s0,-s1,-s2,-s2)
        (四元数)真值测试:            s.isReal();        //return ture, if s1==s2==s3==0.

3.3、Size类:cv::Size()
Size类支持的操作:
        默认构造函数:        cv::Size sz; 
                            cv::Size2i sz;
                            cv::Size2f sz;
        复制构造函数:        cv::Size sz2(sz1);
        值构造函数:            cv::Size2f sz(w,h);
        成员访问:            sz.width, sz.height;
        计算面积:            sz.area();

3.4、Rect类:cv::Rect()
Rect类包含:        
            (1)Point类的成员(x, y)。                矩形左上角
            (2)Size类的成员(width, height)。        代表矩形的大小

4.1 Rect类支持的操作:
        默认构造函数:            cv::Rect r;
        复制构造函数:            cv::Rect r2(r1);
        值构造函数:                cv::Rect(x, y, w, h);
        由起始点和大小构造:        cv::Rect(p, sz);
        由两个对角构造:            cv::Rect(p1, p2);
        成员访问:                r.x, r.y, r.width, r.height;
        计算面积:                r.area();
        提取左上角和右下角:        r.tl(), r.br();
        判断p点是否在矩形r内:    r.contains(p);

4.2 Rect类的覆写操作:
        矩形r1和矩形r2的交集:            cv::Rect r3 = r1&r2; r1 &= r2;
        矩形r1和矩形r2的并集:            cv::Rect r3 = r1|r2; r1 |= r2;
        平移矩形r x个数量:                cv::Rect rx = r+x; r += x;
        扩大矩形r s大小:                cv::Rect rs = r+s; r += s;
        比较矩形r1和矩形r2是否相等:        bool eq = (r1 == r2);
        比较矩形r1和矩形r2是否不相等:    bool ne = (r1 != r2);


3.5、Matx类:cv::Matx()
固定矩阵类(Matx类):
        是为了使编译时就已知矩阵的维度而设计,其内部所有数据都是在堆栈上分配的。
        其是Opencv的c++接口基本类型的核心。
        其继承于固定矩阵类。而其他的类:要么继承于固定向量类,要么转换成固定向量类。
        其是一个模板cv::Matx<>,但独立的矩阵通常通过别名分配。别名的基础格式为cv::Matx{1,2,3,4,5,6}{1,2,3,4,5,6}{f,d}。

5.1 Matx类支持的操作:
        默认构造函数:        cv::Matx33f m22f;
                            cv::Matx43d m43d;
        复制构造函数:        cv::Matx22d m22d(n22d);
        值构造函数:            cv::Matx21f m(x0,x1);
        含相同元素的矩阵:    m33f = cv::Matx33f::all(x);
        全零矩阵:            m23d = cv::Matx23d::zeros();
        元素全是1的矩阵:    m16f = cv::Matx16f::ones();
        单位矩阵:            m33f = cv::Matx33f::eye();
        均匀分布矩阵:        m33f = cv::Matx33f::randu(min,max);
        正态分布矩阵:        m33f = cv::Matx33f::nrandu(mean,variance);
        成员访问:            m(i,j), m(i);

矩阵能正常的进行加减乘除:
        点积:                        float x = m1.dot(m2);
        双精度点积:                    double x = m1.ddot(m2);
        变换操作符:                    m44f = (Matx44f) m44d;
        提取(i, j)处的2*2子矩阵:    m44f.get_minor<2,2>(i, j);
        提取第i行或者j列:            m14f = m44f.row(i),m41f = m44f.col(j);
        提取矩阵的对角线:            m41f = m44f.diag();
        计算矩阵转置:                n44f = m44f.t();
        计算矩阵的逆:                n44f = m44f.inv(method);
        每个元素的乘法:                m1.mul(m2);


3.6、Vec类:cv::Vec()
6.1 Vec类支持的操作:
        默认构造函数:        cv::Vec2s v2s;
        复制构造函数:        cv::Vec3f u3f(v3f);
        值构造函数:            cv::Vec2f v2f(x0,x1);
        成员访问:            v4f[i]; v3w(j);        //[] 和()都是可以的
        向量叉乘:            v3f, cross(u3f);

3.7、Range类:cv::Range()
cv::Range类:用于确定一个连续的整数序列。

函数说明:cv::Range(int start,int end)
输入参数:
        start        起始点
        end            终止点
        
备注:左闭右开,与Python的range()类似。
举例:cv::Range rng(0, 4)        包含[0,1,2,3],但是不包含4。

4、随机数:cv::RNG
(1)cv::RNG rng(int seed);:使用随机数种子seed产生一个64位的随机整数,默认-1。

计算机产生的随机数都是伪随机数,是根据种子点seed和特定算法计算出来的。
只要种子数一定,算法一定,则每次产生的随机数也是一定的。
用系统时间做种子点
(2)cv::RNG rng((unsigned)time(NULL)); 用系统时间作为种子点,需添加头文件#include <time.h>。

4.1、生成一个随机数:cv::RNG::uniform() + cv::RNG::gaussian()
基于随机数的3种生成方法:

(1)cv::RNG rng(int seed)             使用随机数种子seed产生一个64位随机整数,默认-1。(2)cv::RNG::uniform(a, b):        返回一个[a,b)范围的均匀分布的随机数。a, b的数据类型要一致,而且必须是int、float、double中的一种,默认是int。
(3)cv::RNG::gaussian(σ):            返回一个均值为0,标准差为σ的随机数。若要产生均值为λ,标准差为σ的随机数:λ+RNG::gaussian(σ)
 

#include<opencv2\opencv.hpp>
#include <string>
//using namespace cv;
//using namespace std;

int main(int argc,char* argv[])
{
    cv::RNG rng(-1);
    int randomNumber1 = rng;
    double randomNumber2 = rng.uniform(0,99);          
    double randomNumber3 = rng.gaussian(2);            
    
    std::cout << "randomNumber1=" << randomNumber1 << std::endl;        //randomNumber=130063606
    std::cout << "randomNumber2=" << randomNumber2 << std::endl;        //randomNumber=14
    std::cout << "randomNumber3=" << randomNumber3 << std::endl;        //randomNumber=-1.40186
    return 0;
}

4.2、获取下一个随机数:next + operator
(1)    cv::RNG:: next             返回下一个64位随机整数。
(2)    cv::RNG:: operator         返回下一个指定类型的随机数。
 

#include<opencv2\opencv.hpp>
#include <string>
//using namespace cv;
//using namespace std;

int main(int argc,char* argv[])
{
    cv::RNG rng(-1);  
    int randomNumber = rng.next();        //返回下一个随机整数,即N1.next();  
     
    //返回下一个指定类型的随机数  
    int randomNumber1 = rng.operator uchar();         //返回下一个无符号字符数  
    int randomNumber2 = rng.operator schar();         //返回下一个有符号字符数  
    int randomNumber3 = rng.operator ushort();        //返回下一个无符号短型  
    int randomNumber4 = rng.operator short int();     //返回下一个短整型数  
    int randomNumber5 = rng.operator int();           //返回下一个整型数  
    int randomNumber6 = rng.operator unsigned int();  //返回下一个无符号整型数  
    int randomNumber7 = rng.operator float();         //返回下一个浮点数  
    int randomNumber8 = rng.operator double();        //返回下一个double型数  
    int randomNumber9 = rng.operator ()();            //和rng.next( )等价  
    int randomNumber10 = rng.operator ()(100);        //返回[0,100)范围内的随机数  
    
    std::cout << "randomNumber=" << randomNumber << std::endl;        //randomNumber=130063605
    std::cout << "randomNumber1=" << randomNumber1 << std::endl;        //randomNumber=156
    std::cout << "randomNumber2=" << randomNumber2 << std::endl;        //randomNumber=-116
    std::cout << "randomNumber3=" << randomNumber3 << std::endl;        //randomNumber=24389
    std::cout << "randomNumber4=" << randomNumber4 << std::endl;        //randomNumber=31943
    std::cout << "randomNumber5=" << randomNumber5 << std::endl;        //randomNumber=-1348951784
    std::cout << "randomNumber6=" << randomNumber6 << std::endl;        //randomNumber=94037301
    std::cout << "randomNumber7=" << randomNumber7 << std::endl;        //randomNumber=0
    std::cout << "randomNumber8=" << randomNumber8 << std::endl;        //randomNumber=0
    std::cout << "randomNumber9=" << randomNumber9 << std::endl;        //randomNumber=776868985
    std::cout << "randomNumber10=" << randomNumber10 << std::endl;        //randomNumber=94
    return 0;
}


4.3、用随机数填充矩阵:cv::RNG::fill()
函数说明:void fill( InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange=false );
输入参数:
            (1)mat             输入矩阵。2D或N维矩阵,最多支持4通道,超过4通道先用reshape()改变结构。
            (2)distType         分布类型。
                        cv::RNG::UNIFORM        均匀分布。
                        cv::RNG::NORMAL            高斯分布。
            (3)a                 第一分布参数。均匀分布时表示一个下边界(闭区间),正态分布时表示平均值。
            (4)b                 第二分布参数。均匀分布时表示一个上边界(开区间),正态分布时表示标准差。
            (5)saturateRange=false     只针对均匀分布有效。当为真的时候,会先把产生随机数的范围变换到数据类型的范围,再产生随机数。如果为假,会先产生随机数,再进行截断到数据类型的有效区间。
 

#include<opencv2\opencv.hpp>
#include <string>
//using namespace cv;
//using namespace std;

int main(int argc,char* argv[])
{
    cv::RNG rng(-1);
    
    cv::Mat_<int>fillM1(3, 3);            //新建3x3矩阵,int类型
    cv::Mat_<double>fillM2(3, 3);        //新建3x3矩阵,double类型

    rng.fill(fillM1, cv::RNG::UNIFORM, 1, 100);        //随机生成[1,100)均匀分布的int数,并填充fillM。
    rng.fill(fillM2, cv::RNG::NORMAL, 1, 3);        //随机生成均值为1,标准差为3的double数,并填fillN。
    
    std::cout << "filM = " << fillM1 << std::endl;  
    std::cout << "filN = " << fillM2 << std::endl;  
    
    return 0;
}


5、计算消费时间函数
5.1、耗时:cv::getTickCount()
函数说明:int64 cv::getTickCount();
函数作用:读取函数调用前后的时间刻度,来计算执行该函数所损耗的时间。
1
2
5.2、频率:cv::getTickFrequency()
函数说明:double cv::getTickFrequency();
函数作用:将损耗时间除以该函数(频率)以进行单位转换,返回时间的刻度数单位:秒。
1
2
5.3、实战案例

#include <opencv2/opencv.hpp>
//using namespace cv;
//using namespace std;

int main(int argc, const char* argv[]) 
{
    double count1 = cv::getTickCount();
    
    //处理
    //处理
    //处理
    
    double count2 = cv::getTickCount();
    double time_consume = (count2 - count1) / cv::getTickFrequency();
    std::cout << "耗时:" << time_consume << std::endl;
}


6、鼠标与轨迹条操作
【OpenCV】OpenCV基础教程(11)—— HighGUI图形用户界面

6.1、鼠标事件的回调函数:cv::MouseCallback
采用回调函数来处理鼠标事件。

创建一个回调函数
输入参数:触发事件、触发位置
备注:函数还需要被告知用户是否在触发鼠标事件的同时触发了Shift或者Alt等键。
#include <opencv2/highgui.hpp>
函数说明:typedef void(* cv::MouseCallback) (int event, int x, int y, int flags, void *userdata)
输入参数:
                11、event:鼠标事件
                                事件名称                        数值        说明
                                CV_EVENT_MOUSEMOVE            0            指示鼠标指针已在窗口上移动。
                                CV_EVENT_LBUTTONDOWN        1            表示按下了鼠标左键。
                                CV_EVENT_RBUTTONDOWN        2            表示按下了鼠标右键。
                                CV_EVENT_MBUTTONDOWN        3            表示按下了鼠标中键。
                                CV_EVENT_LBUTTONUP            4            表示释放了鼠标左键。
                                CV_EVENT_RBUTTONUP            5            表示释放了鼠标右键。
                                CV_EVENT_MBUTTONUP            6            表示释放了鼠标中键。
                                CV_EVENT_LBUTTONDBLCLK        7            表示双击鼠标左键。
                                CV_EVENT_RBUTTONDBLCLK        8            表示双击鼠标右键。
                                CV_EVENT_MBUTTONDBLCLK        9            表示双击鼠标中键。
                22、(x, y):触发鼠标事件的坐标位置
                33、flags:鼠标状态
                                标志名称                        数值        说明
                                CV_EVENT_FLAG_LBUTTON        1            表示鼠标左键已按下。
                                CV_EVENT_FLAG_RBUTTON        2            表示鼠标右键已按下。
                                CV_EVENT_FLAG_MBUTTON        4            表示鼠标中键已按下。
                                CV_EVENT_FLAG_CTRLKEY        8            表示按下了Ctrl键(8~15)。
                                CV_EVENT_FLAG_SHIFTKEY        16            表示按下了Shift键(16~31)。
                                CV_EVENT_FLAG_ALTKEY        32            表示按下了Alt键(32~39)。
                44、param:(可选参数)可以以任何结构方式传递额外的参数信息。


6.2、设置鼠标事件的回调函数:cv::setMouseCallback()
#include <opencv2/highgui.hpp>
函数说明:void cv::setMouseCallback(const String &winname, MouseCallback onMouse, void *userdata = (void *)0)
输入参数:
                winname            窗口的名称。
                onMouse            鼠标事件的回调函数。
                userdata        传递给回调的可选参数(默认0)。
1
2
3
4
5
6
6.3、创建轨迹条,并将其附加到指定的窗口:cv::createTrackbar()
创建一个具有指定名称和范围的轨迹条(滑块或范围控件),分配一个变量值作为与轨迹条同步的位置,并指定在轨迹条位置更改时调用的回调函数onChange。
创建的轨迹条将显示在指定的窗口winname中。
#include <opencv2/highgui.hpp>
函数说明:int cv::createTrackbar( const String &trackbarname, const String &winname, int *value, int count, TrackbarCallback onChange = 0, void *userdata = 0 )    
输入参数:
                trackbarname        创建轨迹条的名称。
                winname                作为轨迹条的父窗口的名称。
                value                指向整数变量的可选指针,该变量的值反映滑块的位置。创建时,滑块位置由该变量定义。
                count                滑块的最大位置。最小位置始终为0。
                onChange            指向每当滑块改变位置时要调用的函数的指针(默认0)。这个函数应该被原型化为void Foo(int,void*),其中第一个参数是轨迹条位置,第二个参数是用户数据(见下一个参数)。如果回调是NULL指针,则不会调用回调,但只更新值。
                userdata            按原样传递给回调的用户数据(默认0)。它可以在不使用全局变量的情况下用于处理轨迹条事件。

6.4、获取指定轨迹条的当前位置:cv::getTrackbarPos()
#include <opencv2/highgui.hpp>
函数说明:int cv::getTrackbarPos(const String &trackbarname, const String &winname)
输入参数:
                trackbarname        轨迹条的名称。
                winname                作为轨迹条的父窗口的名称。
                
备注:如果轨迹条连接到控制面板,winname可以为空。

6.5、设置指定轨迹条在指定窗口中的位置:cv::setTrackbarPos()
#include <opencv2/highgui.hpp>
函数说明:void cv::setTrackbarPos( const String &trackbarname, const String &winname, int pos )    
输入参数:
                trackbarname        轨迹条的名称。
                winname                作为轨迹条的父窗口的名称。
                pos                    新的位置。
                
备注:如果轨迹条连接到控制面板,winname可以为空。

文章知识点与官方知识档案匹配,可进一步学习相关知识
————————————————

                            版权声明:本文为博主原创文章,未经作者允许不得转载,且不得用于商业目的,否则将追究法律责任。
                        
原文链接:https://blog.csdn.net/shinuone/article/details/130835407

【图像处理OpenCV(C++版)】——2.1 深入理解OpenCV之Mat类及相关成员函数

前言:

😊😊😊欢迎来到本博客😊😊😊

🌟🌟🌟 本专栏主要结合OpenCV和C++来实现一些基本的图像处理算法并详细解释各参数含义,适用于平时学习、工作快速查询等,随时更新。

😊😊😊 具体食用方式:可以点击本专栏【OpenCV快速查找(更新中)】–>搜索你要查询的算子名称或相关知识点,或者通过这篇博客👉通俗易懂OpenCV(C++版)详细教程——OpenCV函数快速查找(不断更新中)]查阅你想知道的知识,即可食用。

🎁🎁🎁支持:如果觉得博主的文章还不错或者您用得到的话,可以悄悄关注一下博主哈,如果三连收藏支持就更好啦!这就是给予我最大的支持!😙😙😙

文章目录
学习目标
一、 什么是Mat类
二、 了解向量Vec类
三、 构造单、多通道Mat对象
3.1 构造单通道Mat对象
3.2 构造多通道Mat对象
四、 获取单、多通道Mat的信息
4.1 获取单通道Mat的信息
(1) 使用成员变量rows和cols获取矩阵的行数和列数
(2) 使用成员函数`size()`获取矩阵的尺寸
(3) 使用成员函数`channels()`得到矩阵的通道数
(4) 成员函数`total()`
(5) 成员变量`dims`
4.2 获取多通道Mat中某区域的值
(1) 使用成员函数row(i)或col(j)得到矩阵的第i行或者第j列
(2) 使用成员函数`rowRange`或`colRange`得到矩阵的连续行或者连续列
(3) 成员函数`clone()`和`copyTo()`
(4) 使用`Rect`类
五、 访问单、多通道Mat对象中的值
5.1 访问单通道Mat对象中的值
(1) 使用成员函数`at`
(2) 使用成员函数`ptr`
(3) 使用成员函数`isContinuous()`和`ptr`
(4) 使用成员变量`step`和`data`
5.2 访问多通道Mat对象中的值
(1) 使用成员函数`at`
(2) 使用成员函数`ptr`
(3) 使用成员函数`isContinuous()`和`ptr`
(4) 使用成员变量`step`和`data`
(5) 分离通道
(6) 合并通道
六、 总结
学习目标
本章内容较多,如有需要,耐心阅读。

 初识OpenCV中Mat类
 构造单、多通道Mat对象
 获取单、多通道Mat的信息
 访问单、多通道Mat对象中的值
一、 什么是Mat类
  Mat类(Matrix)是OpenCV中最核心的类,代表矩阵或者数组的意思,该类的声明在头文件<opencv2\core\core.hpp>中,当然直接声明 <opencv2/opencv.hpp>也可以,所以使用Mat类时要引入相关头文件。
  构造Mat对象相当于构造了一个矩阵(数组),需要四个基本要素:行数(高)、列数(宽)、通道数及其数据类型,Mat类的构造函数如下:

Mat(int rows,int cols,int type)
1
  其中,rows代表矩阵的行数,cols代表矩阵的列数,type代表类型,包括通道数及其数据类型,可以设置为:

类型    注释
<CV_8UC(n)    占1字节的uchar类型
CV_8SC(n)    占1字节的int类型
CV_16SC(n)    占2字节的int类型
CV_16UC(n)    占2字节的uchar类型
CV_32SC(n)    占4字节的int类型
CV_32FC(n)    占4字节的float类型
CV_64FC(n)    占8字节的doule类型
注:8U、8S、16S、16U、32S、32F、64F前面的数字代表Mat中每一个数值所占的bit数,1byte=8bit,C(n)代表通道数,当n=1时,即构造单通道矩阵或称二维矩阵,当n≠1时,即构造多通道矩阵即三维矩阵,直观上就是n个二维矩阵组成的三维矩阵。

  对于Mat构造函数也可以采用以下形式:

Mat(Size(int cols,int rows),int type);
1
  这里使用了OpenCV的Size类,这个类一般用来存储矩阵的列数和行数。需要注意的是:Size的第一个元素是矩阵的列数(宽),第二个元素是矩阵的行数(高),即先存宽,再存高。

  

二、 了解向量Vec类
  后面在介绍多通道Mat对象相关知识中,会涉及到OpenCV的向量类,这里先了解关于OpenCV中的向量类相关内容。
  这里的向量可以理解为我们数学学过的列向量,构造一个_cn×1的列向量,数据类型为_Tp,格式如下:

Vec<TypeName _Tp,int _cn>    
1
  构造一个长度为5,数据类型为int且初始化12,13,14,15,16的列向量:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int  main(int argc, char**argv) {

    Vec<int, 5> vv(12,13,14,15,16);
    cout << "vec列向量的行数为:" << vv.rows << endl;
    cout << "vec列向量的列数为:" << vv.cols << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

  如果我们想访问向量里的元素,则可以利用“[]”或者“()”操作符访问向量中的值:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int  main(int argc, char**argv) {

    Vec<int, 5> vv(12,13,14,15,16);
    cout << "vec列向量的行数为:" << vv.rows << endl;
    cout << "vec列向量的列数为:" << vv.cols << endl;
    cout << "访问第一个元素:" << vv[0] << endl;
    cout << "访问第二个元素:" << vv(1) << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  OpenCV为向量类的声明取了一个别名,例如:

typedef Vec<unchar,3> Vec3b;
typedef Vec<int,2> Vec2i;
typedef Vec<float,4> Vec4f;
typedef Vec<double,3> Vec3d;
1
2
3
4
  注:通道矩阵的每一个元素都是一个数值,多通道矩阵的每一个元素都可以看作一个向量。这是多通道Mat的基础,后面会在彩色图像的数字化详细介绍。

  

三、 构造单、多通道Mat对象
3.1 构造单通道Mat对象
  构造单通道Mat对象的方式有很多,不同的方式有各自特点及注意点,下面介绍各种构造方法。
  构造3行4列float类型的单通道矩阵,代码如下:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造3行4列的矩阵
    Mat m = Mat(3, 4, CV_32FC(1));

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
  也可以采用Size对象构造:

//借助Size对象
Mat m = Mat(Size(4, 3), CV_32FC(1));
1
2
注:这里的Size(4,3)是指4列3行。

  还可以使用Mat中的成员函数create完成Mat对象的构造,代码如下:

Mat m;
m.create(3, 4, CV_32FC(1));
//m.create(Size(4, 3), CV_32FC(1));
1
2
3
注:CV_32FC(1)和CV_32FC1的写法都可以。

  
  在平时操作过程中,常见的0矩阵、1矩阵能够更直接去构造,比如构造一个3行4列全1的float类型的单通道矩阵:

//构造全1的3行4列单通道矩阵
Mat one = Mat::ones(3, 4, CV_32FC(1));
Mat one = Mat::ones(Size(4, 3), CV_32FC(1));
1
2
3
  构造一个3行4列全0的float类型的单通道矩阵:

//构造全0的3行4列单通道矩阵
Mat zero = Mat::zeros(3, 4, CV_32FC(1));
Mat zero = Mat::zeros(Size(4, 3), CV_32FC(1));
1
2
3
  平时在测试一些小项目时,可以快速创建矩阵,那么就可以直接定义:

Mat m = (Mat_<int>(3, 4) << 1, 2, 3, 4, 5, 6);
1
  

3.2 构造多通道Mat对象
  构造一个由n个rows*cols二维浮点型矩阵组成的三维矩阵,具体如下:

Mat(int rows, int cols, CV_32FC(n))
1
  前面讲了当n=1时,就是单通道矩阵了。如果构造一个2行2列的float类型的三通道矩阵,代码如下:

//构造一个2行2列的float类型的三通道矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 22, 21), Vec3f(2, 12, 31), Vec3f(3, 13, 23), Vec3f(4, 24, 34));
1
2
注:这里的Vec3f(1, 22, 21)写法详见上述二、 了解向量Vec类。

  

四、 获取单、多通道Mat的信息
4.1 获取单通道Mat的信息
  下面详细介绍Mat的成员变量和成员函数,以便获得矩阵m的基本信息,以3行2列的二维矩阵为例:


(1) 使用成员变量rows和cols获取矩阵的行数和列数
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);

    //矩阵行数
    cout << "矩阵m的行数为:" << m.rows << endl;
    //矩阵列数
    cout << "矩阵m的列数为:" << m.cols << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  

(2) 使用成员函数size()获取矩阵的尺寸
  上述是通过成员函数获取矩阵行和列,我们还可以通过成员函数size()直接得到矩阵尺寸的Size对象,即矩阵大小:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);

    //矩阵行数
    cout << "矩阵m的行数为:" << m.rows << endl;
    //矩阵列数
    cout << "矩阵m的列数为:" << m.cols << endl;
    
    //矩阵大小
    cout << "矩阵m的大小为:" << m.size() << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

  

(3) 使用成员函数channels()得到矩阵的通道数
  可以通过成员函数channels()得到Mat的通道数:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);

    //矩阵行数
    cout << "矩阵m的行数为:" << m.rows << endl;
    //矩阵列数
    cout << "矩阵m的列数为:" << m.cols << endl;
    
    //矩阵大小
    cout << "矩阵m的大小为:" << m.size() << endl;
    
    //矩阵通道数
    cout << "矩阵m的通道数为:" << m.channels() << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

  

(4) 成员函数total()
  total()的返回值是矩阵的行数乘以列数,即面积。注意和通道数无关,返回的不是矩阵中数据的个数。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);

    //矩阵行数
    cout << "矩阵m的行数为:" << m.rows << endl;
    //矩阵列数
    cout << "矩阵m的列数为:" << m.cols << endl;
    
    //矩阵大小
    cout << "矩阵m的大小为:" << m.size() << endl;
    
    //矩阵通道数
    cout << "矩阵m的通道数为:" << m.channels() << endl;

    //面积
    cout << "矩阵m的面积为:" << m.total() << endl;
    
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

  

(5) 成员变量dims
   dims代表矩阵的维数,对于单通道矩阵来说就是一个二维矩阵,对于多通道矩阵来说就是一个三维矩阵。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);

    //矩阵行数
    cout << "矩阵m的行数为:" << m.rows << endl;
    //矩阵列数
    cout << "矩阵m的列数为:" << m.cols << endl;
    
    //矩阵大小
    cout << "矩阵m的大小为:" << m.size() << endl;
    
    //矩阵通道数
    cout << "矩阵m的通道数为:" << m.channels() << endl;

    //面积
    cout << "矩阵m的面积为:" << m.total() << endl;

    //矩阵维度
    cout << "矩阵m的维度为:" << m.dims << endl;
    
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

  

4.2 获取多通道Mat中某区域的值
(1) 使用成员函数row(i)或col(j)得到矩阵的第i行或者第j列
  对于单通道矩阵而言,获取某行或者某列的所有值很好理解,即:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
    int r = 1;
    int c = 0;

    Mat mr = m.row(r);
    Mat ml = m.col(c);


    cout <<"矩阵m第["<<r<<"]行为:" <<mr << endl;
    cout << "矩阵m第[" << c << "]列为:" << ml << endl;


    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

注意:返回值仍然是一个单通道的Mat类型。

  

(2) 使用成员函数rowRange或colRange得到矩阵的连续行或者连续列
  在学习该成员函数之前,先了解OpenCV中的Range类,该类用于构造连续的整数序列,构造函数如下:

Range(int _start, int _end);
1
注意:这是一个左闭右开的序列[_start,_end),比如Range(2,6)其实产生的是2、3、4、5的序列,不包括6,常用作rowRange和colRange的输入参数,从而访问矩阵中的连续行或者连续列。

  下面我们构造一个4x4的矩阵:


  访问mm的第2、3行:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    Mat mm_range = mm.rowRange(Range(2, 4)); //另一种写法:Mat mm_range = mm.rowRange(2, 4);

    for (int  r = 0; r <mm_range.rows; r++)
    {
        for (int c = 0; c < mm_range.cols; c++)
        {
            cout << mm_range.at<int>(r,c)<< ",";
        }
        cout << endl;
    }

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

  成员函数rowRange是一个重载函数,也可以直接将 Range 的_start 和_end 直接作为rowRange的输入参数,上述获取矩阵连续行的操作可以直接写为:

Mat mm_range = mm.rowRange(2, 4);
1
  同理,想获取矩阵第几列可以用colRange,例如:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    //Mat mm_range = mm.rowRange(Range(2, 4));  //另一种写法:Mat mm_range = mm.rowRange(2, 4);

    Mat mm_range = mm.colRange(Range(2, 4));  //另一种写法:Mat mm_range = mm.colRange(2, 4);

    for (int  r = 0; r <mm_range.rows; r++)
    {
        for (int c = 0; c < mm_range.cols; c++)
        {
            cout << mm_range.at<int>(r,c)<< ",";
        }
        cout << endl;
    }

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

  需要特别注意的是:成员函数rows、cols、rowRange、colRange返回的矩阵是指向原矩阵的,比如改变r_range的第1行第1列的值:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    //Mat mm_range = mm.rowRange(Range(2, 4));  //另一种写法:Mat mm_range = mm.rowRange(2, 4);

    Mat mm_range = mm.colRange(Range(2, 4));  //另一种写法:Mat mm_range = mm.colRange(2, 4);

    for (int  r = 0; r <mm_range.rows; r++)
    {
        for (int c = 0; c < mm_range.cols; c++)
        {
            cout << mm_range.at<int>(r,c)<< ",";
        }
        cout << endl;
    }
    cout <<"===================================="<< endl;

    //改变第1行第1列值
    mm_range.at<int>(1, 1) = 50;
    cout << "改变后的矩阵为:" << endl;
    cout << mm_range << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

   但是,我们只访问原矩阵的某些行或列,不想改变原矩阵的值,该如何做呢?

  

(3) 成员函数clone()和copyTo()
  对于刚刚遗留的问题,从函数名就可以看出,clone()和copyTo()用于将矩阵克隆或者复制一份。

  首先,关于clone()函数:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    //克隆函数
    Mat mm_range_clone = mm.rowRange(Range(2, 4)).clone();

    //改变mm_range_clone第1行第1列值
    mm_range_clone.at<int>(1, 1) = 50;
    cout << "改变后的矩阵为:" << endl;
    cout << mm_range_clone << endl;
    cout << "====================================" << endl;
    cout << "原矩阵为:" << endl;
    cout << mm << endl;
    
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

  将矩阵mm的第2、3行克隆一份,这时候改变mm_range_clone的值,mm中的值是不变的。
  
  接下来,类似操作,也可以使用copyTo()函数:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    //构造矩阵
    Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    //克隆函数
    Mat mm_range_clone = mm.rowRange(Range(2, 4)).clone();

    //改变mm_range_clone第1行第1列值
    mm_range_clone.at<int>(1, 1) = 50;
    cout << "改变后的矩阵为:" << endl;
    cout << mm_range_clone << endl;
    cout << "====================================" << endl;
    cout << "原矩阵为:" << endl;
    cout << mm << endl;
    
    cout << "*******************************************" << endl;
    
    //复制函数
    Mat mm_range_copy;
    mm.rowRange(Range(2, 4)).copyTo(mm_range_copy);

    //改变mm_range_copy第1行第1列值
    mm_range_copy.at<int>(1, 1) = 100;
    cout << "改变后的矩阵为:" << endl;
    cout << mm_range_copy << endl;
    cout << "====================================" << endl;
    cout << "原矩阵为:" << endl;
    cout << mm << endl;
    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

  将mm中的第2、3行复制到mm_range_copy中。
  

(4) 使用Rect类
  如果我们要获取矩阵中某一特定的矩形区域,根据上述方法可以先使用rowRange,再使用colRange来定位。
   OpenCV提供了一种更简单的方式,就是使用Rect类(Rect是Rectangle的缩写,矩形的意思)。构造一个矩形有多种方式:

条件    构造函数
知道左上角的坐标(x,y),还有矩形的宽度和高度    Rect(int_x,int_y,int_width,int_hight)
将_width和_height保存在一个Size中    Rect(int_x,int_y,Size size)
知道左上角和右下角的坐标也可以构造一个矩形    Rect(Point1,Point2) 注意范围为前闭后开,不含点Point2
   当然,还有其他的构造函数,这里不再一一列举,都差不多,掌握这三种也就够了。我们还以4x4的矩阵为例:


#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    //构造矩阵
    Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    Mat rec1 = mm(Rect(Point(1, 1), Point(3, 3)));//左上角坐标、右下角坐标
    Mat rec2 = mm(Rect(1,1,2,2));//左上角坐标、宽、高
    Mat rec3 = mm(Rect(Point(1, 1),Size(2,2)));//左上角坐标、尺寸
    cout << "rec1区域为:" << endl;
    cout << rec1 << endl;
    cout << "====================================" << endl;
    cout << "rec2区域为:" << endl;
    cout << rec2 << endl;
    cout << "====================================" << endl;
    cout << "rec3区域为:" << endl;
    cout << rec3 << endl;


    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

  这样得到的矩形区域是指向原矩阵的,要改变rec1中的值,mm也会发生变化,如果不想这样,则仍然可以使用clone()或者copyTo()。

五、 访问单、多通道Mat对象中的值
5.1 访问单通道Mat对象中的值
(1) 使用成员函数at
  访问Mat对象中的值,最直接的方式是使用Mat的成员函数at,如对于单通道且数据类型为CV_32F的3行2列的二维矩阵矩阵m:


  访问它的第r行第c列的值,格式为:m.at<Typename>(r,c)。我们还是以矩阵m为例,使用at访问它的值。对于矩阵m中的值依次存入下表中,按照行和列的索引取值即可:


  利用成员函数at依次访问m中的所有值并打印:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
    //构造矩阵
int main(int argc,char**argv){
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);

    for (int r = 0; r <m.rows; r++)
    {
        for (int c = 0; c < m.cols; c++)
        {
            cout << m.at<int>(r, c) << ",";
        }
        cout << endl;
    }

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

  
  除了使用成员函数at访问,还可以使用Point类和成员函数at来实现:即将代码m.at<Typename>(r,c)改为m.at<Typename>(Point(c,r)),把行和列的索引变为坐标的形式。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
    //构造矩阵
int main(int argc,char**argv){
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);

    for (int r = 0; r <m.rows; r++)
    {
        for (int c = 0; c < m.cols; c++)
        {
            //cout << m.at<int>(r, c) << ",";
            cout << m.at<int>(Point(c,r)) << ",";
        }
        cout << endl;
    }

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

  注:我们习惯表示矩阵的第r行第c列,也可以利用Point指明矩阵中某一个固定位置,那么该位置就用Point(c,r)来定义,注意第一个元素是列坐标(列号),第二个元素是行坐标(行号),符合我们将水平方向作为x轴,将垂直方向作为y轴的习惯,这里的y轴的方向是朝下的。

  

(2) 使用成员函数ptr
  Mat中的数值在内存中的存储,每一行的值是存储在连续的内存区域中的,我们可以通过成员函数ptr获得指向每一行首地址的指针。
   以矩阵m为例,m中所有的值在内存中的存储方式如下图所示,其中如果行与行之间的存储是有内存间隔的,那么间隔也是相等的。


#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
    //构造矩阵
int main(int argc,char**argv){
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
    
    /*成员函数ptr*/
    for (int r = 0; r <m.rows; r++)
    {    
        //获取矩阵m第r行行首地址
        const int  *ptr = m.ptr<int>(r);
        //打印第r行所有值
        for (int c = 0; c < m.cols; c++)
        {
            cout << ptr[c] << ",";
        }
        cout << endl;
    }
    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

  

(3) 使用成员函数isContinuous()和ptr
   从刚刚的图中可以一目了然,即每一行的所有值存储在连续的内存区域中,行与行之间可能会有间隔,
  如果isContinuous返回值为true,则代表行与行之间也是连续存储的,即所有的值都是连续存储的,如图下图所示。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
    //构造矩阵
    Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
    /*成员函数isContinuous*/
    if (m.isContinuous())
    {    
        cout << "isContinuous is: " << m.isContinuous()<< endl;
        //获取矩阵m第1个值的地址
        const int  *ptr = m.ptr<int>(0);
        for (int n = 0; n < m.rows * m.cols; n++)
        {
            cout << ptr[n] << ",";
        }
    }
    
    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

  

(4) 使用成员变量step和data
  从前面的讨论我们已经知道,Mat中的值在内存中存储分为两种情况,以矩阵m为例,如上述(2)和(3)所述。下面通过这两种情况,介绍两个重要的成员变量——step和data。

   如下图所示是行与行之间有相等的内存间隔的情况,即不连续:


   如下图所示是是连续的情况:


  如上图所示:对于单通道矩阵而言,step[0]代表每一行所占的字节数,而如果有间隔的话,这个间隔也作为字节数的一部分被计算在内; step[1]代表每一个数值所占的字节数;data是指向第一个数值的指针,类型为uchar。

   所以,无论哪一种情况,如访问一个int类型的单通到矩阵的第r行第c列的值,都可以通过以下代码来实现:

*((int*)(m.data+m.step[0]*r+c*m.step[1]))
1
  总结:如果是CV_32F类型,则将int替换成float即可。从取值效率上说,直接使用指针的形式取值是最快的,使用at是最慢的,但是可读性很高。

  

5.2 访问多通道Mat对象中的值
  访问多通道Mat对象中的值使用的成员函数与单通道几乎一致,不同的是多通道所表示的矩阵维度有差异,具体如下。

(1) 使用成员函数at
  成员函数at访问多通道Mat的元素值,可以将多通道Mat看作一个特殊的二维数组,即在每一个位置上不是一个数值而是一个向量(元素)。


  通过上图按行号、列号取出每一个元素Vec3f即可:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
   //构造矩阵
   Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));


   /* 成员函数at*/
   for (int r = 0; r <mm.rows; r++)
   {
       for (int c = 0; c < mm.cols; c++)
       {
           cout << mm.at<Vec3f>(r, c) << ",";
           //cout << m.at<Vec3f>(Point(c, r)) << ",";
       }
       cout << endl;
   }
   return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

  也可以使用cout << m.at<Vec3f>(Point(c, r)) << ",";替换cout << mm.at<Vec3f>(r, c) << ",";,具体注意点与单通道成员函数at一致。

  

(2) 使用成员函数ptr
  多通道Mat的数值在内存中也是按行存储的,且每一行存储在连续的内存区域中,如果行与行之间有内存间隔,这个间隔也是相等的,成员函数ptr可以返回指向指定行的第一个元素(注意不是第一个数值)的指针,如下图所示:


  使用ptr访问多通道矩阵的每一个元素:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
    //构造矩阵
    Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
    /*成员函数ptr*/
    for (int r = 0; r <mm.rows; r++)
    {
    //获取矩阵m第r行行首地址
    const Vec3f  *ptr = mm.ptr<Vec3f>(r);
    //打印第r行所有值
    for (int c = 0; c < mm.cols; c++)
    {
    cout << ptr[c] << ",";
    }
    cout << endl;
    }
    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

  

(3) 使用成员函数isContinuous()和ptr
  与单通道Mat对象类似,通过isContinuous判断整个Mat对象中的元素值是否存储在连续内存区域中,如果返回值是true,即表示是连续存储的,如下图所示:


#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
    //构造矩阵
    Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
    /*成员函数isContinuous*/
    if (m.isContinuous())
    {
        cout << "isContinuous is: " << mm.isContinuous() << endl;
        //指向多通道矩阵mm第1个元素的地址
        const Vec3f  *ptr = mm.ptr<Vec3f>(0);
        for (int n = 0; n < mm.rows * m.cols; n++)
        {
            cout << ptr[n] << ",";
        }
    }
    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

  如果只获取第r行第c列的元素值,那么就可以通过ptr[r*rows+c*cols]命令得到。

  

(4) 使用成员变量step和data
  单通道Mat类似,通过data和step获取多通道Mat的每一个元素。也是分两种情况:行与行之间有内存间隔和连续存储。

   如下图所示是行与行之间有相等的内存间隔的情况,即不连续,step[0]代表包含这个间隔的字节数:


  
   如下图所示是是连续的情况:


  使用data、step、ptr获得多通道矩阵的每一个元素:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
    //构造矩阵
    Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
    // 成员函数data step ptr
    for (int r = 0; r <mm.rows; r++)
    {
        for (int c = 0; c < mm.cols; c++)
        {
            Vec3f  *ptr = (Vec3f*)(mm.data + r*mm.step[0] + c*mm.step[1]);
            cout << *ptr << " ";
    }
        cout << endl;
    }
    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

  

(5) 分离通道
  对于多通道矩阵,可以将其分离为多个单通道矩阵,然后按照单通道矩阵的规则访问其中的值。
  如下图可以形象看出多通道矩阵分离成单通道矩阵的,即把所有向量的第一个值组成的单通道矩阵作为第一通道,将所有向量的第二元素组成的单通道矩阵作为第二通道,依次类推:


  OpenCV中提供了的split()函数来分离多通道,将多通道矩阵mm分离为多个单通道,这些单通道矩阵被存放在vector容器中:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
    //构造矩阵
    Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
    
    /* 成员函数split*/
    vector<Mat> SingleChannel;
    split(mm, SingleChannel);


    //获取每个通道的值
    for (int i = 0; i < SingleChannel.size(); i++)

    {
        cout << "第["<< i+1 <<"]通道:" << endl;
        cout << SingleChannel[i] << endl;
        cout << "==========" << endl;

    }

    return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

  

(6) 合并通道
  有分有合,merge()函数可以将多个单通道矩阵合并为一个三维矩阵。merge()函数声明如下:

void merge(const Mat * mMV, size_ t count, OutputArray dst)
1
  例如将三个2行2列的int类型的单通道矩阵合并为一个多通道矩阵:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
    Mat channel1 = (Mat_<int>(2, 2) << 11, 12, 13, 14);
    Mat channel2 = (Mat_<int>(2, 2) << 21, 22, 23, 24);
    Mat channel3 = (Mat_<int>(2, 2) << 31, 32, 33, 34);

    Mat multiChannel[] = {channel1, channel2, channel3};
    Mat MM;
    merge(multiChannel, 3, MM);
    cout << MM;

    return 0;


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

  上面代码将三个单通道矩阵存储在一个Mat数组中,当然也可以将其存储在vector容器中,使用merge的重载函数:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc,char**argv){
    Mat channel1 = (Mat_<int>(2, 2) << 11, 12, 13, 14);
    Mat channel2 = (Mat_<int>(2, 2) << 21, 22, 23, 24);
    Mat channel3 = (Mat_<int>(2, 2) << 31, 32, 33, 34);

    //Mat multiChannel[] = {channel1, channel2, channel3};
    vector<Mat> multiChannel;
    

    multiChannel.push_back(channel1);
    multiChannel.push_back(channel2);
    multiChannel.push_back(channel3);

    Mat MM;
    //merge(multiChannel, 3, MM);

    merge(multiChannel, MM);

    cout << MM;
    
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

六、 总结
  最后,长话短说,大家看完就好好动手实践一下,切记不能三分钟热度、三天打鱼,两天晒网。OpenCV是学习图像处理理论知识比较好的一个途径,大家也可以自己尝试写写博客,来记录大家平时学习的进度,可以和网上众多学者一起交流、探讨,有什么问题希望大家可以积极评论交流,我也会及时更新,来督促自己学习进度。希望大家觉得不错的可以点赞、关注、收藏。

🚶🚶🚶 今天的文章就到这里啦~
喜欢的话,点赞👍、收藏⭐️、关注💟哦 ~

文章知识点与官方知识档案匹配,可进一步学习相关知识
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_41225961/article/details/128196585

文章目录
学习目标
一、 常见矩阵运算
1.1 加法运算
1.2 减法运算
1.3 点乘运算
1.4 点除运算
1.5 乘法运算
二、 其他运算
2.1 指数和对数运算
2.2 幂指数和开平方运算
2.3 矩阵运算函数表(全)
三、 总结
学习目标
 学会加法运算
 学会减法运算
 学会点乘运算
 学会点除运算
 学会乘法运算
 其他运算介绍
一、 常见矩阵运算
  矩阵运算在学习其他课程(矩阵论、线性代数、高数)多少都有涉及。在数字图像处理中,可以理解为图像即多维矩阵,在图像处理中一般用到的关于矩阵的运算包括:加法、减法、点乘、点除、乘法等,下面我们一一介绍每个运算算子用法及注意点。

1.1 加法运算
  矩阵加法:两个矩阵对应位置的数值相加。例如:

  第一种方式:很简单,就如同数学加法。使用OpenCV重载的“+”运算符:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int  main(int argc, char**argv) {

    /*1.1 加法运算*/
    Mat m1 = (Mat_<uchar>(3, 2) << 11, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<uchar>(3, 2) << 12, 11, 22, 21, 32, 31);

    Mat dst = m1 + m2;
    cout << "加法运算结果为:" << dst << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  我们可以看到200+150应该等于350,得到得结果却是255。原因是两个矩阵的数据类型都是uchar,所以用“+”运算符计算出来的和也是uchar类型的,但是uchar类型范围的最大值是255,所以只好将350截断为255。
  但是,利用“+”运算符计算Mat的和还有一点需要特别注意:两个Mat的数据类型必须是一样的,否则会报错,也就是用“+”求和是比较严格的。

  同时,一个数值与一个Mat对象相加,也可以使用“+”运算符,但是无论这个数值是什么数据类型,返回的Mat的数据类型都与输入的Mat相同。比如一个数值与上面的uchar类型的m2相加,代码如下:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int  main(int argc, char**argv) {


    /*1.1 加法运算*/
    Mat m1 = (Mat_<uchar>(3, 2) << 200, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<uchar>(3, 2) << 150, 11, 22, 21, 32, 31);

    Mat dst = m1 + m2;
    cout << "加法运算结果为:" << dst << endl;


    float value = 50;
    Mat dst1 = value + m2;
    cout << "50+矩阵m2结果为:" << dst1 << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

注意:如果value是float类型的,但是因为m2是uchar类型的,所以返回的dst1也是uchar类型的,结果超过255都会截断为255。

  
  第二种方式:上面介绍中可以看出,第一种方式有一定得局限性,所以我们可以利用OpenCV得函数add(),结构如下:

void add(InputArray srcl, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1)
1
  案例如下:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    /*  函数add*/
    Mat m1 = (Mat_<uchar>(3, 2) << 200, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<float>(3, 2) << 150, 11, 22, 21, 32, 31);
    Mat dst;
    
    add(m1, m2, dst, Mat(), CV_64FC1);
    cout << "加法运算add()结果为:" << dst << endl;
    
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  案例中以看出,add()函数输入矩阵的数据类型可以不同,而输出矩阵的数据类型可以根据情况自行指定。
  需要特别注意的是:如果给dtype赋值为-1,则表示dst的数据类型和src1、src2是相同的,也就是只有当src1和src2的数据类型相同时,才有可能令dtype=-1,否则会报错。

  
  两个向量也可以做加法运算,比如:

    Vec3f v1 = Vec3f(1, 2, 3);
    Vec3f v2 = Vec3f(4,5,6);
    Vec3f dstV = v1 + v2;
    cout << dstV;
1
2
3
4
结果:

[5,7,9]
1
  

1.2 减法运算
  矩阵的减法与加法类似,但是也有几点注意点。
  第一种方式:也很简单,就如同数学减法。使用OpenCV重载的“-”运算符:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    /*1.2 减法运算*/

    /*   运算符 - */
    Mat m1 = (Mat_<uchar>(3, 2) << 200, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<uchar>(3, 2) << 150, 11, 22, 21, 32, 31);

    Mat dst = m1 - m2;
    cout << "减法运算结果为:" << dst << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  同样我们可以看到12-11应该等于-1,以此类推,得到得结果却是0。原因也是两个矩阵的数据类型都是uchar,所以用“-”运算符计算出来的和也是uchar类型的,但是uchar类型范围的最小值是0,所以只好将负数截断为0。

  
  第二种方式:和加法一样,Mat对象与一个数值相减,也可以使用“-”运算符。当然,也存在与“+”运算符一样的不足。所以OpenCV提供了subtract()函数,结构如下:

void subtract(InputArray srcl, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1)
1
  可以实现不同的数据类型的Mat之间做减法运算,与add()函数类似,这里不作详细叙述,可以利用上面代码进行修改即可。

  

1.3 点乘运算
  矩阵的点乘即两个矩阵对应位置的数值相乘。
  对于两个矩阵的点乘,可以利用Mat的成员函数mul(),两个Mat对象的数据类型必须相同才可以进行点乘,返回的矩阵的数据类型不变。我们依旧以m1和m2矩阵为例:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int  main(int argc, char**argv) {
    /*1.3 点乘运算*/
    
    Mat m1 = (Mat_<uchar>(3, 2) << 200, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<uchar>(3, 2) << 150, 11, 22, 21, 32, 31);
    Mat dst = m1.mul(m2);
    cout << "点乘运算结果为:" << dst << endl;
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

  结果可以看出,点乘后对大于255的数值做了截断处理。为了不损失精度,可以将两个矩阵设置为int、float等数值范围更大的数据类型。

  
  OpenCV提供了multiply()函数:

void multiply(InputArray srcl, InputArray src2, OutputArray dst, double scale=1, int dtype=-1)
1
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /*点乘运算multiply() */

    Mat m1 = (Mat_<uchar>(3, 2) << 200, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<float>(3, 2) << 150, 11, 22, 21, 32, 31);

    Mat dst;
    multiply(m1,m2,dst,1.0, CV_64FC1);
    cout << "multiply()点乘运算结果为:" << dst << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  multiply()函数使用起来更加灵活,矩阵m1和m2的数据类型不一定需要相同,可以通过参数dtype指定输出矩阵的数据类型。
  注意:第四个参数:scale(dst=sclae*m1*m2),即在点乘结果的基础上还可以再乘以系数scale。

  

1.4 点除运算
  点除运算与点乘运算类似,是两个矩阵对应位置的数值相除。

  第一种方式:与“+”和“-”类似,可以使用“/”运算符,两个Mat的数据类型相同且返回的Mat也是该数据类型。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /*点除运算  / */

    Mat m1 = (Mat_<uchar>(3, 2) << 200, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<uchar>(3, 2) << 150, 11, 22, 21, 32, 31);


    Mat dst = m1 / m2;
    cout << "/点除运算结果为:" << dst << endl;


    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  需要注意:如果分母为0的除法运算时,默认得到的值为0。
  同样,用一个数值与Mat对象相除也可以使用“/”运算符,且返回的Mat的数据类型与输入的Mat的数据类型相同,与输入数值的数据类型是没有关系的。

  
  第二种方式:使用“/”运算符和上面一样会存在一定局限性,OpenCV提供了divide()函数:

void divide(InputArray srcl, InputArray src2, OutputArray dst, double scale=1, int dtype=-1)
1
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /*点除运算 divide() */

    Mat m1 = (Mat_<uchar>(3, 2) << 200, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<float>(3, 2) << 150, 11, 22, 21, 32, 31);

    Mat dst;
    divide(m1, m2, dst, 1.0, CV_64FC1);
    cout << "divide()点除运算结果为:" << dst << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  同样注意:第四个参数:scale(dst=sclae*m1/m2),即在点除结果的基础上还可以再乘以系数scale。

  

1.5 乘法运算
  对于两个矩阵的乘法(矩阵乘法相关知识,线性代数等相关课程详细讲解了,有需要可以自行查阅相关资料,了解矩阵乘法运算规则),不同于上面得点乘运算。例如:

  使用'*'运算符对两个矩阵进行乘法运算:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /* 乘法运算*  */

    Mat m1 = (Mat_<float>(3, 2) << 11, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<float>(2, 3) << 11, 12, 13, 21, 22, 23);


    Mat dst = m1 * m2;
    cout << "*乘法运算结果为:" << dst << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

  对于Mat对象的乘法,需要注意两个Mat只能同时是float或者double类型,对于其他数据类型的矩阵做乘法会报错。

  上面介绍了两个float或者double类型的单通道矩阵的乘法。例如:

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /* 双通道乘法运算*/
    Mat s1 = (Mat_<Vec2f>(2, 1) << Vec2f(1, 2), Vec2f(3, 4));
    Mat s2 = (Mat_<Vec2f>(1, 2) << Vec2f(5,6), Vec2f(7, 8));

    Mat dst1 = s1 * s2;
    
    cout << "双通道*乘法运算结果为:" << endl;
    for (int i = 0; i < dst1.rows; i++)
    {
        for (int j = 0; j < dst1.cols; j++)
        {
            cout << dst1.at<Vec2f>(i, j) << " ";
        }
        cout << endl;
    }
    //cout << "双通道*乘法运算结果为:" << dst1 << endl;

    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

  奇怪的是:怎么会有负数,两个双通道矩阵也可相乘吗?
  实际上,其实这里是将s1和s2这两个双通道Mat对象当作了两个复数矩阵。第一通道存放的是所有值的实部,第二通道存放的是对应的每一个虚部。例如,Vec2f(1,2)可以看作1+2i。


  结果可以很明显地看出,将Vec2f看作了一个复数,例如输出的[-7,16]分别代表复数-7+16i的实部和虚部。

  
  关于矩阵乘法运算,OpenCV也提供了gemm()函数来实现。

void gemm(InputArray src1, InputArray src2, double alpha, InputArray src3, double beta, OutputArray dst, int flags=0 )
1
  src1:输入类型是CV_ 32F或者CV_64F的单或双通道矩阵;
  src2:输入矩阵,类型与src1一致;
  alpha:src1与src2相乘后得系数;
  src3:输入矩阵,类型与src1一致;
  beta:src3的系数;
  dst:输出矩阵;
  flags:标识符; 0:没有转置;GEMM_1_T:表示src1转置;GEMM_2_T:表示src2转置;GEMM_3_T:表示src3转置;即:


    flags可以组合使用,比如需要src2和src3都进行转置,则令flags=GEMM_2_T+GEMM_3_T。
    当输入矩阵src1、src2、src3是双通道矩阵时,代表是复数矩阵,其中第一通道存储实部,第二通道存储虚部。

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /* 乘法运算gemm() */
    Mat m1 = (Mat_<float>(3, 2) << 11, 12, 21, 22, 31, 32);
    Mat m2 = (Mat_<float>(2, 3) << 11, 12, 13, 21, 22, 23);
    Mat dst ;
    gemm(m1, m2, 1.0, NULL, 0, dst, 0);
    cout << "gemm乘法运算结果为:" << dst << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

  结果与'*'是等价的。需要注意的是:gemm()也只能接受CV_32FC1、CV_64FC1、CV_32FC2、CV_64FC2数据类型的Mat,这一点与使用'*'也是一样的。

二、 其他运算
2.1 指数和对数运算
  我们讨论的对数和指数运算是对矩阵中的每一个数值进行相应的运算。除此之外,我们也可以使用for循环对矩阵中的每一个数值进行相应的运算。
  针对指数和对数运算,OpenCV分别提供了exp()和log()函数(这里log是以e为底的)。
  需要注意的是:这两个函数的输入矩阵的数据类型只能是CV_32F或者CV_64F,否则会报错。

  (1) 指数运算

  指数运算函数exp()表示:e x e^xe 
x
 

exp(InputArray src1,OutputArray dst)
1
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /* 指数运算exp() */
    Mat m1 = (Mat_<float>(3, 2) << 11, 12, 21, 22, 31, 32);
    Mat dst;
    exp(m1, dst);
    cout << "指数运算exp()结果为:" << dst << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

  
  (2) 对数运算

  对数运算函数log()表示:log ⁡ e x \log_exlog 
e

 x=ln ⁡ x \ln xlnx

log(InputArray src1,OutputArray dst)
1
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /* 对数运算log() */
    Mat m1 = (Mat_<float>(3, 2) << 11, 12, 21, 22, 31, 32);
    
    Mat dst;
    log(m1, dst);
    cout << "对数运算log()结果为:" << dst << endl;
    

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

  注意:除了有log()运算(以e为底),也有以10、2为底等等对数函数。


2.2 幂指数和开平方运算
  同样,我们讨论的幂指数和开平方运算是对矩阵中的每一个数值进行相应的运算。也是可以使用for循环对矩阵中的每一个数值进行相应的运算,
  针对幂指数和开平方运算,OpenCV分别提供了pow()和sqrt()函数 。

  同样,需要注意的是:sqrt()的输入矩阵的数据类型只能是CV_32F或者CV_64F,而pow()的输入矩阵的数据类型不受限制,且输出矩阵与输入矩阵的数据类型相同。

  
  (1) 幂指数运算

  对数运算函数pow()表示:x n x^nx 
n
 ,以2次方为例:

pow(InputArray src1,scale,OutputArray dst)
1
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /* 幂指数运算pow()*/
    Mat m1 = (Mat_<float>(3, 2) << 11, 12, 21, 22, 31, 32);
    Mat dst;
    pow(m1,2,dst);

    cout << "幂指数运算pow()结果为:" << dst << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

  如果数据类型为Mat m1 = (Mat_<uchar>(3, 2) << 11, 12, 21, 22, 31, 32);,经过幂指数运算的dst的数据类型仍然是uchar,大于255的数值也会截断为255。

  
  (2) 开平方运算

  开平方运算函数sqrt()表示:x \sqrt x 
x

 

sqrt(InputArray src1,OutputArray dst)
1
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int  main(int argc, char**argv) {
    /* 开平方运算sqrt()*/

    Mat m1 = (Mat_<float>(3, 2) << 11, 12, 21, 22, 31, 32);
    Mat dst;
    sqrt(m1, dst);

    cout << "开平方运算sqrt()结果为:" << dst << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

  

2.3 矩阵运算函数表(全)
  关于矩阵的运算先了解这些最基本的函数即可,OpenCV还提供了很多矩阵运算函数,如取绝对值、求逆、取最大值函数,后面博客中会用到这些函数,到时我们再详细讨论,这边给出其他矩阵运算函数表(供参考,部分函数详细内容可查阅相关资料,或关注本专栏后面博客):

函数名    注释    备注
add()    矩阵加法    详见本章内容
scaleAdd()    矩阵加法,带有缩放因子    dst(I) = scale * src1(I) + src2(I)
subtract()    矩阵减法    详见本章内容
multiply()    矩阵点乘    矩阵逐元素乘法,同mul()函数,与A*B区别
gemm()    广义的矩阵乘法操作    详见本章内容
divide()    矩阵逐元素除法,与A/B区别    详见本章内容
abs()    对每个元素求绝对值    abs(InputArray src)
absdiff()    两个矩阵的差的绝对值    absdiff(InputArray src1,InputArray src2,OutputArray dst)
exp()    指数运算    详见本章内容
pow()    幂运算    详见本章内容
log()    对数运算    详见本章内容
sqrt()    开平方运算    详见本章内容
min, max()    每个元素的最小值或最大值返回这个矩阵 dst(I) = min(src1(I), src2(I)), max同    min(InputArray src1,InputArray src2)
minMaxLoc()    定位矩阵中最小值、最大值的位置    minMaxLoc( const Mat& src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, const Mat& mask=Mat() );
compare()    返回逐个元素比较结果的矩阵    ——
bitwise_and, bitwise_not, bitwise_or, bitwise_xor    每个元素进行位运算,分别是和、非、或、异或, max同    ——
randu    以Uniform分布产生随机数填充矩阵    同 RNG::fill(mat, RNG::UNIFORM))
randn    以Normal分布产生随机数填充矩阵    同 RNG::fill(mat, RNG::NORMAL))
theRNG()    随机打乱一个一维向量的元素顺序返回一个默认构造的RNG类的对象theRNG()::fill(…)    ——
reduce()    矩阵缩成向量    ——
repeat()    矩阵拷贝的时候指定按x/y方向重复    ——
split()    多通道矩阵分解成多个单通道矩阵    见上一章
merge()    多个单通道矩阵合成一个多通道矩阵    见上一章
mixChannels()    矩阵间通道拷贝    如Rgba[]到Rgb[]和Alpha[]
sort, sortIdx()    为矩阵的每行或每列元素排序    ——
setIdentity()    设置单元矩阵    ——
completeSymm()    矩阵上下三角拷贝    ——
inRange()    检查元素的取值范围是否在另两个矩阵的元素取值之间,返回验证矩阵    ——
sum()    求矩阵的元素和    ——
mean()    求均值    ——
meanStdDev()    均值和标准差    ——
countNonZero()    统计非零值个数    ——
cartToPolar, polarToCart()    笛卡尔坐标与极坐标之间的转换    ——
flip()    矩阵翻转    ——
transpose()    矩阵转置    ——
trace()    矩阵的迹    ——
determinant()    行列式    det(A)
eigen()    矩阵的特征值和特征向量    ——
invert()    矩阵的逆或者伪逆    比较 Mat::inv()
magnitude()    向量长度计算    dst(I) = sqrt(x(I)2 + y(I)2)
Mahalanobis()    Mahalanobis距离计算    ——
phase()    相位计算,即两个向量之间的夹角    ——
norm()    求范数,1-范数、2-范数、无穷范数    ——
normalize()    标准化    ——
mulTransposed()    矩阵和它自己的转置相乘 AT * A    dst = scale(src - delta)T(src - delta)
convertScaleAbs()    先缩放元素再取绝对值,最后转换格式为8bit型    ——
calcCovarMatrix()    计算协方差阵    ——
solve()    求解1个或多个线性系统或者求解最小平方问题(least-squares problem)    ——
solveCubic()    求解三次方程的根    ——
solvePoly()    求解多项式的实根和重根    ——
dct, idct()    正、逆离散余弦变换    idct同dct(src, dst, flags
dft, idft()    正、逆离散傅立叶变换    idft同dft(src, dst, flags
LUT()    查表变换    ——
getOptimalDFTSize()    返回一个优化过的DFT大小    ——
mulSpecturms()    两个傅立叶频谱间逐元素的乘法    ——
三、 总结
  最后,长话短说,大家看完就好好动手实践一下,切记不能三分钟热度、三天打鱼,两天晒网。OpenCV是学习图像处理理论知识比较好的一个途径,大家也可以自己尝试写写博客,来记录大家平时学习的进度,可以和网上众多学者一起交流、探讨,有什么问题希望大家可以积极评论交流,我也会及时更新,来督促自己学习进度。希望大家觉得不错的可以点赞、关注、收藏。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_41225961/article/details/128233476
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/u012294613/article/details/140160947

OpenCV-图像像素遍历操作的三种方法对比(程序提速)

场景需求
       使用OpenCV,避免不了的就是对图像像素进行操作,遍历操作更是家常便饭,当图像数据不多时,各类方法的速度差不太多;但面对数据计算次数非常多,尤其在进行迭代、拟合、递归等数据反复计算的工作时,此时不得不面对的就是程序性能、算法提速和优化的问题了。

       在OpenCV中有三种最常用的像素操作方法,分别是动态地址操作法、迭代器操作法和指针操作法。下面简单介绍下该三种方法的优劣和使用场景:

动态地址操作法。最常用,比如直接A.at,操作Mat矩阵A第i行第j列的像素值,简洁明了,适合对某个或某几个值直接操作时使用,定位非常方便,也可以遍历计算使用,但是速度方面比其他两个方法都要慢;
迭代器操作法。运用了C++中STL的理念,通过迭代器的方式,获取矩信息的头尾(begin和end),然后依次操作,该方法适合连续计算时使用,速度和动态地址操作法差不多,比指针法慢,但是胜在安全性好;
指针法。我最喜欢的遍历法,一遇到遍历操作必用ptr,该方法缺点就是指针容易越界,编代码时要慎重,确保安全和稳定,但是速度没得说。
       三种方法一般情况在debug下运行差异性很明显,release下没那么明显;但是一旦遍历的函数复杂性加大了,release下的差异性就会体现出来了,指针法绝对是最快的,我在做图像迭代拟合计算时,用指针法替代动态地址法,速度至少提高5-10倍,一点不夸张。。。

      下面简单写了段测试代码,展示下三种方法的使用方式,顺便测试下debug和release下的运行时间情况。

C++实现代码
// 动态地址操作法
for (int i = 0; i < A.rows; i++)
{    
    for (int j = 0; j < A.cols; j++)
    {
        P.at<float>(i, j) *= 2;
    }
}
 
// 迭代器操作法
Mat_<float>::iterator it = B1.begin<float>();
Mat_<float>::iterator itend = B1.end<float>();
for (; it!=itend; ++it)
{
    (*it)*= 2;
}
 
 
// 指针操作法
for (int i = 0; i < A.rows; i++)
{    
    float *data = B2.ptr<float>(i);
    for (int j = 0; j < A.cols; j++)
    {
        data[j]*= 2;
    }
}

测试代码
#include<iostream>
#include<opencv2/opencv.hpp>
#include<ctime>
using namespace std;
using namespace cv;
int main(void)
{
    Mat A = Mat::zeros(10000, 10000, CV_32FC1);
    // 随意创建一个A矩阵
    for (int i = 0; i < A.rows; i++)
    {
        for (int j = 0; j < A.cols; j++)
        {
            A.at<float>(i, j) = rand()%100/100.f;
        }
    }
    Mat B0,B1,B2;
    B0 = A.clone();
    B1 = A.clone();
    B2 = A.clone();
 
    // 动态地址操作法
    double time0 = static_cast<double>(getTickCount());
    for (int i = 0; i < A.rows; i++)
    {    
        for (int j = 0; j < A.cols; j++)
        {
            B0.at<float>(i, j) *= 2;
        }
    }
    time0 = ((double)getTickCount() - time0) / getTickFrequency();
    cout << "    动态地址法运行时间为:" << time0 << "秒" << endl << endl;
 
    // 迭代器操作法
    double time1 = static_cast<double>(getTickCount());
    Mat_<float>::iterator it = B1.begin<float>();
    Mat_<float>::iterator itend = B1.end<float>();
    for (; it!=itend; ++it)
    {
        (*it)*= 2;
    }
    time1 = ((double)getTickCount() - time1) / getTickFrequency();
    cout << "    迭代器法运行时间为:" << time1 << "秒" << endl << endl;
 
    // 指针操作法
    double time2 = static_cast<double>(getTickCount());
    for (int i = 0; i < A.rows; i++)
    {    
        float *data = B2.ptr<float>(i);
        for (int j = 0; j < A.cols; j++)
        {
            data[j]*= 2;
        }
    }
    time2 = ((double)getTickCount() - time2) / getTickFrequency();
    cout << "    指针法运行时间为:" << time2<< "秒" << endl << endl;
 
    system("pause");
    return 0;
}

测试效果
      release下:


图1 release下运行时间对比
      debug下:


图2 debug下运行时间对比
       综上所述,追求安全性就用迭代器,追求方便和便于定位就用动态地址at,追求极致速度就用ptr指针。

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/zhaitianbao/article/details/116268352

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值