ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

OpenCV基础教程(1)—— 图像数字化

2021-11-09 20:59:32  阅读:207  来源: 互联网

标签:数字化 Mat int dst 矩阵 OpenCV src1 基础教程 cv


1. 图像数字化

1.1 初始OpenCV中的Mat类

1.1.1 初始Mat

Mat类的构造函数如下:

Mat(int rows, int cols, int type)

其中,type代表类型,包括通道数及其数据类型。

CV_8UC(n):占1字节的uchar类型

CV_8SC(n):占1字节的char类型

CV_16UC(n):占2字节的ushort类型

CV_16SC(n):占2字节的short类型

CV_32SC(n):占4字节的int类型

CV_32FC(n):占4字节的float类型

CV_64FC(n):占8字节的double类型

S–代表–signed int-----有符号整形

U–代表–unsigned int-----无符号整形

F–代表–float-----单精度浮点型


C(n)代表通道数,这里括号可省略。

n=1:单通道矩阵/二维矩阵/灰度图片

n=3:三通道矩阵/三维矩阵/RGB彩色图片

n=4:四通道矩阵/三维矩阵/带Alph通道的RGB图片


Mat的构造函数也可采用如下形式:

Mat(Size(int cols, int rows), int type)

其中使用了OpenCV的Size类。需要注意的是,Size的顺序是列×行。

1.1.2 构造单通道Mat对象

Mat对象的构造有如下三种方法:

//构造2行3列的矩阵
Mat m = Mat(2, 3, CV_32FC1);
//也可以直接借助Size对象
Mat m = Mat(Size(3, 2), CV_32FC1);
//也可以使用Mat中的成员函数create完成Mat对象的构造
Mat m;
m.create(2, 3, CV_32FC1);
m.create(Size(3, 2), CV_32FC1);

0矩阵的构造方式

Mat o = Mat::zeros(2, 3, CV_32FC1);
Mat o = Mat::zeros(Size(3, 2), CV_32FC1);

1矩阵的构造方式

Mat m = Mat::ones(2, 3, CV_32FC1);
Mat m = Mat::ones(Size(3, 2), CV_32FC1);

快速创建矩阵的方式

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

1.1.3 获得单通道Mat的基本信息

Mat m = (Mat_<int>(2,3) << 1,2,3,4,5,6);
//获取矩阵的行数
cout << m.rows << endl;
//获取矩阵的列数
cout << m.cols << endl;
//获取矩阵的尺寸
Size size = m.size();
cout << size << endl;	//[2×3]
//获取矩阵的通道数
cout << m.channels() << endl;
//获取矩阵的面积(矩阵的行数乘以列数)
cout << m.total() << endl;
//获取矩阵的维数(单通道矩阵维数为2,多通道矩阵维数为3)
cout << m.dims << endl;

1.1.4 访问单通道Mat对象中的值

1. 利用成员函数at
//格式
m.at<float>(r, c);	//访问第r行第c列的值
2. 利用成员函数ptr

对于Mat中的数值在内存中的存储,每一行的值是存储在连续的内存区域中的,通过成员函数ptr获得指向每一行首地址的指针。

可以利用成员函数ptr返回的指针访问m中的值。

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;
}
3. 利用成员函数isContinuous和ptr

对于Mat中的数值在内存中的存储,每一行的值是存储在连续的内存区域中的,但是行与行之间可能有间隔。

如果isContinuous返回值为true,则代表行与行之间也是连续存储的,即所有的值都是连续存储的。用法如下:

if(m.isContinuous())
{
    //得到矩阵m的第一个值的地址
    int * ptr = m.ptr<int>(0);
    //利用操作符[]取值
    for(int n = 0; n < m.rows * m.cols; n++)
        cout << ptr[n] << ",";
}
4. 利用成员变量step和data

step[0]:表示每一行所占的字节数(如果行与行之间有间隔的话,包括间隔)

step[1]:表示每一个数值所占的字节数

data:表示指向第一个数值的指针

//访问一个int类型的单通道矩阵的第r行第c列的值
*((int *)(m.data + m.step[0] * r + m.step[1] * c));
总结

从取值效率上说,直接使用指针的形式取值是最快的,使用at是最慢的;

从可读性上说,使用at是最高的,直接使用指针的形式取值是最低的。

1.1.5 向量类Vec

这里的向量可以理解为数学意义上的列向量

//构造一个_cn×1的列向量,数据类型为_Tp
Vec<Typename _Tp, int _cn>;
//构造一个3×1的列向量,数据类型为int,初始化为21、32、14
Vec<int, 3> vi(21, 32, 14);

利用[]或者()都可以访问向量中的值

cout << vi[0] << endl;
cout << vi(1) << endl;

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

typedef Vec<uchar, 3> Vec3b;
typedef Vec<int, 2> Vec2i;
typedef Vec<float, 4> Vec4f;
typedef Vec<double, 3> Vec3d;
...
//具体可查看opencv2/core/core.hpp

1.1.6 构造多通道Mat对象

//构造一个2行2列的float类型的三通道矩阵
Mat mm = (Mat_<Vec3f>(2,2)<<Vec3f(1,11,21), Vec3f(2,12,32), Vec3f(3,13,23), Vec3f(4,24,34));

1.1.7 访问多通道Mat对象中的值

1. 利用成员函数at
//获得第r行第c列的元素值
cout << mm.at<Vec3f>(r,c) << ",";	//[1,11,21],[2,12,32],[3,13,23],[4,24,34]
2. 利用成员函数ptr
for(int r = 0; r < mm.rows; r++)
{
    //得到矩阵mm的第r行首的地址
    Vec3f * ptr = m.ptr<Vec3f>(r);
    //打印第r行的所有值
    for(int c = 0; c < mm.cols; c++)
    {
        cout << ptr[c] << ",";	//打印结果与使用成员函数at是相同的
    }
    cout << endl;
}
3. 利用成员函数isContinuous和ptr
if(mm.isContinuous())
{
    //得到矩阵m的第一个值的地址
    Vec3f * ptr = mm.ptr<Vec3f>(0);
    //利用操作符[]取值
    for(int n = 0; n < mm.rows * mm.cols; n++)
        cout << ptr[n] << ",";
}
4. 利用成员变量data和step
//访问一个int类型的单通道矩阵的第r行第c列的值
*((Vec3f *)(mm.data + mm.step[0] * r + mm.step[1] * c));
5. 分离通道
void cv::split(cv::InputArray m, cv::OutputArrayOfArrays mv)
Mat mm = (Mat_<Vec3f>(2,2)<<Vec3f(1,11,21),Vec3f(2,12,32),Vec3f(3,13,23),Vec3f(4,24,34));
vector<Mat> planes;
split(mm,planes);
cout<<planes[1].at<float>(0,0)<<endl;	//11

若原矩阵为三通道矩阵,如mm。则分离后:

planes[0]:代表每一个像素点第1个数组成的单通道矩阵

planes[1]:代表每一个像素点第2个数组成的单通道矩阵

planes[2]:代表每一个像素点第3个数组成的单通道矩阵

6. 合并通道
void cv::merge(const cv::Mat *mv, size_t count, cv::OutputArray dst)
//三个单通道矩阵
Mat plane0 = (Mat_<int>(2,2) << 1, 2, 3, 4);
Mat plane1 = (Mat_<int>(2,2) << 5, 6, 7, 8);
Mat plane2 = (Mat_<int>(2,2) << 9, 10, 11, 12);
//用三个单通道矩阵初始化一个数组
Mat plane[] = {plane0, plane1, plane2};
//合并为一个多通道矩阵
Mat mat;
merge(plane, 3, mat);

也可以:

//void merge(InputArrayOfArrays mv, OutputArray dst)
//三个单通道矩阵
Mat plane0 = (Mat_<int>(2,2) << 1, 2, 3, 4);
Mat plane1 = (Mat_<int>(2,2) << 5, 6, 7, 8);
Mat plane2 = (Mat_<int>(2,2) << 9, 10, 11, 12);
//将三个单通道矩阵依次放入vector容器中
vector<Mat> plane;
plane.push_back(plane0);
plane.push_back(plane1);
plane.push_back(plane2);
//合并为一个多通道矩阵
Mat mat;
merge(plane, mat);

1.1.8 获得Mat中某一区域的值

1. 使用成员函数row(i)或col(j)得到矩阵的第i行或者第j列
int r = 1;
int c = 0;
//矩阵的第r行
Mat mr = m.row(r);
//矩阵的第c列
Mat mc = m.col(c);

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

2. 使用成员函数rowRange或colRange得到矩阵的连续行或者连续列
Mat matrix = (Mat_<int>(5,5)<<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);
//使用OpenCV的Range类,用于构造[_start,_end)的连续整数序列
Mat r_range = matrix.rowRange(Range(2,4));	//索引从0开始
//也可以省略Range()
Mat r_range = matrix.rowRange(2,4);
Mat c_range = matrix.colRange(1,3);

注意:此时若更改r_range或c_range的某个值,原矩阵也会发生改变。

3. 使用成员函数clone和copyTo

可以解决上一点提到的问题。

//clone
Mat r_range = matrix.rowRange(2, 4).clone();
//copyTo
Mat r_range;
matrix.rowRange(2, 4).copyTo(r_range);

注意:此时若更改r_range或c_range的某个值,原矩阵不会发生改变。

4. 使用Rect类
Mat roi1 = matrix(Rect(Point(2,1),Point(3,2))).clone();	//左上角的坐标,右下角的坐标
Mat roi2 = matrix(Rect(2,1,2,2).clone();				//x,y,宽度,高度
Mat roi3 = matrix(Rect(Point(2,1),Size(2,2))).clone();	//左上角的坐标,尺寸

1.2 矩阵的运算

1.2.1 加法运算

Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst = src1 + src2;

注意:

  1. 两个矩阵的数据类型必须相同,否则会报错;
  2. 一个数值可以与一个Mat对象相加,但是无论这个数值是什么数据类型,返回的Mat的数据类型都与输入的Mat相同;
  3. 若相加的结果超出了某类型的最大值,可能会将结果截断为最大值。

为了解决加号+的问题,可以使用add函数:

void cv::add(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, cv::InputArray mask = noArray(), int dtype = -1)
Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst;
add(src1,src2,dst);
//输入矩阵的数据类型可以不同
//输出矩阵的数据类型可以根据情况自行指定
add(src1,src2,dst,Mat(),CV_64FC1);

注意:此处Mat()代表空的矩阵[],该参数值在需要掩模mask操作时会用到,其余时刻使用Mat()即可。


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

Vec3f v1 = Vec3f(1, 2, 3);
Vec3f v2 = Vec3f(10, 1, 12);
Vec3f v = v1 + v2;	//[11,3,15]

1.2.2 减法运算

Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst = src1 - src2;

注意:

  1. 两个矩阵的数据类型必须相同,否则会报错;
  2. 一个Mat对象可以与一个数值相减,但是无论这个数值是什么数据类型,返回的Mat的数据类型都与输入的Mat相同;
  3. 若相加的结果超出了某类型的最小值,可能会将结果截断为最小值。

为了解决减号-的问题,可以使用subtract函数:

void cv::subtract(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, cv::InputArray mask = noArray(), int dtype = -1)
Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst;
subtract(src1,src2,dst);
//输入矩阵的数据类型可以不同
//输出矩阵的数据类型可以根据情况自行指定
subtract(src1,src2,dst,Mat(),CV_64FC1);

两个向量也可以做减法运算:

Vec3f v1 = Vec3f(1, 2, 3);
Vec3f v2 = Vec3f(10, 1, 12);
Vec3f v = v1 - v2;	//[-9,1,-9]

1.2.3 点乘运算(对应位置的数值相乘)

Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst = src1.mul(src2);

注意:

  1. 两个矩阵的数据类型必须相同,否则会报错;
  2. 若相加的结果超出了某类型的最大值,可能会将结果截断为最大值。

为了解决.mul的问题,可以使用multiply函数:

void cv::multiply(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, double scale = (1.0), int dtype = -1)
Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst;
multiply(src1,src2,dst);
//输入矩阵的数据类型可以不同
//输出矩阵的数据类型可以根据情况自行指定
subtract(src1,src2,dst,1.0,CV_64FC1);

注意:这里的dst = scale * src1 * src2,即在点乘结果的基础上还需要再乘以系数scale。

1.2.4 点除运算(对应位置的数值相除)

Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst = src2 / src1;
/*
[  5,   1,   1;
   1,   0,   0]
*/

注意:

  1. 两个矩阵的数据类型必须相同,否则会报错;
  2. 若相除的结果超出了某类型的最大或最小值,可能会将结果截断为最大或最小值。
  3. 如果分母为0,则相除的结果默认为0。

为了解决相除运算符/的问题,可以使用divide函数:

void cv::divide(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, double scale = (1.0), int dtype = -1)
Mat src1 = (Mat_<uchar>(2,3) << 23, 123, 90, 100, 250, 0);
Mat src2 = (Mat_<uchar>(2,3) << 125, 150, 60, 100, 10, 40);
Mat dst;
divide(src2,src1,dst);
//输入矩阵的数据类型可以不同
//输出矩阵的数据类型可以根据情况自行指定
subtract(src2,src1,dst,1.0,CV_64FC1);	
/*
[  5,   1,   1;
   1,   0,   0]
*/

注意:如果分母为0,则相除的结果默认为0。

1.2.5 乘法运算(线性代数中的点乘)

Mat src1 = (Mat_<float>(2,3) << 1, 2, 3, 4, 5, 6);
Mat src2 = (Mat_<float>(3,2) << 6, 5, 4, 3, 2, 1);
Mat dst = src1 * src2;

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


对于Mat的乘法,还可以使用OpenCV提供的gemm函数来实现。

void cv::gemm(cv::InputArray src1, cv::InputArray src2, double alpha, cv::InputArray src3, double beta, cv::OutputArray dst, int flags = 0)

该函数通过flags控制src1,src2,src3是否转置来实现矩阵之间不同的运算。

将flags设置为不同的参数时,输出矩阵为:

dstflags
d s t = a l p h a ∗ s r c 1 ∗ s r c 2 + b e t a ∗ s r c 3 dst = alpha * src1 * src2 + beta * src3 dst=alpha∗src1∗src2+beta∗src3flags=0
d s t = a l p h a ∗ s r c 1 T ∗ s r c 2 + b e t a ∗ s r c 3 dst = alpha * src1^T * src2 + beta * src3 dst=alpha∗src1T∗src2+beta∗src3flags=GEMM_1_T
d s t = a l p h a ∗ s r c 1 ∗ s r c 2 T + b e t a ∗ s r c 3 dst = alpha * src1 * src2^T + beta * src3 dst=alpha∗src1∗src2T+beta∗src3flags=GEMM_2_T
d s t = a l p h a ∗ s r c 1 ∗ s r c 2 + b e t a ∗ s r c 3 T dst = alpha * src1 * src2 + beta * src3^T dst=alpha∗src1∗src2+beta∗src3Tflags=GEMM_3_T

注意:flags可以组合使用。如需要src2和src3都转置时,则令flags=GEMM_2_T+GEMM_3_T。

同样的,gemm只能接受float类型或者double类型的Mat。

1.2.6 指数运算

void cv::exp(cv::InputArray src, cv::OutputArray dst)

1.2.7 对数运算

//此处log以e为底
void cv::log(cv::InputArray src, cv::OutputArray dst)

1.2.8 幂指数

void cv::pow(cv::InputArray src, double power, cv::OutputArray dst)

注意:经过幂指数运算的dst的数据与src的数据类型相同,因此结果可能会产生截断现象。

1.3 图像数字化

1.3.1 图像的读取与显示

cv::Mat cv::imread(const cv::String &filename, int flags = 1)

flags = IMREAD_COLOR:彩色图像

flags = IMREAD_GRAYSCALE:灰度图像

flags = IMREAD_ANYCOLOR:任意图像

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

1.3.2 将RGB彩色图像转换为多通道Mat

对于彩色图像的每一个方格,可以理解为一个Vec3b。

注意:每一个像素的向量不是安装R、G、B分量排列的,而是按照B、G、R顺序排列的。所以通过split函数分离通道后,先后得到的是B、G、R通道。

Mat img = imread("../orange.jpg");
if(img.empty())
    return -1;
imshow("BGR", img);
vector<Mat> planes;
split(img, planes);		//分离通道
imshow("B",planes[0]);	//显示B通道
imshow("G",planes[1]);	//显示G通道
imshow("R",planes[2]);	//显示R通道

标签:数字化,Mat,int,dst,矩阵,OpenCV,src1,基础教程,cv
来源: https://blog.csdn.net/qq_29931565/article/details/121236668

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有