首页
登录 | 注册

【OpenCV】扫描图像、查找表、计时

扫描图像

  扫描图像有四种方法:C指针访问方法、迭代器方法、即时地址计算方法、LUT函数。实现方法及用时比较可参考下方代码。

参考链接:https://docs.opencv.org/master/db/da5/tutorial_how_to_scan_images.html



颜色空间缩减算法

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

【OpenCV】扫描图像、查找表、计时


  简单的颜色空间缩小算法包括遍历图像矩阵的每个像素以及应用上述公式。值得注意的是,乘除法运算对于系统来说是昂贵的。如果可以,尽量通过使用更简单的操作(如加减法等)来避免这种情况。

  因此,对于较大的图像,预先计算所有可能的值,并且通过使用查找表来进行分配。查找表是简单的数组(一个或多维),对于给定的输入值变量保存最终的输出值。其优点在于不需要进行计算,而直接读取结果。

  其实查询表的实质就是:把图像中的数据从之前的比较高的灰度级降下来,例如灰度级是256的char类型的灰度级,通过一个参数(例如10),将原来的256个灰度级降到了26个灰度级,原来图像中灰度值在0-9的数据现在灰度值变成了0,原来灰度值为11-19的图像数据现在灰度值变为了1,以此类推,250-256的灰度值就变为了25。所以通过参数10,图像的灰度级就降到了26。

计时

  OpenCV提供了两个简便的可用于计时的函数 getTickCount() 和 getTickFrequency() 。第一个函数返回CPU自某个事件(如启动电脑)以来走过的时钟周期数,第二个函数返回CPU一秒钟所走的时钟周期数,便于换算成以秒为单位,对运算方法进行计时统计。


#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <iostream>
#include <sstream>                      
                                        // sstream定义了istringstream、ostringstream和stringstream,
                                        // 分别用来进行流的输入、输出和输入输出操作

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(int argc, char* argv[])
{
    Mat I, J;
    String imageName("../data/lena.jpg");               

    char ch = NULL;
    cout << "If U want grayscale,please enter G ! Or any key !" << endl;
    ch = getchar();
    if (ch == 'G')
        I = imread(imageName, IMREAD_GRAYSCALE);            // 灰度图像
    else 
        I = imread(imageName, IMREAD_COLOR);                // 彩色图像

    if (I.empty())                                          // 图像不存在
    {
        cout << "The image could not be loaded." << endl;
        return -1;
    }

    int divideWith = 0;                                     // 减少颜色空间
    stringstream s;
    s << "10";
    s >> divideWith;                                        // 把输入字符串转为整数
    if (!s || !divideWith)
    {
        cout << "Invalid number entered for dividing." << endl;
        return -1;
    }

    uchar table[256];                                       // 建立查找表
    for (int i = 0; i < 256; i++)
        table[i] = (uchar)(divideWith*(i / divideWith));

    const int times = 100;
    double t;

    t = (double)getTickCount();                             // 获取当前时间

    for (int i = 0; i < times; i++)                         // C指针访问方法
    {
        cv::Mat clone_i = I.clone();
        J = ScanImageAndReduceC(clone_i, table);
    }

    t = 1000 * ((double)getTickCount() - t) / getTickFrequency();   
    t /= times;                                             // 计算算法100次所用平均时间

    namedWindow("The original picture", 1);                 // 命名显示原图像窗口  
    namedWindow("The change picture", 1);                   // 命名显示改变图像窗口  
    imshow("The original picture", I);
    imshow("The change picture", J);
    waitKey(0);

    cout << "\n\nTime of reducing with the C operator [] (averaged for "
        << times << " runs): " << t << " milliseconds." << endl;

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)                         // 迭代器方法
    {
        cv::Mat clone_i = I.clone();
        J = ScanImageAndReduceIterator(clone_i, table);
    }

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

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

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)                         // 即时地址计算
    {
        cv::Mat clone_i = I.clone();
        ScanImageAndReduceRandomAccess(clone_i, table);
    }

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

    cout << "Time of reducing with the on-the-fly address generation - at function (averaged for "
        << times << " runs): " << t << " milliseconds." << endl;

    Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.ptr();
    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);                             // OpenCV自带方法

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

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

    getchar();
    return 0;

}

// C指针访问方法
// 获取指向每行开头的指针,执行直到结束。
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    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);                                // 得到存储矩阵的起始地址
        for (j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];
        }
    }
    return I;
}

// 迭代器方法
// 指定图像矩阵的开始位置和结束位置,然后执行迭代器,直到结束位置。
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    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;
}

// 即时地址计算
// 对于图像的每个元素,获得一个新的行指针,以便执行C运算符[]获取列元素
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    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;                                     //使用Mat_类进行运算

        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】扫描图像、查找表、计时
【OpenCV】扫描图像、查找表、计时

Time of reducing with the C operator [] (averaged for 100 runs): 3.12426 milliseconds.
Time of reducing with the iterator (averaged for 100 runs): 80.2366 milliseconds.
Time of reducing with the on-the-fly address generation - at function (averaged for 100 runs): 132.735 milliseconds.
Time of reducing with the LUT function (averaged for 100 runs): 3.386 milliseconds.

换另一张1920×1080的较大图像

【OpenCV】扫描图像、查找表、计时

【OpenCV】扫描图像、查找表、计时

Time of reducing with the C operator [] (averaged for 100 runs): 32.5547 milliseconds.
Time of reducing with the iterator (averaged for 100 runs): 633.987 milliseconds.
Time of reducing with the on-the-fly address generation - at function (averaged for 100 runs): 1114.07 milliseconds.
Time of reducing with the LUT function (averaged for 100 runs): 10.3989 milliseconds.


2020 jeepxie.net webmaster#jeepxie.net
10 q. 0.010 s.
京ICP备10005923号