在《OpenCV 2 Computer Vision Application Programming Cookbook》看到的例子,非常不错,算是对之前的文章<访问Mat图像中每个像素的值>的回顾和补充。
Color Reduce
还是使用经典的Reduce Color的例子,即对图像中的像素表达进行量化。如常见的RGB24图像有256×256×256中颜色,通过Reduce Color将每个通道的像素减少8倍至256/8=32种,则图像只有32×32×32种颜色。假设量化减少的倍数是N,则代码实现时就是简单的value/N*N,通常我们会再加上N/2以得到相邻的N的倍数的中间值,最后图像被量化为(256/N)×(256/N)×(256/N)种颜色。
方法零:.ptr和[]操作符
Mat最直接的访问方法是通过.ptr<>函数得到一行的指针,并用[]操作符访问某一列的像素值。
- //
using .ptr and [] - void
colorReduce0(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols * image.channels(); // total number of elements per line -
for (int j=0; j -
uchar* data= image.ptr(j); -
for (int i=0; i -
data[i]= data[i]/div*div + div/2; -
} -
} - }
方法一:.ptr和指针操作
除了[]操作符,我们可以移动指针*++的组合方法访问某一行中所有像素的值。
- //
using .ptr and * ++ - void
colorReduce1(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols * image.channels(); // total number of elements per line -
for (int j=0; j -
uchar* data= image.ptr(j); -
for (int i=0; i -
*data++= *data/div*div + div/2; -
} // end of row -
} - }
方法二:.ptr、指针操作和取模运算
方法二和方法一的访问方式相同,不同的是color reduce用模运算代替整数除法
- //
using .ptr and * ++ and modulo - void
colorReduce2(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols * image.channels(); // total number of elements per line -
for (int j=0; j -
uchar* data= image.ptr(j); -
for (int i=0; i -
int v= *data; -
*data++= v - v%div + div/2; -
} // end of row -
} - }
方法三:.ptr、指针运算和位运算
由于进行量化的单元div通常是2的整次方,因此所有的乘法和除法都可以用位运算表示。
- //
using .ptr and * ++ and bitwise - void
colorReduce3(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols * image.channels(); // total number of elements per line -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
for (int j=0; j -
uchar* data= image.ptr(j); -
for (int i=0; i -
*data++= *data&mask + div/2; -
} // end of row -
} - }
方法四:指针运算
方法四和方法三量化处理的方法相同,不同的是用指针运算代替*++操作。
- //
direct pointer arithmetic - void
colorReduce4(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols * image.channels(); // total number of elements per line -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
int step= image.step; // effective width -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
// get the pointer to the image buffer -
uchar *data= image.data; -
for (int j=0; j -
for (int i=0; i -
*(data+i)= *data&mask + div/2; -
} // end of row -
data+= step; // next line -
} - }
方法五:.ptr、*++、位运算以及image.cols * image.channels()
这种方法就是没有计算nc,基本是个充数的方法。
- //
using .ptr and * ++ and bitwise with image.cols * image.channels() - void
colorReduce5(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
for (int j=0; j -
uchar* data= image.ptr(j); -
for (int i=0; i -
*data++= *data&mask + div/2; -
} // end of row -
} - }
方法六:连续图像
Mat提供了isContinuous()函数用来查看Mat在内存中是不是连续存储,如果是则图片被存储在一行中。
- //
using .ptr and * ++ and bitwise (continuous) - void
colorReduce6(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols * image.channels(); // total number of elements per line -
if (image.isContinuous()) { -
// then no padded pixels -
nc= nc*nr; -
nr= 1; // it is now a 1D array -
} -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
for (int j=0; j -
uchar* data= image.ptr(j); -
for (int i=0; i -
*data++= *data&mask + div/2; -
} // end of row -
} - }
方法七:continuous+channels
与方法六基本相同,也是充数的。
- //
using .ptr and * ++ and bitwise (continuous+channels) - void
colorReduce7(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols ; // number of columns -
if (image.isContinuous()) { -
// then no padded pixels -
nc= nc*nr; -
nr= 1; // it is now a 1D array -
} -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
for (int j=0; j -
uchar* data= image.ptr(j); -
for (int i=0; i -
*data++= *data&mask + div/2; -
*data++= *data&mask + div/2; -
*data++= *data&mask + div/2; -
} // end of row -
} - }
方法八:Mat _iterator
真正有区别的方法来啦,用Mat提供的迭代器代替前面的[]操作符或指针,血统纯正的官方方法~
- //
using Mat_ iterator - void
colorReduce8(cv::Mat &image, int div=64) { -
// get iterators -
cv::Mat_::iterator it= image.begin(); -
cv::Mat_::iterator itend= image.end(); -
for ( ; it!= itend; ++it) { -
(*it)[0]= (*it)[0]/div*div + div/2; -
(*it)[1]= (*it)[1]/div*div + div/2; -
(*it)[2]= (*it)[2]/div*div + div/2; -
} - }
方法九:Mat_ iterator 和位运算
把方法八中的乘除法换成位运算。
- //
using Mat_ iterator and bitwise - void
colorReduce9(cv::Mat &image, int div=64) { -
// div must be a power of 2 -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
// get iterators -
cv::Mat_::iterator it= image.begin(); -
cv::Mat_::iterator itend= image.end(); -
for ( ; it!= itend; ++it) { -
(*it)[0]= (*it)[0]&mask + div/2; -
(*it)[1]= (*it)[1]&mask + div/2; -
(*it)[2]= (*it)[2]&mask + div/2; -
} - }
方法十:MatIterator_
和方法八基本相同。
- //
using MatIterator_ - void
colorReduce10(cv::Mat &image, int div=64) { -
cv::Mat_ cimage= image; -
cv::Mat_::iterator it=cimage.begin(); -
cv::Mat_::iterator itend=cimage.end(); -
for ( ; it!= itend; it++) { -
(*it)[0]= (*it)[0]/div*div + div/2; -
(*it)[1]= (*it)[1]/div*div + div/2; -
(*it)[2]= (*it)[2]/div*div + div/2; -
} - }
方法十一:图像坐标
- //
using (j,i) - void
colorReduce11(cv::Mat &image, int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols; // number of columns -
for (int j=0; j -
for (int i=0; i -
image.at(j,i)[0]= image.at(j,i)[0]/div*div + div/2; -
image.at(j,i)[1]= image.at(j,i)[1]/div*div + div/2; -
image.at(j,i)[2]= image.at(j,i)[2]/div*div + div/2; -
} // end of row -
} - }
方法十二:创建输出图像
之前的方法都是直接修改原图,方法十二新建了输出图像,主要用于后面的时间对比。
- //
with input/ouput images - void
colorReduce12(const cv::Mat &image, // input image -
cv::Mat &result, // output image -
int div=64) { -
int nr= image.rows; // number of rows -
int nc= image.cols ; // number of columns -
// allocate output image if necessary -
result.create(image.rows,image.cols,image.type()); -
// created images have no padded pixels -
nc= nc*nr; -
nr= 1; // it is now a 1D array -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
for (int j=0; j -
uchar* data= result.ptr(j); -
const uchar* idata= image.ptr(j); -
for (int i=0; i -
*data++= (*idata++)&mask + div/2; -
*data++= (*idata++)&mask + div/2; -
*data++= (*idata++)&mask + div/2; -
} // end of row -
} - }
方法十三:重载操作符
Mat重载了+&等操作符,可以直接将两个Scalar(B,G,R)数据进行位运算和数学运算。
- //
using overloaded operators - void
colorReduce13(cv::Mat &image, int div=64) { -
int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0)); -
// mask used to round the pixel value -
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0 -
// perform color reduction -
image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2); - }
时间对比
通过迭代二十次取平均时间,得到每种方法是运算时间如下。
可以看到,指针*++访问和位运算是最快的方法;而不断的计算image.cols*image.channles()花费了大量重复的时间;另外迭代器访问虽然安全,但性能远低于指针运算;通过图像坐标(j,i)访问时最慢的,使用重载操作符直接运算效率最高。