介绍图像,当然要介绍如何访问图像的像素了~~
1、像素在内存中的存储方式
我们知道,图像是由像素组成。对于一幅图片,我们看到的是场景、颜色等,但是计算机看到的是一个大矩阵,矩阵包含的数据成千上万。那么,图像中的像素在计算机内存中是如何存储的呢?
对于一幅灰度图像,即黑白图片(每个像素都有一个采样颜色,为单通道图像),每个像素值 Mat(i,j) 就是一个灰度值。如下图所示。灰度值一般范围为 0~255,白色为 255 ,黑色为0。
但是彩色图像每个像素是有分量的。什么意思呢?以 RGB 图像为例,图像具有三通道,即每个像素都是由色彩分量 R、G、B组成,也就是说,每个像素都是由三个值组成的,但是最后反映到像素上,却是一个值。需要注意的是,第一个通道是 B 蓝色分量,第三个通道是 R 分量
通常情况下,uchar 类型的三通道图像,RGB各有256级亮度,用数字表示为从0、1、2...直到255。按照计算,256级的RGB色彩总共能组合出约1678万种色彩,即256×256×256=16777216。通常也被简称为1600万色或千万色。也称为24位色(2的24次方)。
2、颜色空间缩减
我们知道,如果是 uchar 类型的三通道图像,存储像素颜色就有255 * 255 *255 = 1600万 多个,这大大增加了计算的复杂度,对算法性能的影响非常大。因此我们可以采用颜色空间缩减。具体做法就是:将颜色空间值除以某个值,来获得较少的颜色数。举个例子,0~10,我取0,10 ~19我取10,,依次类推吧。如果这样的话,就将颜色的取值减少为 26*26 *26。有人说,这样不就降低图像的质量了吗?对,没错,效率和质量有时候是很难共存的。
我们可以利用 int 处罚截余达到我们的目的。代码如下:
uchar table[256]; int n = 10; for(int i = 0;i < 256;i++) { table[i] = n * ( i/n); } 利用这种查表法,预先计算所有的可能值,只需要读取,不需要计算,可以大大较少图像的运算。对于此类操作,OpenCV 推荐使用 LUT函数:Look Up Table。程序如下: Mat lookUpTable(1,256,CV_8U); uchar *p = lookUpTable.date; for(int i = 0; i < 256;++i) { p[i] = table[i]; } for(int i = 0;i < times;i++) LUT(I, lookUpTable,J);//I 是输入,J 是输出 3、遍历像素的方法很显然,如果仅仅利用一个for()循环,把一张图像的每一行、每一列都遍历一遍,显然是非常愚蠢的行为。因为效率会非常非常低!在 OpenCV中,提供了三种方式进行像素的遍历,具体来看一下。
1)动态地址法:利用下标 M.at<cv::Vec3b>(i,j)
先说一下 Mat::at()函数是个什么鬼引用。at() 返回一个数组元素的!image.at<cv::Vec3b>(i,j)[k] 表示的是取出图像image 第 i 行 j 列第 k 个通道的颜色点。其中cv::Vec3b 是图像像素值类型,其函数模板是 typedef Vec<uchar,3>Vec3b,表示3通道 uchar 类型。
直接上程序:
int main() { Mat img = imread("test.jpg"); //动态地址法 Mat img1 = img.clone(); int rows1 = img1.rows; int cols1 = img1.cols; for(int i = 0;i < rows1;i++) { for(int j = 0;j < cols1;j++) { img1.at<Vec3b>(i,j)[0] = 255-img1.at<Vec3b>(i,j)[0]; img1.at<Vec3b>(i,j)[1] = 255-img1.at<Vec3b>(i,j)[1]; img1.at<Vec3b>(i,j)[2] = 255-img1.at<Vec3b>(i,j)[2]; } } imshow("变换后的图像",img1); imwrite("mn.jpg",img1); waitKey(); return 0; } 这个程序实现的是对对每一个像素取反。最终得到的图像是这样的(第一幅为原图,第二个为变换后的图),感觉变帅了点啊哈哈哈
2)指针遍历法:利用 Mat :: ptr<type>
直接上程序
//指针遍历方法 int cols2 = cols1 * img1.channels(); //因为是三通道的,所以,元素数是行数的3倍 for(int i = 0;i < rows1;i++) { uchar *date = img1.ptr<uchar>(i); for(int j = 0;j < cols2;j++) { date[j] = 255 - date[j]; } } imshow("mndeng.jpg",img1); waitKey(); return 0; } 得到的结果和原来是一样的。这里强调一下,彩色图像都是有通道数的,因此,行元素的个数是图像行数 × 通道数3)使用迭代器
这个方法不赘述,迭代器是 C++11 标准里面才有的,我也不喜欢用,算法又耗费时间。所以,在这里就不说了。
那么,哪种方法遍历像素最节省时间呢,当然是指针法了。接下来,咱们用计时函数测试一下。OpenCV 中提供了两个计时函数 getTickCount() 和 getTickFrequency()函数, getTickCount() 函数是返回CPU自某个事件以来时钟周期数(即周期T);getTickFrequency()函数是返回CPU一秒钟走的时钟周期数(即频率),我们把这两个函数结合起来使用,就可以记录运行时间。用法如下:
double time = static_cast<double>(getTickCount());*************开始图像处理 ***************** time = ((double)getTickCount() - time)/getTickFrequency(); cout << "运行时间" << time << endl;完整的程序如下:
#include <opencv2/opencv.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> #include <vector> using namespace std; using namespace cv; int main() { Mat img = imread("test.jpg"); Mat img1 = img.clone(); int rows1 = img1.rows; int cols1 = img1.cols; double time = static_cast<double>(getTickCount()); //动态地址法 for(int i = 0;i < rows1;i++) { for(int j = 0;j < cols1;j++) { img1.at<Vec3b>(i,j)[0] = 255-img1.at<Vec3b>(i,j)[0]; img1.at<Vec3b>(i,j)[1] = 255-img1.at<Vec3b>(i,j)[1]; img1.at<Vec3b>(i,j)[2] = 255-img1.at<Vec3b>(i,j)[2]; } } time = ((double)getTickCount() - time)/getTickFrequency(); cout << "动态地址法遍历像素运行时间" << time << endl; //0.0319265 s // imshow("变换后的图像",img1); // imwrite("mn.jpg",img1); /* //指针遍历方法 int cols2 = cols1 * img1.channels(); //因为是三通道的,所以,元素数是行数的3倍 for(int i = 0;i < rows1;i++) { uchar *date = img1.ptr<uchar>(i); for(int j = 0;j < cols2;j++) { date[j] = 255 - date[j]; } } time = ((double)getTickCount() - time)/getTickFrequency(); cout << "指针法遍历像素运行时间" << time << endl; // 0.00685272 s imshow("mndeng.jpg",img1); */ waitKey(); return 0; } 从实验数据可以看出来,利用指针法遍历像素,效率非常高。建议大家使用!