摘要(Abstract) 通过笔记一的学习,我们已经能够下载、安装OpenCV并新建VS2010项目进行相关的配置,笔记一也已完成第一个程序HelloCV的演示。本文首先通过详细介绍OpenCV中如何从硬盘加载/读取一幅图像,并在窗口中进行显示来对笔记一中的演示程序做详解。其次,本文实现了简单的图像变换,将一幅RGB颜色的图片lena.jpg转化成灰度图像,以达到修改的目的,另外,在此变换中,我们还对如何将图片保存到硬盘上进行讲解。实验结果表明,通过笔记二的学习,不但能够增强对OpenCV的学习兴趣,还能有初体验OpenCV的成就感,吃嘛嘛香,为后续的学习打下坚实的基础。
关键词(Keywords):OpenCV;imread;namedWindow;imshow;cvtColor;imwrite;
1、加载并显示图像(Load and Display an Image)
1.1 学习目标
在本节中,我们预期达到以下学习目标:1) 加载一幅图像(采用imread方法);2)创建一个指定的OpenCV窗口(采用namedWindow方法);3)在OpenCV窗口中显示图像(采用imshow方法)。
1.2 源代码
#include “StdAfx.h”
#include <string>
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
using namespace cv;
using namespace std;
int main()
{
string imageName = “lena.jpg”;
//读入图像
Mat img = imread(imageName, CV_LOAD_IMAGE_COLOR);
//如果读入图像失败
if (img.empty())
{
cout<<”Could not open or find the image!”<<endl;
return -1;
}
//创建窗口
namedWindow(“lena”, CV_WINDOW_AUTOSIZE);
//显示图像
imshow(“lena”, img);
//等待按键,按键盘任意键返回
waitKey();
return 0;
}
1.3 源码详解
1.3.1 头文件
OpenCV有许多不同的模块,每个模块关心图像处理中不同的领域及方法(参见:OpenCV学习笔记(基于OpenCV 2.4)一:哈喽CV),在使用之前我们首先需要对相应的头文件进行包含,一般情况下我们都会用到的两个模块:
1)core section. 这里定义了OpenCV的一些基本的块(Blocks);
2)highgui module. 该模块包含了一些图像的输入输出操作(UI)。
另外,为了能够在控制台做输入输出,我们会包含iostream,而string是用于字符串的处理。接下来,为了防止OpenCV的数据结构或命名与其它库函数比如STL有冲突,我们引入命名空间cv,在有冲突的情况下可以用前缀cv::来指定具体使用哪个库(关于命名空间,我们会在下一讲做详细介绍)。
1.3.2 主函数
①Mat img = imread(imageName, CV_LOAD_IMAGE_COLOR);
imread的函数原型是:Mat imread( const string& filename, int flags=1 );
Mat是OpenCV里的一个数据结构,在这里我们定义一个Mat类型的变量img,用于保存读入的图像,在本文开始有写到,我们用imread函数来读取图像,第一个字段标识图像的文件名(包括扩展名),第二个字段用于指定读入图像的颜色和深度,它的取值可以有以下几种:
1) CV_LOAD_IMAGE_UNCHANGED (<0),以原始图像读取(包括alpha通道),
2) CV_LOAD_IMAGE_GRAYSCALE ( 0),以灰度图像读取
3) CV_LOAD_IMAGE_COLOR (>0),以RGB格式读取
这三点是在OpenCV的官方教程(opencv_tutorials.pdf)里摘录并翻译过来的,但是网上还有关于CV_LOAD_IMAGE_ANYDEPTH和CV_LOAD_IMAGE_ANYCOLOR的传说,而且查看OpenCV的源码可以发现,这些取值放在一个枚举(enum)类型中(opencv\build\include\opencv2\highgui\highgui_c.h),定义如下:
enum
{
/* 8bit, color or not */
CV_LOAD_IMAGE_UNCHANGED =-1,
/* 8bit, gray */
CV_LOAD_IMAGE_GRAYSCALE =0,
/* ?, color */
CV_LOAD_IMAGE_COLOR =1,
/* any depth, ? */
CV_LOAD_IMAGE_ANYDEPTH =2,
/* ?, any color */
CV_LOAD_IMAGE_ANYCOLOR =4
};
关于该枚举类型的详细信息,官方教程的写法跟官方发布的正式版代码描述的不相同,可能是我理解不够深入,或者两者是等价的,这点以后再找时间研究,但这并不影响我们对这一章节的学习。
Note:OpenCV提供了多种格式图像的支持,包括Windows bitmap(bmp),portable image formats (pbm, pgm,ppm) 以及 Sun raster (sr, ras)。关于其它的格式,有JPEG (jpeg, jpg, jpe),JPEG 2000,TIFF 文件 (tiff, tif),portable network graphics (png),还有openEXR格式,如果OpenCV是自己打包的话,读取这些格式需要有插件支持,如果是官方提供的库,则不需再添加插件。
在检查图像是否读取成功之后,我们需要显示读入的图像,因此,我们采用namedWindow函数来创建一个OpenCV窗口,该函数的定义如下:
CV_EXPORTS_W void namedWindow(const string& winname, int flags = WINDOW_AUTOSIZE);
为此,我们需要指定该窗口的名称(窗口标识符, window identifier)以及如何处理窗口大小。①窗口标识符需要唯一指定,如果已经存在一个该名字的窗口,则此函数不进行任何处理;②flags参数目前只支持CV_WINDOW_AUTOSIZE,在highgui_c.h中,OpenCV定义了CV_WINDOW_AUTOSIZE= 0×00000001,如果设置该参数,则表示显示的时候窗口自适应于需要显示的图像,而且不能修改窗口大小,如果不设置(省略此参数),可以通过代码进行修改;③如果将OpenCV用于Qt后端开发,该参数还支持其它值,具体可查看OpenCV开发文档,这里不再赘述。
②imshow(“lena”, img);
imshow用于在我们创建的窗口中显示需要显示的图像,其函数原型为:
void imshow(const string& winname, InputArray mat);
第一个参数winname是指窗口的名称,也就是窗口标识符,第二个参数mat就是我们要显示的图像了。正如我们在namedWindow函数中所描述的那样,如果namedWindow指定了参数CV_WINDOW_AUTOSIZE,则图像会按原始大小显示,否则图像会根据窗口大小进行缩放。
③waitKey();
这条语句表示等待用户键盘操作,waitKey函数的函数原型如下:
int waitKey(int delay = 0);
我们可以看到,该函数可包含一个整形参数,不设置参数的情况下,默认为0,也就是无限制等待。该整数表示需要等待用户操作的毫秒数。
2 加载、修改并保存图像(Load, Modify, and Save an Image)
2.1 学习目标
在这一章,我们将学习:1)加载一副图像(采用imread函数,同第一章);2)将一副图像从RGB格式转换成灰度图(grayscale,采用cvtColor函数);3)保存转换后的图像到磁盘上(采用imwrite函数)。
2.2 源代码
<pre lang=”cpp”>
#include “StdAfx.h”
#include <cv.h>
#include <highgui.h>
#include <string>
using namespace cv;
using namespace std;
int main()
{
char* imageName = “lena.jpg”;
Mat image = imread(imageName, 1);
if (!image.data)
{
cout<<”Could not open or find the image!”<<endl;
return -1;
}
Mat gray_image;
String grayImageName = “lena_gray”;
cvtColor(image,gray_image,CV_RGB2GRAY);//将RGB图像转换成灰度图像
imwrite(“../../lena_gray.jpg”,gray_image);//保存图像
namedWindow(imageName, CV_WINDOW_AUTOSIZE);//创建用于显示元图像窗口
namedWindow(grayImageName,CV_WINDOW_AUTOSIZE);//创建用于显示转换后图像窗口
imshow(imageName,image);
imshow(“grayImageName”, gray_image);
waitKey(0);
return 0;
}
</pre>
2.3 源码详解
有了第一章的基础后,再来理解本章代码相对就很容易,在这一节,关于imread函数的使用则不再赘述,下面给cvtColor和imwrite来一个特写。
①cvtColor(image,gray_image,CV_RGB2GRAY);// 将RGB图像转换成灰度图像
cvtColor函数用于将图像从一个颜色空间转换到另一个颜色空间,其函数原型为:
void cvtColor( InputArray src, OutputArray dst, int code, int dstCn=0 );
参数src:是指需要转化的图像,可以是8位或16位等的无符号型或者是单精度浮点型(Single-Precision Floating-Point);
参数dst:与原始图像具有相同大小和深度的目标图像;
参数code:颜色空间转换代码;
参数dstCn:目标图像的通道数,如果该参数为0,则通道数可由src和code自动获得;
对于一个原图像或目标图像是RGB的转换,我们需要详细地指定通道的顺序(RGB or BGR)。我们注意到,OpenCV默认情况下的颜色格式一般是指RGB,但实际上却进行了一个反转变成BGR,因此对一个标准的24位图像来说,其第一个字节为8位的蓝色部分(Blue Component),其次是绿色,接着是红色,再然后就是第二个像素,同样以BGR的通道顺序排列。
常规的RGB通道的值的范围如下:
对于8位无符号精度图像(CV_8U Images),其范围是0~255
对于16位无符号精度图像(CV_16U Images),其范围是0~65535
对于32位单精度浮点型图像(CV_32F Images),其范围是0~1
在线性变换的情况下,我们可以不用考虑其通道的取值范围,但对于非线性变换(Non-Linear Transformation),一个RGB输入图像应该先做规格化处理(Normalized),以便得到一个合适的范围来获取正确的结果。比如对于一个RGB颜色空间到LUV颜色空间的变换,如果我们需要将一副8位图像转换到一副32位的浮点型精度图像而不进行任何缩放,也就是说,我们将一个从0~255的范围替换成0~1的范围,那么我们首先要将图像按比例缩小(Scale the Image Down):
img *= 1./255;
cvtColor(img, img, CV_BGR2Luv);
如果我们采用8位的图像进行转换,该过程中可能会有信息的丢失,尽管在一般的应用中,这种丢失并不明显(Noticeable),但我们强烈建议使用一个32位的图像或者在变换之前先转换成32位。
备注:关于参数code的跟多取值,可以参见OpenCV 2.4.0官方文档:cvtColor函数指南及使用方法
②imwrite(“../../lena_gray.jpg”,gray_image);// 保存图像
imwrite函数用于将图像保存到指定的文件,其函数原型为:
bool imwrite(const string& filename, InputArray image, const vector<int>& params=vector<int>())
参数filename:指代需要保存文件的名称
参数image:需要保存的图像
参数params:保存至指定格式图像格式时的参数设置
关于params参数的取值如下:
对JPEG图像,它表示图像质量(CV_IMWRITE_JPEG_QUALITY),取值从1~100,值越大质量越高,默认为95;
对PNG图像,它表示图像压缩率(CV_IMWRITE_PNG_COMPRESSION),取值从0~9,值越大压缩率越大压缩时间越长,默认值为3;
对PPM,PGM或PBM,它是一个二进制标识(CV_IMWRITE_PXM_BINARY),取值为0或1,默认为1。
有关于该函数及参数params的详细信息及应用可参见开发文档:imwrite函数指南及使用方法