学习OpenCV C++版

OpenCV C++

  • [1 数据载入、显示与保存](#1 数据载入、显示与保存)
    • [1.1 概念](#1.1 概念)
    • [1.2 Mat 类构造与赋值](#1.2 Mat 类构造与赋值)
    • [1.3 Mat 类的赋值](#1.3 Mat 类的赋值)
    • [1.4 Mat 类支持的运算](#1.4 Mat 类支持的运算)
    • [1.5 图像的读取与显示](#1.5 图像的读取与显示)
    • [1.6 视频加载与摄像头调用](#1.6 视频加载与摄像头调用)
    • [1.7 数据保存](#1.7 数据保存)

参考:《OpenCV4快速入门》作者冯 振 郭延宁 吕跃勇

1 数据载入、显示与保存

1.1 概念

  1. Mat 类 : Mat 类用来保存矩阵类型的数据信息。Mat 类分为 矩阵头和指向存储数据的矩阵指针两部分。矩阵头中包含矩阵的尺寸、存储方法、地址和引用次数等(eg:(h,w,c,a),jpg,0001,2 + p)。在 OpenCV 中复制和传递图像时,只是复制了矩阵头和指向存储数据的指针,而非存放矩阵数据,从而节省计算机资源。
  2. 创建 Mat类
C 复制代码
// 创建 Mat类
cv::Mat a;                   // 创建一个名为a的矩阵头
a = cv::imread('1.jpg');     // 向a中赋值图像数据,a的矩阵指针指向图像数据
cv::Mat b = a;               // 复制矩阵头,并命名为b

// 用Mat 类只存储矩阵头和矩阵指针的优点?
// 通过a,b可以修改图像内容吗?
// 删除a对b有影响吗?
// 
  1. Mat 类可以存储的数据类型:double、float、uchar、unsigned char。用模版制定Mat类存放数据类型。
C 复制代码
cv::Mat A = Mat_<double>(3,3); //创建一个3*3的矩阵用于存放double类型数据
cv::Mat B = Mat_<float>(640,640,256);
  1. 通过 OpenCV 数据类型创建 Mat 类
C 复制代码
// 1. OpenCV 中的数据类型与取值范围
CV_8U        8位无符号整数       0~255     [0~2**8]
CV_8S        8位符号整数         −128~127  [-2**7~2**7-1]
CV_16U       16位无符号整数      0~65535   [0~2**16]
CV_16S       16位符号整数        −32768~32767  [-2**15~2**15-1]
CV_32S       32位符号整数        −2147483648~2147483647  [-2**32~2**32-1]
CV_32F       32位浮点整数        -FLT_MAX~FLT_MAX, INF, NAN
CV_64F       64位浮点整数        -DBL_MAX~DBL_MAX, INF, NAN

// 2. OpenCV 还定义了通道 数标识,C1、C2、C3、C4、C512 分别表示单通道、双通道、3 通道和 4 通道

// 3. OpenCV 中对图像数据类型的完整定义,例如 CV_8UC1 表示的是 8 位单通道数据,用于表示 8 位灰度图,而 CV_8UC3 表示的是 8 位 3 通道数据,用于表示 8 位彩色图。
cv::Mat a(640,480,CV_8UC3); // 创建一个640*480的3通道矩阵用于存放彩色图像
cv::Mat b(3,3,CV_8UC1);     // 创建一个3*3的8位无符号整数的单通道矩阵
cv::Mat c(3,3,CV_8U);       // 创建单通道矩阵,C1标识可以省略
 
// 4. 错误
cv::Mat a = Mat_<CV_8U>(3,3);  // Mat_<>()模版只识别float、double、int等C++的数据类型
cv::Mat a(3,3,uchar);    // 据Mat类构造函数只能用只能用OpenCV 中的数据类型

1.2 Mat 类构造与赋值

  • 使用默认构造函数方式创建Mat
  • 根据输入矩阵尺寸和类型构造
  • 用 Size()结构构造 Mat 类
  • 利用已有矩阵构造 Mat 类
  • 构造已有 Mat 类的子类
  1. 使用默认构造函数方式创建Mat
C 复制代码
cv::Mat::Mat(int rows,
 					int cols,
					int type,
	                const Scalar &  s)
// cv::Mat::Mat();

cv::Mat a;
  1. 根据输入矩阵尺寸和类型构造
C 复制代码
cv::Mat::Mat( int rows,
        int cols,
        int type)
        
cv::Mat a(1280,1280,CV_32FC512);   
  1. 用 Size()结构构造 Mat 类
C 复制代码
cv::Mat::Mat(Size size(cols, rows),
              int  type
               )
               
cv::Mat a(Size(10,20),CV_8S3);      //构造一个行为640、列为480的3通道矩阵
cv::Mat b(Size(480,640),CV_32FC3);  //构造一个行为640、列为480的3通道矩阵
  1. 利用已有矩阵构造 Mat 类
C 复制代码
cv::Mat::Mat( const Mat &  m);

cv::Mat a(Size(10,20),CV_8S3); 
cv::Mat b(a);         // b复制了a的矩阵头,矩阵指针指向的是同一个地址
cv::Mat b=a;          // 同cv::Mat b(a)的效果一样
cv::Mat c=a.clone();  // c复制两个一模一样a,c更改数据,a不受影响。
  1. 构造已有 Mat 类的子类
    通过这种方式构造的 Mat 类与已
    有 Mat 类享有共同的数据,即如果两个 Mat 类中有一个数据发生更改,那么另一个也会随之更改。
C 复制代码
cv::Mat::Mat(const Mat & m,
    const Range &  rowRange,
    const Range &  colRange = Range::all() 
    )

cv::Mat a(Size(10,20),CV_8S3); 
cv::Mat b(a,Range(2,5),Range(2,5));      // 从a中截取部分数据构造b  cv::Mat c(a,Range(2,5));                 // 默认最后一个参数构造c

1.3 Mat 类的赋值

  • 在构造时赋值的方法
  • 枚举法赋值示例
  • 循环法赋值
  • 类方法赋值
  • 利用数组进行赋值示例
  1. 在构造时赋值的方法
C 复制代码
cv::Mat::Mat(int rows,
            int cols,
            int type,
            const Scalar &  s
            )
cv::Mat a(20,20,CV_8UC1,CV::Scalar(255,0,0));  //创建一个3通道矩阵,每个像素都是255,0,0
cv::Mat b(2,2,CV_8UC2,cv::Scalar(0,255)); //创建一个2通道矩阵,每个像素都是0,255
cv::Mat c(2, 2, CV_8UC1, cv::Scalar(255)); //创建一个单通道矩阵,每个像素都是 255

// Scalar 结构中变量的个数一定要与定义中的通道数相对应。如果 Scalar 结 构中变量的个数大于通道数,则位置在大于通道数之后的数值将不会被读取, 例如执行 a(2, 2, CV_8UC2, Scalar(0,0,255))后,每个像素值都将是(0,0),而 255 不会被读取;如果 Scalar 结构中变量的个数小于通道数,则会以 0 补充。
  1. 枚举法赋值
    这种赋值方式是将矩阵中所有的元素一一列举,并用数据流的形式赋值给 Mat 类。
    在采用枚举法时,输入的数据个数一定要与矩阵元素个数相同,否则会现报错。本方法常适用于矩阵数据比较少的情况。
C 复制代码
cv::Mat a=(Mat_<float>(2,4)<< 1.0,2.,3.,4.,5.,6.,7.,8.);
cv::Mat b=(Mat_<int>(2,2)<<2,4,9,16);
  1. 循环法赋值
    循环法赋值也是对矩阵中的每一个元素进行赋值,但是可以不在
    声明变量的时候进行赋值,而且可以对矩阵中的任意部分进行赋值。在给矩阵每个元素赋值的时候,赋值函数中声明的变量类型要与矩阵定义时的变量类型相同,
C 复制代码
cv::Mat a=Mat_<int>(2,3);
for (int i=0;i<a.rows;++i)
{
    for (int j=0;j<a.cols;++j)
    {
        c.at<int>(i,j)=i*j;
    }
}
  1. 类方法赋值
    在 Mat 类里,提供了可以快速赋值的方法,可以初始化指定的矩阵。
C 复制代码
cv::Mat a = cv::Mat::eye(3,3,CV_8Uc1);
cv::Mat b = (cv::Mat_<int>(1,3)<<1,2,3);
cv::Mat c = cv::Mat::diag(b);
cv::Mat d = cv::Mat::ones(3,3,cv_8UC1);
cv::Mat 3 = cv::Mat::zerons(2,3,cv_8UC3);
  1. 利用数组进行赋值示例

这种赋值方式首先将需要存入 Mat 类中的变量存入一个数组中,之后通过设置 Mat 类矩阵的 尺寸和通道数将数组变量拆分成矩阵,这种拆分方式可以自由定义矩阵的通道数。当矩阵中的元素 数目大于数组中的数据时,将用−1.073 741 8e+08 填充赋值给矩阵;当矩阵中元素的数目小于数组 中的数据时,将矩阵赋值完成后,数组中剩余数据将不再赋值。由数组赋值给矩阵的过程是首先将 矩阵中第一个元素的所有通道依次赋值,之后再赋值下一个元素。

C 复制代码
float a[8]={1,2,3,4,5,6,7,8};
cv::Mat b = cv::Mat(2,2,CV_8UC2,a);
cv::Mat c = cv::Mat(2,4,CV_8UC1,a);

cv::Mat d = cv::Mat(2,2,CV_8UC1,a);
cv::Mat e = cv::Mat(3,4,CV_8UC3,a);

1.4 Mat 类支持的运算

  1. Mat 类的加减乘除运算,当两个 Mat 类变量进行加减运算时,必须保证两个矩阵中的数据类型是相同的。
C 复制代码
cv::Mat a = (cv::Mat_<int>(3,3)<<1,2,3,4,5,6,7,8,9);
cv::Mat b = (cv::Mat_<int>(3,3)<<1,2,3,4,5,6,7,8,9);
cv::Mat c = (cv::Mat_<double>(3,3)<<1.,2.,3.,4.,5.,6.,7.,8.,9.);
cv::Mat d = (cv::Mat_<double>(3,3)<<1.1,2.2,3.2,4.4,5.5,6.6,7.7,8.8,9.9);
cv::Mat e,,f,g,h,i;
e = a+b; 
f = c-d;
g = 2*a;
h = d/2.3;
i = a-1;
  1. 两个 Mat 类矩阵的乘法运算
  • *:Mat 类中的数据类型必须是 CV_32FC1、 CV_64FC1、 CV_32FC2、 CV_64FC2 这 4 种中的一种。
  • dot():dot()方法 结果是一个 double 类型的变量,该运算的目的是求取一个行向量和一个列向量点乘。
  • mul():表示两个 Mat 类矩阵对应位的乘积。根据输出结果可以知道 mul() 方法运算结果同样是一个 Mat 类矩阵。
C 复制代码
cv::Mat j,m;
double x;
j = c*d;             // 矩阵乘法,c的列数必须等于d的行数;
double x= a.dot(b);  // 内积
m = a.mul(b);        // 
  1. Mat 类矩阵常用的属性
C 复制代码
'''
a[2,2,3]
[(1,2,3),(4,5,6)
(7,8,9),(10,11,12)]
'''
cv::Mat a(3,4,CV_32FC3);
a.cols         // 4
a.rows;        // 3
a.elemsize();  // 每个元素的字节数,32/8×channels()=12
a.step;        // 以字节为单位的矩阵的有效宽度,每个元素的字节数*cols=12*4=48,矩阵一行的字节数。
a.total;       // 矩阵中元素的个数 3*4*200
a.channels()   // 矩阵的通道数
  1. Mat 类元素的读取
  • 通过 at 方法读取 Mat 类矩阵中的元素
  • 通过指针 ptr 读取 Mat 类矩阵中的元素
  • 通过迭代器访问 Mat 类矩阵中的元素
  • 通过矩阵元素的地址定位方式访问元素

1)at 方法读取 Mat 类单通道矩阵元素。

单通道图像是一个二维矩阵,因此在 at 方法的最后给出二维平面坐标即可访问对应位置 元素

C 复制代码
cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9); 
int value = (int)a.at<uchar>(0, 0);

多通道矩阵每一个元素坐标处都是多个数据,因此引入一个变量用于表示同一元素的多个 数据。在 OpenCV 中,针对三通道矩阵,定义了 cv::Vec3b、cv::Vec3s、cv::Vec3w、cv::Vec3d、cv::Vec3f、 cv::Vec3i 共 6 种类型用于表示同一个元素的 3 个通道数据。b 是 uchar 类型的缩写、s 是 short 类型的缩写、w 是 ushort 类型的缩写、d 是 double 类型的缩写、f 是 float 类型的缩写、i 是 int 类型的缩写。OpenCV 也为二通道和四通道定义了对应的变量类型,其命名方式也遵循这个 命名规则,例如二通道和四通道的 uchar 类型分别用 cv::Vec2b 和 cv::Vec4b 表示。

2)at 方法读取 Mat 类多通道矩阵元素

C 复制代码
cv::Mat b(3, 4, CV_8UC3, cv::Scalar(0, 0, 1));
cv::Vec3b vc3 = b.at<cv::Vec3b>(0, 0);   // vc3的数据类型要与b的数据类型一致,Vec3b中的b 是 uchar 类型的缩写
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];

3)指针 ptr 读取 Mat 类矩阵元素

C 复制代码
cv::Mat b(3,4,CV_8UC3,cv::Scale(0,0,1));
for (int i=0;i<b.rows;++i)
{
    uchar* ptr = b.ptr<uchar>[i];
    for (int j=0;j<b.cols*b.channels;++j)
    {
        cout<<(int)ptr[j]<<endl;
    }
}

b.ptr<uchart>(2)[3]; // 第三行第4个数据

4)通过迭代器访问 Mat 类矩阵中的元素

C 复制代码
cv::Mat a(3,4,CV_8UC3,cv::Scale(0,0,1));
cv::MatIterator_<uchar> it = a.begin<uchar>();
cv::MatIterator_<uchar> it_end = a.end<uchar>();
for (int i=0;it != it_end;++it)
{
    cout<<(int)(*it)<<"";
    if((++i%a.cols)==0)
    {
        cout<<endl;
    }
}
  1. 通过矩阵元素的地址定位方式访问元素
C 复制代码
cv::Mat a(3,4,CV_8UC3,cv::Scale(0,0,1));
(int)(*(a.data+a.step[0]*row+a.step[1]*col+channle));

1.5 图像的读取与显示

C 复制代码
// imread()函数的原型
1. cv::Mat cv::imread(const String & filename,
2.                     int  flags=IMREAD_COLOR
3.                     )

// 图像窗口函数 namedWindow
1. void cv::namedWindow(const String & winname,
2.                       int  flags = WINDOW_AUTOSIZE
3.                       )

// 图像显示函数 imshow
1. void cv::imshow(const String & winname,
2.                  InputArray  mat
3.                  )

// 图像显示时常
cv::waitKey(0)

1.6 视频加载与摄像头调用

1)视频数据的读取

C 复制代码
cv :: VideoCapture :: VideoCapture(); //默认构造函数
cv :: VideoCapture :: VideoCapture(const String& filename,
                                    int apiPreference =CAP_ANY
                                    )
• filename:读取的视频文件或者图像序列名称。
• apiPreference:读取数据时设置的属性,例如编码格式、是否调用 OpenNI 等。

2)

