OpenCV C++(二)----图像数字化

2020-03-08  本文已影响0人  肉松饼饼

一、Mat类

MatMatrix的缩写,代表矩阵或者数组的意思。该 类的声明在头文件opencv2\core\core.hpp中, 所以使用Mat类时要引入该头文件。

1.1、Mat类的构造函数

构造Mat对象相当于构造了一个矩阵(数组),需要四个基本要素:行数(高)、列数(宽)、通道数及其数据类型。

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

其中,

    Mat(Size size, int type);

其中,需要注意的是, Size的第一个元素是矩阵的列数(宽),第二个元素是矩阵的行数(高),即先存 宽, 再存高。即Size(int cols,int rows)

Mat m;
m.create(2,3,CV_32FC1);
m.create(Size(3,2),CV_32FC1);

1.2、初始化Mat类

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

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

1.3、获取单通道Mat的基本信息

1. 使用成员变量rows和 cols获取矩阵的行数和列数
   //构造矩阵
   Mat m=(Mat_<int>(3,2)<<1,2,3,4,5,6);
   //矩阵的行数
   cout<<"行数:"<<m.rows<<endl;
   //矩阵的列数
   cout<<"列数:"<<m.cols<<endl;
2. 使用成员函数size() 获取矩阵的尺寸
   Size size=m.size();
   cout<<"尺寸:"<<size<<endl;
3. 使用成员函数 channels() 得到矩阵的通道数
   cout<<"通道数:"<<m.channels()<<endl;
4. 使用成员函数 total()得到矩阵的行数乘以列数, 即面积。 注意和通道数无关, 返回的不是矩阵中数据的个数
   cout<<"面积:"<<m.total()<<endl;
5. 使用成员变量 dims 得到矩阵的维数。 显然对于单通道矩阵来说就是一个二维矩阵, 对于多通道矩 阵来说就是一个三维矩阵。
   cout<<"维数:"<<m.dims<<endl; 

1.4、访问单通道Mat对象中的值

1. 利用成员函数at
   //构造单通道矩阵
   Mat m=(Mat_<int>(3,2)<<11,22,33,44,55,66);
   //通过for循环打印M中的每一个值
   for(int r=0;r<m.rows;r++)
   {
    for(int c=0;c<m.cols;c++)
    {
           cout<<m.at<int>(r,c)<<",";//第r行第c列的值
           cout<<m.at<int>(Point(c,r))<<",";//等价于上面一行代码
    }
    cout<<endl'
   }
2. 利用成员函数ptr

对于Mat中的数值在内存中的存储, 每一行的值是存储在连续的内存区域中的, 通过成员函数ptr获得指向每一行首地址的指针。 仍以“利用成员函数at”部分的m存储为例, m中所有的值在内存中的存储方式如图2-1所示, 其中如果行与行之间的存储是有内存间隔的, 那么间隔也是相等的。

   for(int=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;
   }
1. 利用成员函数 isContinuous和ptr

每一行的所有值存储在连续的内存区域中, 行与行之间可能会有间隔, 如果isContinuous返回值为true, 则代表行与行之间也是连续存储的, 即所有的值都是连续存储的。

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

对于单通道矩阵来说,step[0]代表每一行所占的字节数,而如果有间隔的话, 这个间隔也作为字节数的一部分被计算在内;step[1]代表每一个数值所占的字节数,data是指向第一个数值 的指针, 类型为uchar。 所以, 无论哪一种情况, 如访问一个int类型的单通到矩阵的第r行 第c列的值, 都可以通过以下代码来实现。

     *((int*)(m.data+m.step[0]*r+c*m.step[1])) 

1.5、向量类Vec

默认是列向量

   //构造一个长度为3,数据类型为int并且初始化为11、22、33的列向量
   Vec<int,3> vi(11,22,33);
   cout<<"向量的行数"<<vi.rows<<endl;
   cout<<"向量的列数<<vi.cols<<endl;
   cout<<"访问滴0个元素:"<<vi[0]<<endl;

OpenCV为向量类的声明取了一个别名,在matx.hpp 401行开始 例如:

   typedef Vec<uchar, 2> Vec2b;
   typedef Vec<uchar, 3> Vec3b;
   typedef Vec<uchar, 4> Vec4b;
   
   typedef Vec<int, 2> Vec2i;
   typedef Vec<int, 3> Vec3i;
   ...

单通道矩阵的每一个元素都是一个数值, 多通道矩阵的每一个元素都可以看作一个向量。

1.6、构造多通道的Mat对象

   Mat mm=(Mat_<Vec3f>(2,2)<<Vec3f(1,1,1),Vec3f(2,2,2),Vec3f(3,3,3),Vec3f(4,4,4));
   //打印第0行第0列的元素值
   int r=0;
   int c=0;
   cout<<mm.at<Vec3f>(r,c)<<endl;

其余同单通道方法,只是类型变成了向量Vec
(1)分离通道

   vector<Mat> planes;
   split(mm,planes);

(2)合并通道

   //三个单通道矩阵
   Mat plane0=(Mat_<int>(2,2)(1,2,3,4);
   Mat plane1=(Mat_<int>(2,2)(11,12,13,14);
   Mat plane2=(Mat_<int>(2,2)(21,22,23,24);
   //用三个单通道矩阵初始化一个数组
   Mat plane[]={plane0,plane1,plane2};
   Mat mat;
   merge(plane,3,mat);
   //将三个单通道矩阵一次放入vector容器中
   vector<Mat> plane;
   plane.push_back(plane0);
   plane.push_back(plane1);
   plane.push_back(plane2);
   Mat mat;
   merge(plane,mat);

1.7、获得Mat中某一区域的值

1. 使用成员函数row(i) 或 col(j) 得到矩阵的第i行或者第 j列
2. 使用成员函数rowRange或 colRange得到矩阵的连续行或者连续列
          Range(int _start,int _end);

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

          Mat r_range=mm.rowRange(Range(2,4));
          //Mat r_range=mm.rowRange(2,4);等价于上面
          for(int r=0;r<r_range.rows;r++)
          {
              for(int c=0;c<r_range.cols;c++)
              {
                  cout<<r_range.at<int>(r,c)<<",";
              }
              cout<<endl;
          }

需要特别注意的是, 成员函数rowcolrowRangecolRange返回的矩阵其实是指向原矩阵的;有时候, 我们只访问原矩阵的某些行或列, 但是不改变原矩阵的值,需要使用复制的方法

3. 使用成员函数 clone和copy To
      Mat r_range=mm.rowRange(2,4).clone();
      Mat c_range=mm.colRange(1,3).copyTo(c_range);
4. 使用 Rect类
  Rect的构造函数
      Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height);
      Rect_(const Rect_& r);
      Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz);
      Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2);
      Mat ROI1=mm(Rect(2,1,2,2));
      Mat roi2=mm(Rect(Point(2,1),Size(2,2)));
      Mat roi3=mm(Rect(Point(2,1),Point(3,2)));

但是与使用colRangerowRange类似, 这样得到的矩形区域是指向原矩阵的, 要改变roi中的值, matrix也会发生变化, 如果不想这样, 则仍然可以使用clone或者copyTo

二、矩阵的运算

2.1、加法运算

矩阵的加法就是两个矩阵对应位置的数值相加

Mat src1=(Mat_<uchar>(2,2)<<11,22,33,60);
Mat src2=(Mat_<uchar>(2,2)<<191,192,193,204);
Mat dst=src1+src2;

注意:

为了弥补“+”运算符的这两个缺点, 我们可以使用OpenCV提供的另一 个函数

void add(InputArray src1, InputArray src2, OutputArray dst,InputArray mask = noArray(), int dtype = -1);

使用add函数时, 输入矩阵的数据类型可以不同, 而输出矩阵的数据类型 可以根据情况自行指定。 需要特别注意的是, 如果给dtype赋值为-1, 则表示dst的数据类型和src1src2是相同的, 也就是只有当src1src2的数据类型相同时,才有可能令 dty pe=-1,否则仍然会报错。

Mat dst;
add(src1,src2,dst,Mat(),CV_64FC1);

2.2、减法运算

矩阵的减法与加法类似

Mat dst=src1-src2;

注意:

当然, 也存在与“+”运算符 一样的不足, OpenCV提供的函数:

void subtract(InputArray src1, InputArray src2, OutputArray dst,
                           InputArray mask = noArray(), int dtype = -1);

可以实现不同的数据类型的Mat之间做减法运算, 其与add函数类似。

2.3、点乘运算

矩阵的点乘即两个矩阵对应位置的数值相乘。

Mat dst=src1.mul(src2);

注意

从打印结果就可以看出,也是对大于255的数值做了截断处理。 所以为了不损失精度,可以将两个矩阵设置为intfloat等数值范围更大的数据类型。

对于Mat的点乘, 也可以利用OpenCV提供的函数:

void multiply(InputArray src1, InputArray src2,
                           OutputArray dst, double scale = 1, int dtype = -1);

这里的dst=sclae*src1*src2, 即在点乘结果的基础上还可以再乘以系数scale

2.4、点除运算

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

Mat dst=src2/src1;

注意

对于Mat的点除, 也可以利用OpenCV提供的函数:

divide(InputArray src1, InputArray src2, OutputArray dst,
                         double scale = 1, int dtype = -1);

2.5、乘法运算

相当于卷积

Mat dst=src1*src2;

注意:

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

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

注意:gemm也只能接受CV_32FC1CV_64FC1CV_32FC2CV_64FC2数据类型的Mat

该函数通过flags控制src1src2src3是否转置来实现矩阵之间不同的运算, 当将flags设置为不同的参数时, 输出矩阵为:
当然,flags可以组合使用, 比如需要src2src3都进行转置, 则令flags=GEMM_2_T+GEMM_3_T

2.6、其他运算

开平方运算

void sqrt(InputArray src, OutputArray dst);

注意:sqrt的输入矩阵的数据类型只能是 CV_32F或者CV_64F

幂指数运算

void pow(InputArray src, double power, OutputArray dst);

三、灰度图像数字化

Mat imread( const String& filename, int flags = IMREAD_COLOR );   
void imshow(const String& winname, InputArray mat);

四、彩色图像数字化

灰度图像的每一个像素都是由一个数字量化的, 而彩色图像的每一个像素都是由三个数字组成的向量量化的。 最常用的是由RGB三个分量来量化的, RGB模型使用加 性色彩混合以获知需要发出什么样的光来产生给定的色彩, 源于使用阴极射线管(CRT) 的彩色电视, 具体色彩的值用三个元素的向量来表示, 这三个元素的数值分别代表三种基色: Red、Green、Blue的亮度。 假设每种基色的数值量化成m=2^n个数, 如同8位灰度 图像一样, 将灰度量化成28=256个数。 RGB图像的红、绿、蓝三个通道的图像都是一张8 位图, 因此颜色的总数为2563 =16777216, 如(0, 0, 0) 代表黑色,(255, 255, 255) 代表白色, (255, 0, 0) 代表红色。

对于彩色图像的每一个方格, 我们可以理解为一个Vec3b。 需要注意的是, 每一个像素的向量不是按照RGB分量排列的, 而是按照BGR顺序排列的, 所以通过split函数分离通道后, 先后得到的是BGR通道。

Mat img=imread("apple.jpg",CV_LOAD_IMAGE_GRAYSCALE);
if(img.empty())
{
    return -1;
}
imshow("BGR",img);
vector<Mat> planes;
split(img,planes);
imshow("B",planes[0]);
imshow("G",planes[1]);
imshow("R",planes[2]);
waitKey(0);

在OpenCV中实现将彩色像素(一个向量) 转化为灰度像素(一个数值) 的公式如 下:

image.png
上一篇下一篇

猜你喜欢

热点阅读