• OpenCV(2)-Mat数据结构及访问Mat中像素


    Mat数据结构

    一开始OpenCV是基于C语言的,在比较早的教材例如《学习OpenCV》中,讲解的存储图像的数据结构还是IplImage,这样需要手动管理内存。现在存储图像的基本数据结构是Mat
    Mat是opencv中保存图像数据的基本容器。其定义如下:

    class CV_EXPORTS Mat
    {
    public:
        // ... a lot of methods ...
        ...
    
        /*! includes several bit-fields:
             - the magic signature
             - continuity flag
             - depth
             - number of channels
         */
        int flags;
        //! the array dimensionality, >= 2
        int dims;
        //! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
        int rows, cols;
        //! pointer to the data
        uchar* data;
    
        //! pointer to the reference counter;
        // when array points to user-allocated data, the pointer is NULL
        int* refcount;
    
        // other members
        ...
    };
    

    Mat类可以表示n维的单通道或多通道数组,它可以存储实数/复数的向量和矩阵,单色或彩色图像等。向量(M)的布局是由数组(M.step[])决定的,元素((i_0, ..., i_{M.dims-1}))的地址为(其中(0 leq i_k < M.size[k])):

    [addr(M_{i_0, ..., i_{M.dims-1}})=M.data + M.step[0]*i_0 + M.step[1]*i_1+...+M.step[M.dims-1]*i_{M.dims-1} ]

    Mat对象的数据布局和CvMat、Numpy等兼容,实际上它和以step(strides)方式计算像素地址方式的数据结构兼容。
    在上面的数据结构可以看出,Mat数据结构中指针信息可以共享,即矩阵头信息独立,矩阵数据可以共享,使用引用计数器,类似智能指针。这样用户使用时,用户可以分配Mat的头信息,共享数据信息,并在原地处理信息,这样可以极大的节省内存。

    创建Mat对象

    1、使用构造函数Mat(nrows, ncols, type[, fillValue])或者create(nrows, ncols, type)
    这样可以创建一个nrows行,ncol列的矩阵,类型为type。例如CV_8UC1表示8位单通道, CV_32FC2表示双通道32位floating-point双通道。

    // make a 7x7 complex matrix filled with 1+3j.
    Mat M(7,7,CV_32FC2,Scalar(1,3));
    // and now turn M to a 100x60 15-channel 8-bit matrix.
    // The old content will be deallocated
    M.create(100,60,CV_8UC(15));
    

    对于type,格式为CV_位数+数值类型+C通道数,例如:
    CV_8UC1表示:单通道阵列,8bit无符号整数
    CV_8US2表示:2通道阵列,8bit有符号整数)
    2、创建多维矩阵

    // create a 100x100x100 8-bit array
    int sz[] = {100, 100, 100};
    Mat bigCube(3, sz, CV_8U, Scalar::all(0));
    

    3、使用拷贝构造函数或赋值操作符时,只是创建了矩阵头,共享了矩阵信息,时间复杂度为O(1)。Mat::clone()函数是深拷贝,拷贝了Mat的所有信息。

    4、只创建信息头部分,时间复杂度为O(1),可以使用这个特征Mat局部信息:

    // add the 5-th row, multiplied by 3 to the 3rd row
    M.row(3) = M.row(3) + M.row(5)*3;
    
    // now copy the 7-th column to the 1-st column
    // M.col(1) = M.col(7); // this will not work
    Mat M1 = M.col(1);
    M.col(7).copyTo(M1);
    
    // create a new 320x240 image
    Mat img(Size(320,240),CV_8UC3);
    // select a ROI
    Mat roi(img, Rect(10,10,100,100));
    // fill the ROI with (0,255,0) (which is green in RGB space);
    // the original 320x240 image will be modified
    roi = Scalar(0,255,0);
    

    可以创建ROI(Region of interest)区域

    Mat A = Mat::eye(10, 10, CV_32S);
    // extracts A columns, 1 (inclusive) to 3 (exclusive).
    Mat B = A(Range::all(), Range(1, 3));
    // extracts B rows, 5 (inclusive) to 9 (exclusive).
    // that is, C ~ A(Range(5, 9), Range(1, 3))
    Mat C = B(Range(5, 9), Range::all());
    Size size; Point ofs;
    C.locateROI(size, ofs);
    // size will be (width=10,height=10) and the ofs will be (x=1, y=5)
    

    5、使用用户开辟的数据创建Mat的header部分

    6、使用Mat::eye(), Mat::zeros(), Mat::ones()创建矩阵;或使用Mat_<destination_type>()

    Mat E = Mat::ones(2, 2, CV_32F);
    Mat O = (Mat_<float>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
    

    矩阵运算

    Mat支持矩阵运算。A、B表示Mat类型对象,s表示标量,alpha表示实数,支持以下运算:
    ** 加:A+B, A-B, A+s, s+B,-A
    ** 缩放:Aalpha
    ** 对应像素相乘/除:A.mul(B),A/B, alpha/A。这里要求A、B大小相等,数据类型通道数一致。
    ** 转置:A.t(),相当于A^T
    ** 求逆和伪逆矩阵。A.inv([method])宝石求逆,A.inv([method])
    B表示求X,其中X满足AX=B
    ** 逻辑位操作 A & B, ~A
    **内积:A.cross(B), A.dot(B),表示对应像素相乘,求和。

    常用接口

    1、C++: size_t Mat::total() const
    返回像素总数
    2、C++: int Mat::depth() const
    返回矩阵type类型对应的数值。
    3、C++: int Mat::channels() const
    返回通道数
    4、C++: bool Mat::empty() const
    Mat::total() = 0 或 Mat::data = NULL,则方法返回 true


    访问Mat中的像素

    Mat中存储的图像像素,具体如何存储取决于使用的颜色模型和通道数,例如RGB图像对应的存储矩阵如下

    image

    RGB存储的子列通道是反过来的:BGR。如果内存足够大,可以连续存储,通过方法Mat::isContinuous()可以判断矩阵是否连续。
    访问图像的像素,即访问某位置像素在内存中对应的地址。以提取彩色RGB图像某一通道图像为例:可以有如下方法:

    1、使用指针

    Mat存储的图像,每一行都是连续的,可以取得每一行开头指针来访问图像像素。例如提取一副图像中R通道的图像,G、B通道像素全部置零,可以获取每一行开头的指针,使用指针遍历每一行的所有像素。如果图像在内存中的存储是连续的,还可以一次遍历所有像素。

    /*
    original:原图像
    new_image:新图像
    channel:提取的通道 0 1 2分别表示RGB
    */
    void ExtractRGB(Mat& original, Mat& new_image, int channel){
    	CV_Assert(channel < 3);
    	// accept only char type matrices
    	CV_Assert(original.depth() != sizeof(uchar));
    
    	int channels = original.channels();
    	//只接受3通道图像
    	CV_Assert(channels == 3);
    
    	new_image = original.clone();
    
    	int nRows = new_image.rows;
    	int nCols = new_image.cols * channels;
    
    	if (new_image.isContinuous())
    	{
    		nCols *= nRows;
    		nRows = 1;
    	}
    
    	int i, j;
    	uchar* p;
    	for (i = 0; i < nRows; ++i)
    	{
    		p = new_image.ptr<uchar>(i);
    		for (j = 0; j < nCols; ++j)
    		{
    			if (0 == (j + 1 + channel) % 3){
    				//保留
    			}
    			else
    				p[j] = 0;
    			
    		}
    	}
    	return ;
    }
    

    2、使用迭代器

    在上面的使用裸指针的方法,不安全,不注意的话会造成内存越界访问。迭代器是封装了的指针,相对指针更加安全。

    /*
    original:原图像
    new_image:新图像
    channel:提取的通道 0 1 2分别表示BGR
    */
    void ExtractRGBIterator(Mat& original, Mat& new_image, int channel){
    	CV_Assert(channel < 3);
    	// accept only char type matrices
    	CV_Assert(original.depth() != sizeof(uchar));
    
    	int channels = original.channels();
    	//只接受3通道图像
    	CV_Assert(channels == 3);
    
    	new_image = original.clone();
    
    	int i = (channel + 1) % 3;
    	int j = (channel + 2) % 3;
    
    	MatIterator_<Vec3b> it, end;
    	//3通道的图像,迭代器对应三个像素(*it)[0]、(*it)[1]、(*it)[2]
    	for (it = new_image.begin<Vec3b>(), end = new_image.end<Vec3b>(); it != end; ++it)
    	{
    		(*it)[i] = 0;
    		(*it)[j] = 0;
    	}
    }
    

    3、实时计算

    如果想随机获取某一位置像素,例如(i,j)出的像素,要动态实时计算其偏移,OpenCV提供相关接口

    /*
    original:原图像
    new_image:新图像
    channel:提取的通道 0 1 2分别表示BGR
    */
    void ExtractRGBRandomAcccess(Mat& original, Mat& new_image, int channel){
    	CV_Assert(channel < 3);
    	// accept only char type matrices
    	CV_Assert(original.depth() != sizeof(uchar));
    
    	int channels = original.channels();
    	//只接受3通道图像
    	CV_Assert(channels == 3);
    
    	new_image = original.clone();
    
    	Mat_<Vec3b> _I = new_image;
    
    	int m = (channel + 1) % 3;
    	int n = (channel + 2) % 3;
    
    	for (int i = 0; i < new_image.rows; ++i)
    		for (int j = 0; j < new_image.cols; ++j)
    		{
    			_I(i, j)[m] = 0;
    			_I(i, j)[n] = 0;
    			
    		}
    }
    

    以上3个方法中,第一种最快,第三种最慢;因为第三种是随机访问像素使用的,每次都会计算(i,j)像素对应的地址。

  • 相关阅读:
    CentOS 安装 Xamarin官方Mono
    mongo命令行下去除重复的数据
    通过前端js将页面表格导出为PDF(二)
    通过前端js将页面表格导出为PDF(一)
    ubuntu下手动安装MongoDB
    在django项目下执行定时任务
    django+uwsgi+nginx部署在ubuntu系统上
    Nginx(三)
    Nginx(二)
    Nginx(一)
  • 原文地址:https://www.cnblogs.com/korbin/p/5611217.html
Copyright © 2020-2023  润新知