转自:http://hi.baidu.com/fengjingge815/item/c44caaf0a7a2601ec6dc45f3
数字图像处理--CImg类
class CImg
{
public:
// 构造函数
CImg();
// Copy构造函数
CImg(CImg& gray);
// 重载“=”操作符来创建新的对象
void operator = (CImg& gray); //图像赋值
BOOL operator == (CImg& gray); //判断2幅图像是否相同
CImg operator & (CImg& gray); //图像按位与
CImg operator | (CImg& gray); //图像按位或
CImg operator + (CImg gray); //图像相加
CImg operator - (CImg& gray); //图像减法
CImg operator ! (); //图像反色
// 析构函数
virtual ~CImg();
public:
// 判断位图是否有效
BOOL IsValidate() { return m_pBMIH != NULL; }
// 将所有像素的值初始化为color
void InitPixels(BYTE color);
// 从文件加载位图
BOOL AttachFromFile(LPCTSTR lpcPathName);
BOOL AttachFromFile(CFile &file);
// 将位图保存到文件
BOOL SaveToFile(LPCTSTR lpcPathName);
BOOL SaveToFile(CFile &file);
// 在DC上绘制位图
BOOL Draw(CDC* pDC);
BOOL Draw(CDC* pDC, CRect rt);
// 设置像素的值
void SetPixel(int x, int y, COLORREF color);
// 获取像素的值
COLORREF GetPixel(int x, int y);
// 获取灰度值
BYTE GetGray(int x, int y);
// 获取一行的字节数
int GetWidthByte();
// 获取一行的像素数
int GetWidthPixel();
// 获取高度
int GetHeight();
//改变位图的尺寸
void ImResize(int nHeight, int nWidth);
public:
// 绘画函数
// 绘制直线
void Line(POINT ptStart, POINT ptEnd);
void Line(POINT ptStart, int nLen, int nWide, BOOL bHor);
// 绘制矩形
void Rectangle(int x, int y, int r = 5);
void Rectangle(POINT ptLT, int r = 5);
void Rectangle(POINT ptLT, POINT ptRB);
void Circle(int x, int y, int r = 5){};
public:
// 判断是否是二值图像
BOOL IsBinaryImg();
// 判断是否是索引图像
BOOL IsIndexedImg();
// 256色索引图像转灰度图像
bool Index2Gray();
LPVOID GetColorTable(){return m_lpvColorTable;}
int GetColorTableEntriesNum(){return m_nColorTableEntries;}
private:
void CleanUp();
public:
// 文件数据
BITMAPINFOHEADER *m_pBMIH;
LPBYTE *m_lpData;
protected:
int m_nColorTableEntries;
LPVOID m_lpvColorTable;
};
下文转自:http://blog.163.com/xglla_1129/blog/static/81073510201061145439483/
CImg是一个跨平台的C++的图像处理库,提供了加载、处理、显示、保存等一系列功能,其中的图像处理功能尤其强大。
首先,建议先到这里欣赏一下使用CImg代码做的Demo,就是它使我这个没有图像处理经验的童鞋也心动得以致于研究了一星期^_^
主页地址:http://cimg.sourceforge.net/
下载地址:http://cimg.sourceforge.net/download.shtml
下载时注意应该下载源码包,里面附带的大量的例程。而实际上CImg库只是一个头文件CImg.h,这个头文件里包含了CImg库所有的代码。
另外不要错过下载列表中的一个部分完成的《CImg中文参考手册》。
CImg的Hello World
这段代码是从《CImg参考手册》里抄的,可以大致了解一下CImg的框架。
以VC为例:新建控制台程序,输入下面的代码。项目属性的链接器附加依赖项加入kernel.lib user32.lib gdi32.lib。最后,把CImg.h拷贝到项目路径下,即可成功编译运行
- #include "CImg.h"
- using namespace cimg_library;
- int main()
- {
- // 定义一个每个颜色 8 位(bit)的 640x400 的彩色图像
- CImg
img(640,400,1,3); - //将像素值设为 0(黑色)
- img.fill(0);
- // 定义一个紫色
- unsigned char purple[] = { 255,0,255 };
- // 在坐标(100, 100)处画一个紫色的“Hello world”
- img.draw_text(100,100,"Hello World",purple);
- // 在一个标题为“My first CImg code”的窗口中显示这幅图像
- img.display("My first CImg code");
- return 0;
- }
运行这段代码,显示结果:
在CImg体系中,图像有x,y,z,v四个轴,前三个当然是3维空间的三个方向(知道了吧?CImg可以处理3维图像),第四个v一般表示色彩通道数,比如RGB三色就是3。
上面的代码每行的注释已经写得很详细,从代码里可以看出CImg处于namespace cimg_library名空间之下;模板类CImg<>是主要的图像类,提供了大量的图像处理方法。
在CImg库里,还有一个重要的类是CImgDisplay,它提供了一个显示窗口,不仅可以显示CImg的内容,还可以接收键盘鼠标事件,我们可以暂时把它看成是一个CImg专用窗体类。
CImg类介绍
CImg类提供的方法非常多,为了便于查阅,我用Doxygen重新生成了一份CImg库的说明文档,并做成chm格式,可以本文后面找到下载地址。
CImg模板类提供了图像的载入、保存、处理功能,是整个库的核心组件。它的声明如下:
template<typename T> struct cimg_library::CImg;
模板参数T指明CImg中元素的类型。在自带的参考手册中称这些元素为像素(pixel),不过因为这里的像素和我们平时的像素概念稍微有点不同。因为前面说过CImg体系中有xyzv四个轴,CImg手册称它为4维图像,把4维图像里的单个元素称为像素。而事实上第四维的v通常就是我们的色彩通道,所以要在屏幕上显示出一个真正的像素往往要取同一xyz轴上所有v轴的点(所有色彩通道合成一个真正的彩色像素)。在本文中我决定把这个组成图像的最小单位称为元素,由所有色彩通道v组成一个像素。
CImg类中的6个成员变量:
- // 分别对应x,y,z,v四个轴的大小,即宽度、高度、深度和通道数。
- unsigned int width, height, depth, dim;
- // 指向内存中的图像数据
- T *data;
- // 指明data是否是共用的,即是否有data的拥有权。
- bool is_shared;
CImg类里的成员变量都是public的,我们可以直接存取它们,不过为了防止破坏完整性,建议使用成员方法如dimx(), dimy(), dimz(), dimv() 和ptr()来操作。
CImg的构造函数,大部分构造函数都很直白,就不列出了,可以查看手册,下面列出的是一些特殊的构造函数。
- // 以字符串指定的数据填充,其中的values字符串包含了一串十进制数字表示的数据。
- // 比如参数values为字符串"20,30,40,50", repeat_pattern为true时
- // 图像内的数据就以20,30,40,50,20,30,40,50,20...填充。
- CImg (
- const unsigned int dx,
- const unsigned int dy,
- const unsigned int dz,
- const unsigned int dv,
- const char *const values,
- const bool repeat_pattern)
- // 由shared参数决定是否直接引用img中的data数据还是自己持有一份拷贝(是否共享)
- CImg (const CImg< T > &img, const bool shared)
- // 参考img的大小构造一个新的CImg对象
- CImg (const CImg< t > &img, const char *const dimensions)
这个构造函数的dimensions参数由一串数字或转义符组成,分别对应x,y,z,v的大小。比如:
参数为"20 20 1 3"时新CImg对象的x,y,z,v的大小分别是20 20 1 3。
转义符以%开头,后缀可以是:
x, dx, dimx, width 表示img.width y, dy, dimy, height 表示img.height z, dz, dimz, depth 表示img.depth v, dv, dimv, dim 表示img.dim
比如CImg(img, "%y %x 1 3");可以生成一个和img的宽高正好互换的CImg对象。
- // 从文件里载入图像,文件类型由扩展名确定
- CImg (const char *const filename)
CImg库本身支持BMP,RAW,HDR,INR,PGM,PPM,PAN,DLM格式
安装了ImageMagick(Unix系)后可支持JPG,GIF,PNG,TIF等多种格式
CImg也能使用jpeg库,zlib/png库,tiff库等来支持多种图像格式,只需编译时加入这些库即可。可以到这里下载这些库文件。
- // 从CImgDisplay对象的内容创建图像
- CImg (const CImgDisplay &disp)
CImg部分成员方法
CImg提供了丰富的成员方法,在这里全部列出是不可能的,这里只作一些简单介绍。更多方法请参考手册。- // 赋值,它有多个重载的版本,参数和构造函数一样,实际上构造函数最终都是调用它来实现的。
- CImg< T > & assign ();
- // 把data数据转交给img,自己不再有data数据的拥有权
- CImg< T > & transfer_to (CImg< T > &img);
- // 清除数据
- CImg< T > & clear ();
- // 获得所有元素总数
- unsigned long size () const
- // 获得某个轴向的大小
- int dimx () const;
- int dimy () const;
- int dimz () const;
- int dimv () const;
- // 取得遍历元素的迭代器,这里的iterator其实就是T*。
- iterator begin ();
- iterator end ();
- // 取得首尾数据
- T & first ();
- T & last ();
- // 取得图像内部数据(第二个版本取得数据并定位到指定位置)
- T * ptr ();
- T * ptr (
- const unsigned int x,
- const unsigned int y=0,
- const unsigned int z=0,
- const unsigned int v=0);
- // 快速存取指定位置上的数据
- T & operator() (
- const unsigned int x,
- const unsigned int y=0,
- const unsigned int z=0,
- const unsigned int v=0);
- // 按索引直接存取data数组
- T & operator[] (const unsigned long off)
- // 得到指定位置的索引
- long offset (
- const int x,
- const int y=0,
- const int z=0,
- const int v=0) const;
- // 存取指定位置上的数据,当指定的轴的超出边界时返回最边上的值
- T & at (const int off);
- T& atX(const int x, const int y, const int z, const int v);
- T& atXY(const int x, const int y, const int z, const int v);
- T& atXYZ(const int x, const int y, const int z, const int v);
- T& atXYZV(const int x, const int y, const int z, const int v);
- // 存取指定位置上的数据,当指定的轴的超出边界时返回out_val
- T & at (const int off, const T out_val);
- T& atX(const int x, const int y, const int z, const int v, const T out_val);
- T& atXY(const int x, const int y, const int z, const int v, const T out_val);
- T& atXYZ(const int x, const int y, const int z, const int v, const T out_val);
- T& atXYZV(const int x, const int y, const int z, const int v, const T out_val);
值得一提的是还有两种插值版本的at方法,分别是线性插值和三次插值。其中线性插值以linear_作为前缀,三次插值以cubic_作为前缀。参数和上面的类似,只是各轴位置的类型不是int而是float,这批方法会按插值法算出小数点位置上的数据。
到这里可以发现CImg类实际上提供了类似于vector容器的编程接口,这样我们的STL算法也能用于CImg的操作了,随后就可以看到库里有时也会把CImg类直接当作数据容器来使用。比如下面CImg中就有这个方法:
- // 返回字符串的形式的图像中的数据,默认是逗号分隔的一长串数字。
- //这里返回的CImg
不代表图像,而是一个一维的字符串数据,这时它只是一个容器而已,可以把它看作是vector 。 - CImg< charT > value_string (const char separator=',', const unsigned int max_size=0);
上回介绍了CImg模板类的一些函数,象我这种不在图像处理行业混的人来说很多术语实在是太专业了-_-,不理不理,看不懂就直接写测试代码看它们的作用是什么不就知道啦~~嘿嘿^_^。
上测试代码先:
- #include "CImg.h"
- using namespace cimg_library;
- int main()
- {
- CImg
src("test.bmp"); - // 设置原图大小,貌似haar计算要求图像宽高是4的倍数
- src.resize( src.width-src.width%4, src.height-src.height%4);
- CImgList
visu; - visu
- <
- <
- <
- <
- <
- <
- <
- <
- <
- <
- <
- <
- <
- <
- <
- ; //如果愿意可以测试更多CImg的图像处理方法
- // 用来显示效果
- CImgDisplay disp(src.width*2, src.height);
- int i=0;
- unsigned char textcolor[] = { 255,255,255 };
- while(!disp.is_closed && !disp.is_keyQ && !disp.is_keyESC)
- {
- i = i % visu.size;
- char buf[20];
- ::sprintf(buf,"img:%d",i);
- //显示效果,(CImg << CImg)会生成一个新的CImgList
- //左边是原图,右边是处理图,外加写了个序号在上面以便区别
- disp.display( src << (+visu[i]).draw_text(0,0,buf,textcolor) ).wait();
- //按方向键下则显示下一个
- if(disp.is_keyARROWDOWN) i++;
- //方向键上则显示上一个
- if(disp.is_keyARROWUP)
- {
- i--;
- if(i<0) i=visu.size-1;
- }
- }
- return 0;
- }
- <
这个例子用到了CImg、CImgList、CImgDisplay三个类。
CImg类前面已有介绍。
CImgList是CImg的容器,用来保存一组CImg,主要方法有:
- CImgList
& remove(const unsigned int pos) //删除指定位置 - CImgList
& pop_back() //删除最后一个 - CImgList
& pop_front() //删除前端 - CImgList
& push_back(const CImg & img)//从后面添加 - CImgList
& push_front(const CImg & img)//从前面添加 - CImgList
& insert(const CImg & img, const unsigned int pos) //插入到指定位置之前 - CImgList
& clear() //清空 - CImg
& operator[](const unsigned int pos) //取指定位置的图像
上面这些是它作为容器的基本功能,同时它也重载了一些操作符以便于使用,比如本例中的"<<"操作其实就是push_back方法。另外,它还有大量的运算功能用于给容器中的图像批量运算。最后,还有一些好玩的方法不可错过:从视频中载入或把图像保存到视频中:
- CImgList
& load_ffmpeg(const char *const filename, - const unsigned int first_frame=0,
- const unsigned int last_frame=~0U,
- const unsigned int step_frame=1,
- const bool pixel_format=true,
- const bool resume=false)
- const CImgList
& save_ffmpeg( - const char *const filename,
- const unsigned int first_frame=0,
- const unsigned int last_frame=~0U,
- const unsigned int fps=25)
这两个方法要求链接ffmpeg库,如果没有这个库文件,还可以使用load_ffmpeg_external和save_ffmpeg_external方法调用已外部安装的ffmpeg程序编解码。
CImgDisplay类是一个窗口类,它主要用来显示CImg和CImgList。一般使用它的流程是:
- 新建CImgDisplay对象
- 设置它的大小,除直接输入宽高外也能用直接用CImg、CImgList或另一个CImgDisplay对象作为调整大小的依据。这时,CImgDisplay对象内部已经建立了一个窗口了。
- 使用display方法显示图像
- 使用wait方法等待事件发生(键盘、鼠标、超时等)
- 检查is_keyXXXX、is_closed、button、wheel等成员变量确定是什么事件,再决定我们该做什么操作。
- 如果需要,循环回第三步
- 析构时窗口收回。
在本例中,如果窗体关闭或按了Q键或按了ESC键则退出循环,程序结束。或者显示由原图和处理后的图组成的CImgList图像,如果按了上下方向键,则改变当前显示的处理图。
这是本例运行时的截图:
水墨画风格的《清明上河图》
CImg的图像处理方法绝不止上面例子中写的那么一点点,
之前我们一直玩的是CImg的二维图像处理,从CImg<>类的成员方法上看,显然它还有更多能力。比如那些带3d后缀的方法显然是为三维图像准备的,还有dijkstra方法,貌似是图论方面的内容,CImg库管得可真够宽的-_- 图论方面的先不管它,这方面还是Boost::Graph比较在行,我比较感兴趣的还是它的三维能力,毕竟这是个Head Only的跨平台库,并且没有调用OpenGL,有三维处理能力还是蛮让人兴奋D~~,呵呵:)
现在,我们从一根三维的直线开始:
下面的代码使用VC2005 Express编译,为了直观,使用了中文变量名.
- #include "CImg.h"
- using namespace cimg_library;
- //三维直线
- int main()
- {
- CImg<> 顶点(2,3,1,1,
- 0,50
- 0,50
- 0,50);
- CImgList
图元( CImg ::vector(0,1) ); - CImg<> 不透明度 = CImg<>::vector(1.0f);
- CImgList
颜色表( CImg ::vector(255,0,255) ); - //300*300,深度是1,3个色彩通道,以0填充
- CImg<> visu(300,300,1,3,0);
- visu.display_object3d("三维直线",顶点,图元,颜色表,不透明度);
- return 0;
- }
编译运行,显示一条直线,在窗体内按住鼠标拖曳,可以改变视角.不过就一根直线看上去不是很爽,没立体感,下面我们用直接组成一个立方体:
一个立方体有8个顶点, 12条边
- //多根三维直线
- int main()
- {
- // 8个顶点
- CImg<> 顶点(8,3,1,1,
- 0, 0, 50, 50, 50, 0, 0, 50,
- 0, 50, 50, 0, 0, 0, 50, 50,
- 0, 0, 0, 0, 50, 50, 50, 50);
- // 12条边
- CImgList
图元 = - CImg
::vector(0,1) << - CImg
::vector(1,2) << - CImg
::vector(2,3) << - CImg
::vector(3,4) << - CImg
::vector(4,5) << - CImg
::vector(5,6) << - CImg
::vector(6,7) << - CImg
::vector(0,5) << - CImg
::vector(1,6) << - CImg
::vector(2,7) << - CImg
::vector(0,3) << - CImg
::vector(4,7) - ;
- CImg<> 不透明度(1,12,1,1, 1.0f);
- CImgList
颜色表( 12, CImg ::vector(255,0,255) ); - //300*300,深度是1,3个色彩通道,以0填充
- CImg<> visu(300,300,1,3,0);
- visu.display_object3d("三维直线",顶点,图元,颜色表,不透明度);
- return 0;
- }
显示图如下:
在CImg里,一幅三维图被分解成:顶点(vertices)和图元(primitives)两个部分。
顶点是一个CImg<>对象,它存放三维图中所有的顶点数据。这个CImg<>对象此时只是一个数据容器,我们姑且把它看作图像的话,那么它相当于一个“宽(width)=顶点数,高(height)=3,深(depth)=1,色彩通道(dim)=1”的图像,在“这个图像指定x位置上的3个y轴数据(因为高度总是为3)”指明了顶点所在的x,y,z方位。
比如上例中的顶点规格为8*3*1*1,表明顶点数为8,后面的数据从纵向正好是8组三维方位数据。这里这样写只是为了方便,如果要运行期动态加入顶点,可以使用append方法,上面的顶点初始化代码可以改写成:
- CImg<> 顶点;
- 顶点.append(CImg<>::vector(0,0,0),'x')
- .append(CImg<>::vector(0,50,0),'x')
- .append(CImg<>::vector(50,50,0),'x')
- .append(CImg<>::vector(50,0,0),'x')
- .append(CImg<>::vector(50,0,50),'x')
- .append(CImg<>::vector(0,0,50),'x')
- .append(CImg<>::vector(0,50,50),'x')
- .append(CImg<>::vector(50,50,50),'x');
这里的静态方法vector返回一个由它的所有参数组成的CImg<>对象,最多可接受16个输入参数。vector生成的CImg<>对象中数据是按列排的,也就是说生成的CImg<>对象宽度总是1,高度正好是参数个数,(0,y)点上的数据对应第y个参数。
append方法把当前图像和输入的图像按指定轴的方向合成一个新的图像,这是就是以'x'轴方向把这些1X3的“图像”合并到一起。顺便说一下,CImg的大部分方法都是返回自身的引用,所以可以连续用.操作。
图元是组成三维图的最小图形,一般的三维处理都以三角形作为图元,所以显卡测试里有三角形生成速度一项,扯远了~~:-P
在CImg中图元是一个CImgList
另外,显示时还需要给出每个图元的不透明度和颜色表(或纹理),不透明度是CImg
颜色表是一个CImgList<>数据,其中至少要有图元数目个CImg<>对象,每个CImg<>以RGB的顺序纵向存放了对应图元的颜色数据(或者纹理,这个后面再说)。
在本例中我们的所有图元都是直线,我们也可以用四个点把图元描述成一个面,这样,一个三维立方体诞生了:
- #include "CImg.h"
- using namespace cimg_library;
- //三维立方体
- int main()
- {
- //8个顶点
- CImg<> 顶点;
- 顶点.append(CImg<>::vector(0,0,0),'x')
- .append(CImg<>::vector(0,50,0),'x')
- .append(CImg<>::vector(50,50,0),'x')
- .append(CImg<>::vector(50,0,0),'x')
- .append(CImg<>::vector(50,0,50),'x')
- .append(CImg<>::vector(0,0,50),'x')
- .append(CImg<>::vector(0,50,50),'x')
- .append(CImg<>::vector(50,50,50),'x');
- CImgList
图元 = - CImg
::vector(0,1,2,3) << - CImg
::vector(4,5,6,7) << - CImg
::vector(0,1,6,5) << - CImg
::vector(2,7,4,3) - ;
- CImg<> 不透明度(1,12,1,1, 1.0f);
- CImgList
颜色表( 12, CImg ::vector(255,0,255) ); - 颜色表[0] = CImg
::vector(255,0,0); - 颜色表[1] = CImg
::vector(0,255,0); - 颜色表[2] = CImg
::vector(0,0,255); - //300*300,深度是1,3个色彩通道,以0填充
- CImg<> visu(300,300,1,3,0);
- visu.display_object3d("三维立方体",顶点,图元,颜色表,不透明度,true,4,4,true);
- return 0;
- }
对了,前面都忘说display_object3d方法了:
该方法声明如下:
- const CImg
& cimg_library::CImg< T >::display_object3d ( CImgDisplay &disp /*或 const char *const title*/, - const CImg< tp > & points,
- const CImgList< tf > & primitives,
- const CImgList< tc > & colors,
- const to & opacities,
- const bool centering = true,
- const int render_static = 4,
- const int render_motion = 1,
- const bool double_sided = false,
- const float focale = 500,
- const float specular_light = 0.2f,
- const float specular_shine = 0.1f,
- const bool display_axes = true,
- float *const pose_matrix = 0
- )
centering 指定是否把0,0,0点作为窗体中心 render_static和render_motion 指定静态渲染级别和动态渲染级别: 0=点, 1=线, 2=面(没有亮光), 3=面(平面), 4=面(边缘柔和) double_sided 指定是否双面渲染 focale 焦距
咳,由于空间想象力太差,组成立方体的六个面偶只放了四个,结果生成的图形如下: