OpenCVを使用してjpgファイル(画像の横幅は4の倍数に限定されない)を読み込みGDIで表示しさらにMat型に変換する

icon 項目のみ表示/展開表示の切り替え

概要

OpenCVのMat型を使用してtest.jpgファイルを読み込み、メモリデバイスコンテキストで新規に確保したビットマップ用のメモリにMat型のイメージをコピーし、GDIによりウィンドウに表示する。さらにメモリデバイスコンテキストのビットマップをMat型に変換してOpenCVでウィンドウ表示するプログラムである。
Mat型とメモリデバイスコンテキストでは同じアドレスを共有していないのでOpen CVで画像を操作した場合は、メモリデバイスコンテキストへコピーしなおす必要がある。
内部的には1ピクセルあたり3byte割り当てられている。
ウィンドウの処理等はWindowsの流儀で記述できるので楽である。

Visual C++ を対象としておりOpenCVのインストールが必要である。

テスト環境

コンパイラ

Visual C++ 2008 Express(32bit)/Standard(32/64bit) with OpenCV2.3.1
Visual C++ 2013 Express 32bit/64bit with OpenCV2.4.10

実行環境

Windows 7 32/64bit

OpenCVのインストール及び環境設定

プログラムソースの概要

#include の後の行からusing namespace cv;の手前までは、Visual C++のバージョン及びRelease/Debug 32bit/64bit 静的リンク/動的リンクに合わせてロードするLIBのファイル名を自動的に作成するために記述しています。
固定された環境であれば、簡素に記述可能です。
例えば Visual C++ 2013 32bit Release 動的リンクであれば、以下のように簡素に記述できます。
#pragma comment(lib, "c:/opencv2.4.10/opencv/build/vc12/x86/lib/opencv_core2410.lib")
#pragma comment(lib, "c:/opencv2.4.10/opencv/build/vc12/x86/lib/opencv_highgui2410.lib")
using namespace cv; により名前空間をcvにします。これを定義しない場合は、OpenCVの関数を使う場合には関数名の前にcv::を付加する必要があります。

_tWinMain

ウィンドウを開きます。

WndProc

WM_CREATE

ウィンドウの初期化時に呼び出されます。
グローバル変数fileName変数で開くjpgファイルのファイル名を指定しています。
imread関数でjpgファイルをMat型の変数imgにロードします。
imgのメンバー変数のdataがNULLの場合は、ロードに失敗しているのでプログラムを終了させます。
GetDC APIによりウィンドウのデバイスコンテキストハンドルを取得します。
mat2memHDC構造体の()関数によりウィンドウと互換性のあるメモリデバイスコンテキストを作成します。
memHDC2Mat構造体の()関数によりメモリデバイスコンテキストから新規にMat型を作成します。
Release APIによりウィンドウのデバイスコンテキストハンドルを解放します。
InvalidateRect APIによりウィンドウの再描画を要求します。

WM_PAINT

ウィンドウの再描画が必要な場合に呼び出されます。
mat2memHDC構造体のBitBlt関数によりメモリからウィンドウへイメージを転送します。

WM_DESTROY

ウィンドウが閉じるときに呼び出されます。

Mat2MemHDC構造体

Open CVのMat型をGDIで使用するために定義しています。
struct Mat2MemHDC{
        BITMAPINFO bmi;
        HBITMAP hbmp;
        BYTE *pBits;
        HDC memHDC;

        Mat2MemHDC();
        ~Mat2MemHDC();
        //      Mat型の画像とメモリデバイスコンテキストに変換する
        bool operator()(HDC hdc, Mat img);
        int width(void);
        int height(void);
        void BitBlt(HDC hdc,int dx,int dy,DWORD rop);
};

operator()(HDC hdc, Mat img);

デバイスコンテキストハンドルとMat型を与えて呼び出すと指定したデバイスコンテキストと互換性のあるメモリデバイスコンテキストを作成し、ビットマップデータ用のメモリが確保されていない場合または、画像サイズが変わった場合は新規に作成します。
Mat型の画像の横幅が4の倍数でない場合を想定して、1ラインごとにコピーし、4の倍数のあまりのピクセルは白を書き込みます。

BitBlt

