言: OpenCV3编程入门已经进入第五章 (Core组件进阶) 学习了,后面也越来越多对图像的处理,这一节主要是对像素的认识。
一、访问图形中的像素
1、图像在内存之中的存储方式
图像矩阵的大小取决于所用的颜色模型,也就是说取决于所用的通道数。如下灰度图像:
对于多通道的图像,矩阵中的列会包含多个子列,子列个数与通道数相等。
2、颜色空间缩减
(1)问题提出:对于单通道像素,有256个不同的值,若三通道图像,颜色的存储值就有一千六百万多种。
解决: 如此之多的颜色数值,但是达到同样效果的只占很小一部分。颜色空间缩减(color space reduction)可以大大降低运算复杂度。它的做法:将现有颜色空间值除以某个输入值,以获得较少的颜色数,比如:0-9可以取0,10-19取10,以此类推。
(2)uchar类型的三通道图像,每个通道取值0~255,也就是256x256不同的值。
- 0~9范围的像素值为0
- 10~19范围的像素值是10
- 20~29范围的像素值是20
这样操作,将颜色取值降低为26x26x26种情况。即:Inew=(Iold/10)*10。因为uchar类型的值除以int值,结果仍是char类型,所以求出来的小数也要向下取整。
(3)为了防止处理图像像素时,每一步计算都会花销时间,于是可以把计算好的结果提前存入表table中,这样不需要计算,直接取结果即可。
// 1、遍历图像矩阵的每一个像素 // 2、对像素应用上述公式 int divideWith=10; uchar table[256]; for(int i=0;i<256;i++){ table[i]=divideWith*(i/divideWith);//table[i]存放值为i像素减小颜色空间结果 } //p[j]=table[p[j]];
总结:对于较大的图像,有效的方法是预先计算所有可能的值,然后需要这些值的时候,利用查找表直接赋值即可。
3、LUT函数:Look up table操作
说明:函数原型为:operationsOnArrays:LUT()的函数来进行,用于批量进行图像元素查找、扫描与操作图像。
//首先建立一个Mat型用于查表 Mat lookUpTable(1,256,CV_8U); uchar* p=lookUpTable.data; for(int i=0;i<256;i++){ p[i]=table[i]; } //然后调用函数(I输入,J输出) for(int i=0;i<times;i++){ LUT(I,lookUpTable,J); }
这个地方书上讲的有点模糊,不是很懂,就去参考其他大佬
(1)对于单通道图像,其像素灰度值为0-255,LUT函数可以将函数的一个灰度值转换成其他灰度值。如:将一张图片0-100的像素的灰度值变成0,101-200的像素变成100,201-255的像素变成255。
(2)对于多通道图像,其内部每一个通道都做这样的映射,这样每一个通道的处理就和单通道差不多了。
(3)LUT函数为:
void LUT(InputArray src,InpitArray lut,OutputArray dst);
- src: 输入图像(单通道或者3通道)
- lut: 表示查找表,下面会用具体示例介绍
- dst: 输出图像
(4)单通道图像处理
#include<opencv2/highgui.hpp> using namespace cv; int main() { uchar lutData[256]; for (int i = 0; i < 256; i++) { if (i <= 100) lutData[i] = 0; if (i > 100 && i <= 200) lutData[i] = 100; if (i > 200) lutData[i] = 255; } Mat lut(1, 256, CV_8UC1); Mat a = imread("12.jpg", CV_LOAD_IMAGE_GRAYSCALE),b; namedWindow("原灰度图像", CV_WINDOW_AUTOSIZE); namedWindow("转灰度图像", CV_WINDOW_AUTOSIZE); imshow("原灰度图像", a); LUT(a, lut, b); imshow("转灰度图像", b); waitKey(0); }
4、计时函数
说明:计时函数:getTickCount()和getTickFrequency()
- getTickCount()函数返回CPU自某个事件(如启动电脑)以来走过的时钟周期数
- getTickFrequency()函数返回CPU一秒钟走的时钟周期数,我们就可以以秒为单位对某运算计时。
double time0=static_cast<double>(getTickCount());//记录起始时间 //进行图像处理操作。。。。 time0=((double)getTickCount()-time0)/getTickFrequency(); printf("输出运行时间%f\n",time0);
5、访问图像像素的三类方法
说明:任何图像处理算法,都是从操作每个像素开始的。
提供三种访问每个像素的方法
- 指针访问:C操作符[ ];
- 迭代器iterator;
- 动态地址计算
这三种方法在访问速度上略有差异,在debug模式下,比较明显。程序的目的是减少图像中颜色的数量,比如原来的图像是256种颜色,变成64只需要将原来的颜色除以4再乘以4.
示例程序:主程序中调用colorReduce函数来完成减少颜色的工作。
#include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> #include<iostream> using namespace cv; using namespace std; //全局函数声明 void colorReduce(Mat &inputImage, Mat& outputImage, int div); int main() { Mat srcImage = imread("12.jpg"); imshow("原始图像",srcImage); //将原始图的参数规格来创建效果图 Mat dstImage; dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());//效果图的大小、类型和原图片相同 //记录起始时间 double time0 = static_cast<double>(getTickCount()); //调用颜色空间缩减函数 colorReduce(srcImage, dstImage, 128); //计算运行时间并输出 time0 = ((double)getTickCount() - time0) / getTickFrequency(); printf("此方法运行时间为:%f\n", time0); imshow("效果图",dstImage); waitKey(0); }
(1)用指针访问像素
说明:用指针访问像素发这种方法利用的是C语言操作符[ ],这种方法比较快。
void colorReduce(Mat &inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone();//复制实参到临时变量 int rowNumber = outputImage.rows;//行数 int colNumber = outputImage.cols*outputImage.channels();//列数x通道数=每一行元素的个数 //双重循环,遍历所有的像素值 for (int i = 0; i < rowNumber; i++) {//行循环 uchar* data = outputImage.ptr<uchar>(i);//获取第i行的首地址 for (int j = 0; j < colNumber; j++) { //列循环 //处理每一个像素 data[j] = data[j] / div * div + div / 2; } } }
补充:简化指针运算,Mat类提供了ptr函数可以得到图像任意行的首地址。
uchar* data = outputImage.ptr<uchar>(i);//获取第i行的首地址
(2)用迭代器操作像素
说明:在迭代法中,获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前。
/* 迭代器 */ void colorReduce(Mat &inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone();//复制实参到临时变量 //获取迭代器 Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>();//初始位置迭代器 Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>();//终止位置迭代器 //获取彩色图像像素 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;//红 } }
(3)动态地址计算
说明:使用动态地址运算配合at方法的colorReduce函数的代码,这种方法简洁,便于对像素的直观认识。
/* 动态地址法 */ void colorReduce(Mat &inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone();//复制实参到临时变量 int rowNumber = outputImage.rows;//行数 int colNumber = outputImage.cols;//行数 //获取彩色图像像素 for (int i = 0; i < rowNumber; i++) { for (int j = 0; j < colNumber; j++) { //开始处理每一个像素,0/1/2表示通道数 outputImage.at<Vec3b>(i, j)[0] = outputImage.at<Vec3b>(i, j)[0] / div * div + div / 2;//蓝色通道 outputImage.at<Vec3b>(i, j)[1] = outputImage.at<Vec3b>(i, j)[1] / div * div + div / 2;//绿色通道 outputImage.at<Vec3b>(i, j)[2] = outputImage.at<Vec3b>(i, j)[2] / div * div + div / 2;//红色通道 } } }
补充:
- Vec3b:由三个unsigned char组成的向量
- image.at(i,j):取出灰度图像中i行j列的点。
- image.at(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点,k=[0,1,2],分别代表B,G,R。
实际效果: