|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. 引言
MFC(Microsoft Foundation Class)是微软提供的一套C++类库,用于简化Windows应用程序开发。而OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。在实际开发中,我们经常需要在MFC应用程序中集成OpenCV功能,如图像处理、视频分析等。然而,由于两者内存管理机制的不同,特别是在处理OpenCV的Mat对象时,常常会遇到内存泄漏、程序崩溃等问题。本文将详细介绍在MFC环境下正确管理OpenCV Mat对象内存的方法,并提供常见问题的解决方案。
2. OpenCV Mat对象简介
OpenCV的Mat(Matrix)类是用于表示图像和矩阵的主要数据结构。Mat对象包含两部分信息:
• 矩阵头(包含矩阵大小、存储方法、存储地址等信息)
• 指向像素矩阵的指针(包含实际像素值)
Mat对象采用引用计数机制进行内存管理。当进行赋值操作或拷贝构造时,默认只复制矩阵头,而不会复制像素矩阵,多个Mat对象可以指向同一个像素矩阵。只有当所有引用该像素矩阵的Mat对象都被释放时,像素矩阵才会被真正释放。
- // 示例1:Mat对象的引用计数机制
- cv::Mat img1 = cv::imread("image.jpg");
- cv::Mat img2 = img1; // 只复制矩阵头,img1和img2共享像素矩阵
- // 修改img2也会影响img1,因为它们共享像素矩阵
- img2(cv::Rect(10, 10, 100, 100)) = cv::Scalar(0, 0, 255);
- // 此时img1的相应区域也会被修改
复制代码
3. MFC环境下的内存管理特点
MFC应用程序有其特定的内存管理机制,主要包括:
1. 文档/视图结构:MFC的文档/视图架构中,文档对象负责数据的存储和管理,视图对象负责数据的显示。
2. 消息映射机制:MFC使用消息映射来处理Windows消息,这可能导致对象的生命周期与消息处理过程相关联。
3. 对话框和控件:在对话框和控件中使用OpenCV Mat对象时,需要特别注意对象的生命周期。
4. 线程安全:MFC应用程序可能涉及多线程操作,需要确保Mat对象的线程安全访问。
文档/视图结构:MFC的文档/视图架构中,文档对象负责数据的存储和管理,视图对象负责数据的显示。
消息映射机制:MFC使用消息映射来处理Windows消息,这可能导致对象的生命周期与消息处理过程相关联。
对话框和控件:在对话框和控件中使用OpenCV Mat对象时,需要特别注意对象的生命周期。
线程安全:MFC应用程序可能涉及多线程操作,需要确保Mat对象的线程安全访问。
4. MFC环境下Mat对象的常见内存问题
4.1 内存泄漏
内存泄漏是指在MFC应用程序中,Mat对象没有被正确释放,导致程序运行时占用的内存不断增加。
- // 示例2:导致内存泄漏的代码
- void CMyView::OnProcessImage()
- {
- // 每次调用都创建新的Mat对象,但没有释放
- cv::Mat* img = new cv::Mat(cv::imread("image.jpg"));
-
- // 处理图像...
-
- // 忘记释放内存
- // delete img; // 这行被注释掉了,导致内存泄漏
- }
复制代码
4.2 重复释放
重复释放是指对同一个Mat对象多次调用释放操作,导致程序崩溃。
- // 示例3:导致重复释放的代码
- void CMyView::OnProcessImage()
- {
- cv::Mat* img = new cv::Mat(cv::imread("image.jpg"));
-
- // 处理图像...
-
- delete img; // 第一次释放
-
- // 在其他地方再次释放
- delete img; // 第二次释放,导致程序崩溃
- }
复制代码
4.3 悬挂指针
悬挂指针是指指针指向的内存已经被释放,但指针仍然被使用。
- // 示例4:导致悬挂指针的代码
- cv::Mat* g_img = nullptr;
- void CMyView::OnLoadImage()
- {
- g_img = new cv::Mat(cv::imread("image.jpg"));
- }
- void CMyView::OnProcessImage()
- {
- if (g_img)
- {
- // 处理图像...
- }
- }
- void CMyView::OnReleaseImage()
- {
- delete g_img;
- g_img = nullptr;
- }
- // 如果在调用OnProcessImage之前调用了OnReleaseImage,
- // 将导致悬挂指针问题
复制代码
4.4 跨线程访问问题
在MFC多线程应用程序中,如果多个线程同时访问同一个Mat对象,可能导致数据不一致或程序崩溃。
- // 示例5:跨线程访问问题
- cv::Mat g_img;
- UINT ThreadFunc(LPVOID pParam)
- {
- // 线程1:不断修改图像
- while (true)
- {
- g_img = cv::imread("image.jpg");
- // 处理图像...
- }
- return 0;
- }
- void CMyView::OnProcessImage()
- {
- // 主线程:同时访问图像
- // 处理g_img...
- // 这可能导致数据不一致或崩溃
- }
复制代码
5. MFC环境下Mat对象内存释放的正确方法
5.1 使用局部变量
在函数内部使用局部Mat对象是最简单、最安全的内存管理方式。局部对象会在函数结束时自动调用析构函数释放内存。
- // 示例6:使用局部变量
- void CMyView::OnProcessImage()
- {
- // 使用局部变量,函数结束时自动释放
- cv::Mat img = cv::imread("image.jpg");
-
- // 处理图像...
-
- // 函数结束时,img的析构函数会被自动调用,释放内存
- }
复制代码
5.2 使用智能指针
C++11引入的智能指针可以自动管理内存,避免内存泄漏和重复释放问题。
- // 示例7:使用智能指针
- #include <memory>
- void CMyView::OnProcessImage()
- {
- // 使用unique_ptr管理Mat对象
- std::unique_ptr<cv::Mat> img(new cv::Mat(cv::imread("image.jpg")));
-
- // 处理图像...
-
- // 函数结束时,unique_ptr会自动释放内存
- }
- // 或者使用shared_ptr,当多个地方需要共享同一个Mat对象时
- std::shared_ptr<cv::Mat> g_img;
- void CMyView::OnLoadImage()
- {
- g_img = std::make_shared<cv::Mat>(cv::imread("image.jpg"));
- }
- void CMyView::OnProcessImage()
- {
- if (g_img)
- {
- // 处理图像...
- }
- }
复制代码
5.3 在MFC文档类中管理Mat对象
在MFC文档/视图结构中,通常在文档类中管理数据,包括Mat对象。
- // 示例8:在文档类中管理Mat对象
- // MyDocument.h
- class CMyDocument : public CDocument
- {
- protected:
- cv::Mat m_image; // 文档类中保存图像数据
-
- public:
- const cv::Mat& GetImage() const { return m_image; }
- void SetImage(const cv::Mat& img) { m_image = img; }
- void LoadImage(const CString& filePath);
- void ProcessImage();
- };
- // MyDocument.cpp
- void CMyDocument::LoadImage(const CString& filePath)
- {
- // 将CString转换为std::string
- CT2CA pszConvertedAnsiString(filePath);
- std::string strPath(pszConvertedAnsiString);
-
- // 加载图像
- m_image = cv::imread(strPath);
-
- if (m_image.empty())
- {
- AfxMessageBox(_T("Failed to load image!"));
- }
- else
- {
- UpdateAllViews(nullptr); // 通知所有视图更新
- }
- }
- void CMyDocument::ProcessImage()
- {
- if (!m_image.empty())
- {
- // 处理图像...
- cv::GaussianBlur(m_image, m_image, cv::Size(5, 5), 1.5);
-
- // 通知所有视图更新
- UpdateAllViews(nullptr);
- }
- }
- // MyView.cpp
- void CMyView::OnDraw(CDC* pDC)
- {
- CMyDocument* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
-
- // 获取图像数据
- const cv::Mat& img = pDoc->GetImage();
-
- if (!img.empty())
- {
- // 将OpenCV Mat转换为CBitmap并显示
- // ... 转换和显示代码 ...
- }
- }
复制代码
5.4 在对话框类中管理Mat对象
在MFC对话框中使用Mat对象时,可以在对话框类中声明成员变量,并在适当的时候初始化和释放。
- // 示例9:在对话框类中管理Mat对象
- // MyDialog.h
- class CMyDialog : public CDialogEx
- {
- DECLARE_DYNAMIC(CMyDialog)
-
- private:
- cv::Mat m_image; // 对话框类中保存图像数据
-
- public:
- CMyDialog(CWnd* pParent = nullptr); // 标准构造函数
- virtual ~CMyDialog();
-
- void LoadImage(const CString& filePath);
- void ProcessImage();
-
- protected:
- virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
-
- DECLARE_MESSAGE_MAP()
- };
- // MyDialog.cpp
- CMyDialog::CMyDialog(CWnd* pParent /*=nullptr*/)
- : CDialogEx(IDD_MY_DIALOG, pParent)
- {
- }
- CMyDialog::~CMyDialog()
- {
- // 析构函数中不需要手动释放m_image,
- // 因为它是类成员,会自动调用析构函数
- }
- void CMyDialog::LoadImage(const CString& filePath)
- {
- // 将CString转换为std::string
- CT2CA pszConvertedAnsiString(filePath);
- std::string strPath(pszConvertedAnsiString);
-
- // 加载图像
- m_image = cv::imread(strPath);
-
- if (m_image.empty())
- {
- AfxMessageBox(_T("Failed to load image!"));
- }
- else
- {
- // 更新显示
- UpdateData(FALSE);
- Invalidate();
- }
- }
- void CMyDialog::ProcessImage()
- {
- if (!m_image.empty())
- {
- // 处理图像...
- cv::GaussianBlur(m_image, m_image, cv::Size(5, 5), 1.5);
-
- // 更新显示
- UpdateData(FALSE);
- Invalidate();
- }
- }
- void CMyDialog::OnPaint()
- {
- CPaintDC dc(this); // device context for painting
-
- if (!m_image.empty())
- {
- // 将OpenCV Mat转换为CBitmap并显示
- // ... 转换和显示代码 ...
- }
- }
复制代码
5.5 使用RAII模式封装Mat对象
RAII(Resource Acquisition Is Initialization)是一种C++编程技术,可以将资源管理与对象生命周期绑定。我们可以创建一个RAII类来封装Mat对象。
- // 示例10:使用RAII模式封装Mat对象
- // MatWrapper.h
- class MatWrapper
- {
- private:
- cv::Mat m_mat;
-
- public:
- MatWrapper() {}
-
- explicit MatWrapper(const cv::Mat& mat) : m_mat(mat) {}
-
- MatWrapper(const std::string& filePath)
- {
- m_mat = cv::imread(filePath);
- }
-
- ~MatWrapper()
- {
- // 析构函数中不需要手动释放m_mat,
- // 因为它是类成员,会自动调用析构函数
- }
-
- // 禁止拷贝构造和赋值
- MatWrapper(const MatWrapper&) = delete;
- MatWrapper& operator=(const MatWrapper&) = delete;
-
- // 允许移动构造和赋值
- MatWrapper(MatWrapper&& other) noexcept : m_mat(std::move(other.m_mat)) {}
-
- MatWrapper& operator=(MatWrapper&& other) noexcept
- {
- if (this != &other)
- {
- m_mat = std::move(other.m_mat);
- }
- return *this;
- }
-
- // 获取底层Mat对象
- cv::Mat& GetMat() { return m_mat; }
- const cv::Mat& GetMat() const { return m_mat; }
-
- // 检查是否为空
- bool IsEmpty() const { return m_mat.empty(); }
-
- // 加载图像
- bool LoadImage(const std::string& filePath)
- {
- m_mat = cv::imread(filePath);
- return !m_mat.empty();
- }
-
- // 处理图像
- void ProcessImage()
- {
- if (!m_mat.empty())
- {
- cv::GaussianBlur(m_mat, m_mat, cv::Size(5, 5), 1.5);
- }
- }
- };
- // 使用MatWrapper
- void CMyView::OnProcessImage()
- {
- MatWrapper imgWrapper;
-
- if (imgWrapper.LoadImage("image.jpg"))
- {
- imgWrapper.ProcessImage();
-
- // 获取处理后的图像
- cv::Mat& img = imgWrapper.GetMat();
-
- // 使用图像...
- }
-
- // 函数结束时,imgWrapper的析构函数会被自动调用,
- // 从而释放Mat对象的内存
- }
复制代码
5.6 在多线程环境下安全使用Mat对象
在MFC多线程应用程序中,需要确保Mat对象的线程安全访问。可以使用互斥量(mutex)来保护共享的Mat对象。
- // 示例11:在多线程环境下安全使用Mat对象
- #include <mutex>
- class CMyDocument : public CDocument
- {
- private:
- cv::Mat m_image;
- std::mutex m_mutex; // 用于保护m_image的互斥量
-
- public:
- void LoadImage(const CString& filePath);
- void ProcessImage();
- cv::Mat GetImageCopy(); // 返回图像的副本,避免外部直接修改
- };
- void CMyDocument::LoadImage(const CString& filePath)
- {
- // 锁定互斥量
- std::lock_guard<std::mutex> lock(m_mutex);
-
- // 将CString转换为std::string
- CT2CA pszConvertedAnsiString(filePath);
- std::string strPath(pszConvertedAnsiString);
-
- // 加载图像
- m_image = cv::imread(strPath);
-
- if (m_image.empty())
- {
- AfxMessageBox(_T("Failed to load image!"));
- }
- else
- {
- UpdateAllViews(nullptr); // 通知所有视图更新
- }
- }
- void CMyDocument::ProcessImage()
- {
- // 锁定互斥量
- std::lock_guard<std::mutex> lock(m_mutex);
-
- if (!m_image.empty())
- {
- // 处理图像...
- cv::GaussianBlur(m_image, m_image, cv::Size(5, 5), 1.5);
-
- // 通知所有视图更新
- UpdateAllViews(nullptr);
- }
- }
- cv::Mat CMyDocument::GetImageCopy()
- {
- // 锁定互斥量
- std::lock_guard<std::mutex> lock(m_mutex);
-
- // 返回图像的副本
- return m_image.clone();
- }
- // 工作线程函数
- UINT ImageProcessingThread(LPVOID pParam)
- {
- CMyDocument* pDoc = (CMyDocument*)pParam;
-
- while (true)
- {
- // 处理图像
- pDoc->ProcessImage();
-
- // 休眠一段时间
- Sleep(100);
- }
-
- return 0;
- }
- // 在视图类中启动工作线程
- void CMyView::OnStartProcessing()
- {
- CMyDocument* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
-
- // 创建并启动工作线程
- AfxBeginThread(ImageProcessingThread, pDoc);
- }
复制代码
6. MFC与OpenCV图像数据转换
在MFC应用程序中显示OpenCV Mat对象时,通常需要将Mat对象转换为MFC可以识别的格式,如CBitmap或HBITMAP。以下是转换方法的示例:
- // 示例12:将OpenCV Mat转换为CBitmap
- CBitmap* MatToCBitmap(const cv::Mat& mat)
- {
- if (mat.empty())
- return nullptr;
-
- // 获取图像尺寸和通道数
- int width = mat.cols;
- int height = mat.rows;
- int channels = mat.channels();
-
- // 创建设备上下文
- CDC dc;
- dc.CreateCompatibleDC(nullptr);
-
- // 创建位图
- CBitmap* bitmap = new CBitmap();
- bitmap->CreateCompatibleBitmap(&dc, width, height);
-
- // 选择位图到设备上下文
- CBitmap* oldBitmap = dc.SelectObject(bitmap);
-
- // 根据通道数设置位图信息
- BITMAPINFOHEADER bi = { 0 };
- bi.biSize = sizeof(BITMAPINFOHEADER);
- bi.biWidth = width;
- bi.biHeight = -height; // 负值表示位图是从上到下的
- bi.biPlanes = 1;
- bi.biBitCount = channels * 8;
- bi.biCompression = BI_RGB;
-
- // 将Mat数据转换为位图数据
- if (channels == 1)
- {
- // 灰度图像
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- uchar val = mat.at<uchar>(y, x);
- dc.SetPixel(x, y, RGB(val, val, val));
- }
- }
- }
- else if (channels == 3)
- {
- // 彩色图像
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- cv::Vec3b pixel = mat.at<cv::Vec3b>(y, x);
- dc.SetPixel(x, y, RGB(pixel[2], pixel[1], pixel[0])); // OpenCV是BGR顺序
- }
- }
- }
-
- // 恢复原来的位图
- dc.SelectObject(oldBitmap);
-
- return bitmap;
- }
- // 更高效的转换方法,使用SetDIBitsToDevice
- void DrawMatToDC(CDC* pDC, const cv::Mat& mat, CPoint destPos)
- {
- if (mat.empty() || !pDC)
- return;
-
- // 获取图像尺寸和通道数
- int width = mat.cols;
- int height = mat.rows;
- int channels = mat.channels();
-
- // 根据通道数设置位图信息
- BITMAPINFOHEADER bi = { 0 };
- bi.biSize = sizeof(BITMAPINFOHEADER);
- bi.biWidth = width;
- bi.biHeight = -height; // 负值表示位图是从上到下的
- bi.biPlanes = 1;
- bi.biBitCount = channels * 8;
- bi.biCompression = BI_RGB;
-
- // 设置位图信息
- BITMAPINFO bmi = { 0 };
- bmi.bmiHeader = bi;
-
- // 将Mat数据绘制到设备上下文
- if (channels == 1)
- {
- // 灰度图像
- // 创建调色板
- RGBQUAD palette[256];
- for (int i = 0; i < 256; i++)
- {
- palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
- palette[i].rgbReserved = 0;
- }
-
- // 设置位图信息
- memcpy(bmi.bmiColors, palette, 256 * sizeof(RGBQUAD));
-
- // 绘制图像
- ::SetDIBitsToDevice(
- pDC->GetSafeHdc(), // 设备上下文句柄
- destPos.x, // 目标x坐标
- destPos.y, // 目标y坐标
- width, // 源宽度
- height, // 源高度
- 0, // 源x坐标
- 0, // 源y坐标
- 0, // 起始扫描线
- height, // 扫描线数
- mat.data, // 像素数据
- &bmi, // 位图信息
- DIB_RGB_COLORS); // 颜色使用方式
- }
- else if (channels == 3)
- {
- // 彩色图像
- // 绘制图像
- ::SetDIBitsToDevice(
- pDC->GetSafeHdc(), // 设备上下文句柄
- destPos.x, // 目标x坐标
- destPos.y, // 目标y坐标
- width, // 源宽度
- height, // 源高度
- 0, // 源x坐标
- 0, // 源y坐标
- 0, // 起始扫描线
- height, // 扫描线数
- mat.data, // 像素数据
- &bmi, // 位图信息
- DIB_RGB_COLORS); // 颜色使用方式
- }
- }
- // 在视图类中使用上述函数显示Mat对象
- void CMyView::OnDraw(CDC* pDC)
- {
- CMyDocument* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
-
- // 获取图像数据
- const cv::Mat& img = pDoc->GetImage();
-
- if (!img.empty())
- {
- // 使用DrawMatToDC函数绘制图像
- DrawMatToDC(pDC, img, CPoint(0, 0));
- }
- }
复制代码
7. 常见问题解决方案
7.1 内存泄漏检测
在MFC应用程序中,可以使用内存泄漏检测工具来检测Mat对象的内存泄漏问题。Visual Studio提供了内置的内存泄漏检测功能。
- // 示例13:启用内存泄漏检测
- // 在应用程序的入口函数(如InitInstance)中添加以下代码
- #ifdef _DEBUG
- _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
- #endif
- // 在程序退出时,如果有内存泄漏,Visual Studio的输出窗口会显示泄漏信息
复制代码
7.2 Mat对象与MFC控件结合使用
在MFC应用程序中,经常需要将Mat对象显示在Picture控件等MFC控件上。以下是实现方法:
- // 示例14:将Mat对象显示在Picture控件上
- void CMyDialog::DisplayMatInPictureControl(cv::Mat& mat, UINT nCtrlID)
- {
- if (mat.empty())
- return;
-
- // 获取Picture控件
- CStatic* pStatic = (CStatic*)GetDlgItem(nCtrlID);
- if (!pStatic)
- return;
-
- // 获取控件尺寸
- CRect rect;
- pStatic->GetClientRect(&rect);
-
- // 调整图像大小以适应控件
- cv::Mat resizedMat;
- cv::resize(mat, resizedMat, cv::Size(rect.Width(), rect.Height()));
-
- // 创建设备上下文
- CClientDC dc(pStatic);
-
- // 绘制图像
- DrawMatToDC(&dc, resizedMat, CPoint(0, 0));
- }
- // 在对话框类中使用上述函数
- void CMyDialog::OnLoadImage()
- {
- // 加载图像
- cv::Mat img = cv::imread("image.jpg");
-
- if (!img.empty())
- {
- // 在Picture控件上显示图像
- DisplayMatInPictureControl(img, IDC_PICTURE_CONTROL);
- }
- else
- {
- AfxMessageBox(_T("Failed to load image!"));
- }
- }
复制代码
7.3 处理大图像时的内存优化
处理大图像时,可能会遇到内存不足的问题。以下是一些优化方法:
- // 示例15:处理大图像时的内存优化
- void ProcessLargeImage(const std::string& filePath)
- {
- // 使用IMREAD_REDUCED_COLOR_2或IMREAD_REDUCED_COLOR_4标志
- // 加载缩小版本的图像,减少内存使用
- cv::Mat img = cv::imread(filePath, cv::IMREAD_REDUCED_COLOR_2);
-
- if (img.empty())
- {
- std::cerr << "Failed to load image!" << std::endl;
- return;
- }
-
- // 处理图像...
-
- // 如果需要处理原始大小的图像,可以分块处理
- cv::Mat originalImg = cv::imread(filePath);
- if (!originalImg.empty())
- {
- // 定义块大小
- int blockSize = 1024;
-
- // 分块处理
- for (int y = 0; y < originalImg.rows; y += blockSize)
- {
- for (int x = 0; x < originalImg.cols; x += blockSize)
- {
- // 计算当前块的边界
- cv::Rect blockRect(
- x,
- y,
- std::min(blockSize, originalImg.cols - x),
- std::min(blockSize, originalImg.rows - y));
-
- // 提取当前块
- cv::Mat block = originalImg(blockRect);
-
- // 处理当前块
- cv::GaussianBlur(block, block, cv::Size(5, 5), 1.5);
-
- // 如果需要,可以将处理后的块复制回原图
- // block.copyTo(originalImg(blockRect));
- }
- }
- }
- }
复制代码
7.4 Mat对象与GDI+结合使用
在某些情况下,可能需要将Mat对象与GDI+结合使用。以下是转换方法:
- // 示例16:将Mat对象转换为GDI+ Bitmap
- #include <gdiplus.h>
- #pragma comment(lib, "gdiplus.lib")
- Gdiplus::Bitmap* MatToGdiPlusBitmap(const cv::Mat& mat)
- {
- if (mat.empty())
- return nullptr;
-
- // 获取图像尺寸和通道数
- int width = mat.cols;
- int height = mat.rows;
- int channels = mat.channels();
-
- // 根据通道数确定像素格式
- Gdiplus::PixelFormat pixelFormat;
- if (channels == 1)
- pixelFormat = PixelFormat8bppIndexed;
- else if (channels == 3)
- pixelFormat = PixelFormat24bppRGB;
- else if (channels == 4)
- pixelFormat = PixelFormat32bppARGB;
- else
- return nullptr;
-
- // 创建GDI+ Bitmap
- Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(width, height, pixelFormat);
-
- // 锁定位图数据
- Gdiplus::BitmapData bitmapData;
- Gdiplus::Rect rect(0, 0, width, height);
- bitmap->LockBits(&rect, Gdiplus::ImageLockModeWrite, pixelFormat, &bitmapData);
-
- // 获取位图数据指针
- uchar* dest = (uchar*)bitmapData.Scan0;
-
- // 复制数据
- if (channels == 1)
- {
- // 灰度图像
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- dest[y * bitmapData.Stride + x] = mat.at<uchar>(y, x);
- }
- }
- }
- else if (channels == 3)
- {
- // 彩色图像,OpenCV是BGR顺序,GDI+是RGB顺序
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- cv::Vec3b pixel = mat.at<cv::Vec3b>(y, x);
- dest[y * bitmapData.Stride + x * 3] = pixel[2]; // R
- dest[y * bitmapData.Stride + x * 3 + 1] = pixel[1]; // G
- dest[y * bitmapData.Stride + x * 3 + 2] = pixel[0]; // B
- }
- }
- }
- else if (channels == 4)
- {
- // 带alpha通道的图像
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- cv::Vec4b pixel = mat.at<cv::Vec4b>(y, x);
- dest[y * bitmapData.Stride + x * 4] = pixel[2]; // R
- dest[y * bitmapData.Stride + x * 4 + 1] = pixel[1]; // G
- dest[y * bitmapData.Stride + x * 4 + 2] = pixel[0]; // B
- dest[y * bitmapData.Stride + x * 4 + 3] = pixel[3]; // A
- }
- }
- }
-
- // 解锁位图数据
- bitmap->UnlockBits(&bitmapData);
-
- return bitmap;
- }
- // 在MFC应用程序中使用GDI+ Bitmap
- void CMyView::OnDraw(CDC* pDC)
- {
- CMyDocument* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
-
- // 获取图像数据
- const cv::Mat& img = pDoc->GetImage();
-
- if (!img.empty())
- {
- // 初始化GDI+
- Gdiplus::GdiplusStartupInput gdiplusStartupInput;
- ULONG_PTR gdiplusToken;
- Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
-
- // 将Mat转换为GDI+ Bitmap
- Gdiplus::Bitmap* bitmap = MatToGdiPlusBitmap(img);
- if (bitmap)
- {
- // 创建GDI+ Graphics对象
- Gdiplus::Graphics graphics(pDC->GetSafeHdc());
-
- // 绘制图像
- graphics.DrawImage(bitmap, 0, 0);
-
- // 释放Bitmap对象
- delete bitmap;
- }
-
- // 关闭GDI+
- Gdiplus::GdiplusShutdown(gdiplusToken);
- }
- }
复制代码
8. 总结
在MFC环境下使用OpenCV Mat对象时,正确的内存管理至关重要。本文介绍了几种有效的Mat对象内存管理方法:
1. 使用局部变量:最简单、最安全的方式,适用于临时使用Mat对象的场景。
2. 使用智能指针:C++11的智能指针可以自动管理内存,避免内存泄漏和重复释放问题。
3. 在MFC文档类中管理Mat对象:符合MFC的文档/视图架构,适合需要在多个视图间共享数据的场景。
4. 在对话框类中管理Mat对象:适合在对话框中使用Mat对象的场景。
5. 使用RAII模式封装Mat对象:通过创建RAII类,将资源管理与对象生命周期绑定,提高代码的安全性和可维护性。
6. 在多线程环境下安全使用Mat对象:使用互斥量保护共享的Mat对象,确保线程安全。
使用局部变量:最简单、最安全的方式,适用于临时使用Mat对象的场景。
使用智能指针:C++11的智能指针可以自动管理内存,避免内存泄漏和重复释放问题。
在MFC文档类中管理Mat对象:符合MFC的文档/视图架构,适合需要在多个视图间共享数据的场景。
在对话框类中管理Mat对象:适合在对话框中使用Mat对象的场景。
使用RAII模式封装Mat对象:通过创建RAII类,将资源管理与对象生命周期绑定,提高代码的安全性和可维护性。
在多线程环境下安全使用Mat对象:使用互斥量保护共享的Mat对象,确保线程安全。
同时,本文还介绍了MFC与OpenCV图像数据转换的方法,以及常见问题的解决方案,如内存泄漏检测、Mat对象与MFC控件结合使用、处理大图像时的内存优化、Mat对象与GDI+结合使用等。
通过正确地管理Mat对象的内存,可以避免内存泄漏、重复释放、悬挂指针等问题,提高MFC应用程序的稳定性和性能。希望本文的内容对读者在MFC环境下使用OpenCV有所帮助。
版权声明
1、转载或引用本网站内容(MFC环境下OpenCV Mat对象内存释放的正确方法与常见问题解决方案)须注明原网址及作者(威震华夏关云长),并标明本网站网址(https://pixtech.cc/)。
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任。
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利。
本文地址: https://pixtech.cc/thread-40498-1-1.html
|
|