对于一副图像,如果矩阵元素存储的是单通道像素,使用C或C++的无符号字符类型,那么像素可有256个不同值。但若是三通道图像,这种存储格式的颜色数就太多了(确切地说,有一千六百多万种)。用如此之多的颜色可能会对我们的算法性能造成严重影响。其实有时候,仅用这些颜色的一小部分,就足以达到同样效果。

这种情况下,常用的一种方法是 颜色空间缩减 

其做法是:将现有颜色空间值除以某个输入值,以获得较少的颜色数。例如,颜色值0到9可取为新值0,10到19可取为10,以此类推。

uchar (无符号字符,即0到255之间取值的数)类型的值除以 int 值,结果仍是 char 。因为结果是char类型的,所以求出来小数也要向下取整。利用这一点,刚才提到在 uchar 定义域中进行的颜色缩减运算就可以表达为下列形式:

                                                                                                         I_{new} = (\frac{I_{old}}{10}) * 10

这样的话,简单的颜色空间缩减算法就可由下面两步组成:一、遍历图像矩阵的每一个像素;二、对像素应用上述公式。值得注意的是,我们这里用到了除法和乘法运算,而这两种运算又特别费时,所以,我们应尽可能用代价较低的加、减、赋值等运算替换它们。此外,还应注意到,上述运算的输入仅能在某个有限范围内取值,如 uchar 类型可取256个值。

由此可知,对于较大的图像,有效的方法是预先计算所有可能的值,然后需要这些值的时候,利用查找表直接赋值即可。查找表是一维或多维数组,存储了不同输入值所对应的输出值,其优势在于只需读取。

图像矩阵是如何存储在内存之中的?

图像矩阵的大小取决于我们所用的颜色模型,确切地说,取决于所用通道数。如果是灰度图像,矩阵就会像这样:

\newcommand{\tabItG}[1] { \textcolor{black}{#1} \cellcolor[gray]{0.8}}
\begin{tabular} {ccccc}
~ & \multicolumn{1}{c}{Column 0} &   \multicolumn{1}{c}{Column 1} &   \multicolumn{1}{c}{Column ...} & \multicolumn{1}{c}{Column m}\\
Row 0 & \tabItG{0,0} & \tabItG{0,1} & \tabItG{...}  & \tabItG{0, m} \\
Row 1 & \tabItG{1,0} & \tabItG{1,1} & \tabItG{...}  & \tabItG{1, m} \\
Row ... & \tabItG{...,0} & \tabItG{...,1} & \tabItG{...} & \tabItG{..., m} \\
Row n & \tabItG{n,0} & \tabItG{n,1} & \tabItG{n,...} & \tabItG{n, m} \\
\end{tabular}

而对多通道图像来说,矩阵中的列会包含多个子列,其子列个数与通道数相等。例如,RGB颜色模型的矩阵:

\newcommand{\tabIt}[1] { \textcolor{yellow}{#1} \cellcolor{blue} &  \textcolor{black}{#1} \cellcolor{green} & \textcolor{black}{#1} \cellcolor{red}}
\begin{tabular} {ccccccccccccc}
~ & \multicolumn{3}{c}{Column 0} &   \multicolumn{3}{c}{Column 1} &   \multicolumn{3}{c}{Column ...} & \multicolumn{3}{c}{Column m}\\
Row 0 & \tabIt{0,0} & \tabIt{0,1} & \tabIt{...}  & \tabIt{0, m} \\
Row 1 & \tabIt{1,0} & \tabIt{1,1} & \tabIt{...}  & \tabIt{1, m} \\
Row ... & \tabIt{...,0} & \tabIt{...,1} & \tabIt{...} & \tabIt{..., m} \\
Row n & \tabIt{n,0} & \tabIt{n,1} & \tabIt{n,...} & \tabIt{n, m} \\
\end{tabular}

注意到,子列的通道顺序是反过来的:BGR而不是RGB。

很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用isContinuous() 来去判断矩阵是否是连续存储的.

遍历图像像素的方法:

1,直接用C风格的内存访问操作符[]遍历图像

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
// accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    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);
         for ( j = 0; j < nCols; ++j)
          {
                p[j] = table[p[j]];             
                       }
    }
    return I; 

}
View Code

2,迭代器iterator遍历图像

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    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; 
}
View Code

3,动态地址计算遍历图像

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    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;
}
View Code

 三种方法的效率比较:

直接用C风格的内存访问操作符[]遍历图像 >迭代器iterator遍历图像>动态地址计算遍历图像

 

//////////////////////////////////////////////////////////////

以下程序完成的功能:

1、以命令行参数形式读入图像,并用命令行参数给出的整数进行颜色缩减;

2、使用3中图像像素遍历的方法;

3、介绍并使用查找表的核心函数LUT;

4、介绍并使用时间计时函数getTickCount(),getTickFrequency();

    getTickCount()函数返回CPU自某个事件以来走过的时钟周期数;

    getTickFrequency()函数返回CPU一秒钟所走的时钟周期数.       

double t = (double)getTickCount();

     // do something...

    t=((double)getTickCount() - t)/getTickFrequency();

备注:t 结果为以秒为单位;

---------------------------------------------------------------------------------------------------------------------------------------------------------------

 扩展:

 

精确获取时间:

 

QueryPerformanceFrequency() - 基本介绍

 

类型:Win32API

 

原型:BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);

 

作用:返回硬件支持的高精度计数器的频率。

 

返回值:非零,硬件支持高精度计数器;零,硬件不支持,读取失败。

 

QueryPerformanceFrequency() - 技术特点

 

供WIN9X使用的高精度定时器:QueryPerformanceFrequency()和QueryPerformanceCounter(),要求计算机从硬件上支持高精度定时器。需包含windows.h头文件。

 

函数的原形是:

 

BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);

 

BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);

 

数据类型LARGEINTEGER既可以是一个作为8字节长的整数,也可以是作为两个4字节长的整数的联合结构,其具体用法根据编译器是否支持64位而定。

----------------------------------------------------------------------------------------------------------------------------------------------- 

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

using namespace std; 
using namespace cv;



Mat& ScanImageAndReduceC(Mat& I, const uchar* table);
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table);
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar * table);

int main( )
{
    Mat I, J,K,L;
    
    I = imread("D:\\Fruits.jpg", CV_LOAD_IMAGE_COLOR);

    if (!I.data)
    {
        cout << "The image could not be loaded." << endl;
        return -1;
    }

    namedWindow("original image");
    imshow("original image",I);
    waitKey(0);

    int divideWith; 
    stringstream s;
    s << 10;
    s >> divideWith;
    if (!s)
    {
        cout << "Invalid number entered for dividing. " << endl; 
        return -1;
    }

    uchar table[256]; 
    for (int i = 0; i < 256; ++i)   
    {
        table[i] = divideWith* (i/divideWith); //计算查找表 事先计算所有值
    }


    const int times = 100; 
    double t;
    double t2;

    t = (double)getTickCount();    //开始计时 

    for (int i = 0; i < times; ++i)
        J = ScanImageAndReduceC(I.clone(), table);

    t = 1000*((double)getTickCount() - t)/getTickFrequency();    
    cout << "Time of reducing with the C operator [] "<< t << " seconds."<< endl;

    namedWindow("C operator []");
    imshow("C operator []",J);
    waitKey(0);
    
    t = (double)getTickCount();    
    for (int i = 0; i < times; ++i)
        K = ScanImageAndReduceIterator(I.clone(), table);

    t = 1000*((double)getTickCount() - t)/getTickFrequency();
    cout << "Time of reducing with the iterator " << t << " seconds."<< endl;  
    namedWindow("the iterator ");
    imshow("the iterator ",K);
    waitKey(0);
    t = (double)getTickCount();    

    for (int i = 0; i < times; ++i)
    L=    ScanImageAndReduceRandomAccess(I.clone(), table);

    t = 1000*((double)getTickCount() - t)/getTickFrequency();
    cout << "Time of reducing with the on-the-fly address generation" << t << " seconds."<< endl;  

    namedWindow("on-the-fly address");
    imshow("on-the-fly address",L);
    waitKey(0);
    Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.data; 
    for( int i = 0; i < 256; ++i)
        p[i] = table[i];

    t = (double)getTickCount();    

    for (int i = 0; i < times; ++i)
        LUT(I, lookUpTable, J);

    t = 1000*((double)getTickCount() - t)/getTickFrequency();
    

    cout << "Time of reducing with the LUT function (averaged for " 
        << times << " runs): " << t << " milliseconds."<< endl;  

    waitKey(0);
    return 0; 
}

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    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);
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];             
        }
    }
    return I; 
}

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    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; 
}

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    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;
}
View Code

 

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

程序结果:

        

 

参考博客及网页:1,http://www.cppblog.com/deane/articles/113151.html

                     2,http://blog.csdn.net/xiaowei_cqu/article/details/7771760

                     3,http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#howtoscanimagesopencv

 

posted on 2015-10-22 11:39  Chen5138221  阅读(532)  评论(0编辑  收藏  举报