使用LUT(lookup table)检索表的方法,提高color reduce时对像素读取的速度。

实现对Mat对象中数据的读取,并计算color reduce的速度。

方法一:使用Mat的ptr()遍历行(row),效率较高,使用时需要小心细节

 #include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <sstream> using namespace std;
using namespace cv; static void help(){
cout
<< "Author: BJTShang" << endl
<< "2016-12-22, CityU" << endl
<< "Use: " << "./1222_LUP imageName divideWith [G]" << endl
<<endl;
} Mat& ScanImageAndReduceC(Mat& I, const uchar* table);
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table); int main(int argc, char** argv){
help();
if(argc<){
cout << "Not enough parameters!" << endl;
return -;
} Mat I;
char* imageName = argv[];
if(argc== && !strcmp(argv[],"G")){
I = imread(imageName, CV_LOAD_IMAGE_GRAYSCALE);
}else{
I = imread(imageName, CV_LOAD_IMAGE_COLOR);
} if(!I.data){
cout << "The image" << imageName << " has no data!";
return -;
} int divideWith = ;
stringstream s;
s << argv[];
s >> divideWith;
if(!s || !divideWith){
cout << "Invalid divideWith, input again (positive integer)!" << endl;
return -;
} // use this table to search for (by simple assignment) reduced intensity,
// instead of calculating for each pixel, which is computational high-cost
uchar table[];
for(int i=; i < ; ++i){
table[i] = uchar((i/divideWith)*divideWith);
} int64 t0 = getTickCount();
Mat J = I.clone();
J = ScanImageAndReduceC(J, table);
double t = (double)(getTickCount() - t0)/getTickFrequency();
cout << "Elapse time = " << t* << " ms" <<endl; namedWindow("before", CV_WINDOW_AUTOSIZE);
namedWindow("after color reduce by LUT", CV_WINDOW_AUTOSIZE);
imshow("before", I);
imshow("after color reduce by LUT", J);
waitKey();
return ;
} Mat& ScanImageAndReduceC(Mat& I, const uchar* table){
CV_Assert(I.depth() == CV_8U);
const int channels = I.channels(); int nRows = I.rows;
int nCols = I.cols*channels; if (I.isContinuous()){
nCols *= nRows;
nRows = ;
} uchar* p = NULL;
for(size_t i=; i<nRows; ++i){
p = I.ptr<uchar>(i);
for(size_t j=; j<nCols; ++j){
p[j] = table[p[j]];
}
}
// Mat结构的ptr()方法,返回指向Mat每一行的头元素指针
// Mat结构的data属性,返回指向元素的指针,p++指针自加到下一块元素地址。
// 如果Mat中元素连续,才可以使用*p++取出所有元素值;若Mat中元素不连续,*p++取出第二行开始肯定错误!
// uchar* p = I.data;
// for(size_t i = 0; i < nRows*nCols; ++i){
// *p++ = table[*p];
// }
return I;
}

结果:

读取图像,LUT以及计算耗时-LMLPHP

如果使用*p++的方法,结果不同:

读取图像,LUT以及计算耗时-LMLPHP

这种方法,需要自己考虑图像每一行之间的gap,以及元素类型(uchar和float32f的不同)。效率较高,但不小心会出问题。

方法二:通过MatIterator类安全地访问Mat结构

效果相同,但是运行时间会变长几倍(我的电脑上大约是3倍)

 Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table){
CV_Assert(I.depth() == CV_8U);
int channels = I.channels(); switch(channels){
case :
{
MatIterator_<uchar> it,end;
for(it = I.begin<uchar>(), end = I.end<uchar>(); it!=end; ++it)
*it = table[*it];
break;
}
case :
{
MatIterator_<Vec3b> it, end;
for(it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it!=end; ++it){
(*it)[] = table[(*it)[]];
(*it)[] = table[(*it)[]];
(*it)[] = table[(*it)[]];
}
break;
}
}
return I;
}

对于3通道彩色图像,需要指定迭代器中元素类型为short vector: <Vec3b>。如果指定为uchar,迭代器器将只会扫描B蓝色通道;当然,这里的情况因为使用了[]操作符访问short vector中的sub colomun, 编译就通不过。。。

方法三:最容易理解,但是最不推荐的是使用Mat的at()方法

 Mat& ScanImageAndReduceRandomAccsee(Mat& I, const uchar* table){
CV_Assert(I.depth() == CV_8U); const int channels = I.channels();
switch(channels){
case :
{
for(int i=; i<I.rows; ++i)
for(int j=; j<I.cols; ++j)
I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
break;
}
case :
{
Mat_<Vec3b> _I = I; // this was used to check the data (3 channels), as well as to use [] operator to access different channels
for(int i=; i<I.rows; ++i)
for(int j=; j<I.cols; ++j){
_I(i,j)[] = table[_I(i,j)[]]; // equal to _I.at(i,j*3) = table[_I.at(i,j*3)]
_I(i,j)[] = table[_I(i,j)[]]; // equal to _I.at(i,J*3+1) = table[_I.at(i,j*3+1)]
_I(i,j)[] = table[_I(i,j)[]]; // equal to _I.at(i,j*3+2) = table[_I.at(i,j*3+2)]
}
I = _I;
break;
}
}
return I;
}

效率和安全的使用MatIterator类差不多,想访问矩阵中特定位置的元素使用这种方法是比较合适的。

最后,OpenCV封装了LUT的color reduce方法。平常直接使用即可,效率比较高。

   Mat lookUpTable(, , CV_8U);
uchar* p = lookUpTable.data;
for(int i=; i<; i++)
p[i] = table[i];
LUT(I,lookUpTable,J);
05-06 08:00