使用FreeImage帮助OpenCV读出更多图像(转)
OpenCV读图像的能力比较弱,尤其是对常见的 Tiff 支持得很差。通过研究 OpenCV 的源代码,发现它针对 Tiff 只能读出以 LZW 和 JPEG 两种压缩格式保存的单页 RGBA 图像,而大量使用的 G4 等二值化图像和多页图像的 Tiff 文件是不能被 OpenCV 直接读出的。这可以使用 libTiff 帮助读入这些 Tiff 文件。不过使用 FreeImage 更好一些。因为在 FreeImage 中已经把 libTiff 封装成一个 filter 了,FreeImage 提供了一个高层的图像访问统一接口,支持了大量的图像编解码 filter,并且能够找到大量基于 FreeImage 的 filter ,所以使用 FreeImage 是一个更好的选择。
当读入图像的位深度小于一个字节时,不能直接在 opencv 中创建 IplImage,可以使用 FreeImage 将这类图像转化为灰度图像,然后再输入 opencv 中。
另一个需要注意的是 FreeImage 中的图像如果直接输入 IplImage,则出现了水平翻转,因此不能直接使用从 FreeImage 获得的图像作为 IplImage 的数据。
以下是一段把 FreeImage 的位图转换成 opencv 图像的示例代码(片段):
void zig_image_group::load_bitmap(FIBITMAP* bitmap) {
if (!bitmap) return;
// 取得位图数据
unsigned int imageWidth = FreeImage_GetWidth(bitmap);
unsigned int imageHeight = FreeImage_GetHeight(bitmap);
unsigned short channels = 1;
unsigned short depth = FreeImage_GetBPP(bitmap);
FIBITMAP * bitmap_temp = 0;
unsigned char * data = 0;
if (depth < 8) {
// opencv 只能创建 8 位的图像
depth = 8;
if( NULL != (bitmap_temp = FreeImage_ConvertToGreyscale(bitmap)) ) {
data = FreeImage_GetBits(bitmap_temp);
}
} else {
assert (depth % 8 == 0);
channels = depth / 8;
depth = 8;
data = FreeImage_GetBits(bitmap);
}
// 创建 opencv 图像,读入位图信息
if(data) {
CvSize size = cvSize((int) imageWidth, (int) imageHeight);
// 如果直接从 FreeImage 得到图像,则进行了水平翻转,因此需要使用一个中间图像来纠正翻转
// 同时因为从 FreeImage 得到的图像最终也需要复制到 IplImage 图像中,所以只需要一次内存操作
// 创建一个 IplImage 中间图像并直接使用 FreeImage 的图像,然后创建一个新的 IplImage 图像,
// 并把中间图像翻转到新 IplImage 中,然后关闭中间图像(只是头部结构)
IplImage* head = cvCreateImageHeader(size, depth, channels);
cvSetData(head, data, head->widthStep); // 注意 widthStep 是能被 4 整除的数,为了整数对齐,加速运算
object_ptr spImage(new image(size, depth, (int) channels));// 智能数据对象指针,封装了IplImage
cvFlip(head, spImage->get_IplImage(), 0); // 翻转并复制 FreeImage 位图到 OpenCV 中
cvReleaseImageHeader(&head); // 释放 head,但不释放位图,因为位图是从 FreeImage 中“借”来的
m_images.push_back(adapt< object_ptr > (spImage)); // 保存这个 IplImage 到 std::vector 中
}
if(bitmap_temp) {
FreeImage_Unload(bitmap_temp);
}
}
当图像类型为 TIFF, ICO, GIF 时,可以使用 FreeImage 的多位图功能读出一组的图像,下面是使用 FreeImage 读取图像的代码片段:
void zig_image_group::load_image(const char * filename) {
// 获取图像类型
FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
if( FIF_UNKNOWN ==
(fif = FreeImage_GetFileType(filename, 0)) ) {
fif = FreeImage_GetFIFFromFilename(filename);
}
if( (fif == FIF_UNKNOWN) || ! FreeImage_FIFSupportsReading(fif) ) {
// 使用 free image 不能打开影像,这时再尝试使用 opencv 打开影像
object_ptr sp_image(new image(filename, CV_LOAD_IMAGE_UNCHANGED));
if(sp_image->is_valid()) {
m_images.push_back(adapt< object_ptr >(sp_image));
} else {
cerr << "load image failure: " << filename << endl;
}
return;
}
if (fif == FIF_TIFF || fif == FIF_ICO || fif == FIF_GIF) {
// 读一组图像
FIMULTIBITMAP * mdib =
FreeImage_OpenMultiBitmap(fif, filename, false, true);
if(mdib) {
int pages = FreeImage_GetPageCount(mdib);
for(int page=0; page
FIBITMAP * dib = FreeImage_LockPage(mdib, page);
if(dib) {
load_bitmap(dib);
FreeImage_UnlockPage(mdib, dib, false);
}
}
FreeImage_CloseMultiBitmap(mdib);
}
} else {
// 读一张图像
FIBITMAP * dib = FreeImage_Load(fif, filename);
if(dib) {
load_bitmap(dib);
FreeImage_Unload(dib);
}
}
}
注意,以上代码中使用了类似于 ATL 的两个智能指针模板:object_ptr (类似于 atl::CComPtr), adapt (类似于 atl::CAdapt)。有了这两个智能指针模板,就可以方便地使用 STL 容器存放各种数据对象了。