本人正在学习 OpenCV 计算机视觉相关知识,此篇为个人学习笔记,参考书籍为冯振等人的《OpenCV4 快速入门》。主要记录 OpenCV 在 Linux 环境下的编译、运行实操流程与相关知识点总结。

笔记内容均为亲自实操验证,过程中踩过不少编译配置、运行参数的坑,整理出来一方面用于自己复盘巩固,另一方面也希望能帮助到刚入门 OpenCV、在编译运行示例程序时遇到问题的小伙伴。文中内容如有疏漏或错误之处,欢迎各位大佬评论区指正交流,共同进步~

!!层级标题编号与书中编号一致,以方便查阅对应书籍。

第二章 数据载入、显示与保存

2.1 图像读取容器

2.1.1 Mat类介绍

定义:用来存矩阵类型的数据信息,包括向量、矩阵、灰度或色彩图像等数据。

分类:矩阵头(包括矩阵尺寸/存储方法/地址和引用次数等)、矩阵指针(指向存储数据)

特点:矩阵头是常数,不会随矩阵尺寸的变化而变化。

使用:可以先创建矩阵头,再赋值数据

创建Mat类代码:

cv::Mat a;    #创建名为a的矩阵头
a = cv::imread("test.jpg")    #向a中赋值图像数据,矩阵指针指向像素数据
cv::Mat b=a;   #复制矩阵头,并命名为b

为了学习方便好找,这里创建专门学习文件夹

操作步骤:

mkdir opencv_learn   # 创建文件夹
cd opencv_learn     # 进入文件夹
gedit mat_demo.cpp   #创建文件并打开

Mat类可存储数据类型包括:double,float,uchar,unsigned char,自定义模块等

声明一个指定类型的Mat类:

cv::Mat A = Mat_<double>(3,3);   #创建一个3*3的矩阵用于存放double类型数据

C1,C2,C3,C4表示单通道、双通道、3通道、4通道(灰度图像数据是单通道,彩色数据是3通道或4通道) ,,,,单通道图像是一个二维矩阵,多通道矩阵每个元素坐标都是多个数据

CV_8UC1:表示8位单通道数据,表示8位灰度图

CV_8UC3:表示8位3通道数据,表示8位彩色图

CV_8UC(n):表示多通道矩阵,n<=512

创建声明通道数和数据类型的Mat类型:

cv::Mat a(640,480,CV_8UC3)   //创建一个640*480的3通道矩阵用于存放彩色图像
cv::Mat a(3,3,CV_8UC1)    //创建一个3*3的8位无符号整数单通道矩阵
cv::Mat a(3,3,CV_8U)   //同第二行

2.1.2 Mat类构造与赋值

一、构造
  1. 默认构造函数方式:不需要任何参数,后面变量赋值会自动判断矩阵的类型和大小,实现灵活的存储
cv::Mat::Mat();
  1. 根据输入矩阵尺寸和类型构造(好处:清晰直观。相机的内参矩阵/物体的旋转矩阵)

rows表示矩阵行数,cols表示矩阵列数,type表示存储的数据类型

cv::Mat::Mat(int rows,
        int cols,
        int type
)
  1. 用Size()构造Mat类

size:二维数组变量尺寸,通过Size(cols,rows)进行赋值

cv::Mat::Mat(Size size(),
            int type    
            )

示例:

cv::Mat a(Size(480,640), CV_8UC1);    //构造行640,列480的单通道矩阵
cv::Mat b(Size(480,640), CV_32FC3);   //构造行640,列480的3通道矩阵
  1. 利用已有构造函数构造

注意:只复制了已有函数的矩阵头,矩阵指针指向的是同一地址

cv::Mat::Mat( const Mat &  m);

示例:需要构造的矩阵类型比已有矩阵小,并且存储的是已有矩阵的子内容

m表示已经构建完成的Mat类矩阵数据

rowRange表示已有矩阵中需要截取的行数范围,不输入任何值时,表示所有行都被截取

cv::Mat::Mat(const Mat &  m,
             const  Range &  rowRange,
             const Range &  colRange = Range::all()
)

示例:在原图中截图使用,在原Mat类截取子Mat类

cv::Mat b(a, Range(2,5), Range(2,5));   //从a中截取部分数据构造b
cv::Mat c(a, Range(2,5));    //默认最后一个参数构造c
二、赋值(创建完后,变量里没有数据,需要进行赋值)
  1. 构造时赋值
cv::Mat::Mat(int rows,
        int cols,
        int type,
        const Scalar &  s
)

示例:Scalar结构中的变量的个数要与通道数对应

cv::Mat a(2,2,CV_8UC3, cv::Scalar(0,0,255)); //创建一个3通道矩阵,每个像素都是0,0,255
cv::Mat a(2,2,CV_8UC2, cv::Scalar(0,255)); //创建一个双通道矩阵,每个像素都是0,255
cv::Mat a(2,2,CV_8UC1, cv::Scalar(255)); //创建一个单通道矩阵,每个像素都是255
  1. 枚举法赋值:将所有元素一一列举,并用数据流形式赋值给Mat类。

示例:第一行:第一行存放1、2、3;第二行存放4、5、6;第三行存放7、8、9.

cv::Mat a = (cv::Mat_<int>(3,3) << 1,2,3,4,5,6,7,8,9);
cv::Mat b = (cv::Mat_<double>(2,3) << 1.0,2.1,3.2,4.0,5.1,6.2);
  1. 循环法赋值:对每一个元素进行赋值,但可以不在声明变量时赋值
cv::Mat c = cv::Mat_<int>(3,3);   //定义一个3*3矩阵
for (int i = 0; i< c.rows; i++)   //矩阵行数循环
{
    for (int j = 0; j<c.cols; j++)
    {
        c.at<int>(i,j) = i+j;
    }
}
  1. 类方法赋值:在Mat类里,提供了可以快速赋值的方法,可以初始化指定的矩阵。例如:生成单位矩阵/对角矩阵/全1矩阵/全0矩阵

  1. 数组赋值
float a[8] = { 5,6,7,8,1,2,3,4 };
cv::Mat b = cv::Mat(2,2,CV_32FC2, a);
cv::Mat c = cv::Mat(2,4,CV_32FC1, a);

将Mat类中的变量存入一个数组中,之后设置类矩阵的尺寸和通道数将数组变量拆分成矩阵

2.1.3 Mat类支持的运算

在加减运算中,要保证两个矩阵的数据类型是相同的

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.0,2.1,3.2,4.0,5.1,6.2,2,2,2);
cv::Mat d = (cv::Mat_<double>(3,3) <<1.0,2.1,3.2,4.0,5.1,6.2,2,2,2);
cv::Mat e,f,g,h,i;
e=a+b;
f=c-d;
g=2*a;
h=d/2.0;
i=a-1;

cv::Mat j,m;
double k;
j=c*d;
k=a.dot(b);    //两个矩阵的内积
m=a.mul(b);    //两个矩阵的乘积,注意数据溢出问题

图像处理中,常用的数据类型是CV_8U,范围是0-255

2.1.4 Mat类元素的读取

先存储第一个元素的每个通道数据,再存储第二个元素的每个通道数据

  1. at方法读取Mat类矩阵中的元素

需要at后+数据类型,,,当数据类型是uchar,要强制转换成int类型进行输出,否则输出不为整数。

//读取单通道矩阵元素
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);

三通道矩阵表示方法:(6种),,,,,,2通道把“3”变“2”

cv::Vec3b、cv::Vec3s、cv::Vec3w、cv::Vec3d、cv::Vec3f、cv::Vec3i

b是uchar ,s是short, w是ushort, d是double,f是float, i是int

//at方法读取Mat类多通道矩阵元素
cv::Mat b(3,4, CV_8UC3,cv::Scalar(0,0,1));
cv::Vec3b vc3 = b.at<cv::Vec3b>(0,0);
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];
  1. 通过指针ptr读取

找到每一行元素的起始地址位置,读取矩阵每一行不同位置的元素时将指针在起始位置向后移动即可。

cv::Mat b(3,4, CV_8UC3,cv::Scalar(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;
    }
}
  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. 通过矩阵元素的地址访问

直接给出行、列和通道数进行读取

(int)(*(b.data + b.step[0] *row + b.step[1] *col +channel));

2.2 图像的读取与显示

2.2.1 图像读取函数imread

imread原型:

用于读取指定的图像并返回给一个Mat类变量,可通过data属性返回值为0或者empty()返回值为1,说明图像读取失败。

filename:需要读取的图像文件名称,有图像地址、名称和文件拓展名

flags:读取图像形式的标志,如彩色图像按照灰度图读取

cv::Mat cv::imread(const String &  filename,
                    int flags-IMREAD_COLOR
                    )

标志参数不冲突下,可声明多个,中间用“|”隔开

2.2.2 图像窗口函数namedWindow

一般会自动生成,但如果添加内存条,此时就需要创建图像窗口

namedWindow()函数原型:

winname:窗口名称,用作窗口的标识符

flags:窗口属性设置标志 ,设置窗口大小、图像是否铺满窗口等

两个关闭窗口的函数cv::destoryWindow() 『用于关闭指定名称窗口』和cv :: destoryAllWindows() 『关闭所有窗口』 但不!!需要

void cv::namedWindow(const String &  winname,
                        int flags = WINDOW_AUTOSIZE
                     )

一般用“打钩”的这几个组合,用“|”隔开