VideoCapture.cpp 读取视频文件

C 复制代码
#include<opencv2\opencv.hpp>
include<iostream>

using namespace std;
using namespace cv;

int main()
{
    system("color F0");   //更改输出界面颜色
    VideoCapture video("cup.mp4");  // 实例化一个视频
    if(video.isOpened())   // 判断视频是否可以正确读取
    {   
        // 显示视频属性
        cout << "视频中图像的宽度=" << video.get(CAP_PROP_FRAME_WIDTH) << endl;
        cout << "视频中图像的高度=" << video.get(CAP_PROP_FRAME_HEIGHT) << endl;
        cout << "视频帧率=" << video.get(CAP_PROP_FPS) << endl;
        cout << "视频的总帧数=" << video.get(CAP_PROP_FRAME_COUNT);
    }
    else
    {
        cout << "请确认视频文件名称是否正确" << endl;
        return -1;
    }
    while(1)
    {
        Mat frame;
        video >> frame;   // 获取当前帧
        if(frame.empty())
        {
            break;
        }
        imshow("video",frame);
        waitKey(1000/video.get(CAP_PROP_FPS));
    }
    waitKey();
    return 0;
}

3)摄像头的直接调用

C 复制代码
cv :: VideoCapture :: VideoCapture(int index,
                                     int apiPreference = CAP_ANY
                                    )

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

using namespace std;
using namespace cv;

int main()
{
    system("color F0");   //更改输出界面颜色
    VideoCapture video(0);  // 实例化一个视频
    if(video.isOpened())   // 判断视频是否可以正确读取
    {   
        // 显示视频属性
        cout << "视频中图像的宽度=" << video.get(CAP_PROP_FRAME_WIDTH) << endl;
        cout << "视频中图像的高度=" << video.get(CAP_PROP_FRAME_HEIGHT) << endl;
        cout << "视频帧率=" << video.get(CAP_PROP_FPS) << endl;
        cout << "视频的总帧数=" << video.get(CAP_PROP_FRAME_COUNT);
    }
    else
    {
        cout << "请确认视频文件名称是否正确" << endl;
        return -1;
    }
    while(1)
    {
        Mat frame;
        video >> frame;   // 获取当前帧
        if(frame.empty())
        {
            break;
        }
        imshow("video",frame);
        waitKey(1000/video.get(CAP_PROP_FPS));
    }
    waitKey();
    return 0;
}

1.7 数据保存

OpenCV 提供 imwrite()函数用于将 Mat 类矩阵保存成图像文件

  1. 图像的保存
C 复制代码
1. bool cv :: imwrite(const String& filename,
2.                     InputArray img,
3.                     Const std::vector<int>& params =std::vector<int>()
4. )