指定されたデバイスコンテキストにビットマップを転送します。

MemHDC2Mat

ビットマップをMat型に変換します。
struct MemHDC2Mat{
        Mat* mat;
        int width, height;
        MemHDC2Mat();
        void operator()(HBITMAP hbmp);
        ~MemHDC2Mat();
};

void operator()(HBITMAP hbmp);

Mat型のメモリが確保されていないとき及び画像サイズが異なる場合は、新たに確保します。
画像のメモリサイズを計算してからメモリ間のコピーを行います。

ソースコード

//      OpenCV Mat型とGDI BITMAPの相互変換サンプル
//
//      test.jpgファイルをMat型に読み込み、GDI BITMAPに変換してウィンドウに描画
//      GDI BITMAPをMat型に変換してOpenCVでウィンドウを開いて表示
//
//      Open CV 2.3.1/2.4.10 サポート
//      例えばVisual C++ 2013でOpen CV 2.4.10でコンパイルする場合の設定は以下の通りとなる
//              VCのインクルードディレクトリに C:\opencv2.4.10\opencv\build\include; を付加
//              動的リンクで作成した場合は、サンプルの実行には環境変数PATHに以下のフォルダーを登録する必要がある。
//              win64   C:\opencv2.4.10\opencv\build\x64\vc12\bin
//              win32   C:\opencv2.4.10\opencv\build\x86\vc12\bin
//      LIBファイルは本ソースのプラグマで設定しているので、CV_INST_DIR CV_INSt_DIR_SUBマクロでLIBファイルのパスを設定する必要がある。
//              本ソースは、c:\opencvメジャー番号.マイナー番号.サブマイナー番号\opencv\build を想定
//      動作確認
//              Visual C++ 2008 Standard Release 64bit 動的/静的    OpenCV 2.3.1
//              Visual C++ 2008 Standard Debug 64bit 動的/静的              OpenCV 2.3.1
//              Visual C++ 2008 Standard Release 32bit 動的/静的    OpenCV 2.3.1
//              Visual C++ 2008 Standard Debug 32bit 動的/静的              OpenCV 2.3.1
//              Visual C++ 2013 Express Debug 32bit 動的/静的               OpenCV 2.4.10
//              Visual C++ 2013 Express Release 32bit 動的/静的             OpenCV 2.4.10
//              Visual C++ 2013 Express Debug 64bit 動的/静的               OpenCV 2.4.10
//              Visual C++ 2013 Express Release 64bit 動的/静的             OpenCV 2.4.10

#include <windows.h>
#include <opencv2/opencv.hpp>
#include <tchar.h>
#include <commctrl.h> 

#ifdef _DLL     //      動的リンク
 #define CV_LINK_MODE "/lib/"
#else   //      静的リンク
 #define CV_LINK_MODE "/staticlib/"
#endif

// バージョン取得
#define CV_VERSION_STR CVAUX_STR(CV_MAJOR_VERSION) CVAUX_STR(CV_MINOR_VERSION) CVAUX_STR(CV_SUBMINOR_VERSION)

#define CV_INST_SUB_DIR  "opencv" ## CVAUX_STR(CV_MAJOR_VERSION) ## "." CVAUX_STR(CV_MINOR_VERSION) ## "." CVAUX_STR(CV_SUBMINOR_VERSION)

#define CV_INST_DIR "c:/" ## CV_INST_SUB_DIR ## "/opencv/build"


//      32bit/64bit ランタイムのリンクモード等に応じてLIBファイルのフォルダー名(CV_LIB_DIR)を作成

#ifdef _WIN64
 #ifdef _DLL
  #define CV_LIB_DIR  CV_INST_DIR ## "/x64/" ## CV_MS_VER ## "/lib/"
 #else
  #define CV_LIB_DIR  CV_INST_DIR ## "/x64/" ## CV_MS_VER ## "/staticlib/"
 #endif
#else
 #ifdef _DLL
  #define CV_LIB_DIR  CV_INST_DIR ## "/x86/" ## CV_MS_VER ## "/lib/"
 #else
  #define CV_LIB_DIR  CV_INST_DIR ## "/x86/" ## CV_MS_VER ## "/staticlib/"
 #endif
#endif


