您的位置:首页 > 运维架构

opencv学习(三)之图像像素遍历(颜色空间缩减、查找表)

2016-11-06 21:55 295 查看
在图像处理中不可避免的要涉及到对图像像素的操作,这篇文章将介绍对图像像素的访问及遍历图像像素的方法。

1.颜色空间缩减及查找表

设想一种简单的C\C++类型的无符号字符型矩阵的存储结构,对于单通道图像而言,图像像素最多可以由256个像素值。如果图像是三通道图像,那么图像像素存储的颜色可以达到惊人的1600w。处理如此多的颜色类型对于算法的运算是一种沉重的负担。有时候我们可以找到一些既能够降低颜色数量但是并不会影响其处理结果的方法。通常我们缩减颜色空间。这就意味着我们用新输入的数值和更少的颜色来划分当前的颜色空间。

例如我们可以将值在0-9范围内的像素值看做0,将值位于10-19范围内的像素值看做10等等。当我们用int类型的数值代替uchar(unsigned char-值位于0-255之间)类型得到的结果仍为char类型。这些数值只是char类型的值,所以求出来的小数要向下取整。公式可以总结如下:



遍历整幅图像像素并应用上述公式就是一个简单的颜色空间缩减算法。对于较大的图像需要在执行操作可以前提前计算好其像素值存储到查找表中。查找变是一种简单的数组(可能是一维或多维),对于给定的输入变量给出最终的输出值。在进行图像处理时,像素取值范围在0-255之间其实就是一共有256种情况,所以将这些计算结果提前存储于查找表中,进行图像处理时,不需要重新计算像素值,可以直接从查找表调用。其优势在于只进行读取操作,不进行运算。

结合上述公和查找表如下:

int divideWith = 0;
stringstream s;
s << argv[2];
s >> divideWith;
if(!s || !divideWith)
{
cout << "输入的划分间隔无效." << endl;
return -1;
}
uchar table[256];
for(int i = 0;i < 256; ++i)
table[i] = (uchar)(divideWith * (i * divideWith));


程序中table[i]存放的是值为i的像素缩减空间的结果。例如i = 25,则table[i]=20.这样看来颜色空间缩减算法可分为两部分:

(1).遍历图像矩阵像素

(2).将上述公式应用于每个像素

值得注意的是,此公式用到了乘法和除法,而这两种计算方式相对来讲比较费时,所以在设计像素缩减空间算法时,应尽量使用加减和赋值运算代替。

2.opencv计时函数

在上面分析中提到用乘除法会加大程序的耗时,那么怎么计算程序运行中的耗时呢?opencv中提供了两个简便的计时函数getTickCount()和getTickFrequency()。其中getTickCount()用来获取CPU时钟周期,getTickFrequency()函数用来获取CPU时钟频率。这样就能以秒为单位对程序运行进行耗时分析,其用法如下:

double t = (double)getTickCount();
//...
//program...
//...
t = ((double)getTickCount()-t)/getTickFrequency();


3.图像矩阵在内存中的存储方式

opencv学习(一)之Mat类中介绍Mat类的创建等内容,同时也应该能够了解到图像的数据结构为Mat类,是一种矩阵结构。图像矩阵大小取决于所用的颜色模型,更确切的来说是取决于图像所用通道数。如果是灰度图像,其矩阵结构如下图所示:



对于多通道图像来说,矩阵的列会包含多个子列,其子列个数与通道数相等。RGB颜色模型矩阵如下图所示:



在opencv中图像的RGB顺序如上图所示正好是反过来的,其排序为BGR。在很多情况下可以如果内存足够大可以实现连续存储。连续存储有助于提升图像扫描速度,可以使用isContinuous()来判断矩阵是否是连续存储。

当涉及到程序性能时,没有比C风格的操作符”[]”(指针)更高效了。测试代码如下:

#include <iostream>
#include <cstring>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

Mat& ScanImageAndReduce(Mat& I,const uchar* const table);

int main(int argc, char** argv)
{
int divideWith = 0;

Mat srcImage = imread("lena.jpg");

//image is load sucessful?
if(srcImage.data)
cout << "Success" << endl;
else
return -1;

imshow("srcImage",srcImage);

cout << "input divideWith: ";
cin >> divideWith;

if(!divideWith)
{
cout << "输入的划分间隔无效." << endl;
return -1;
}
uchar table[256];
for(int i = 0;i < 256; ++i)
table[i] = (uchar)(divideWith * (i * divideWith));

ScanImageAndReduce(srcImage,table);
waitKey(0);

return 0;
}

Mat& ScanImageAndReduce(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);        //获取矩阵第i行的首地址
for(j = 0; j < nCols; ++j)  //列循环进行处理
{
p[j] = table[p[j]];
}
}
imshow("reduce-100",I);         //根据输入值对窗口名字进行更改

return I;
}


当设置不同的结果时,其最终输出结果不同如下所示:





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息