首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

OpenCV IplImage 與 MFC CBitmap 互轉:基於 CreateDIBitmap 的高效實作

     編輯:關於C語言

在 MFC 專案中整合 OpenCV (舊版 C 介面 `IplImage`) 時,常需將影像資料繪製至 MFC 視窗或轉為 `CBitmap` 進行 GDI 操作。本文提供基於 `CreateDIBitmap` 的零拷貝 (或最小拷貝) 轉換函數,並解析為何 `CDC::StretchBlt` 失效而 `StretchDIBits` 成功的關鍵差異。

核心轉換函數:IplImage → HBITMAP → CBitmap

CBitmap* IplImageToCBitmap(IplImage* pImg, CDC* pDC) {
    if (!pImg || pImg->depth != IPL_DEPTH_8U) return nullptr;

    // 1. 建立 BITMAPINFOHEADER (含調色盤)
    int width = pImg->width;
    int height = pImg->height;
    int bpp = (pImg->depth & 255) * pImg->nChannels; // 8/24/32
    int origin = pImg->origin; // 0=底部優先, 1=頂部優先

    // 分配足夠空間給 BITMAPINFOHEADER + 調色盤 (最大 256*4 bytes)
    BYTE buffer[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
    BITMAPINFO* pBMI = (BITMAPINFO*)buffer;
    BITMAPINFOHEADER* pBMIH = &pBMI->bmiHeader;

    memset(pBMIH, 0, sizeof(*pBMIH));
    pBMIH->biSize = sizeof(BITMAPINFOHEADER);
    pBMIH->biWidth = width;
    pBMIH->biHeight = origin ? abs(height) : -abs(height); // 正高度=底部優先 (DIB), 負高度=頂部優先 (BMP)
    pBMIH->biPlanes = 1;
    pBMIH->biBitCount = (WORD)bpp;
    pBMIH->biCompression = BI_RGB;

    // 8 位灰階需填入灰階調色盤
    if (bpp == 8) {
        RGBQUAD* palette = pBMI->bmiColors;
        for (int i = 0; i < 256; ++i) {
            palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
            palette[i].rgbReserved = 0;
        }
    }

    // 2. 關鍵:CreateDIBitmap 需要「目標裝置內容」以正確處理調色盤實現
    HBITMAP hBmp = CreateDIBitmap(
        pDC->GetSafeHdc(),        // 必須傳入有效 HDC,NULL 會建立裝置無關位圖 (DIB Section) 導致調色盤錯誤
        pBMIH,
        CBM_INIT,
        pImg->imageData,          // 直接使用 IplImage 資料指標 (零拷貝)
        pBMI,
        DIB_RGB_COLORS
    );
    if (!hBmp) return nullptr;

    // 3. 封裝為 CBitmap
    CBitmap* pBmp = new CBitmap;
    pBmp->Attach(hBmp);
    return pBmp; // 呼叫端負責 delete
}

為何 `CDC::StretchBlt` 失效而 `StretchDIBits` 可行?

  • CvvImage::DrawToHDC 內部呼叫 ::StretchDIBits,直接將 **DIB 記憶體資料** 繪製至目標 DC,繞過 GDI 位圖物件選入機制。
  • CDC::StretchBlt 要求來源 DC 已選入一個 **相容位圖 (DDB)**。若直接將 `IplImage` 資料 `GetBitmapBits` 寫入 `CBitmap`,該位圖可能為 **DIB Section** 而非裝置相容位圖,導致 `StretchBlt` 失敗 (返回 0,`GetLastError`=ERROR_INVALID_HANDLE)。

反向轉換:CBitmap / HBITMAP → IplImage

IplImage* CBitmapToIplImage(CBitmap* pBmp) {
    BITMAP bm; pBmp->GetBitmap(&bm);
    IplImage* pImg = cvCreateImage(cvSize(bm.bmWidth, bm.bmHeight), IPL_DEPTH_8U, bm.bmBitsPixel / 8);
    // 注意:bm.bmBits 可能為 NULL (DDB 無法直接存取),需先 GetDIBits
    // 簡易版假設為 DIB Section:
    pBmp->GetBitmapBits(pImg->imageSize, pImg->imageData);
    return pImg; // 呼叫端需 cvReleaseImage
}

現代化建議:棄用 IplImage,改用 cv::Mat

OpenCV 2.0+ 強烈建議使用 C++ 介面 cv::Mat,配合 cv::cvarrToMat 互轉。MFC 整合時可直接用 Mat 資料指標建立 `CImage` 或 `HBITMAP`,程式碼更簡潔且具備參考計數自動管理記憶體。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved