这次我们主要学习使用opencv的图片读取。
Opencv 提供了imread 函数能够很快的读取图片,你可以用Mat类型的对象进行接收,imread函数的原型是:
1 Mat imread(const string& filename, int flags);
第一个参数是文件名称第二个是读取的方式,Imread函数使用是:
1 Mat img = imread(filename);
如果你读入一个jpg文件,缺省情况下将创建一个3通道图像。如果你需要灰度(单通道)图像,使用如下语句:
Mat img = imread(filename,0);
现在你会读入图像了,那么下一步必定想知道如何对图像进行处理,要进行处理就必须先知道如何遍历图像,会遍历图像的话那么你就会将复杂的算法和逻辑运用到你要处理的图像上去了,然后你就可以完成你要实现的功能。
回归到正题,如果你要对图像进行操作那你就先必须用一个指针指向你要遍历的图像地址。然后对图像的每一个像素进行遍历(在此我们进行行遍历,也就是一行行的扫描每一个像素),然而我们又知道图片的每一个像素都是由三个通道(对应自然界的三原色
RGB)构成的,在opencv中这三个通道的顺序并不是RGB而是BGR,至于为什么,额~,我也不知道,以后知道了补上。然后计算机会通过RGB(R,G,B)这个函数求出一个值这就是RGB颜色空间的值,也就是这个点像素的值。所以我们遍历像素时如果需要对某
一个通道进行操作的话,那必须把列的数量*3。如果不需要对通道进行操作,那么图像的列和行就是你用鼠标在windows资源管理器中所看到的值(xxx*xxx)。
好了,说了这么多关于图像的基础知识,我们实现一个例子,这个例子网上都是有的,功能是,用颜色空间缩减的方法来提高对图像操作的效率。做法是:将现有颜色空间值除以某个输入值,以获得较少的颜色数。例如,颜色值0到9可取为新值0,10到19可取为10,以此类推。
我们的例子提供了两种做法,一种就是直接自己读取,一种则是用opencv的函数库进行处理。两两正好可以用来对比。先看例子:
1 #include <opencv2/opencv.hpp> 2 3 using namespace std; 4 using namespace cv; 5 #define divideWith 50 6 Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table); 7 int main(int argc, char* argv[]) 8 { 9 Mat I, J; 10 const char* imagename = "../test.jpg"; 11 12 //! read a image 13 I = imread(imagename, CV_LOAD_IMAGE_COLOR); 14 //! read image fail 15 if (I.empty()) 16 { 17 fprintf(stderr, "Can not load image %s/n", imagename); 18 return -1; 19 } 20 //! show image 21 imshow("image", I); 22 waitKey(); 23 //! define a table to store the preprocessed data 24 uchar table[256]; 25 for (int i = 0; i < 256; ++i) 26 table[i] = divideWith* (i / divideWith);//example:Iold=14; Inew=(Iold/10)*10=(14/10)*10=1*10=10; 27 double t; 28 t = (double)getTickCount(); 29 //! use The iterator (safe) method to realize this function 30 J = ScanImageAndReduceIterator(I.clone(), table); 31 t = 1000 * ((double)getTickCount() - t) / getTickFrequency(); 32 cout << "Time of reducing with the iterator :" << t << " milliseconds." << endl; 33 imshow("image1", J); 34 waitKey(); 35 //! use the lookUpTable which provided by opencv to realize this function 36 t = (double)getTickCount(); 37 Mat lookUpTable(1, 256, CV_8U); 38 uchar* p = lookUpTable.data; 39 for (int i = 0; i < 256; ++i) 40 p[i] = table[i]; 41 LUT(I, lookUpTable, J); 42 t = 1000 * ((double)getTickCount() - t) / getTickFrequency(); 43 cout << "Time of use lookUpTable :" << t << " milliseconds." << endl; 44 imshow("image2", J); 45 waitKey(); 46 system("pause"); 47 return 0; 48 } 49 //! The iterator (safe) method 50 Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table) 51 { 52 //! accept only char type matrices 53 CV_Assert(I.depth() != sizeof(uchar)); 54 55 const int channels = I.channels(); 56 switch (channels) 57 { 58 case 1: 59 { 60 MatIterator_<uchar> it, end; 61 for (it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it) 62 *it = table[*it]; 63 break; 64 } 65 case 3: 66 { 67 MatIterator_<Vec3b> it, end; 68 for (it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it) 69 {
70 (*it)[0] = table[(*it)[0]]; 71 (*it)[1] = table[(*it)[1]]; 72 (*it)[2] = table[(*it)[2]]; 73 } 74 } 75 } 76 77 return I; 78 }
上面例子中 ScanImageAndReduceIterator函数就是遍历图像,channels就是我们上面说的通道数,这加了判断,对不同通道数的图像进行不同的处理,比如单通道数就直接用uchar(0~256)接收,而三通道则用到了向量模板<Vec3b>这个很好理解,就是每个像素下都存在一个向量,这个向量包含 三个元素也就是三个通道,因此得到这个像素后,可以通过<Vec3b>方便的进行操作。 MatIterator_<Vec3b>这是个迭代器,指定I.begin<Vec3b>()头,和I.end<Vec3b>()尾就相当于for循环进行操作了,非常方便。这样图像的遍历就完成了。
上面说我们我们程序的功能就是要实现对图像颜色的压缩,压缩公式是: ,所以我们把处理好的数据都放到一个table[256]中,然后对每个像素重新进行赋值。大家应该也看到了我们先用Mat的构造函数new出了也Mat对象lookUpTable,然后把我们之前处理好的table[256]直接赋值给到lookUpTable对象的data中,如下:
1 Mat lookUpTable(1, 256, CV_8U); 2 uchar* p = lookUpTable.data; 3 for (int i = 0; i < 256; ++i) 4 p[i] = table[i]; 5 LUT(I, lookUpTable, J);
然后调用LUT(原图像,查找表,输出图像);直接到达我们的效果,好我们自己遍历的效果一样。我们看看结果~
原图:
1、自己扫描赋值
2、LUT函数处理
时间是: ,由此可见我们的效率还差很多,所以我们最好能用到指针来操作,以后我会实现~
最后,我有个想法就是接下来我会写一个系列,关于图像处理的,具体的思路是先把基础的图像操作实现一遍,然后将我的研究和大家分享~希望大家能帮我指正错误。