• filename:保存图像的地址和文件名,包含图像格式。
• img:将要保存的 Mat 类矩阵变量。
• params:保存图片格式属性设置标志。
  1. imgWriter.cpp 保存图像
C 复制代码
#include <opencv2\opencv.hpp>
#include <iostream>

using  namespace std;
using  namespace cv;

void AlphaMat(Mat &mat)
{
    CV_Assert(mat.channels() == 4);
    for (int i = 0; i < mat.rows; ++i)
    {
        for (int j = 0; j < mat.cols; ++j)
        {
            Vec4b& bgra = mat.at<Vec4b>(i, j);
            bgra[0] = UCHAR_MAX; // 蓝色通道
            bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols)* UCHAR_MAX); // 绿色通道
            bgra[2] = saturate_cast<uchar>((float(mat.rows - i)) / ((float)mat.rows) * UCHAR_MAX); // 红色通道
            bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2])); // Alpha 通道
        }
    }
}

int main(int agrc, char** agrv)
{
    Mat mat(480, 640, CV_8UC4);
    AlphaMat(mat);
    // imwrite()函数中第三个参数设置方式
    vector<int> compression_params;
    compression_params.push_back(IMWRITE_PNG_COMPRESSION); //PNG格式图像压缩标志
    compression_params.push_back(9); //设置最高压缩质量
    bool result = imwrite("alpha.png", mat, compression_params);
    if (!result)
    {
        cout<< "保存成 PNG 格式图像失败" << endl;
        return -1;
    }
    cout << "保存成功" << endl;
    return 0;
}