2.2.3图像显示函数imshow:在指定窗口中显示图像

函数原型:

winname:显示图像窗口的名字

mat:显示图像的矩阵

InputArray:用作输入参数标识

规则:如果没有创建窗口,则创建一个窗口,显示原始图像的大小;

如果创建,就会缩放图像适应窗口属性:

如果图像是8位无符号类型,原图显示;

如果是16位无符号类型或者32位整数类型,会将像素处以256,将范围由[0,255*256]映射到【0,255】

如果是32位或64位浮点类型,将像素乘以255,将范围由[0,1]映射到[0,255]

void cv::imshow(const String &  winname,
                InputArray mat
                )

2.3 视频加载与摄像头调用

2.3.1 视频数据读取VideoCapture()

读取视频文件ViedoCapture类构造函数:读取和处理视频文件的视频流

filename:读取的视频文件

apiPreference:数据读取时设置的属性,如编码格式/是否调用OpenNI等

是否读取成功显示:isOpened()函数,成功返回true,失败返回false

是否读取完毕:empty()函数

查看视频属性:get()函数

cv::VideoCapture :: VideoCapture();    //默认构造函数
cv::VideoCapture :: VideoCapture(const String &  filename,
                                int apiPreference =CAP_ANY
                                 )

示例:读取视频文件

#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;
}

2.3.2 摄像头的直接调用

第一个参数是打开摄像头设备的ID,

cv :: VideoCapture :: VidEoCapture(int index,
                                    int apiPreference = CAP_ANY
                                    )

2.4 数据保存

2.4.1 图像保存imwrite()函数

imwrite()函数原型:

img:将要保存的Mat类矩阵变量

params:保存图片格式属性设置标志

如果保存成功返回true,失败返回false

通道对应保存格式:16位无符号------PNG/JPEG/TIFF

32位浮点---------PFM/TIFF/OpenEXR/Radiance HDR

4通道-------PNG

注意:第三个参数一般不写,当保存的数据比较特殊(16位深度数据),则需要设置第三个参数,包含第三个参数可选的标志和作用

bool cv::imwrite(const String& filename,
                InputArray img,
                Const std::vector<int>& params = std::vector<int>()
                )

示例:保存图像

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

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)
{
    // Create mat with alpha channel
    Mat mat(480, 640, CV_8UC4);
    AlphaMat(mat);
    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 << "保存成图像格式失败" << endl;
        return -1;
    }
    cout << "保存成功" << endl;
    return 0;
}

2.4.2 视频的保存

视频构造函数:

fourcc:压缩帧的4字符编解码器代码,,,赋值“-1”,会自动搜索合适的编解码器

fps:保存视频的帧率,实现原倍速还是2倍速等

frameSize:视频帧的尺寸,需要与图像尺寸一样

isColor:保存视频是否为彩色视频,默认为彩色

同样可以使用isOpened函数和get函数

cv:: VideoWriter();
cv:: VideoWriter()::VideoWriter(const String& filename,
                                int fourcc,
                                double fps,
                                Size frameSize,
                                bool is Color=true
                                        )

示例:

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

using namespace cv;
using namespace std;

int main()
{
    Mat img;
    VideoCapture video(0);  //使用某个摄像头

    //读取视频
    //VideoCapture video;
    //video.open("cup.mp4");  

    if (!video.isOpened())  // 判断是否调用成功
    {
        cout << "打开摄像头失败,请确实摄像头是否安装成功";
        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');  // 选择编码格式
    //OpenCV 4.0版本设置编码格式
    //int codec = CV_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);  //把图像写入视频流
        //writer << img;
        imshow("Live", img);  //显示图像
        char c = waitKey(50);
        if (c == 27)  //按ESC案件退出视频保存
        {
            break;
        }
    }
    // 退出程序时刻自动关闭视频流
    //video.release();
    //writer.release();    
    return 0;
}

2.4.3 保存和读取xml和ymal文件:FileStorage类

  • XML是元标记语言,使用者可以根据自身需求定义自己的标记

例:表示age值为24: <age>24</age>

表示color含有red的100和blue的150数据:<color><red>100</red><blue>150</blue></color>

  • ymal文件是以数据为中心的语言“变量:数值”来表示

encoding:编码格式,支持UTF-8 XML

可以使用isOpened()和open()函数和write()函数『将不同类型的变量名称写入文件』

例:表示age值为24:file<<"age"<<24 ,,,,,表示隶属关系用“{}”

cv::FileStorage::FileStorage(const String &  filename,
                             int flags,
                             const String &  encoding = String()
                             )

2.5 总结

至此,第二章就结束了,望各位读者“一键三连”,再次感谢读到这里的各位读者。有任何问题欢迎评论区留言或者私信。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