zoukankan      html  css  js  c++  java
  • OpenCV(二)

    1. 前言:Mat类简介

      OpenCV 作为强大的计算机视觉开源库,很大程度上参考了MatLab的实现细节和语法风格,比如说,在OpenCV2.x版本以后,越来越多的函数实现了MatLab所具有的功能,甚至干脆连函数名都一模一样(如 imread, imshow,imwriter等)。

      在计算机内存中,数字图像以矩阵的形式存储和运算,比如,在MatLab中,图像读取之后对应一个矩阵,在OpenCV中,同样也是如此。

      在早期的OpenCV1.x版本中,图像的处理是通过IplImage(该名称源于Intel的另一个开源库Intel Image Processing Library ,缩写成IplImage)结构来实现的。早期的OpenCV是用C语言编写,因此提供的借口也是C语言接口,其源代码完全是C的编程风格。IplImage结构是OpenCV矩阵运算的基本数据结构,但是由于是由c语言写的,所以内存等等完全要自己进行控制,很容易出现内存泄漏等等。

      到OpenCV2.x版本,OpenCV开源库引入了面向对象编程思想,大量源代码用C++重写,Mat类 (Matrix的缩写) 是OpenCV用于处理图像而引入的一个封装类。从功能上讲,Mat类在IplImage结构的基础上进一步增强,并且,由于引入C++高级编程特性,Mat类的扩展性大大提高,Mat类的内容在后期的版本中不断丰富,如果你查看Mat类的定义的话(OpenCV3.1sourcesmodulescoreincludeopencv2coremat.hpp),会发现其设计实现十分全面而具体,基本覆盖计算机视觉对于图像处理的基本要求。

      因此,在当前的OpenCV开发中,Mat可以说是最最最常见的数据单元,深入了解Mat类对于OpenCV深入开发有着重大意义。

    通道和位深

    矩阵数据类型:

     – CV_<bit_depth>(S|U|F)C<number_of_channels>
    

      S = 符号整型 U = 无符号整型 F = 浮点型

    CV_8UC1 是指一个8位无符号整型单通道矩阵,
    CV_32FC2是指一个32位浮点型双通道矩阵
    CV_8UC1 CV_8SC1 CV_16U C1 CV_16SC1 
    CV_8UC2 CV_8SC2 CV_16UC2 CV_16SC2 
    CV_8UC3 CV_8SC3 CV_16UC3 CV_16SC3 
    CV_8UC4 CV_8SC4 CV_16UC4 CV_16SC4 
    CV_32SC1 CV_32FC1 CV_64FC1
    CV_32SC2 CV_32FC2 CV_64FC2
    CV_32SC3 CV_32FC3 CV_64FC3
    CV_32SC4 CV_32FC4 CV_64FC4
    

      其中,通道表示每个点能存放多少个数,类似于RGB彩色图中的每个像素点有三个值,即三通道的。图片中的深度表示每个值由多少位来存储,是一个精度问题,一般图片是8bit(位)的,则深度是8.

    (一):单通道图:

    俗称灰度图,每个像素点只能有有一个值表示颜色,它的像素值在0到255之间,0是黑色,255是白色,中间值是一些不同等级的灰色。(也有3通道的灰度图,3通道灰度图是3个通道的值都一样)。

    (二):三通道图:

    每个像素点都有3个值表示 ,所以就是3通道。也有4通道的图。例如RGB图片即为三通道图片,RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。总之,每一个点由三个值表示,每一行的数据格式是 [Rdata,Gdata,Bdata,Rdata,Gdata,Bdata,Rdata,Gdata,Bdata........] 

    下面用一个简单的例子说明三通道图片和单通道图片的区别

    #include<iostream>
    #include<opencv2corecore.hpp>
    #include<opencv2highguihighgui.hpp>
    #include<opencv2imgprocimgproc.hpp>
    #include<opencv2opencv.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main()
    {
    	//载入一张彩色图片并显示
    	Mat srcImage = imread("D:/视频跟踪学习/images and videos/lena.png", 1);
    	namedWindow("Image", WINDOW_AUTOSIZE);
    	imshow("Image", srcImage);
    
    	int nHeight = srcImage.rows;
    	int nWidth = srcImage.cols;
    
    	//载入一张灰度图并显示,这里使用同一张图片 只是imread函数的最后一个参数不一样  效果是相同的
    	Mat grayImage = imread("D:/视频跟踪学习/images and videos/lena.png", 0);
    	namedWindow("grayImage", WINDOW_AUTOSIZE);
    	imshow("grayImage", grayImage);
    
    	//基本信息
    	cout << "图像的高度" << nHeight << endl;
    	cout << "图像的宽度" << nWidth << endl;
    	cout << "Image的通道数" << srcImage.channels() << endl;      //彩色图片的通道数
    	cout << "grayImage的通道数" << grayImage.channels() << endl; //灰度图片的通道数
    
    	for (int i = 0; i<nHeight; i++)
    	{
    		for (int j = 0; j<nWidth; j++)
    		{  //将前三分之一列的RGB通道数据都设置为0
    			srcImage.at<uchar>(i, j) = 0;
    			grayImage.at<uchar>(i, j) = 0;
    		}
    	}
    	namedWindow("彩色图片处理后对应黑色图片", WINDOW_AUTOSIZE);
    	imshow("彩色图片处理后对应黑色图片", srcImage);
    
    	namedWindow("灰度图片处理后对应黑色图片", WINDOW_AUTOSIZE);
    	imshow("灰度图片处理后对应黑色图片", grayImage);
    
    	cvWaitKey(0);
    	cvDestroyWindow("Image");
    	cvDestroyWindow("grayImage");
    	cvDestroyWindow("彩色图片处理后对应黑色图片");
    	cvDestroyWindow("灰度图片处理后对应黑色图片");
    
    	return 0;
    }
    

      程序结果:

    上面代码中for循环是将前三分之一列的RGB通道数据都设置为0,所以处理后的图案少了三分之一

    2. Mat类常用成员函数和成员变量

      Mat类十分庞大,其所涉及的成员函数和变量难以一一细数,在这里,仅学习记录其最最最常见的部分,以便日常使用。

    2.1 构造函数

    • 2.1.1 默认构造函数
    cv::Mat::Mat()

    默认构造函数,生成一个矩阵并由OpenCV提供的函数(一般是Mat::create() 和 cv::imread() )来分配储存空间。

    Mat类可以分为两个部分:矩阵头和指向像素数据的矩阵指针

    矩阵头 包括数字图像的矩阵尺寸、存储方法、存储地址和引用次数等,矩阵头的大小是一个常数,不会随着图像的大小而改变,但是保存图像像素数据的矩阵则会随着图像的大小而改变,通常数据量会很大,比矩阵头大几个数量级。这样,在图像复制和传递过程中,主要的开销是由存放图像像素的矩阵而引起的。因此,OpenCV使用了引用次数,当进行图像复制和传递时,不再复制整个Mat数据,而只是复制矩阵头和指向像素矩阵的指针,例如:

    cv::Mat a ; //默认构造函数,创建矩阵头
    a = cv::imread("test.jpg");//读入图像,矩阵指针指向该像素数据
    cv::Mat b = a ;//复制 

    上面的a,b有各自的矩阵头,但是其矩阵指针指向同一个矩阵,也就是其中任何一个改变了矩阵数据都会影响另外一个。
    那么,多个Mat共用一个矩阵数据,最后谁来释放矩阵数据呢?
    这就是引用计数的作用,当Mat对象每被复制一次时,就会将引用计数加1,而每销毁一个Mat对象(共用同一个矩阵数据)时引用计数会被减1,当引用计数为0时,矩阵数据会被清理。

    • 2.1.2 常用构造函数

    (一)

    cv::Mat::Mat(int rows,int cols,int type)

    重载的构造函数,这也是常用构造函数之一,在创建对象同时,提供矩阵的大小(rows,行数;cols ,列数),以及存储类型(type)
    该类型表示矩阵中每一个元素在计算机内存的存储类型,如CV_8UC3,具体含义为“3通道8位无符号数”。

    使用举例:

    Mat src(10,10,CV_32FC3);

    表示src是一个10*10的矩阵,且矩阵元素以32位float型存储

    (二)

    类似,OpenCV还提供了一种Size() 数据结构来构造Mat对象

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

    Size类等效于一个成对数据,size::Size(cols,rows),特别注意 cols和rows的位置

    举个例子

    Mat src1(3, 4, CV_32FC3);
    Mat src2(Size(3, 4), CV_32FC3);
    cout << "src1.rows=" << src1.rows << " src1.cols=" << src1.cols <<endl;
    cout << "src2.rows=" << src2.rows << " src2.cols=" << src2.cols << endl;
    cout << "src1.size="<<src1.size() << endl <<"src2.size=" << src2.size() <<endl;

    输出结果

     

    不得不说,这个Size类的数据结构有点“反人类”,但这样做的好处是方便了计算机内部的运算(比如OpenCV很多函数计算Size相关的数据也是按这个顺序来的,具体为什么这样,我也不太清楚,个人理解为行业标准)。

    还有,我们平时所说分辨率,也是Size的类型,比如屏幕分别率 1440*900,其中cols=1440,rows=900。

    (三)

    cv::Mat::Mat(int ndims,const int * sizes,int type,const Scalar& s) 

    该构造函数与使用了Scalar参数,作用是能够通过Scalar数据类来初始化元素值,例如,我们要生成一张白色背景的图片:

    Mat src1(300, 400, CV_8UC3,Scalar(255,255,255));
    imshow("test", src1);

    运行结果

    其中,(255,255,255)对应以8位无符号数存储,RGB色域的白色值。

    (四)

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

    引用m矩阵,注意,这里是引用值

    (五)

    C++: void Mat::create(int rows, int cols, int type)
    C++: void Mat::create(Size size, int type)
    C++: void Mat::create(int ndims, const int* sizes, inttype)
    

      使用实例:

    Mat M;
    M.create(4, 4, CV_8UC(2));
    cout << "M = " << endl << " " << M << endl << endl;

    输出结果:

    这个创建方法不能为矩阵设初值,它只是在改变尺寸时重新为矩阵数据开辟内存。(不过上面结果的205怎么得到的我不知道)。

    (六)

     MATLAB形式的初始化方式: zeros()ones(), :eyes() 。使用以下方式指定尺寸和数据类型:

    int main()
    {
    	Mat E = Mat::eye(4, 4, CV_64F);
    	cout << "E = " << endl << " " << E << endl << endl;
    
    	Mat O = Mat::ones(2, 2, CV_32F);
    	cout << "O = " << endl << " " << O << endl << endl;
    
    	Mat Z = Mat::zeros(3, 3, CV_8UC1);
    	cout << "Z = " << endl << " " << Z << endl << endl;
    	waitKey(0);
    	return 0;
    }
    

      结果为:

    对于小矩阵你可以用逗号分隔的初始化函数:

    Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    cout << "C = " << endl << " " << C << endl << endl;
    

      结果为:

    使用 clone() 或者 copyTo() 为一个存在的 Mat 对象创建一个新的信息头。

    int main()
    {
    	Mat C = Mat::eye(4, 4, CV_64F);
    	cout << "C = " << endl << " " << C << endl << endl;
    	Mat RowClone = C.row(1).clone();
    	cout << "RowClone = " << endl << " " << RowClone << endl << endl;
    	waitKey(0);
    	return 0;
    }

    结果为:

    具体成员变量和成员函数参照下面两篇文章

    Opencv Mat类成员函数详解

    https://blog.csdn.net/CV_Jason/article/details/54928920#22-%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0

  • 相关阅读:
    第二次作业——结对项目之需求分析与原型模型设计
    第三次作业——结对编程
    采用PowerDesigner 设计数据库
    关于Android开发环境的演变
    广东地区DNS再次瘫痪
    SQL Server 2005 TSQL 中的OUTPUT子句语法
    Visual Studio 里的版本兼容问题
    Javascript 对象扩展积累
    IBatisNet.DataMapper 升级到 1.6.2 之后报错
    ExtJs Extender controls 不错的例子
  • 原文地址:https://www.cnblogs.com/wlx97e6/p/9977152.html
Copyright © 2011-2022 走看看