3)视频的保存

C 复制代码
1. cv :: VideoWriter :: VideoWriter(); //默认构造函数
2. cv :: VideoWriter :: VideoWriter(const String& filename,
3.                                   int fourcc,
4.                                   double  fps,
5.                                   Size frameSize,
6.                                   bool  isColor=true
7. )
• filename:保存视频的地址和文件名,包含视频格式。
• fourcc:压缩帧的 4 字符编解码器代码,详细参数在表 2-7 中给出。
• fps:保存视频的帧率,即视频中每秒图像的张数。
• frameSize:视频帧的尺寸。
• isColor:保存视频是否为彩色视频。


// 保存视频文件
#include<opencv2\opencv.hpp>
include<iostream>

using namespace std;
using namespace cv;

int main()
{
    Mat img;
    VideoCapture video(0);  // 实例化一个视频
    if(!video.isOpened())   // 判断视频是否可以正确读取
    {   
        cout << "打开摄像头失败,请确认摄像头是否安装成功
        return -1;
    }
    else
    {
        cout << "请确认视频文件名称是否正确" << endl;
        return -1;
    }
    video >> img; //获取图像
    //检测是否成功获取图像
    if (img.empty())
    {
        cout << "没有获取到图像" << endl;
        return -1;
    }
    bool isColor = (img.type() == CV_8UC3); //判断相机(视频)类型是否为彩色
    
    VideoWriter writer;
    int codec = VideoWriter::fourcc('M', 'J', 'P', 'G'); // 选择编码格式
    double fps = 25.0; //设置视频帧率
    string filename = "live.avi"; //保存的视频文件名称
    writer.open(filename, codec, fps, img.size(), isColor); //创建保存视频文件的视频流
    if (!writer.isOpened()) //判断视频流是否创建成功
    {
        cout << "打开视频文件失败,请确认是否为合法输入" << endl;
        return -1;
    }
    
    
    while(1)
    {
        if (!video.read(img))
        {
            cout << "摄像头断开连接或者视频读取完成" << endl;
            break;
        }
        writer.write(img); //把图像写入视频流
        imshow("Live", img); //显示图像
        char c = waitKey(50);
        if (c == 27) //按"Esc"键退出视频保存
        {
            break;
        }
    return 0;
}
相关推荐
jndingxin1 小时前
OpenCV 图形API(27)图像滤波-----膨胀函数dilate()
opencv·计算机视觉
Chandler241 小时前
Go:方法
开发语言·c++·golang
whoarethenext4 小时前
qt的基本使用
开发语言·c++·后端·qt
_zsw5 小时前
Spring三级缓存学习
学习·spring·缓存
Amor风信子7 小时前
【大模型微调】如何解决llamaFactory微调效果与vllm部署效果不一致如何解决
人工智能·学习·vllm
虾球xz7 小时前
游戏引擎学习第220天
c++·学习·游戏引擎
愚润求学8 小时前
【C++】Stack && Queue && 仿函数
c++·stl·deque·queue·stack·priority queue
努力奋斗的小杨8 小时前
学习MySQL的第八天
数据库·笔记·学习·mysql·navicat
New个大鸭8 小时前
ATEngin开发记录_4_使用Premake5 自动化构建跨平台项目文件
c++·自动化·游戏引擎
空雲.9 小时前
牛客周赛88
数据结构·c++·算法