#if _MSC_VER==1500
 #define VCVER 2008
 #define CV_MS_VER "vc9"
#endif

#if _MSC_VER==1600
 #define VCVER 2010
 #define CV_MS_VER "vc10"
#endif

#if _MSC_VER==1700
 #define VCVER 2012
 #define CV_MS_VER "vc11"
#endif

#if _MSC_VER==1800
 #define VCVER 2008
 #define CV_MS_VER "vc12"
#endif


#ifdef _DEBUG   //      デバック
#define CV_EXT_STR "d.lib"
#else   //      リリース
#define CV_EXT_STR ".lib"
#endif
//
#pragma comment(lib, CV_LIB_DIR "opencv_core"  CV_VERSION_STR CV_EXT_STR)
#pragma comment(lib, CV_LIB_DIR "opencv_highgui" CV_VERSION_STR CV_EXT_STR)

#ifdef _DLL     //      動的リンク

#else
 #pragma comment(lib,"comctl32.lib")

 #pragma comment(linker, "/nodefaultlib:\"msvcprt"  CV_EXT_STR "\"")
 #if CV_MAJOR_VERSION==2 && CV_MINOR_VERSION==4 && CV_SUBMINOR_VERSION==10
  #pragma comment(lib, CV_LIB_DIR "IlmImf"  CV_EXT_STR )
 #endif
 #pragma comment(lib, CV_LIB_DIR "libjasper"  CV_EXT_STR )
 #pragma comment(lib, CV_LIB_DIR "libjpeg"  CV_EXT_STR )
 #pragma comment(lib, CV_LIB_DIR "libpng"  CV_EXT_STR )
 #pragma comment(lib, CV_LIB_DIR "libtiff"  CV_EXT_STR )
 #pragma comment(lib, CV_LIB_DIR "zlib"  CV_EXT_STR )
#endif

using namespace cv;

//      ウィンドウプロシージャー

TCHAR* szClassName = _TEXT("GDI BitBlt");


//      Mat型をメモリデバイスコンテキストに変換するクラス

struct Mat2MemHDC{
        BITMAPINFO bmi;
        HBITMAP hbmp;
        BYTE *pBits;
        HDC memHDC;

        Mat2MemHDC(){
                pBits = 0;
        }
        ~Mat2MemHDC(){
                if (pBits){
                        DeleteObject(hbmp);
                        DeleteDC(memHDC);
                }
        }
        //      Mat型の画像とメモリデバイスコンテキストに変換する
        bool operator()(HDC hdc, Mat img){
                //      横幅を4の倍数に合わせる
                int sx = img.cols;
                if (sx % 4){
                        sx = (sx + 4) & 0xfffffffc;
                }
                if (pBits == NULL || (pBits && (sx != bmi.bmiHeader.biWidth || img.rows != bmi.bmiHeader.biHeight)) ){
                                DeleteObject(hbmp);
                                DeleteDC(memHDC);
                                bmi.bmiHeader.biSize = sizeof(bmi);
                                bmi.bmiHeader.biWidth = sx;
                                bmi.bmiHeader.biHeight = -img.rows;     //      上下反転
                                bmi.bmiHeader.biBitCount = img.channels() * 8;

                                bmi.bmiHeader.biPlanes = 1;
                                bmi.bmiHeader.biCompression = BI_RGB;
                                bmi.bmiHeader.biSizeImage = 0;
                                bmi.bmiHeader.biXPelsPerMeter = 0;
                                bmi.bmiHeader.biYPelsPerMeter = 0;
                                bmi.bmiHeader.biClrUsed = 0;
                                bmi.bmiHeader.biClrImportant = 0;

                                hbmp = CreateDIBSection(NULL, &bmi, 0, (void**)&pBits, NULL, 0);
                                if (pBits == NULL){     //      メモリ不足
                                        return false;
                                }
                                memHDC = CreateCompatibleDC(hdc);
                                SelectObject(memHDC, hbmp);
                        }
        
                //              imgの横幅が4の倍数でない場合があるので、1ラインずつコピーする
                for (int y = 0; y < height(); y++){
                        memcpy(pBits + y*width()*img.channels(), img.data + y*img.cols*img.channels(), img.cols*img.channels());
                        //      右横の余白を白にする
                        BYTE* p = pBits + (y*width()+img.cols)*img.channels();
                        for (int x = img.cols; x < width(); x++){
                                for (int n = 0; n < img.channels(); n++){
                                        *p++ = 0xff;
                                }
                        }
                }
                return true;
        }
        int width(void){
                return bmi.bmiHeader.biWidth;
        }
        int height(void){
                return -bmi.bmiHeader.biHeight;
        }
        void BitBlt(HDC hdc,int dx,int dy,DWORD rop){
                ::BitBlt(hdc, 0, 0, width(), height(), memHDC, dx, dy, rop);
        }
};

//      ビットマップをMat型に変換するクラス

struct MemHDC2Mat{
        Mat* mat;
        int width, height;
        MemHDC2Mat(){
                mat = 0;
        }
        void operator()(HBITMAP hbmp){
                BITMAP bmp;
                GetObject(hbmp, sizeof(bmp), &bmp);
                if (mat==0 || (mat && (bmp.bmHeight!=height || bmp.bmWidth!=width)) ){
                        delete mat;
                        mat = new Mat(bmp.bmHeight, bmp.bmWidth, CV_8UC(bmp.bmBitsPixel / 8));
                }
                width = bmp.bmWidth;
                height = bmp.bmHeight;
                size_t sz = bmp.bmWidth*bmp.bmHeight*(bmp.bmBitsPixel / 8);
                memcpy(mat->data, bmp.bmBits, sz);
        }
        ~MemHDC2Mat(){
                if (mat)
                        delete mat;
        }
};


Mat2MemHDC mat2memHdc;
MemHDC2Mat memHDC2Mat;

Mat img;

char* fileName = "test.jpg";

// ウィンドウを作成/閉じる/移動等のメッセージにより起動される関数
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);


int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPreInst, char* CmdLine, int nCmdShow){
        HWND hWnd;
        MSG lpMsg;
        WNDCLASS myProg;

        if (!hPreInst) {
                myProg.style = CS_HREDRAW | CS_VREDRAW;
                myProg.lpfnWndProc = WndProc;
                myProg.cbClsExtra = 0;
                myProg.cbWndExtra = 0;
                myProg.hInstance = hPreInst;
                myProg.hIcon = NULL;
                myProg.hCursor = LoadCursor(NULL, IDC_ARROW);
                myProg.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
                myProg.lpszMenuName = NULL;
                myProg.lpszClassName = szClassName;
                if (!RegisterClass(&myProg))
                        return FALSE;
        }
        hWnd = CreateWindow(szClassName,
                szClassName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hPreInst,
                NULL);
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);
        while (GetMessage(&lpMsg, NULL, 0, 0)){
                TranslateMessage(&lpMsg);
                DispatchMessage(&lpMsg);
        }

        return int(lpMsg.wParam);
}

// ウィンドウを作成/閉じる/移動等のメッセージにより起動される関数

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
        HDC hdc;
        PAINTSTRUCT ps;

        switch (msg) {
        case WM_CREATE:
                img = imread(fileName); //      jpgファイルをimgにロードする
                hdc = GetDC(hWnd);
                mat2memHdc(hdc, img);   //      Mat型をメモリデバイスコンテキストに変換する
                memHDC2Mat(mat2memHdc.hbmp);

                namedWindow("OpenCV namedWindow", CV_WINDOW_AUTOSIZE);
                imshow("OpenCV namedWindow", *memHDC2Mat.mat);

                ReleaseDC(hWnd, hdc);
                InvalidateRect(hWnd, 0, TRUE);
                break;
        case WM_PAINT:  // ウィンドウの描画が必要な場合に呼び出される。
                hdc = BeginPaint(hWnd, &ps);
                mat2memHdc.BitBlt(hdc, 0, 0,SRCCOPY);   //      メモリデバイスコンテキストをウィンドウに表示
                EndPaint(hWnd, &ps);
                break;
        case WM_DESTROY:        // ウィンドウを閉じる場合に呼び出される。
                PostQuitMessage(0);
                break;
        default:
                return (DefWindowProc(hWnd, msg, wp, lp));
        }
        return 0L;
}

実行ファイルとソースファイルのダウンロード(view_gdi3.zip)