(9月)《opencv入门》读后整理

@《opencv入门》读后整理

《opencv入门》读后整理

打算从今天开始,每当我看完一本书之后,都将学到的知识或体会到的东西写在博客中。
今天要整理的是在opencv中文网上下载的深圳大学的老师所编写的opencv入门PDF版。
链接:https://pan.baidu.com/s/1TMNZzn6trZ4qmbM05CN-8g
提取码:8hm2

一预备知识
1.1编程的流程
一个编程的基本流程是编辑、编译和连接。编辑即编写代码,编译是将某语言源码z转换成目标文件(.obj),连接则是将多个目标文件,以及库文件生成可执行的文件(或静态库、动态库)(.exe)
在这里插入图片描述

1.2 头文件
考虑一个简单的小例子:程序中有两个函数 main()和 foo()。main()函数位于 main.cpp,foo()函数位于 foo.cpp,main()函数中调用 foo()函数。在编译阶段,由于编译是对单个文件进行编译,所以编译 main.cpp 时,编译器不知道是否存在 foo()函数以及 foo()调用是否正确,因此需要头文件辅助。
在这里插入图片描述
另外一种调用函数方法:在main.cpp中直接声明foo()函数,然后用g++一起编译main.cpp和foo.cpp,生成可执行文件。

1.3 库文件
foo.cpp 源文件中实现了 foo()函数,我们假设 foo()函数是包含重要算法的函数,我们需要将 foo()函数提供给客户使用,但是不希望客户看到算法源代码。为了达到这一目的,我们可以将 foo.cpp 编译程库文件,库文件是二进制的,在库文件中是看不到原始的源代码的。库和可执行文件的区别是,库不是独立程序,他们是向其他程序提供服务的代码。
在这里插入图片描述

二 图像的基本操作
2.1 图像的表示
一副尺寸为 M × N 的图像可以用一个 M × N 的矩阵来表示,矩阵元素的值表示这个位置上的像素的亮度,一般来说像素值越大表示该点越亮。一般来说,灰度图用二维j矩阵表示,彩色(多通道)图像用3维矩阵(M× N× 3)表示。
在这里插入图片描述
在这里插入图片描述
2.2 Mat类
早期的OpenCVs使用IplImage和CvMat数据结构来表示图像,两者都是c语言的结构,问题这两个结构的内存需要手动管理。而Mat类能够自动管理内存。

class CV_EXPORTS Mat
{
public:
//一系列函数
...
/* flag 参数中包含许多关于矩阵的信息,如:
-Mat 的标识
-数据是否连续
-深度
-通道数目
*/
int flags;
//矩阵的维数,取值应该大于或等于 2
int dims;
//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
int rows, cols;
//指向数据的指针
uchar* data;
//指向引用计数的指针
//如果数据是由用户分配的,则为 NULL
int* refcount;
24
//其他成员变量和成员函数
...
};

2.3 创建Mat对象
Mat是一个非常优秀的图像类,它同时也是一个通用的矩阵类,可以用来创建和操作多维矩阵。

2.3.1 构造函数方法
Mat M(3,2,CV_8UC3,Scalar(0,0,255));
cout<<"M= "<<endl<<M<<endl;
常用的构造函数:

  1. Mat::Mat() 无参数构造方法
  2. Mat::Mat(int rows,int cols,int type)
  3. Mat::Mat(Size size,int type)
  4. Mat::Mat(int rows,int cols,int type,const Scalar& s) 将所有像素初始化为s
  5. Mat::Mat(Size size,int type,const Scalar& s)
  6. Mat::Mat(const Mat& m) 将m复制给新建的对象,不会对图像数据进行复制,两者共用
  7. Mat::Mat(int rows,int cols,int type,void* data,size_t step=AUTO_STEP) 此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由step指定
  8. Mat::Mat(const Mat& m,const Range& rowRange,const Range& colRange) 创建的新图像为m的一部分,具体范围由Range指定,新图像与m共用图像数据
  9. Mat::Mat(const Mat& m,const Rect& roi) 创建的图像为m的一部分,具体范围由roi指定,两者共用图像数据

2.3.2 create()函数创建对象
Mat m(2,2,CV_8UC1);
m.create(2,3,CV_8UC3); //释放内存,重新创建图像
【注意】create()函数无法设置图像像素初始值

2.3.3 矩阵的基本元素表达
对于单通道图像,其元素一般为8U。
对于多通道图像,如RGB彩色图像,需要用三个通道来表示。在此情况下,将图像看作一个二维矩阵,则矩阵元素不再是基本的数据类型了。OpenCV中有模板类Vec,可以表示一个向量。

typedef Vec<uchar,2> Vec2b;
typedef Vec<uchar,3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;

对于Vec对象,可以使用[]符号如操作数组般读写q其元素:
Vec3b color;
color[0]=255; //B
color[1]=0; //G
color[2]=0; //R

2.5 像素值的读写
(1)需要读取某个像素值,或者设置某个像素值;
(2)需要对整个图像的所有像素进行遍历

2.5.1 at()函数
函数at()用来实现对某个像素的值读取或者设置。
Mat grayim;
uchar value=grayim.at(i,j); //读取像素值
grayim.at(i,j)=129; //赋值

例程

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

using namespace std;
using namespace cv;

int main(int argc,char* argv[])
{
    Mat grayim(600,800,CV_8UC1);
    Mat colorim(600,800,CV_8UC3);
    for (int i=0;i<grayim.rows;++i)
        for (int j=0;j<grayim.cols;++j)
            grayim.at<uchar>(i,j)=128;
    for (int i=0;i<colorim.rows;++i)
        for (int j=0;j<colorim.cols;++j)
        {
            Vec3b pixel;
            pixel[0]=255;
            pixel[1]=0;
            pixel[2]=0;
            colorim.at<Vec3b>(i,j)=pixel;
        }
    imshow("grayim",grayim);
    imshow("colorim",colorim);
    waitKey(0);
    return 0;
}

2.5.2 使用迭代器
使用迭代器,而不是行数和列数,来遍历像素。MatIterator_类

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

using namespace std;
using namespace cv;
int main(int argc,char* argv[])
{
    Mat grayim(600,800,CV_8UC1);
    Mat colorim(600,800,CV_8UC3);
    MatIterator_<uchar> grayit,grayend;
    MatIterator_<Vec3b> colorit,colorend;

    for (grayit=grayim.begin<uchar>(),grayend=grayim.end<uchar>();grayit!=grayend;++grayit)
        *grayit=rand()%255;
    for (colorit=colorim.begin<Vec3b>(),colorend=colorim.end<Vec3b>();colorit!=colorend;++colorit)
    {
        (*colorit)[0]=rand()%255;
        (*colorit)[1]=0;
        (*colorit)[2]=0;
    }
    imshow("grayim",grayim);
    imshow("colorim",colorim);
    waitKey(0);
    return 0;
}

2.5.3 通过数据指针访问像素
通过指针操作来访问像素是非常高效的,但是你务必十分地小心。C/C++中的指针操作是不进行类型以及越界检查的,如果指针访问出错,程序运行时有时候可能看上去一切正常,有时候却突然弹出“段错误”(segment fault)。

当程序规模较大,且逻辑复杂时,查找指针错误十分困难。对于不熟悉指针的编程者来说,指针就如同噩梦。如果你对指针使用没有自信,则不建议直接通过指针操作来访问像素。虽然 at()函数和迭代器也不能保证对像素访问进行充分的检查,但是总是比指针操作要可靠一些。

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

using namespace std;
using namespace cv;

int main(int argc,char* argv[])
{
    Mat grayim(600,800,CV_8UC1);
    Mat colorim(600,800,CV_8UC3);
    for (int i=0;i<grayim.rows;++i)
    {
        uchar *p=grayim.ptr<uchar>(i);
        for (int j=0;j<grayim.cols;++j)
            p[j]=128;
    }
    for (int i=0;i<colorim.rows;++i)
    {
        Vec3b *p=colorim.ptr<Vec3b>(i);
        for (int j=0;j<colorim.cols;++j)
        {
            p[j][0]=255;
            p[j][1]=0;
            p[j][2]=0;
        }
    }
    imshow("grayim",grayim);
    imshow("colorim",colorim);
    waitKey(0);
    return 0;
}

2.6 选取图像局部区域
Mat类提供了许多方法来选择图像的局部区域,使用这些方法时需要注意,这些方法不进行内存的复制操作。如果将局部区域赋值给新的Mat对象,新对象和原对象共用相同的数据区域,不再申请新的内存,因此这些方法速度都比较快。

2.6.1 单行或者单列选取
函数声明如下:
Mat Mat::row(int i) const
Mat Mat::col(int j) const

取出A矩阵的第i行可以使用:
Mat line=A.row(i);

2.6.2 用Range类选择多行和多列
Range 是 OpenCV 中新增的类,该类有两个关键变量 start和 end。Range 对象可以用来表示矩阵的多个连续的行或者多个连续的列。其表示的范围为从 start到 end,包含 start,但不包含 end。Range 类还提供了一个静态方法 all(),这个方法的作用如同 Matlab 中的“:”,表示所有的行或者所有的列。
//创建一个单位阵
Mat A=Mat::eye(10,10,CV_32s);
//提取第1到第3列(不包含3)
Mat B=A(Range::all(),Range(1,3));
//提取B的第5行到第9行(不含9)
//等价于C=A(Range(5,9),Range(1,3));
Mat C=B(Range(5,9),Range::all());

2.6.3 感兴趣区域(Region of interest,ROI)
(1)使用构造函数
Mat img(Size(320,240),CV_8UC3);
Mat roi(img,Rect(10,10,100,100));
(2) 使用括号运算符
Mat roi2=img(Rect(10,10,100,100));
(3)使用Range
//使用括号运算符
Mat roi3=img(Range(10,100),Range(10,100));
//使用构造函数
Mat roi4(img,Range(10,100),Range(10,100));

