opencv 4.0.0教程系列01.2-core模块-how_to_scan_images

How to scan images, lookup tables and time measurement with OpenCV {#tutorial_how_to_scan_images}

如何扫描图像,查找表和时间测量?

@prev_tutorial{tutorial_mat_the_basic_image_container}
https://blog.csdn.net/weixin_42142612/article/details/88809262
@next_tutorial{tutorial_mat_mask_operations}

Goal 目标

We’ll seek answers for the following questions:

  • How to go through each and every pixel of an image?
  • How is OpenCV matrix values stored?
  • How to measure the performance of our algorithm?
  • What are lookup tables and why use them?

我们将会寻找下面这些问题的答案:

  • 如何遍历图像中的每一个像素?
  • OpenCV的矩阵值是如何存储的?
  • 如何测量算法的性能?
  • 什么是查找表以及为什么要使用查找表?

Our test case 测试用例

Let us consider a simple color reduction method. By using the unsigned char C and C++ type for matrix item storing, a channel of pixel may have up to 256 different values. For a three channel image this can allow the formation of way too many colors (16 million to be exact). Working with so many color shades may give a heavy blow to our algorithm performance. However, sometimes it is enough to work with a lot less of them to get the same final result.
我们先考虑一种简单的减色方法。使用c/c++中的uchar类型存储矩阵元素,一个通道的像素值一共有256个值(范围0-255)。对于一个三通道的图像,有更多的颜色信息(确切的说是1600万)。 使用这么多颜色信息将会严重影响算法的性能。然而,有时我们使用其中一些颜色就足够我们得到相同的最终结果了。

In this cases it’s common that we make a color space reduction. This means that we divide the color space current value with a new input value to end up with fewer colors. For instance every value between zero and nine takes the new value zero, every value between ten and nineteen the value ten and so on.
在这种情况下,通常我们想到的是减色方案。这意味着,我们用当前的颜色空间值除以一个输入值,这样可以得到更少的颜色值。例如,每个处于0-9范围内的值取0,每个处于10-19范围内的值取10,依次类推。

When you divide an uchar (unsigned char - aka values between zero and 255) value with an int value the result will be also char. These values may only be char values. Therefore, any fraction will be rounded down. Taking advantage of this fact the upper operation in the uchar domain may be expressed as:

\f[I_{new} = (\frac{I_{old}}{10}) * 10\f]
用一个uchar型的值除以一个int型的值,结果依然是uchar类型。这些值可能只是char型值。因此,任何分数将被四舍五入。利用这个事实,uchar域中的上层操作可表示为:
I_new = (I_old / 10) * 10

A simple color space reduction algorithm would consist of just passing through every pixel of an image matrix and applying this formula. It’s worth noting that we do a divide and a multiplication operation. These operations are bloody expensive for a system. If possible it’s worth avoiding them by using cheaper operations such as a few subtractions, addition or in best case a simple assignment. Furthermore, note that we only have a limited number of input values for the upper operation. In case of the uchar system this is 256 to be exact.
一个简单的减色算法可以对图像矩阵中每个像素点应用这个公式得到。值得注意的是,我们做了一次除法操作和一次乘法操作。这些操作对一个系统来说是非常耗时的。如果可能的话,有必要用其他一些比较高效的运算去代替乘除,比如一些减法,加法或者最好是一个简单的赋值。此外,注意对于上述的这些操作仅有一个限制值。对于uchar类型,这个限制值准确的来说就是256.

Therefore, for larger images it would be wise to calculate all possible values beforehand and during
the assignment just make the assignment, by using a lookup table. Lookup tables are simple arrays
(having one or more dimensions) that for a given input value variation holds the final output value.
Its strength lies that we do not need to make the calculation, we just need to read the result.
因此,对于一些较大的图像,比较明智的做法是把所有可能的值提前计算出来, 然后在赋值期间只需要执行赋值,这个可以通过使用查找表实现。查找表示一些简单的数组(一维或者多维),给定一个输入值,最终的输出值就是确定的。它的好处在于我们后续不需要再计算,只需读取结果就行。

Our test case program (and the sample presented here) will do the following: read in a console line
argument image (that may be either color or gray scale - console line argument too) and apply the
reduction with the given console line argument integer value. In OpenCV, at the moment there are
three major ways of going through an image pixel by pixel. To make things a little more interesting
will make the scanning for each image using all of these methods, and print out how long it took.
我们的测试用例程序(以及此处提供的示例)将执行以下操作:
从命令行参数中读入一幅图像(可以是灰度图或者有颜色图,也是通过命令行参数指定),然后用命令行参数指定的int型值去做减色处理。在OpenCV中,目前扫描图像的方法主要有三种。为了让事情变得更有趣,我们用这三种方法扫描图像,并且打印出来每种方法的耗时。

You can download the full source code here
or look it up in
the samples directory of OpenCV at the cpp tutorial code for the core section. Its basic usage is:
how_to_scan_images imageName.jpg intValueToReduce [G]
The final argument is optional. If given the image will be loaded in gray scale format, otherwise the BGR color space is used. The first thing is to calculate the lookup table.
https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/core/how_to_scan_images/how_to_scan_images.cpp下载整个源代码,也可以在opencv的安装路径中相同目录处找到源代码。源码的基本使用方法:(此处是指命令行参数的顺序)
**how_to_scan_images imageName.jpg intValueToReduce [G] **
最后一个参数是可选参数。如果给出了G,那么图像将会以灰度图的形式载入,否则以BGR颜色图的形式载入。首先要做的事情就是计算查找表。
注意此处的参数都是字符串,在填写命令行参数的时候要用双引号"“括起来。例如我填写的:
“E:/My Pictures/xin.jpg” “8”
注意argv[0]是不需要填写的,argv[0] 指向程序运行的全路径名,是默认的。argv[1]我填写的是"E:/My Pictures/xin.jpg”,argv[2]我填写的是"8",argv[3]我没有填写。

@snippet how_to_scan_images.cpp dividewith

Here we first use the C++ stringstream class to convert the third command line argument from text
to an integer format. Then we use a simple look and the upper formula to calculate the lookup table.
No OpenCV specific stuff here.
此处我们首先使用c++的stringstream类将第三个命令参数由文本型转化为整型。然后我们使用上面的公式去计算查找表。

Another issue is how do we measure time? Well OpenCV offers two simple functions to achieve this cv::getTickCount() and cv::getTickFrequency() . The first returns the number of ticks of your systems CPU from a certain event (like since you booted your system). The second returns how many times your CPU emits a tick during a second. So to measure in seconds the number of time elapsed between two operations is easy as:

double t = (double)getTickCount();
// do something ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;  

另一个问题是我们如何测量时间呢?其实,OpenCV提供了两个简单的函数去计算时间,cv::getTickCount() and cv::getTickFrequency() .cv::getTickCount()函数返回从某一个时间开始(比如从您启动系统之后)系统CPU的时钟数。cv::getTickFrequency()函数返回1s内CPU时钟频数。因此测量两个操作之间耗费的秒数就可以用下面两个简单的操作完成。

double t = (double)getTickCount();
// do something ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;  

@anchor tutorial_how_to_scan_images_storing
How is the image matrix stored in memory? 图像矩阵在内存中是如何存储的?

As you could already read in my @ref tutorial_mat_the_basic_image_container tutorial the size of the matrix depends on the color system used. More accurately, it depends from the number of channels used. In case of a gray scale image we have something like:
相信你已经在我的上一篇教程tutorial_mat_the_basic_image_container中读到,图像矩阵的大小取决于使用的颜色系统。更准确的说,它取决于通道数。在一个灰度图中我们可以看到:

在这里插入图片描述

For multichannel images the columns contain as many sub columns as the number of channels. For example in case of an BGR color system:
对于多通道图像,这些列都包含通道数对应的子列。例如BGR颜色系统中:

在这里插入图片描述

Note that the order of the channels is inverse: BGR instead of RGB. Because in many cases the memory is large enough to store the rows in a successive fashion the rows may follow one after another, creating a single long row. Because everything is in a single place following one after another this may help to speed up the scanning process. We can use the cv::Mat::isContinuous() function to ask the matrix if this is the case. Continue on to the next section to find an example.
注意,通道的顺序是相反的:BGR而不是RGB。因为在很多情况下,存储空间足够大,可以以连续的方式存储这些行一个接一个,从而创建一个长行。因为所有的东西都在一个地方,一个接着一个,这可能有助于加快扫描过程。我们可以使用cv::Mat::isContinuous()函数来查询矩阵是否属于这种连续存储的情况。继续到下一节会发现一个示例。

The efficient way 高效的方法

When it comes to performance you cannot beat the classic C style operator[] (pointer) access. Therefore, the most efficient method we can recommend for making the assignment is:
在性能方面,经典的C风格操作符访问是无可匹敌的。因此,我们建议最有效的赋值方法是:

@snippet how_to_scan_images.cpp scan-c

Here we basically just acquire a pointer to the start of each row and go through it until it ends. In the special case that the matrix is stored in a continuous manner we only need to request the pointer a single time and go all the way to the end. We need to look out for color images: we have three channels so we need to pass through three times more items in each row.
我们只需提供一个指针,指向图像中的每一行,遍历整个图像直到最后一行。在矩阵以连续方式存储的特殊情况下,我们只需要一次请求指针并一直到最后。我们需要注意彩色图像:彩色图像有三个通道,所以我们需要在每一行中遍历三倍多的项。

There’s another way of this. The data data member of a Mat object returns the pointer to the first row, first column. If this pointer is null you have no valid input in that object. Checking this is the simplest method to check if your image loading was a success. In case the storage is continuous we can use this to go through the whole data pointer. In case of a gray scale image this would look like:
@code{.cpp}
uchar* p = I.data;

for( unsigned int i =0; i < ncol*nrows; ++i)
*p++ = table[*p];
@endcode
You would get the same result. However, this code is a lot harder to read later on. It gets even
harder if you have some more advanced technique there. Moreover, in practice I’ve observed you’ll get the same performance result (as most of the modern compilers will probably make this small optimization trick automatically for you).
还有另一种方法。Mat对象的data成员返回指向第一行第一列的指针。如果该指针为空,则该对象中没有有效的输入。检查这个data是检查图像加载是否成功的最简单方法。如果存储空间是连续的,我们可以使用它来遍历整个数据指针。在灰度图像的情况下,如下所示:
uchar* p = I.data; for( unsigned int i =0; i < ncol*nrows; ++i) *p++ = table[*p]
你会得到同样的结果。然而,这段代码在以后的阅读中要困难得多。如果你有更先进的技术,那就更难了。此外,在实践中,我注意到您将得到相同的性能结果(因为大多数现代编译器可能会自动为您完成这个小小的优化技巧)。

The iterator (safe) method迭代器的方法

In case of the efficient way making sure that you pass through the right amount of uchar fields and to skip the gaps that may occur between the rows was your responsibility. The iterator method is considered a safer way as it takes over these tasks from the user. All you need to do is ask the begin and the end of the image matrix and then just increase the begin iterator until you reach the end. To acquire the value pointed by the iterator use the * operator (add it before it).
使用高效的方法时确保传递正确的uchar数量和可能跳过行与行之间的间隙是你的责任。迭代器的方法对使用者来说是一种更加安全的方法。你所需要做的就是询问图像矩阵的开始和结束,然后增加开始迭代器,直到到达结束。要获取迭代器指向的值,可以使用*操作符(在它之前添加)。

@snippet how_to_scan_images.cpp scan-iterator

In case of color images we have three uchar items per column. This may be considered a short vector of uchar items, that has been baptized in OpenCV with the Vec3b name. To access the n-th sub column we use simple operator[] access. It’s important to remember that OpenCV iterators go through the columns and automatically skip to the next row. Therefore in case of color images if you use a simple uchar iterator you’ll be able to access only the blue channel values.
在彩色图像中,每一列都有三个uchar项。在OpenCV中用Vec3b表示。用[]操作符就可以获取第n个子列。重要的是要记住OpenCV迭代器遍历列并自动跳到下一行。因此,对于彩色图像,如果您使用一个简单的uchar迭代器,您将只能访问蓝色通道值。

On-the-fly address calculation with reference returning实时地址计算与参考返回

The final method isn’t recommended for scanning. It was made to acquire or modify somehow random elements in the image. Its basic usage is to specify the row and column number of the item you want to access. During our earlier scanning methods you could already observe that is important through what type we are looking at the image. It’s no different here as you need to manually specify what type to use at the automatic lookup. You can observe this in case of the gray scale images for the following source code (the usage of the + cv::Mat::at() function):
建议不要使用最后一种方法进行扫描。这种方法一般用于获取或修改图像中的随机元素。它的基本用法是指定所需项的行号和列号访问。 在我们早期的扫描方法中,您已经可以观察到我们正在遍历什么类型的图像这一点非常重要。这里没有什么不同,因为您需要手动指定要在自动查找时使用的类型。如果是灰度图像,你可以观察到这一点以下源代码(使用cv::Mat::at()函数)

@snippet how_to_scan_images.cpp scan-random

The functions takes your input type and coordinates and calculates on the fly the address of the queried item. Then returns a reference to that. This may be a constant when you get the value and non-constant when you set the value. As a safety step in debug mode only* there is performed a check that your input coordinates are valid and does exist. If this isn’t the case you’ll get a nice output message of this on the standard error output stream. Compared to the efficient way in release mode the only difference in using this is that for every element of the image you’ll get a
new row pointer for what we use the C operator[] to acquire the column element.
这些函数获取您的输入类型和坐标,并实时计算查询项的地址 然后返回对它的引用。 当你得到值时,这可能是一个常量,当你设置值时,这可能不是一个常量。 作为调试模式的安全步骤,执行检查输入坐标是否有效且确实存在。 如果不是这样的话,你会得到一个标准错误输出流上的友好的输出消息。与release模式中的高效方法相比,使用高效方法的惟一区别是,对于图像的每个元素,您将获得一个新的行指针,我们使用C操作符[]来获取列元素。

If you need to do multiple lookups using this method for an image it may be troublesome and time consuming to enter the type and the at keyword for each of the accesses. To solve this problem OpenCV has a cv::Mat_ data type. It’s the same as Mat with the extra need that at definition you need to specify the data type through what to look at the data matrix, however in return you can use the operator() for fast access of items. To make things even better this is easily convertible from and to the usual cv::Mat data type. A sample usage of this you can see in case of the color images of the upper function. Nevertheless, it’s important to note that the same operation (with the same runtime speed) could have been done with the cv::Mat::at function. It’s just a less
to write for the lazy programmer trick.
如果您需要使用此方法对图像进行多次查找,那么为每次访问输入type和at关键字可能会很麻烦,而且很耗时。为了解决这个问题,OpenCV有一个cv::Mat_数据类型。它与Mat相同,只是需要在定义时指定数据类型,通过什么来查看数据矩阵,但是作为回报,可以使用运算符()快速访问项。为了更好地实现这一点,可以很容易地从cv::Mat数据类型转换为通常的cv::Mat数据类型。这个例子的用法你可以在上面的彩色图像函数中看到。尽管如此,重要的是要注意相同的操作(在相同的运行时速度下)可以使用cv::Mat::at函数来完成。对于懒惰的程序员来说,这只是一个小技巧。

The Core Function核心函数

This is a bonus method of achieving lookup table modification in an image. In image
processing it’s quite common that you want to modify all of a given image values to some other value. OpenCV provides a function for modifying image values, without the need to write the scanning logic of the image. We use the cv::LUT() function of the core module. First we build a Mat type of the lookup table:

@snippet how_to_scan_images.cpp table-init

Finally call the function (I is our input image and J the output one):

@snippet how_to_scan_images.cpp table-use
这是实现图像中查找表修改的额外方法。在图像处理中,通常需要将所有给定的图像值修改为其他值。OpenCV提供了一个修改图像值的函数,不需要编写图像的扫描逻辑。我们使用核心模块的cv::LUT()函数。首先,我们构建Mat类型的查找表。

Performance Difference性能差异

For the best result compile the program and run it on your own speed. To make the differences more clear, I’ve used a quite large (2560 X 1600) image. The performance presented here are for color images. For a more accurate value I’ve averaged the value I got from the call of the function for hundred times.
为了得到最好的结果,编译程序并以您自己的速度运行它。为了使区别更清楚,我使用了一个相当大的(2560 X 1600)图像。这里展示的性能是针对彩色图像的。为了得到更精确的值,我将函数调用得到的值取了100次平均值。

MethodTime
Efficient Way79.4717 milliseconds
Iterator83.7201 milliseconds
On-The-Fly RA93.7878 milliseconds
LUT function32.5759 milliseconds

We can conclude a couple of things. If possible, use the already made functions of OpenCV (instead of reinventing these). The fastest method turns out to be the LUT function. This is because the OpenCV library is multi-thread enabled via Intel Threaded Building Blocks. However, if you need to write a simple image scan prefer the pointer method. The iterator is a safer bet, however quite slower.
Using the on-the-fly reference access method for full image scan is the most costly in debug mode. In the release mode it may beat the iterator approach or not, however it surely sacrifices for this the safety trait of iterators.
我们可以得出几个结论。如果可能的话,使用OpenCV已经创建的函数(而不是重新创建这些函数)。最快的方法是使用LUT函数。这是因为OpenCV库是通过Intel线程构建块启用多线程的。不过,如果需要编写一个简单的图像扫描,最好使用指针方法。迭代器是一种更安全的选择,但是相当慢。在Debug模式下,使用实时引用访问方法进行全图像扫描是最昂贵的。在Release模式中,它可能优于迭代器方法,也可能不优于迭代器方法,但是它肯定会为此做出牺牲。

Finally, you may watch a sample run of the program on the video posted on our YouTube channel.

@youtube{fB3AN5fjgwc}

学习how_to_scan_images.cpp

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

using namespace std;
using namespace cv;

static void help()
{
    cout
        << "\n--------------------------------------------------------------------------" << endl
        << "This program shows how to scan image objects in OpenCV (cv::Mat). As use case"
        << " we take an input image and divide the native color palette (255) with the "  << endl
        << "input. Shows C operator[] method, iterators and at function for on-the-fly item address calculation."<< endl
        << "Usage:"                                                                       << endl
        << "./how_to_scan_images <imageNameToUse> <divideWith> [G]"                       << endl
        << "if you add a G parameter the image is processed in gray scale"                << endl
        << "--------------------------------------------------------------------------"   << endl
        << endl;
}

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[])
{
    help();
    if (argc < 3)
    {
        cout << "Not enough parameters" << endl;
        return -1;
    }

    Mat I, J;
    if( argc == 4 && !strcmp(argv[3],"G") )
        I = imread(argv[1], IMREAD_GRAYSCALE);
    else
        I = imread(argv[1], IMREAD_COLOR);

    if (I.empty())
    {
        cout << "The image" << argv[1] << " could not be loaded." << endl;
        return -1;
    }

    //! [dividewith]
    int divideWith = 0; // convert our input string to number - C++ style
    stringstream s;
    s << argv[2];
    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));
    //! [dividewith]

    const int times = 100;
    double t;

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)
    {
        cv::Mat clone_i = I.clone();
        J = ScanImageAndReduceC(clone_i, table);
    }

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

    cout << "Time 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;

    //! [table-init]
    Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.ptr();
    for( int i = 0; i < 256; ++i)
        p[i] = table[i];
    //! [table-init]

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)
        //! [table-use]
        LUT(I, lookUpTable, J);
        //! [table-use]

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

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

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

