【问题标题】:OpenCV / Tesseract: How to replace libpng, libtiff etc with GDI+ Bitmap (Load into cv::Mat via GDI+)OpenCV / Tesseract:如何用 GDI+ 位图替换 libpng、libtiff 等(通过 GDI+ 加载到 cv::Mat)
【发布时间】:2014-09-03 16:44:58
【问题描述】:

我正在开发一个使用 OpenCV 和 Tesseract 的项目。这两个库都基于 libpng、libtiff、libjpeg 等。加载/保存图像文件。

但是 Tesseract(基于 Leptonica)使用这些库的旧版本,这些库具有不兼容的参数。所以我不能对两者使用相同的图像库:OpenCV 和 Tesseract。

因此,如果我动态编译我的项目,我将不得不在我的项目中提供一堆 DLL。 如果我静态编译,我会产生一个巨大的输出文件,放大了几兆字节。

这太丑了。我不想那样。

另一个问题是,如果在 Windows 上编译,几乎所有的开源项目——主要是在 Linux/MAC 世界中开发的——都不支持 Unicode。在内部,它们都将std::string 传递给fopen()。在 Linux 上,使用 UTF8 对路径进行编码的解决方法可能会起作用,但在 Windows 上则不会。因此,日本用户无法打开具有日本名称的文件夹中的图像文件。尽管微软在 1990 年代初期已经做出了巨大的努力来将整个 Windows NT 操作系统转换为 100% Unicode 兼容,但 20 年后的大多数开源项目(如 libpng)仍然不支持通过std::wstring 传递路径。

重要提示:如果您要创建支持日语或中文的国际项目,则不得在 Windows 上使用 OpenCV 命令 imread()imwrite()

所以,我想要的是: 从我的项目中完全消除 libtiff、libpng、libjpeg 等:

在 OpenCV 中注释掉:

// #define HAVE_JASPER
// #define HAVE_JPEG
// #define HAVE_PNG
// #define HAVE_TIFF
etc..

在 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 项目中缺少这样一个有用的类。我的第一次试验表明,这并不像乍一看那样微不足道,因为必须进行很多转换。

是否已经为此目的创建了一个类?

【问题讨论】:

    标签: c++ opencv unicode gdi+ libpng


    【解决方案1】:

    我找不到现成的课程,所以我自己写了:

    我希望它对某人有用,我希望它将作为 Windows 用户的可选附加组件包含到 OpenCV 项目中。

    优点:

    1. 摆脱已在 Windows 中实现的多个库,
    2. Unicode 支持,
    3. 位图可以直接传递给 C# 应用程序。

    当您研究代码时,您会发现有几个陷阱,cv::MatGdiplus::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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-10
      • 2012-08-21
      • 2011-12-03
      • 2011-09-05
      • 2011-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多