2.6.4 取对角线元素
使用Mat类的diag()函数获取,函数定义如下:
Mat Mat::diag(int d) const
[注]d=0,取主对角线;d>o,取主对角线下方的次对角线;d<0,取主对角线上方的次对角线。(不进行内存复制)

2.7 Mat表达式
如果A和B大小相同,可以使用:
C=A+B+1;
在这里插入图片描述
2.8 输出
Mat类重载了>>和<<运算符,所以可以方便地使用流操作来输出矩阵。例如:
Mat img(320,240,CV_8UC3);
randu(img,Range::all(0),Range::all(255));
cout<<"R= "<<endl<<R<<endl;

Python格式:
cout<<"R= "<<endl<<format(R,“python”)<<endl;
CSV:format(R,“csv”)
等等类似

3 数据获取和存储
3.1 读写图像文件
将图像文件存入内存,可以使用imread()函数;
将Mat对象以图像文件格式写入内存可以使用imwrite()函数。

3.1.1 读图像文件
imread()函数返回的是 Mat 对象,(Mat im=imread(“lena.jpg”,0);)如果读取文件失败,则会返回一个空矩阵,即 Mat::data 的值是 NULL。执行 imread()之后,需要检查文件是否成功读入,你可以使用 **Mat::empty()**函数进行检查。imread()函数的定义如下:

Mat imread(const string& filename,int flags)

很明显参数 filename 是被读取或者保存的图像文件名;在 imread()函数中,flag 参数值有三种情况:
 flag>0,该函数返回 3 通道图像,如果磁盘上的图像文件是单通道的灰
度图像,则会被强制转为 3 通道;
 flag=0,该函数返回单通道图像,如果磁盘的图像文件是多通道图像,则
会被强制转为单通道;
 flag<0,则函数不对图像进行通道转换。

3.1.2 写图像文件
imwrite()函数定义:
bool imwrite(const string& filename,InputArray image,const vector& params=vector())

下面例程展示了如何读入一副图像,然后对图像进行 Canny 边缘操作,最后将结果保存到图像文件中。

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

using namespace std;
using namespace cv;

int main(int argc,char* argv[])
{
    Mat im=imread("lena.jpg",0);
    if (im.empty())
    {
        cout<<"error"<<endl;
        return -1;
    }
    Mat result;
    Canny(im,result,50,100);
    imwrite("lena-canny.png",result);
    return 0;
}

3.2 读写视频
3.2.1 读视频
VideoCapture既可以从视频文件读取图像,也可以从摄像头读取图像,可以使用该类的构造函数打开视频文件h或者摄像头。
如果VideoCaptured对象已经创建,也可以使用VideoCapture::open()打开。

如果要读取一帧,可以使用VideoCapture::read()函数,VideoCapture类重载了>>操作符,实现帧读取的功能。

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

using namespace std;
using namespace cv;

int main(int argc,char* argv[])
{
//摄像头
    VideoCapture cap(0);
    //视频文件
   // VideoCapture cap("lalala.avi");
    if (!cap.isOpened())
    {
        cerr<<"wrong"<<endl;
        return -1;
    }

    Mat edges;
    namedWindow("edges",1);

    for (;;)
    {
        Mat frame;
        cap>>frame;
        if (frame.empty())
            break;
        cvtColor(frame,edges,CV_BGR2GRAY);
        Canny(edges,edges,30,70);
        imshow("edges",edges);
        waitKey(0);
    }
    return 0;
}

3.2.2 写视频
将图像写入视频可以使用 VideoWriter::write()函数,VideoWriter 类中也重载了<<操作符,使用起来非常方便。另外需要注意:待写入的图像尺寸必须与创建视频时指定的尺寸一致。
下面例程演示了如何写视频文件。本例程将生成一个视频文件,视频的第 0帧上是一个红色的“0”,第 1 帧上是个红色的“1”,以此类推,共 100 帧。

#include <iostream>
#include <stdio.h>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main(int argc,char** argv)
{
    Size s(320,240);
    //创建writer,并指定FOURCC及fps参数
    VideoWriter writer=VideoWriter("myvideo.avi",CV_FOURCC('M','J','P','G'),25,s);
    if(!writer.isOpened())
    {
        cerr<<"wrong"<<endl;
        return -1;
    }
    //视频帧
    Mat frame(s,CV_8UC3);
    for (int i=0;i<100;i++)
    {
    //将图像置为黑色
        frame=Scalar::all(0);
        //将整数i转为i字符串类型
        char text[128];
        snprintf(text,sizeof(text),"%d",i);
        //将数字绘到画面上
        putText(frame,text,Point(s.width/3,s.height/3),FONT_HERSHEY_SCRIPT_SIMPLEX,3,Scalar(0,0,255),3,8);
        writer<<frame;
    }
    return 0;
}

END~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值