OpenCV官方教程学习----Core moudle----图片访问

Core moudle(基础模块)

1.How to scan images, lookup tables and time measurement with OpenCV

1.1 测试用实例描述

OpenCV官方文档中此部分的应用背景:一种简答的色彩还原方法

问题描述:由于元素在矩阵中的存储最少也是256个不同值,如果直接采用一对一的映射将十分的需要资源,为了解决这个问题,首先对图像进行一个简单的色彩空间缩减算法,原理很简单,通过C++中的取商后乘十来实现,具体公式如下;
I n e w = ( I o l d 10 ) ∗ 10 I_{new}=(\frac{I_{old}}{10})*10 Inew​=(10Iold​​)∗10
这样以个256的像素值就会被压缩很多,如此一来就基本上解决了数据过大的问题,然而,就算使用这个公式进行计算,由于图像像素多,对于每个图像都应用这个公式进行操作也十分消耗时间,为此使用查找表的方式,先计算斌存好映射关系和结果,到时候直接根据像素值进行查找就可以了,省去实际计算时间。

首先是计算查找表

int divideWith = 0; //划分的宽度,由输入而来
stringstream s;
s << argv[2];
s >> divideWith;		//将输入的字符转换成int型
if (!s || !divideWith)
{
    cout << "Invalid number entered for dividing. " << endl;
    return -1;
}
uchar table[256];		//查找表计算,256个像素值对应26个结果,多对一的映射
for (int i = 0; i < 256; ++i)
    table[i] = (uchar)(divideWith * (i/divideWith));

如何衡量时间

  1. cv::getTickCount(),返回来自某个事件的系统 CPU 的滴答数,一直在变化。
  2. cv::getTickFrequency(),返回您的 CPU 在一秒内发出滴答声的次数。
double t = (double) getTickCount ();//起始时间
// 做点什么 ...
t = ((double) getTickCount () - t)/ getTickFrequency ();	//时间差除以CPU每秒滴答数=运行时间s
cout << "时间以秒为单位:" << t << endl;

1.2 图像矩阵在内存中如何存储。

一个图像矩阵的存储方式取决于:颜色系统(通道数),分辨位数。

对于灰度图像只有一个通道,以及像素值。
OpenCV官方教程学习----Core moudle----图片访问

对于多通道图像,每一列中包含多个子列

OpenCV官方教程学习----Core moudle----图片访问

注意:通道的存储方式是BGR不是RGB

在内存足够的情况下,每一个元素的存储时连续的,从而创建一个超级长的一行数据,可以通过cv::Mat::isContinuous()来查看是否图像存储是否连续。

1.3 有效的访问方式

在性能方面,OpenCV自己表示C风格的指针访问或者[]访问效率高,所以依然要使用C中访问数组元素的方式。

于是就有一个问题,我们要怎么样来对如此庞大的数据进行重新组合,或者有效访问呢

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);	//这是一个异常判断的函数,无符号八位存储方式很重要
    int channels = I.channels();	//获取通道数
    int nRows = I.rows;				//获取行数
    int nCols = I.cols * channels;	//获取列数,包括每个通道
    if (I.isContinuous())			//存储方式是否连续,如果是就可以按一行来判断,不是的话就要按每一行来访问
    {		
        nCols *= nRows;
        nRows = 1;
    }
    int i,j;
    uchar* p;
    for( i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);		//p指向每一行的首地址
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];		//根据每行元素的值进行查找
        }
    }
    return I;		//经过处理后,矩阵的数据完全变化了,而其他信息没有变化
}

另一种方法是使用Mat对象的data数据成员生成指针,因为data数据成员的指针指向第一行第一列的元素的地址。

uchar* p = I.data;
for( unsigned int i = 0; i < ncol*nrows; ++i)
    *p++ = table[*p];

这种方式访问是十分不直观的,无法知道是操作的是哪一行那一列数据。

1.4 迭代器方法----安全

迭代器访问不需要我们自己去计算从哪访问,要访问多少个,迭代器的方式更加模块化。

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    const int channels = I.channels();
    switch(channels)
    {
    case 1:
        {
            MatIterator_<uchar> it, end;	//一个通道的迭代器
            for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = table[*it];
            break;
        }
    case 3:
        {
            MatIterator_<Vec3b> it, end;	//三个通道的迭代器
            for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
        }
    }
    return I;
}

如果用<char>型的迭代器去访问三个通道的图像,这样只能访问蓝色通道所有数据。可以看出,迭代器得到的类似于“每个像素的首地址”,并且迭代器访问完一行行后自动换行

1.5 On-the-fly address calculation with reference returning(这种方法不建议用)

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    const int channels = I.channels();
    switch(channels)
    {
    case 1:
        {
            for( int i = 0; i < I.rows; ++i)
                for( int j = 0; j < I.cols; ++j )
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];//用行列的方式定位元素,并按照一定数据类型进行解释
            break;
        }
    case 3:
        {
         Mat_<Vec3b> _I = I;		//这里确定了数据类的解释方式
         for( int i = 0; i < I.rows; ++i)
            for( int j = 0; j < I.cols; ++j )
               {
                   _I(i,j)[0] = table[_I(i,j)[0]];
                   _I(i,j)[1] = table[_I(i,j)[1]];
                   _I(i,j)[2] = table[_I(i,j)[2]];
            }
         I = _I;
         break;
        }
    }
    return I;
}

这种方式访问数据,每次都要输入行和列以及数据的解释形式,容易出错且效率低。为了方便操作,OpenCV中提供了一种数据类型,cv::Mat_这种数据类型需要在定义的时候额外添加数据类型Mat_<double> M(20,20),如此一来以后访问就不用指定访问的数据类型了,可以直接使用()cv::Mat::at.

1.6 使用核心函数

在Core Function中提供了一种直接根据查找表操作进行的函数cv::LUT().

//创建查找表
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)
    p[i] = table[i];
//使用函数直接完成查找,I根据lookUpTable进行查找并得到J,J是输出
LUT(I, lookUpTable, J);

1.7几种方法的速度

OpenCV官方教程学习----Core moudle----图片访问

咳咳,事实证明,能用函数就不要自己写,费力不讨好。。。解决开始提出的问题的源码地址

上一篇:[论文理解] 人脸识别论文总结(一)


下一篇:conda 命令、CUDA版本查看、CUDA设备检测、CUDA设备带宽检测方法