现在开始一个基于CImg的程序的例子;演示了如何使用CImg来加载、创建图像实例;以及如何显示图像和处理鼠标事件;下面的程序实现了如下功能:加载一张彩色图像“lena.jpg”,然后执行平滑操作,并将它显示在窗口上,创建事件循环并在鼠标点击图片的时候,在另一个窗口绘制图片被点击对应的这一行像素点的R、G、B像素值统计信息,so,let's go:
#include "CImg.h"
using namespace cimg_library;
int main() {
CImg<unsigned char> image("lena.jpg"), visu(500,400,1,3,0);
const unsigned char red[3]={255,0,0}, green[3]={0,255,0}, blue[3]={0,0,255};
image.blur(2.5);
CImgDisplay main_disp(image,"Click a point"), draw_disp(visu,"Intensity profile");
while (!main_disp.closed && !draw_disp.closed) {
main_disp.wait();
if (main_disp.button && main_disp.mouse_y>=0) {
const int y = main_disp.mouse_y;
visu.fill(0).draw_graph(image.get_crop(0,y,0,0,image.dimx()-1,y,0,0),red,0,256,0);
visu.draw_graph(image.get_crop(0,y,0,1,image.dimx()-1,y,0,1),green,0,256,0);
visu.draw_graph(image.get_crop(0,y,0,2,image.dimx()-1,y,0,2),blue,0,256,0).display(draw_disp);
}
}
return 0;
}
示例图(略)
下面是对上述代码的详细解释:
-
#include "CImg.h"
包含CImg的唯一头文件; -
using namespace cimg_library;
使用CImg的主命名空间 -
int main() {
定义主函数 -
CImg<unsigned char> image("lena.jpg"), visu(500,400,1,3,0);
创建两个unsigned char
类型的图像对象;第一个图像对象从磁盘上的lena.jpg
文件读取而来;此处lena.jpg
和当前可执行文件放在同一个目录;同时ImageMagick
必须已经安装,用来读取JPG格式的文件;
第二个图片对象visu
初始化成一个5004001(2D图像)的彩色图像(有RGB三个通道);最后一个参数表示图像visu
所有像素全部置为0;因此visu
将被初始化为纯黑色; -
const unsigned char red[3]={255,0,0}, green[3]={0,255,0}, blue[3]={0,0,255};
此处以数组的方式定义了三种不同的unsigned char
类型的颜色变量;这些颜色变量将被用于接下来的绘制; -
image.blur(2.5);
对图像进行平滑;使用方差为2.5的高斯平滑函数来平滑图像;注意,很多CImg的函数有两个版本;不带get_前缀的函数(例如blur)是直接处理传入的图像对象;带get_前缀的函数(例如get_blur)会将处理后的图像对象返回(会花费更长的时间,因为需要额外的像素值拷贝操作,通过image = image.get_blur(2.5);
这样的方式调用); -
CImgDisplay main_disp(image,"Click a point"), draw_disp(visu,"Intensity profile");
创建两个显示窗口,第一个用来显示输入图像,第二个显示visu
对象,显示相对应的信息;
默认情况下,CImg显示对象将处理用户鼠标和键盘事件;同时在Windows上,能够创建全屏显示窗口; -
while (!main_disp.closed && !draw_disp.closed) {
进入事件循环,当显示窗口关闭时,将退出事件循环; -
main_disp.wait();
在事件循环中等待接收main_disp
的鼠标和键盘事件; -
if (main_disp.button && main_disp.mouse_y>=0) {
检测鼠标是否点击在图像显示区域,鼠标事件可以区分不同类型的鼠标点击事件,但是在此并不需要区分; -
const int y = main_disp.mouse_y;
获取被点击的图像的y座标; -
visu.fill(0).draw_graph(image.get_crop(0,y,0,0,image.dimx()-1,y,0,0),red,0,256,0);
该行演示了大部分CImg类函数都支持的链式调用处理;第一个函数fill(0)
将图像所有像素值设为0,然后将visu对象返回,从而可以进行链式调用draw_graph()
函数;draw_graph()
函数在图像中绘制一个多边形;多边形数据来自image.get_crop函数的返回值,此处get_crop返回image对象的R通道第y行的数据。注意:CImg<T>
对于彩色图像是4维数据;R,G,B通道的数据通过设置v=0,v=1,v=2获取到; - ** visu.draw_graph(image.get_crop(0,y,0,1,image.dimx()-1,y,0,1),green,0,256,0); **
绘制鼠标点击行处原始图像的G通道信息; -
visu.draw_graph(image.get_crop(0,y,0,2,image.dimx()-1,y,0,2),blue,0,256,0).display(draw_disp);
同上行,绘制鼠标点击行处原始图像的B通道信息; -
...till the end
后续代码,无需解释。
如上,使用CImg
库能够写出精简直观的代码,同时写出的代码能够正常运行在Windows和Unix系统中;
CImg库提供了一系列的示例程序在源码的examples
目录下,展示了更多精简的CImg库方法使用示例;总能找到你需要示例;
同时,CImg_test.cpp
文件包含了很多简短而又多样化的关于CImg的使用示例;在该文件中,所有的CImg库中的类都被使用到,并且这些代码都非常易读。建议从该文件开始学习如何使用CImg。
CImg图形绘制函数
从CImg的HTML文档里能找到详细的图形绘制函数列表和用法;使用前需要注意的几点:
- 图形绘制函数会直接绘制在传入参数中,并且会将传入的图像实例返回,从而实现图形绘制函数的链式调用(后面有示例用法);绘制函数通过发生在2D图像上,但是也可以进行3D图像的绘制;
- 绘制的时候通常需要一个颜色参数,颜色参数必须以C数组的形式定义。
使用CImg访问和操作图像像素
CImg定义了一系列宏来简化繁琐的for循环嵌套逐点访问和操作图像像素数据;使用CImg预定义的宏可以写出简洁的代码实现逐点访问和操作图像像素数据的功能(副作用是如果你不懂这些宏,基本看不懂使用CImg写出来代码);下面是这些宏的介绍,主要分为四类:
- 逐点访问图像像素
cimg_map(img,ptr,T)
: 该宏定义了使用类型为T
的指针ptr
从最末尾像素到最开始像素依次访问img
的像素数据,ptr指针每次指向图像像素数据;注意:-
img
必须是非空的cimg_library::CImg
类实例变量,像素数据类型必须为T
; - ptr为T*类型的指针;
该宏不经常使用,需要用到该宏的地方一般都可以用CImg的类函数实现,例如:
-
CImg<float> img(320,200);
cimg_map(img,ptr,float) { *ptr=0; } // Equivalent to 'img.fill(0);'
cimg_mapoff(img,off)
: 该宏通过off
访问像素数据,从图像起始到图像末尾;注意: off
是内部循环变量;访问图像第一个像素位置时off=0,访问到图像最后一个像素位置时,off=img.size()-1。示例:
CImg<float> img(320,200);
cimg_mapoff(img,off) { img[off]=0; } // Equivalent to 'img.fill(0);'
Github上CImg项目中已有详细的中文入门PDF文档,所以后续不再翻译入门文档。CImg.h最新版本有5万8千行代码,有时间对CImg的具体函数进行介绍。