//! [scan-iterator]
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;
}
//! [scan-iterator]

//! [scan-random]
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;
}
//! [scan-random]

(1)我使用800*500的彩色图像测试了程序,100次平均时间如下图
在这里插入图片描述
(2)验证一下减色方案的效果,我输入图像的(0,0)处像素值为src(B|G|R) = src(162|140|128),我输入的减色值为8, 也就是dst[i] = src[i] / 8 * 8;那么对原图(0,0)使用减色公式之后(0,0)处的像素值为dst(B|G|R) = dst(160|136|128).利用ImageWatch查看一下dst中的第一个像素处的像素值确实如此。

在这里插入图片描述
在这里插入图片描述
(3)查看一下查找表的构造,确实如公式中所展现的,0-7值为0,8-15值为8。。。
在这里插入图片描述
(4)学习一下LUT函数

void cv::LUT( InputArray _src, InputArray _lut, OutputArray _dst )
{
    CV_INSTRUMENT_REGION();

    int cn = _src.channels(), depth = _src.depth();
    int lutcn = _lut.channels();

    CV_Assert( (lutcn == cn || lutcn == 1) &&
        _lut.total() == 256 && _lut.isContinuous() &&
        (depth == CV_8U || depth == CV_8S) );

    CV_OCL_RUN(_dst.isUMat() && _src.dims() <= 2,
               ocl_LUT(_src, _lut, _dst))

    Mat src = _src.getMat(), lut = _lut.getMat();
    _dst.create(src.dims, src.size, CV_MAKETYPE(_lut.depth(), cn));
    Mat dst = _dst.getMat();

    CV_OVX_RUN(!ovx::skipSmallImages<VX_KERNEL_TABLE_LOOKUP>(src.cols, src.rows),
               openvx_LUT(src, dst, lut))

#if !IPP_DISABLE_PERF_LUT
    CV_IPP_RUN(_src.dims() <= 2, ipp_lut(src, lut, dst));
#endif

    if (_src.dims() <= 2)
    {
        bool ok = false;
        LUTParallelBody body(src, lut, dst, &ok);
        if (ok)
        {
            Range all(0, dst.rows);
            if (dst.total() >= (size_t)(1<<18))
                parallel_for_(all, body, (double)std::max((size_t)1, dst.total()>>16));
            else
                body(all);
            if (ok)
                return;
        }
    }

    LUTFunc func = lutTab[lut.depth()];
    CV_Assert( func != 0 );

    const Mat* arrays[] = {&src, &dst, 0};
    uchar* ptrs[2] = {};
    NAryMatIterator it(arrays, ptrs);
    int len = (int)it.size;

    for( size_t i = 0; i < it.nplanes; i++, ++it )
        func(ptrs[0], lut.ptr(), ptrs[1], len, cn, lutcn);
}

额,源码我现在还看不懂。先了解怎么用吧,学习博客:
https://blog.csdn.net/anjisi/article/details/53899222

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值