第一件事:访问图像中的像素
1. 图像的数据,是如何以指定的颜色空间和数据类型存储在内存中的?
同一个图像的矩阵的大小取决于所用的颜色空间和数据类型,确切的说取决于通道数和深度。
灰度空间矩阵:
RGB空间矩阵:
可以发现 OpenCV 在内存中子列的通道顺序与通常习惯的 RBG 顺序相反——BGR。通常内存足够的情况下,可以实现图像在内存中的连续存储,这样极大地提高了图像的扫描速度,可以使用 isContinuous() 函数来判断矩阵在内存中是否为连续存储。
2. 颜色空间缩减
若图像是单通道的并且使用 8 位字符类型,那么一个像素位置可能有256个不同值,那么如果有三通道呢?那么一个像素位置将会有一千六百多万种可能颜色,若果我们队这些颜色进行逐一分类处理的话那么将对我们算法性能造成极其严重的影响。所以我们需要对颜色空间进行一些缩减,比如颜色值 0-9 的按0计算,10-19 的按10计算,以此类推。
在对整形 "/" 运算中,会自动去掉余数那么我们可以这样操作简单地实现上面的需求:
int b = 13;
int a = (b/10)*10 = (13/10)*10 = 1*10 = 10;
但是如果每一次都对每个像素进行这样的计算过程,也是需要很大的时间花销,而且这两种运算(乘、除)又特别费时。我们可以先将像素的可能性存在表中,需要用的时候直接从表里面取出来即可。
//先存在表里面
int divideWidth = 10;
uchar table[256];
for (int i = 0,i < 256;i++){
table[i] = divideWidth * (i/divideWidth);
}
//查找对应值
int key = 111;
int value = table[key];
3. LUT 函数:生成缩减矩阵
在上一点中生成了一个查找表,用来查找某个通道对应的缩减值而非每次都进行计算。接下来我们就需要将目标图像矩阵进行缩减生成缩减图像,思路是:
- 先生成查找表;
- 遍历目标矩阵每一个元素;
- 将目标矩阵中每一个元素在查找表中找到对应缩减值;
- 将缩减值存入新的矩阵。
很快就写出了代码。但是!之前又讲到OpenCV是一个开发 工具 包,作为工具包这点事情都不能帮我们轻松解决还算什么工具包?所以OpenCV中为我们封装了一个函数,并且这个函数OpenCV官方文档中是极力推荐我们使用的,那就是 LUT 函数,函数原型:
void LUT(InputArray src, InputArray lut, OutputArray dst);
参数 | 意义 |
---|---|
InputArray src | 源矩阵 |
InputArray lut | 查找表 |
OutputArray dst | 输出矩阵 |
示例程序:
//建立查找表
int div = 100;//值大一点效果比较明显
//LUT 函数中需要输入的是一个InputArray类型
cv::Mat luTable(1,256, CV_8U);
uchar* table = luTable.data;
for (int i = 0; i < 256; i++) {
table[i] = div * (i/div);
}
cv::Mat srcImage = cv::imread("/Users/wangxin/Desktop/1.jpg");
cv::Mat dstImage;
cv::LUT(srcImage, luTable, dstImage);
cv::imshow("srcImage", srcImage);
cv::imshow("dstImage", dstImage);
cv::waitKey();
4. 计时函数
可以利用 getTickCount() 和 getTickFrequency 函数来进行计时。
- getTickCount 函数:该函数返回CPU自某个时间以来所经历的时间周期数。
- getTickFrequency() 函数:该函数返回CPU一秒钟走的时钟周期数。这样我们就可以对某个运算进行计时了。
//起始状态的时间周期
double time0 = static_cast<double>(cv::getTickCount());
//图像处理操作
//~
//结束时的时间周期数
double time1 = static_cast<double>(cv::getTickCount());double
//计算得出花费时间
time = (time1 - time0)/cv::getTickFrequency;
//输出运行时间
cout<< "此方法运行时间为:" << time << "秒" << endl;
5. 访问像素的三种方法
- 指针访问:C操作符[];(最快)
核心代码:
int rowNumber = image.rows;
//列数*通道数
int colNumber = image.cols*image.channels();
//循环遍历像素
for(int i = 0; i < rowNumber; i++){
uchar* data = image.ptr<uchar>(i);//获取第i行首地址
for(int j = 0; j < colNUmber; j++){
//处理像素
data[j];
}
}
-
STL 中的迭代器 iterator(慢)
核心代码:
Mat_<Vec3b>::iterator it = outputImage.begen<Vec3b>();//初始位置
Mat_<Vec3b>::iterator itend = outputImage,end<Vec3b>();//结束位置
for(;it != itend;it++){
//开始处理每个像素
(*it)[0];//通道1,蓝色通道
(*it)[1];//通道2,绿色通道
(*it)[2];//通道3,红色通道
}
- 动态地址计算(最慢)
利用 at 函数
核心代码:
int rowNumber = image.rows;
//列数*通道数
int colNumber = image.cols;
//循环遍历像素
for(int i = 0; i < rowNumber; i++){
for(int j = 0; j < colNUmber; j++){
//处理像素
image.at<Vec3b>(i,j)[0];//通道1,蓝色通道
image.at<Vec3b>(i,j)[1];//通道2,绿色通道
image.at<Vec3b>(i,j)[2];//通道3,红色通道
}
}