如何将CBitmap转换为cv::Mat?求正确实现方案
我之前在项目里也遇到过CBitmap转cv::Mat的坑,试了好几种方法才找到稳定的实现,给你分享两个靠谱的方案,顺便说下常见的踩坑点,应该能解决你的问题:
方法一:纯GDI实现(不依赖额外库)
这个方法直接用Windows GDI API获取位图数据,兼容性好,不需要额外链接库。核心是处理DDB(设备相关位图)和DIB(设备无关位图)的区别——如果你的CBitmap是DDB,不能直接访问像素数据,必须通过GetDIBits导出为DIB格式。
#include <opencv2/opencv.hpp> #include <afxwin.h> // MFC头文件,包含CBitmap定义 cv::Mat CBitmapToMat(CBitmap& bmp) { BITMAP bmpInfo; bmp.GetBitmap(&bmpInfo); const int width = bmpInfo.bmWidth; const int height = bmpInfo.bmHeight; const int bitsPerPixel = bmpInfo.bmBitsPixel; const int channels = bitsPerPixel / 8; void* pPixelData = bmpInfo.bmBits; std::vector<BYTE> tempBuffer; // 处理DDB(无法直接访问bmBits的情况) if (!pPixelData) { CDC tempDC; tempDC.CreateCompatibleDC(nullptr); CBitmap* pOldBmp = tempDC.SelectObject(&bmp); // 计算DIB数据所需的内存(考虑行对齐) const int bufferSize = ((width * bitsPerPixel + 31) / 32) * 4 * height; tempBuffer.resize(bufferSize); BITMAPINFOHEADER biHeader = {0}; biHeader.biSize = sizeof(BITMAPINFOHEADER); biHeader.biWidth = width; biHeader.biHeight = -height; // 负高度表示像素从上到下存储(和OpenCV一致) biHeader.biPlanes = 1; biHeader.biBitCount = bitsPerPixel; biHeader.biCompression = BI_RGB; // 从DC中提取DIB数据 GetDIBits(tempDC.m_hDC, bmp.m_hObject, 0, height, tempBuffer.data(), reinterpret_cast<BITMAPINFO*>(&biHeader), DIB_RGB_COLORS); pPixelData = tempBuffer.data(); tempDC.SelectObject(pOldBmp); } // 根据位图格式创建对应的cv::Mat cv::Mat resultMat; switch (bitsPerPixel) { case 24: // Windows 24位DIB是BGR通道顺序,和OpenCV默认格式一致,无需转换 resultMat = cv::Mat(height, width, CV_8UC3, pPixelData, bmpInfo.bmWidthBytes); break; case 8: // 8位灰度图(如果带调色板,需确保调色板是灰度映射,否则要额外处理) resultMat = cv::Mat(height, width, CV_8UC1, pPixelData, bmpInfo.bmWidthBytes); break; case 32: // 32位一般是BGRA(或BGRX,X为填充位),可按需转成BGR resultMat = cv::Mat(height, width, CV_8UC4, pPixelData, bmpInfo.bmWidthBytes); // 如果不需要Alpha通道:cv::cvtColor(resultMat, resultMat, cv::COLOR_BGRA2BGR); break; default: throw std::runtime_error("Unsupported bitmap format (only 8/24/32 bits are supported)"); } // 返回数据拷贝,避免临时内存释放后Mat悬空 return resultMat.clone(); }
方法二:GDI+实现(代码更简洁)
如果你可以接受链接GDI+库,这个方法代码更简洁,能自动处理更多格式细节,比如通道转换、行对齐等。
#include <opencv2/opencv.hpp> #include <afxwin.h> #include <gdiplus.h> #pragma comment(lib, "gdiplus.lib") // 链接GDI+库 using namespace Gdiplus; cv::Mat CBitmapToMatGDIPlus(CBitmap& bmp) { BITMAP bmpInfo; bmp.GetBitmap(&bmpInfo); const int width = bmpInfo.bmWidth; const int height = bmpInfo.bmHeight; // 初始化GDI+ GdiplusStartupInput startupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &startupInput, nullptr); // 将CBitmap转换为GDI+ Bitmap对象 HBITMAP hBmp = static_cast<HBITMAP>(bmp); Bitmap* pGdiBitmap = Bitmap::FromHBITMAP(hBmp, nullptr); // 锁定Bitmap获取像素数据 BitmapData bitmapData; const Rect pixelRect(0, 0, width, height); pGdiBitmap->LockBits(&pixelRect, ImageLockModeRead, PixelFormat24bppRGB, &bitmapData); // 创建cv::Mat并转换通道顺序(GDI+是RGB,OpenCV默认是BGR) cv::Mat resultMat(height, width, CV_8UC3, bitmapData.Scan0, bitmapData.Stride); cv::cvtColor(resultMat, resultMat, cv::COLOR_RGB2BGR); // 清理资源 pGdiBitmap->UnlockBits(&bitmapData); delete pGdiBitmap; GdiplusShutdown(gdiplusToken); return resultMat.clone(); }
常见踩坑点(为什么你的代码之前跑不通?)
- 行对齐问题:Windows位图的每行像素数据是按4字节对齐的,不能直接用
width * channels作为步长,必须使用bmWidthBytes(GDI)或Stride(GDI+)。 - 通道顺序混乱:Windows DIB是BGR顺序,GDI+是RGB顺序,和OpenCV默认的BGR格式要对应,否则图像颜色会出现红蓝反转。
- DDB vs DIB混淆:如果你的CBitmap是设备相关位图(DDB),直接访问
bmBits会得到空指针,必须用GetDIBits或GDI+导出数据。 - 悬空指针问题:如果直接用临时内存的指针创建cv::Mat,当临时内存释放后,Mat会变成无效的悬空指针,必须调用
clone()拷贝数据。
内容的提问来源于stack exchange,提问作者Bobby Dillanl
相关产品推荐
相关产品推荐

