OpenCV / Tesseract:如何用GDI+位图替换libpng, libtiff等(通过GDI+加载到cv::
OpenCV / Tesseract: How to replace libpng, libtiff etc with GDI+ Bitmap (Load into cv::Mat via GDI+)
我正在做一个使用OpenCV和Tesseract的项目。这两个库都基于Libpng、libtiff、libjpeg等。加载/保存图像文件。
但是Tesseract(基于Leptonica)使用这些库的旧版本,这些库具有不兼容的参数。所以我不能使用相同的图像库:OpenCV和Tesseract。
所以如果我动态编译我的项目,我将不得不交付一堆dll与我的项目。如果我静态编译,我会生成一个巨大的输出文件,有几兆字节。
这是丑陋的。我不想那样。
另一个问题是,几乎所有的开源项目——主要是在Linux/MAC世界开发的——在Windows上编译时不支持Unicode。在内部它们都传递一个std::string
到fopen()
。在Linux上,用UTF8编码路径的解决方案可能起作用,但在Windows上不起作用。因此,日本用户无法打开带有日本名称的文件夹中的图像文件。虽然微软在20世纪90年代早期已经做出了巨大的努力,将整个Windows NT操作系统转换为100% Unicode兼容,但20年后,大多数开源项目(如libpng)仍然不支持通过std::wstring
传递路径。
IMPORTANT:如果你想创建一个支持日语或中文的国际项目,OpenCV命令imread()
和imwrite()
不能在Windows上使用!
所以,我想要的是:从我的项目中完全删除libtiff, libpng, libjpeg等文件:
在OpenCV注释出来:
// #define HAVE_JASPER
// #define HAVE_JPEG
// #define HAVE_PNG
// #define HAVE_TIFF
etc..
In Tesseract/Leptonica:
#define HAVE_LIBJPEG 0
#define HAVE_LIBTIFF 0
#define HAVE_LIBPNG 0
#define HAVE_LIBZ 0
#define HAVE_LIBGIF 0
#define HAVE_LIBUNGIF 0
etc..
. .并使用GDI+,这是Windows操作系统的一部分,支持加载/保存BMP, TIF, PNG, JPG, GIF。此外,GDI+是Unicode兼容的。
我知道这可以用几行代码完成,但是在OpenCV项目中缺少这样一个有用的类。我的第一次试验表明,这并不像乍一看那么微不足道,因为必须进行大量转换。
是否有一个已经为此目的创建的类?
我找不到现成的类,所以我自己写了一个:
我希望它对某些人有用,我希望它将作为Windows用户的可选附加组件包含在OpenCV项目中。
优势:
- 删除几个已经在Windows中实现的库, Unicode支持,
- 位图可以直接传递给c#应用程序。
当您研究代码时,您将看到有几个陷阱,并且cv::Mat
和Gdiplus::Bitmap
之间的转换并不像看起来那么简单。
注:此代码支持黑/白(2位),灰度调色板(8位),24位RGB和32位ARGB图像。不支持调色板图像。但这并不重要,因为OpenCV也不支持它们,而且。net对它们的支持也非常有限。
头文件:
#pragma once
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
// IMPORTANT:
// This must be included AFTER gdiplus !!
// (OpenCV #undefine's min(), max())
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
class CGdiPlus
{
public:
static void Init();
static Mat ImgRead(const WCHAR* u16_File);
static void ImgWrite(Mat i_Mat, const WCHAR* u16_File);
static Mat CopyBmpToMat(Gdiplus::Bitmap* pi_Bmp);
static Mat CopyBmpDataToMat(Gdiplus::BitmapData* pi_Data);
static Gdiplus::Bitmap* CopyMatToBmp(Mat& i_Mat);
private:
static CLSID GetEncoderClsid(const WCHAR* u16_File);
static BOOL mb_InitDone;
};
CPP文件:
#include "stdafx.h"
#include "CGdiPlus.h"
using namespace Gdiplus;
BOOL CGdiPlus::mb_InitDone = FALSE;
// Do not call this function in the DLL loader lock!
void CGdiPlus::Init()
{
if (mb_InitDone)
return;
GdiplusStartupInput k_Input;
ULONG_PTR u32_Token;
if (Ok != GdiplusStartup(&u32_Token, &k_Input, NULL))
throw L"Error initializing GDI+";
mb_InitDone = TRUE;
}
Mat CGdiPlus::CopyBmpToMat(Bitmap* pi_Bmp)
{
assert(mb_InitDone);
BitmapData i_Data;
Gdiplus::Rect k_Rect(0, 0, pi_Bmp->GetWidth(), pi_Bmp->GetHeight());
if (Ok != pi_Bmp->LockBits(&k_Rect, ImageLockModeRead, pi_Bmp->GetPixelFormat(), &i_Data))
throw L"Error locking Bitmap.";
Mat i_Mat = CopyBmpDataToMat(&i_Data);
pi_Bmp->UnlockBits(&i_Data);
return i_Mat;
}
Mat CGdiPlus::CopyBmpDataToMat(BitmapData* pi_Data)
{
assert(mb_InitDone);
int s32_CvType;
switch (pi_Data->PixelFormat)
{
case PixelFormat1bppIndexed:
case PixelFormat8bppIndexed:
// Special case treated separately below
break;
case PixelFormat24bppRGB: // 24 bit
s32_CvType = CV_8UC3;
break;
case PixelFormat32bppRGB: // 32 bit
case PixelFormat32bppARGB: // 32 bit + Alpha channel
s32_CvType = CV_8UC4;
break;
default:
throw L"Image format not supported.";
}
Mat i_Mat;
if (pi_Data->PixelFormat == PixelFormat1bppIndexed) // 1 bit (special case)
{
i_Mat = Mat(pi_Data->Height, pi_Data->Width, CV_8UC1);
for (UINT Y=0; Y<pi_Data->Height; Y++)
{
BYTE* pu8_Src = (BYTE*)pi_Data->Scan0 + Y * pi_Data->Stride;
BYTE* pu8_Dst = i_Mat.ptr<BYTE>(Y);
BYTE u8_Mask = 0x80;
for (UINT X=0; X<pi_Data->Width; X++)
{
pu8_Dst[0] = (pu8_Src[0] & u8_Mask) ? 255 : 0;
pu8_Dst++;
u8_Mask >>= 1;
if (u8_Mask == 0)
{
pu8_Src++;
u8_Mask = 0x80;
}
}
}
}
else if (pi_Data->PixelFormat == PixelFormat8bppIndexed) // 8 bit gray scale palette (special case)
{
i_Mat = Mat(pi_Data->Height, pi_Data->Width, CV_8UC1);
BYTE* u8_Src = (BYTE*)pi_Data->Scan0;
BYTE* u8_Dst = i_Mat.data;
for (UINT R=0; R<pi_Data->Height; R++)
{
memcpy(u8_Dst, u8_Src, pi_Data->Width);
u8_Src += pi_Data->Stride;
u8_Dst += i_Mat.step;
}
}
else // 24 Bit / 32 Bit
{
// Create a Mat pointing to external memory
Mat i_Ext(pi_Data->Height, pi_Data->Width, s32_CvType, pi_Data->Scan0, pi_Data->Stride);
// Create a Mat with own memory
i_Ext.copyTo(i_Mat);
}
return i_Mat;
}
Bitmap* CGdiPlus::CopyMatToBmp(Mat& i_Mat)
{
assert(mb_InitDone);
PixelFormat e_Format;
switch (i_Mat.channels())
{
case 1: e_Format = PixelFormat8bppIndexed; break;
case 3: e_Format = PixelFormat24bppRGB; break;
case 4: e_Format = PixelFormat32bppARGB; break;
default: throw L"Image format not supported.";
}
// Create Bitmap with own memory
Bitmap* pi_Bmp = new Bitmap(i_Mat.cols, i_Mat.rows, e_Format);
BitmapData i_Data;
Gdiplus::Rect k_Rect(0, 0, i_Mat.cols, i_Mat.rows);
if (Ok != pi_Bmp->LockBits(&k_Rect, ImageLockModeWrite, e_Format, &i_Data))
{
delete pi_Bmp;
throw L"Error locking Bitmap.";
}
if (i_Mat.elemSize1() == 1) // 1 Byte per channel (8 bit gray scale palette)
{
BYTE* u8_Src = i_Mat.data;
BYTE* u8_Dst = (BYTE*)i_Data.Scan0;
int s32_RowLen = i_Mat.cols * i_Mat.channels(); // != i_Mat.step !!
// The Windows Bitmap format requires all rows to be DWORD aligned (always!)
// while OpenCV by default stores bitmap data sequentially.
for (int R=0; R<i_Mat.rows; R++)
{
memcpy(u8_Dst, u8_Src, s32_RowLen);
u8_Src += i_Mat.step; // step may be e.g 3729
u8_Dst += i_Data.Stride; // while Stride is 3732
}
}
else // i_Mat may contain e.g. float data (CV_32F -> 4 Bytes per pixel grayscale)
{
int s32_Type;
switch (i_Mat.channels())
{
case 1: s32_Type = CV_8UC1; break;
case 3: s32_Type = CV_8UC3; break;
default: throw L"Image format not supported.";
}
CvMat i_Dst;
cvInitMatHeader(&i_Dst, i_Mat.rows, i_Mat.cols, s32_Type, i_Data.Scan0, i_Data.Stride);
CvMat i_Img = i_Mat;
cvConvertImage(&i_Img, &i_Dst, 0);
}
pi_Bmp->UnlockBits(&i_Data);
// Add the grayscale palette if required.
if (e_Format == PixelFormat8bppIndexed)
{
CByteArray i_Arr;
i_Arr.SetSize(sizeof(ColorPalette) + 256 * sizeof(ARGB));
ColorPalette* pk_Palette = (ColorPalette*)i_Arr.GetData();
pk_Palette->Count = 256;
pk_Palette->Flags = PaletteFlagsGrayScale;
ARGB* pk_Color = &pk_Palette->Entries[0];
for (int i=0; i<256; i++)
{
pk_Color[i] = Color::MakeARGB(255, i, i, i);
}
if (Ok != pi_Bmp->SetPalette(pk_Palette))
{
delete pi_Bmp;
throw L"Error setting grayscale palette.";
}
}
return pi_Bmp;
}
Mat CGdiPlus::ImgRead(const WCHAR* u16_File)
{
assert(mb_InitDone);
Bitmap i_Bmp(u16_File);
if (!i_Bmp.GetWidth() || !i_Bmp.GetHeight())
throw L"Error loading image from file.";
return CopyBmpToMat(&i_Bmp);
}
void CGdiPlus::ImgWrite(Mat i_Mat, const WCHAR* u16_File)
{
assert(mb_InitDone);
CLSID k_Clsid = GetEncoderClsid(u16_File);
Bitmap* pi_Bmp = CopyMatToBmp(i_Mat);
Status e_Status = pi_Bmp->Save(u16_File, &k_Clsid);
delete pi_Bmp;
if (e_Status != Ok)
throw L"Error saving image to file.";
}
// Get the class identifier of the image encoder for the given file extension.
// e.g. {557CF406-1A04-11D3-9A73-0000F81EF32E} for PNG images
CLSID CGdiPlus::GetEncoderClsid(const WCHAR* u16_File)
{
assert(mb_InitDone);
UINT u32_Encoders, u32_Size;
if (Ok != GetImageEncodersSize(&u32_Encoders, &u32_Size))
throw L"Error obtaining image encoders size";
CByteArray i_Arr;
i_Arr.SetSize(u32_Size);
ImageCodecInfo* pi_Info = (ImageCodecInfo*)i_Arr.GetData();
if (Ok != GetImageEncoders(u32_Encoders, u32_Size, pi_Info))
throw L"Error obtaining image encoders";
CStringW s_Ext = u16_File;
int Pos = s_Ext.ReverseFind('.');
if (Pos < 0)
throw L"Invalid image filename.";
// s_Ext = "*.TIF;"
s_Ext = L"*" + s_Ext.Mid(Pos) + L";";
s_Ext.MakeUpper();
// Search the file extension
for (UINT i=0; i<u32_Encoders; i++)
{
CStringW s_Extensions = pi_Info->FilenameExtension;
s_Extensions += ';';
// s_Extensions = "*.TIFF;*.TIF;"
if (s_Extensions.Find(s_Ext) >= 0)
return pi_Info->Clsid;
pi_Info ++;
}
throw L"No image encoder found for file extension " + s_Ext;
}
- 使用 GDI+ 旋转位图,然后转换为 HDC
- 没有专用显卡的 Direct2D 与 GDI+
- GDI 绘制到外部窗口 (C++)
- C++ WinAPI[GDI].自定义 gui 正确重绘
- C++ Gdi+将图像转换为灰度
- C++GDI+ 选择调色板
- 如何从内存中分配GDI+ POINT类地址?
- 无法链接 libtiff
- GDI+-无法对Gdiplus::Graphics(C++)执行任何操作
- C++gdi::内存中的位图到PNG图像
- GDI+ flickering
- UINT8/16 /32/etc 和 INT8/16/32/etc 在 libtiff 中不起作用?
- 如何在C++中为 Windows 的 GDI 正确设置库?
- 使用 Win32 将 GDI 绘制大小缩放为窗口大小
- CMake / 错误时链接 libfreeimage / libtiff.
- 如何从依赖于设备的 HBITMAP 构造 GDI+ 位图对象
- 从 OpenGL 切换到 GDI
- 使用 GDI+ 在C++中制作流畅的动画
- avcodec_receive_packet错误(gdi 屏幕截图 + ffmpeg)
- OpenCV / Tesseract:如何用GDI+位图替换libpng, libtiff等(通过GDI+加载到cv::