如果你想对图像进行简单处理,你一般会想到用什么?可能多数人想到的是OpenCV。
对,OpenCV是个非常强大的图像视觉工具库,用途非常广泛。简单的图像处理用它肯定是可以的。
但OpenCV实在太庞大了,用起来有时反而不方便,就好比你现在肚子饿了只想简单吃个午饭,你是选择街边的饭馆买一份快餐15分钟解决问题,还是打电话给高级西餐厅订个位子要排队等到下周一才能吃上?
用OpenCV完成一些简单的图像处理就好比用一把牛刀杀鸡,能用但不好用,比如要写一个简单的测试程序,需要加载显示图像文件,并对图像做一个简单的处理(缩放,旋转,绘图),用OpenCV,就要多一个依赖库,可能还要为此编译OpenCV.而库中90%的功能都用不到,想想就好麻烦。
CImg是一个小型的C++语言跨平台的图像处理开源库,有多小型?核心代码只有一个CImg.h
文件。但是这个工具就像一把瑞士军刀,拥有的功能却非常多,常用的图像显示,图像格式解码,矩阵运算,色彩空间转换,简单绘图。。。等等该有的功能都有了。
所以我在写一些没有性能有要求的测试程序的时候,会选择用CImg来完成,没有依赖库,编译出的代码到哪里都能运行,方便啊,不然呢,你写个测试程序给客户,客户的电脑上不了,为啥没装OpenCV,客户问啥是OpenCV?..你再解释一遍,再教客户安装OpenCV,想想都头大。
几年前就用过CImg,当时是用它在测试程序中做简单的图像显示,非常方便。在libjpeg的支持下也用它加载JPEG图像文件,当时还在困扰CImg没有提供对JPEG格式图像内存编码/解码的功能。所以为此花了挺大精力自己实现了jpeg图像的内存编码/解码功能,参见我之前的博文:
《libjpeg:实现jpeg内存解压缩塈转换色彩空间/压缩分辨率》
《libjpeg:实现jpeg内存压缩暨error_exit错误异常处理和个性化参数设置》
最近的工作中又要对JPEG图像进行内存解码了,原打算用之前写的代码,但我重新看了CImg的代码。才发现CImg在核心代码CImg.h
之外还提供了很多插件(plugins),如下:
│ CImg.h
│ README.md
│
├─examples
│ └─img
│
├─plugins
│ add_fileformat.h
│ bayer.h
│ chlpca.h
│ cvMat.h
│ draw_gradient.h
│ inpaint.h
│ ipl.h
│ ipl_alt.h
│ jpeg_buffer.h
│ loop_macros.h
│ matlab.h
│ nlmeans.h
│ patchmatch.h
│ skeleton.h
│ tiff_stream.h
│ tinymatwriter.h
│ vrml.h
│ vtk.h
│
└─resources
compile_win_icl.bat
compile_win_visualcpp.bat
可以看到有个plugins\jpeg_buffer.h
,就是实现jpeg内存压缩和解压缩的。有了这个插件的支持,CImg类就多了load_jpeg_buffer
和save_jpeg_buffer
两个成员函数,分别用于jpeg文件的压缩和解压缩。具体怎么用呢?examples
文件夹下use_jpeg_buffer.cpp
就是示例代码。以下代码来自use_jpeg_buffer.cpp
,本文作者只是添加了中文注释
#include
// JPEG文件的读写需要libjpeg的支持,所以这里必须要include jpeglib.h jerror.h
#include
#include
// 这一行放在#include "CImg.h"前面,用于将jpeg_buffer.h插件加入CImg类的定义
// CImg.h中有多个下面这样的代码,将你定义的插件头文件 include到CImg类定义中
// #ifdef cimg_plugin
// #include cimg_plugin
// #endif
// #ifdef cimg_plugin1
// #include cimg_plugin1
// #endif
// 以此类推,如果你同时也想让CImg对象能转换成OpenCV的矩阵对象cv::Mat
// 就可以定义 cimg_plugin1 为 "plugins/cvMat.h"
// #define cimg_plugin1 "plugins/cvMat.h"
#define cimg_plugin "plugins/jpeg_buffer.h"
#include "CImg.h"
using namespace cimg_library;
int main() {
// 将一个JPEG文件的数据读取到内存缓冲区 'buffer_input'中
const char *filename_input = "foo.jpg";
std::fprintf(stderr," - Reading file '%s'\n",filename_input);
std::FILE *file_input = std::fopen(filename_input,"rb");
if (!file_input) { std::fprintf(stderr,"Input JPEG file not found !"); std::exit(0); }
std::fprintf(stderr," - Construct input JPEG-coded buffer\n");
unsigned buf_size = 500000; // 这里定义文件长度
JOCTET *buffer_input = new JOCTET[buf_size];
if (std::fread(buffer_input,sizeof(JOCTET),buf_size,file_input)) std::fclose(file_input);
// -> 'buffer_input' is now a valid jpeg-coded memory buffer.
std::fprintf(stderr," - Create CImg instance from JPEG-coded buffer\n");
CImg<unsigned char> img;
// 将内存缓冲区 'buffer_input'中的图像数据调用load_jpeg_buffer函数实现内存解压缩,buffer_input用完就可以删除了。
img.load_jpeg_buffer(buffer_input, buf_size);
delete[] buffer_input;
// 然后你可以在CImg对象上做你想要的图像处理,比如下面的代码在图像上写文字 ‘ Hello!’,并显示出来
std::fprintf(stderr," - Do simple processing\n");
const unsigned char purple[] = { 255, 0, 0 };
const unsigned char black[] = { 0, 0, 0 };
img.mirror('y').draw_text(0,0," Hello! ",purple,black,1,57);
// Display image to see if everything's fine.
img.display("Using 'jpeg_buffer.h' plugin");
// 定义一个JPEG压缩输出缓冲区,因为无法预测JPEG压缩输出的数据尺寸,所以这里定义了原文件尺寸2倍。
// 实际应用中为保险起见,应该以图像分辨率来决定缓冲区的大小,
std::fprintf(stderr," - Construct output JPEG-coded buffer\n");
JOCTET *buffer_output = new JOCTET[2*buf_size];
// 调用save_jpeg_buffer函数将处理过的CImg对象的图像数据压缩成JPEG格式写入输出缓冲区‘buffer_output ’
// 调用结束时'buf_size'中会输出实际输出的数据长度
img.save_jpeg_buffer(buffer_output,buf_size,60);
// 将输出缓冲区‘buffer_output ’中的JPEG图像数据写入一个新文件
const char *filename_output = "foo_output.jpg";
std::fprintf(stderr," - Save output file '%s'\n",filename_output);
std::FILE* file_output = std::fopen(filename_output,"wb");
std::fwrite(buffer_output, sizeof(JOCTET), buf_size, file_output);
std::fclose(file_output);
delete[] buffer_output;
std::fprintf(stderr," - All done !\n");
return 0;
}
使用很简单吧?
示例代码虽然啰里啰嗦一大堆,关键代码其实就只有两行。唉,几年我要是多仔细看CImg一眼,知道plugins下还有宝可挖,我又何必费力自己实现JPEG内存解码呢,重复发明轮子,真的好无奈。
如果你想知道CImg可以都能干哪些工作,运行一下它的demo就知道了。
windows下编译DEMO很简单,在执行resources
文件夹下的批处理程序\compile_win_visualcpp.bat
就会自动编译所有的DEMO,因为CImg.h
文件很大,所以编译的时间有点久。
编译完成之后,运行CImg_demo.exe
就会出现下面的界面,你可以选择你要执行的DEMO程序
本文作者写这篇文章时用的CImg版本还是几年前下载的1.6.8
,现在CImg的版本已经升级到2.3.6
了,参见CImg官网 http://cimg.eu/ 或 github 上的官方仓库: https://github.com/dtschump/CImg/releases
另外作为一个简单小型的图像处理工具库,它有啥缺点呢?我觉得就最大的缺点就是编译时间偏长,CImg.h
一个头文件就有2.8MB,编译这么大的源文件,编译器的负载很重,所以编译时间比较长,建议在尽量集中在一个cpp源码中使用CImg.h
时不要到处随意#include
,否则会让整项目的代码编译耗时大大增加。