目录
OpenCV如何扫描图像、利用查找表和计时
-
如何计算函数运行时间?
-
Mat图像如何存储?
-
如何高效遍历图像像素?
-
查找表是什么?为什么要用它?
1.函数计算时间测试case
OpenCV提供了两个简便的可用于计时的函数 getTickCount() 和 getTickFrequency() 。
第一个函数返回你的CPU自某个事件(如启动电脑)以来走过的时钟周期数.
第二个函数返回你的CPU一秒钟所走的时钟周期数。这样,我们就能轻松地以秒为单位对某运算计时。
double t = (double)getTickCount(); // 做点什么 ... t = ((double)getTickCount() - t)/getTickFrequency(); cout << "Times passed in seconds: " << t << endl;
2. Mat图像的存储机理
图像矩阵的大小取决于我们所用的颜色模型,确切地说,取决于所用通道数。如果是灰度图像,矩阵就会像这样:
而对多通道图像来说,矩阵中的列会包含多个子列,其子列个数与通道数相等。例如,RGB颜色模型的矩阵:
注意到,子列的通道顺序是反过来的:BGR而不是RGB。很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用 isContinuous() 来去判断矩阵是否是连续存储的. 实例见4。
3. 像素遍历的3--4种方式
3.1高效的方法 Efficient Way
说到性能,经典的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 * channels; int nCols = I.cols; 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; }
这里,我们获取了每一行开始处的指针,然后遍历至该行末尾。如果矩阵是以连续方式存储的,我们只需请求一次指针、然后一路遍历下去就行。彩色图像的情况有必要加以注意:因为三个通道的原因,我们需要遍历的元素数目也是3倍。
这里有另外一种方法来实现遍历功能,就是使用 data , data会从 Mat 中返回指向矩阵第一行第一列的指针。注意如果该指针为NULL则表明对象里面无输入,所以这是一种简单的检查图像是否被成功读入的方法。当矩阵是连续存储时,我们就可以通过遍历 data 来扫描整个图像。例如,一个灰度图像,其操作如下:
uchar* p = I.data; for( unsigned int i =0; i < ncol*nrows; ++i) *p++ = table[*p];
这回得出和前面相同的结果。但是这种方法编写的代码可读性方面差,并且进一步操作困难。同时,在实际应用中,该方法的性能表现上并不明显优于前一种(因为现在大多数编译器都会对这类操作做出优化)。
因此推荐采用该方法遍历
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]]; } }
3.2迭代法 The iterator (safe) method
在高性能法(the efficient way)中,我们可以通过遍历正确的 uchar 域并跳过行与行之间可能的空缺-你必须自己来确认是否有空缺,来实现图像扫描,迭代法则被认为是一种以更安全的方式来实现这一功能。在迭代法中,你所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。
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; }
对于彩色图像中的一行,每列中有3个uchar元素,这可以被认为是一个小的包含uchar元素的vector,OpenCV中用 Vec3b 来命名。如果要访问第n个子列,我们只需要简单的利用[]来操作就可以。需要指出的是,OpenCV的迭代在扫描过一行中所有列后会自动跳至下一行,所以说如果在彩色图像中如果只使用一个简单的 uchar 而不是 Vec3b 迭代的话就只能获得蓝色通道(B)里的值。
3.3通过相关返回值的On-the-fly地址计算
事实上这个方法并不推荐被用来进行图像扫描,它本来是被用于获取或更改图像中的随机元素。它的基本用途是要确定你试图访问的元素的所在行数与列数。在前面的扫描方法中,我们观察到知道所查询的图像数据类型是很重要的。这里同样的你得手动指定好你要查找的数据类型。下面的代码中是一个关于灰度图像的示例(运用 + at() 函数):
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; }
该函数输入为数据类型及需求元素的坐标,返回的是一个对应的值-如果用 get 则是constant,如果是用 set 、则为non-constant. 处于程序安全,当且仅当在 debug 模式下 它会检查你的输入坐标是否有效或者超出范围. 如果坐标有误,则会输出一个标准的错误信息. 和高性能法(the efficient way)相比, 在 release模式下,它们之间的区别仅仅是On-the-fly方法对于图像矩阵的每个元素,都会获取一个新的行指针,通过该指针和[]操作来获取列元素.
当你对一张图片进行多次查询操作时,为避免反复输入数据类型和at带来的麻烦和浪费的时间,OpenCV 提供了:basicstructures:Mat_ <id3> data type. 它同样可以被用于获知矩阵的数据类型,你可以简单利用()操作返回值来快速获取查询结果。
3.4 核心函数LUT(The Core Function)
这是最被推荐的用于实现批量图像元素查找和更该操作图像方法。在图像处理中,对于一个给定的值,将其替换成其他的值是一个很常见的操作,OpenCV 提供里一个函数直接实现该操作,并不需要你自己扫描图像,就是:operationsOnArrays:LUT() <lut> ,一个包含于core module的函数. 首先我们建立一个mat型用于查表:
Mat lookUpTable(1, 256, CV_8U); uchar* p = lookUpTable.data; for( int i = 0; i < 256; ++i) p[i] = table[i];
然后我们调用函数 (I 是输入 J 是输出):
LUT(I, lookUpTable, J);
4. 实例
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> #include <sstream> using namespace std; using namespace cv; 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 << "./howToScanImages 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], CV_LOAD_IMAGE_GRAYSCALE); else I = imread(argv[1], CV_LOAD_IMAGE_COLOR); if (!I.data) { cout << "The image" << argv[1] << " could not be loaded." << endl; return -1; } int divideWith; // convert our input string to number - C++ style stringstream s; s << argv[2]; 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; t = (double)getTickCount(); for (int i = 0; i < times; ++i) J = ScanImageAndReduceC(I.clone(), 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) J = ScanImageAndReduceIterator(I.clone(), 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) ScanImageAndReduceRandomAccess(I.clone(), 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.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(); t /= times; cout << "Time of reducing with the LUT function (averaged for " << times << " runs): " << t << " milliseconds."<< endl; 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 * channels; int nCols = I.cols; 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; }