在
但是如果是自己定义的特殊影像格式,cvSaveImage/cvLoadImage则不能正确处理。
不过opencv本身的影像读写模块设计的比较好,很容易进行扩充。而且opencv在处理 tiff等格式的图像的时候也是借用了libtiff库。因此,我们自己也可以给opencv的 cvSaveImage/cvLoadImage增加新的图像格式支持。
例如,假设定义了一个txt格式的图像。那么就可以用以下代码进行读写:
IplImage *img;
img = cvLoadImage("test.txt", 1);
cvSaveImage("save.txt", img);
具体的示例代码现在没有完全写好,等完善之后会将驱动的实现细节整理出来。
由于内容比较多,我另开了一帖 OpenCV的图像读写框架.
关于cvLoadImage/cvSaveImage的读写框架已经分析过。 现在我们来尝试自己实现一个图像格式的读写操作。
现在,我们定义图像后缀名为bin,例如image.bin等。 bin格式的结构和IplImage结构基本对应,具体如下:
"BIN" // 文件类型,三个字节
width // 图像宽度,int
height // 图像高度,int
depth // 像素深度,int
channels // 通道数,int
data // 每个像素数据
bin格式图像文件为二进制格式。由于opencv内部实现的一些限制, 对于文本格式的图像文件支持可能相对困难一些(后面会提到)。
现在,参考grfmt_bmp代码,创建对应的grfmt_bin.h/grfmt_bin.cpp文件。
grfmt_bin.h内容如下:
#ifndef _GRFMT_BIN_H_
#define _GRFMT_BIN_H_
#include "grfmt_base.h"
// 测试:自己实现的图像格式
// 采用内存格式
// 内部保存一个img图像
// IplImage
class GrFmtBinReader : public GrFmtReader
{
public:
GrFmtBinReader( const char* filename );
~GrFmtBinReader();
bool ReadData( uchar* data, int step, int color );
bool ReadHeader();
void Close();
protected:
FILE* m_fp; // 文件指针
int m_channels; // 通道数
};
// ... writer
class GrFmtBinWriter : public GrFmtWriter
{
public:
GrFmtBinWriter( const char* filename );
~GrFmtBinWriter();
bool WriteImage( const uchar* data, int step,
int width, int height, int depth, int channels );
protected:
FILE* m_fp; // 文件指针
};
// ... and filter factory
class GrFmtBin : public GrFmtFilterFactory
{
public:
GrFmtBin();
~GrFmtBin();
GrFmtReader* NewReader( const char* filename );
GrFmtWriter* NewWriter( const char* filename );
};
#endif/*_GRFMT_BIN_H_*/
grfmt_bin.cpp对应各个成员函数的实现。首先包含必要的头文件:
#include "_highgui.h"
#include "grfmt_bin.h"
GrFmtBin对应bin格式的对象工厂,其中NewReader/NewWriter用于创建 用于读/写bin格式图像的驱动。
GrFmtBin::GrFmtBin()
{
// 文件标志在开头,对应3个字节
m_sign_len = 3;
// 文件标志为"BIN"
m_signature = "BIN";
// 图像文件名描述,小括弧中的为后缀名
m_description = "Bin image (*.bin)";
// 只有后缀名为bin,并且以"BIN"3个字节开头的
// 文件才会被当作bin格式图像。
}
GrFmtBin::~GrFmtBin()
{
}
// 构造读驱动
GrFmtReader* GrFmtBin::NewReader( const char* filename )
{
return new GrFmtBinReader( filename );
}
// 构造写驱动
GrFmtWriter* GrFmtBin::NewWriter( const char* filename )
{
return new GrFmtBinWriter( filename );
}
读驱动为GrFmtBinReader,从GrFmtReader派生:
GrFmtBinReader::GrFmtBinReader( const char* filename ) : GrFmtReader( filename )
{
m_fp = NULL;
}
GrFmtBinReader::~GrFmtBinReader()
{
}
// 关闭图像文件
void GrFmtBinReader::Close()
{
if(m_fp) fclose(m_fp);
GrFmtReader::Close();
}
// 读图像的属性
bool GrFmtBinReader::ReadHeader()
{
// 打开图像文件
m_fp = fopen(m_filename, "rb");
if(!m_fp) return false;
// 跳过图像格式"BIN"
char signature[3];
fread(signature, 1, 3, m_fp);
// int t[] = { width, height, depth, channels };
// fwrite(t, sizeof(t), 1, m_fp);
int t[4];
fread(t, sizeof(t), 1, m_fp);
m_width = t[0];
m_height = t[1];
m_bit_depth = t[2];
m_channels = t[3];
m_iscolor = (m_channels>1)? true: false;
assert(m_height > 0 && m_width > 0);
return true;
}
// 图像的像素数据
bool GrFmtBinReader::ReadData( uchar* data, int step, int color)
{
if(color && m_channels == 1)
{
// 转换为彩色
int y;
for( y = 0; y < m_height; y++, data += step )
{
int x;
for(x = 0; x < m_width; ++x)
{
int size = m_channels*m_bit_depth/8;
char buf[8];
fread(buf, 1, size, m_fp);
data[size*x+0] = buf[0];
data[size*x+1] = buf[1];
data[size*x+2] = buf[2];
}
}
}
else
{
// 这里有待完善
int y;
for( y = 0; y < m_height; y++, data += step )
{
int size = m_channels*m_bit_depth*m_width/8;
fread(data, 1, size, m_fp);
}
}
return true;
}
写驱动为GrFmtBinWriter,从GrFmtWriter派生:
GrFmtBinWriter::GrFmtBinWriter( const char* filename ) : GrFmtWriter( filename )
{
}
GrFmtBinWriter::~GrFmtBinWriter()
{
}
bool GrFmtBinWriter::WriteImage( const uchar* data, int step,
int width, int height, int depth, int channels )
{
m_fp = fopen(m_filename, "wb");
if(!m_fp) return false;
const char *signature = fmtSignBin;
fwrite(signature, 1, 3, m_fp);
{
int t[] = { width, height, depth, channels };
fwrite(t, sizeof(t), 1, m_fp);
}
int i;
for(i = 0; i < height; ++i, data += step)
{
fwrite(data, depth*channels/8, width, m_fp);
}
fclose(m_fp);
return true;
}
到这里bin格式的驱动已经基本实现。下面还需要将驱动集成到opencv中。
1. 在grfmts.h包含bin格式图像驱动
#include "grfmt_bin.h"
2. 在loadsave.cpp中构造g_Filters的时候,添加驱动到链表
具体代码在CvImageFilters构造函数中完成:
CvImageFilters::CvImageFilters()
{
m_factories = new GrFmtFactoriesList;
m_factories->AddFactory( new GrFmtBmp() );
m_factories->AddFactory( new GrFmtJpeg() );
m_factories->AddFactory( new GrFmtSunRaster() );
m_factories->AddFactory( new GrFmtPxM() );
m_factories->AddFactory( new GrFmtTiff() );
#ifdef HAVE_PNG
m_factories->AddFactory( new GrFmtPng() );
#endif
#ifdef HAVE_JASPER
m_factories->AddFactory( new GrFmtJpeg2000() );
#endif
#ifdef HAVE_ILMIMF
m_factories->AddFactory( new GrFmtExr() );
#endif
// Bin格式图像驱动
m_factories->AddFactory( new GrFmtBin() );
}
这里需要补充的是,m_factories链表中的顺序可能会影响 FindReader/FindWriter查找结果。在默认实现中是顺序查找, 因此链表中排在前面的驱动被优先查找。
现在bin格式图像的驱动已经全部实现,重新编译highgui工程。
下一节将讲述cvLoadImage/cvSaveImage的具体执行流程。
测试bin格式的驱动代码:
#include <assert.h>
#include <highgui.h>
int main()
{
const char *file_bmp = "E:\\TestXX\\gtalk-Alien.bmp";
const char *file_bin = "E:\\TestXX\\gtalk-Alien.bin";
// 将bmp转换为bin格式
IplImage *img_bmp = cvLoadImage(file_bmp, 1);
assert(img_bmp != NULL);
cvSaveImage(file_bin, img_bmp);
// 从bin格式装载图像
// 目前bin驱动还不完善,第二个参数需设置为1
IplImage *img_bin = cvLoadImage(file_bin, 1);
assert(img_bin != NULL);
// 创建窗口,用于显示图像
const char *win_bmp = "Bmp Image";
const char *win_bin = "Bin Image";
cvNamedWindow(win_bmp);
cvNamedWindow(win_bin);
// 显示2个图像
cvShowImage(win_bmp, img_bmp);
cvShowImage(win_bin, img_bin);
// 等待
cvWaitKey(0);
// 释放资源
cvDestroyAllWindows();
cvReleaseImage(&img_bmp);
cvReleaseImage(&img_bin);
return 0;
}
转换后的bin格式图像见附件,用户可以自己尝试打开bin图像文件。
调试驱动:
假设上面的测试对应的程序为C:\testBin.exe。 用VC6打开opencv的工程,设置highgui工程的属性:在调试可以执行栏中输入C:\testBin.exe。
现在就可以调试highgui了。可以单步调试,也可以在bin的相关位置设置断点。 需要注意的是,highgui工程和testBin工程默认的路径可能不同,因此在打开/保存图像的时候 最好使用绝对路径。