opencv

http://segmentfault.com/blog/epsilon/1190000000597080

那么图像数据时如何在Mat中存储的呢?

Class Mat

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初解

Mat是包含两个数据部分的基本类:
1. 矩阵头文件。
包含矩阵大小(rows,cols)、矩阵维数(dims)等等。
分别用成员变量rows、cols、dims保存。
2. 指向存储像素值的矩阵的指针(data)。
成员变量data给出了像素矩阵存储的初始位置,如今矩阵的大小,矩阵维数已知,想确定像素矩阵的内存位置,
我们还需要知道像素数据的类型,因为不同数据类型占用内存大小可能不相同。
常见类型有:

    enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };
    \\ 8-bit unsigned integer (uchar)
    \\ 8-bit signed integer (schar)
    \\ 16-bit unsigned integer (ushort)
    \\ 16-bit signed integer (short)
    \\ 32-bit signed integer (int)
    \\ 32-bit floating-point number (float)
    \\ 64-bit floating-point number (double)

以上类型,我们在Mat的数据结构中,常称之为depth

彩色图像的存在,Mat同样支持多通道数据。
CV_8U_C1,CV_8U_C2,CV_8U_C3,...,CV_64F_C4,对应于通道1-4。
当然,还可以有更多的通道,通过宏CV_MAKETYPE(depth, n) == ((depth&7)<<3) + (n-1)定义。

常用的Mat成员变量及函数

成员变量:

int dims,//矩阵维数
int rows,//矩阵行数
int cols,//矩阵列数,不考虑通道影响,亦即若是rgb彩色图像,一列中包含了rgb三个通道的数据
uchar *data,//像素矩阵的起始位置
Mstep step,//step[i]描述了在维度i中相邻元素的内存距离
//对于一个2维矩阵:维度0,行;维度1,列
//注意到step[i] >= step[i+1]*size[i+1],这也意味着
//二维矩阵按行存储,三维矩阵按面存储,
//step[dims-1] = elemSize()。

成员函数:
OpenCV2的Mat类中定义了很多类似Matlab矩阵操作的成员函数,比如

+,-,*,等运算符的重定义;

Mat row(int y),Mat col(int x),
Mat rowRange(int startrow, int endrow) const,Mat rowRange(const Range& r) const,
Mat colRange(int startrow, int endrow) const,Mat colRange(const Range& r) const,等行列操作;

MatExpr t() const //转置,
MatExpr inv(int method=DECOMP_LU) const//求逆,等等矩阵运算;

图像相关函数,

bool isContinuous() const //像素矩阵在内存中是否是连续的
size_t elemSize() const //像素矩阵单个数据的字节数,此时单个数据以列为单位,不计通道数,如RGB三通道数据视为一个单元
//所以CV_16SC3数据类型矩阵的返回值为3*sizeof(short) = 6
size_t elemSize1() const //像素矩阵单个数据的字节数,此时单个数据以列为单位,计通道数,如RGB三通道数据视为三个单元
//所以CV_16SC3数据类型矩阵的返回值为sizeof(short) = 2
int type() const //返回矩阵的数据类型,
int depth() const //返回矩阵的深度,即去掉通道数的类型,
int channels() const //返回矩阵的通道数
uchar* ptr(int i0=0),
const uchar* ptr(int i0=0) const,
template<typename _Tp> _Tp* ptr(int i0=0),
template<typename _Tp> const _Tp* ptr(int i0=0) const//返回i0行的首地址 。

图像像素矩阵在内存中的存储方式

我们已经知道用函数imread读取的图像像素值存储在data所指向的内存中,
那么该数据在内存中如何存储的呢?

单通道图像,如灰度图像,存储方式如下
grayscale image

多通道图像,每列数据包含通道个数的子列,如采用RGB色彩,存储方式如下
RGB
值得注意的是,数据存储顺序是BGR,与RGB顺序相反。

从上述两图,还可以发现:Mat中cols的概念并不是简单的矩阵列数,应该考虑上channels的影响。
存储矩阵的列数应为cols*nchannels

对于一个二维的图像矩阵M,其坐标为(i,j)数据的内存地址为
addr(M_{i,j}) = M.data + i*M.step[0] + j*M.step[1]。


《The OpenCV Tutorials》给出3种遍历Mat中图像像素矩阵的方法,分别是指针遍历(ptr)、迭代器(MatIterator)、
at函数,前两者亦分别称作高效方法、安全方法。其中最为高效的是指针方法。
本文只介绍指针方法。

以下代码实现对图像像素数据的遍历,我们打印图像的红色分量。

代码

#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"

using namespace std;
using namespace cv;

int main(int argc, const char *argv[])
{
    if (argc != 2)
    {
        cout<<"Number of Parameters is Wrong!"<<endl;
        return -1;
    }

    Mat src = imread(argv[1],IMREAD_COLOR);

    int channels = src.channels();

    int nRows = src.rows;
    //图像数据列需要考虑通道数的影响;
    int nCols = src.cols * channels;    

    if (src.isContinuous())//连续存储的数据,按一行处理
    {
        nCols *= nRows;
        nRows = 1;
    }

    int i,j;
    uchar* p;
    for( i = 0; i < nRows; ++i)
    {
        p = src.ptr<uchar>(i);
        for ( j = 2; j < nCols; j+=3)//注意通道顺序为BGR,红色为第三个数据;
        {
            cout<<int(p[j])<<" ";   
        }
        cout<<endl;
    }


    return 0;
}

释义

  1. 如果只是单纯打印图像数据的话,可以直接用cout<<src来实现,OpenCV2重定义了<<运算符,实现
    了矩阵的格式化输出。
    详细内容参见The OpenCV Tutorials Release 2.4.9.0,p.148。
  2. 图像坐标系和像素矩阵。
    图像左上角作为原点,按行和列展开。故图像左上角像素对应矩阵的0行0列数据,这里列不计通道。
  3. OpenCV默认使用BGR的通道顺序。
  4. 行数据的填补。
    宽W高H的真彩色图像,像素数据存储需要WxHx3个uchar构成的内存块,但是出于效率考虑,每行可能
    会填补一些额外像素,亦即存储像素数据的宽度不一定是W,往往填补为4或8的倍数,因为这样一些多
    媒体处理芯片可以更高效的处理图像。
    图像的宽高分别由colsrows给出,行像素个数由cols*channels()给出,实际行的字节数由step[0]
    给出。对于有额外填补的行数据,cols!= step[0]/elemSize(),反之,cols==step[0]/elemSize()
    我们可以用成员函数isContinuous()判断图像是否对行进行了填补,对于没有进行填补的图像,我们视其
    像素数据为一个长为WxHxchannels()的一维数组,加快循环速度。
  5. 行首地址。
    Mat的成员函数ptr(int j)返回第j行的首地址。
  6. 如果仔细阅读过Lecture 2 opencv2系列之初识Mat,对于遍历Mat,自然想到用data+step+elemSize()
    实现,这也是基于指针的方法。
    核心循环代码为:
   int i,j;
   uchar *p;
   for (i = 0; i < nRows; i++)
   {
       p = src.<uchar>data + i * src.step[0];
       for (j = 2; j < nCols; j += 3)
       {            
           cout<<int(p[j*src.elemSize1()])<<" ";//本例中,src.elemSize1()=1;故可简写为p[j]。
       }
   }

本质上是因为src.ptr(i) == src.data + i * src.step[0]
在《OpenCV2 计算机视觉编程手册》一书中,Laganiere不建议用这种方法,原因是:容易出错,并且不适用于带“感兴趣
区域”的图像。

下面的一个程序是读入一个灰度图像tt.jpg, 然后输出它的值,值得研究的是它的rbg都有值,并且channels是3,但是在视觉上还是黑白图片

接着是创建了一个channels为1的图片t1.jpg, 只有灰度上的值

这两张图片看起来是一样的,但是蕴含的信息不一致

如果把t1.jpg读入并打印,值和之前的有稍微的不同

#include <opencv2/opencv.hpp>
#include <iostream>
#include <stdint.h>

using namespace std;

int main() {
    cv::Mat img;
    img = cv::imread("tt.jpg");
    cout << "channels " << img.channels()<<endl;
    cout << img << endl << endl;

    for (int i = 0; i < img.rows; ++i) {
        for (int j = 0; j < img.cols;++j) {
            cv::Vec3b bgr = img.at<cv::Vec3b>(i,j);
            cout <<(unsigned)bgr[0]<<"/"<<(unsigned)bgr[1]<<"/"<<(unsigned)bgr[2]<<"\t";
        }
        cout <<endl;
    }
    cout <<endl;

    cv::Mat h(img.rows,img.cols, CV_8U);
    for (int i = 0; i < h.rows; ++i) {
        for (int j = 0; j < h.cols; ++j) {
            h.at<int8_t>(i,j) = img.at<cv::Vec3b>(i,j)[0];
        }
    }

    cv::imwrite("t1.jpg", h);

    return 1;
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值