MFC使用Opencv接口记录

  这次在写一个使用Opencv做图像处理,并使用MFC做窗口界面程序的Demo。在MFC中使用GDI来显示图片需要使用bitmap格式的图片,但Opencv里是使用Mat对象来作为图像接口的,因此需要自己转换一下。为避免重复造轮(在之前确实没有记录笔记的习惯,重复写了好几次),所以这次记录一下,方便下次查阅。


正文开始


  既然为了避免重复造轮所以,自己写了一个类来完成opencv与MFC的接口工作

类声明如下

#pragma once
//导入opencv的库
#include <opencv2/opencv.hpp>
//导入字符串类型所需要的库
#include <string>

using namespace cv;

class MyPicture
{
//析构与构造函数
private:
    MyPicture();
    ~MyPicture();

//成员
public:
    Mat* mat;//opencv接口对象指针
    bool mat_flag;//标志mat是否已加载图像
    MyBitmap* bmp;//MFC显示图像所需Bmp数据指针

//方法
public:
    void ReadPicture(LPCTSTR adr);//读取图片接口函数
    void DeletePicture();//清除bitmap图片缓存,为读取新图片做准备
    void MatToBmp();//将Mat转换为Bitmap图片数据
    void ShowBmp(CDC * dc);//MFC显示图片接口
};
  • 由于MFC提供的显示图片接口StretchDIBits(),需要位图的信息头指针LPBITMAPINFO以及位图数据指针byte*,在保存图片时还需要向图片文件写入位图文件头BITMAPFILEHEADER。所以自己定义了一个结构体来方便管理。
    上面说的两个数据类型MFC已经为我们声明好了,所以导入MFC的库即可

//导入BITMAP类所必要的头文件
#include <afxwin.h>

//定义GDI使用的Bmp位图数据结构体
struct MyBitmap
{
    BITMAPFILEHEADER* pFileHead;//位图文件头数据
    LPBITMAPINFO pDibInfo;//位图信息结构体
    unsigned char* pBmp;//图像位数据缓冲区

    //图片加载标记
    bool flag;
};

声明完之后开始定义类中的方法

  • 首先是构造和析构函数
//构造函数
MyPicture:MyPicture()
{
    mat = NULL;
    mat_flag = false;
    bmp = NULL;
}

//析构函数
MyPicture:~MyPicture()
{
    if(mat != NULL)
    {
        delete mat;
    }
    if(bmp != NULL)
    {
        delete bmp;
    }
}
  • 读取图片的接口函数
void MyPicture::ReadPicture(LPCTSTR adr)
{
    CString cfilename(adr);//将LPCTSTR转为CString
    std::string filename = CStringA(cfilename);//将CString转为string

    //若上一次已加载过图片,则清空内存
    if (mat_flag)
    {
        mat_flag = false;
        delete mat;
    }

    //调用CV读取图片接口
    mat = new Mat(imread(filename));
    //标记图片已加载
    mat_flag = true;

}
  • Mat转bitmap格式函数
void MyPicture::MatToBmp()
{
    //若未加载图像则直接退出
    if (!mat_flag)
        return;

    //清除bmp成员对象之前占用的内存空间
    DeletePicture();

    bmp->pDibInfo = (LPBITMAPINFO)(new BYTE[40]);

    //将图片宽度变换为4的整数倍
    int MatWidth = WIDTHBYTES(mat->cols);

    //创建位图信息结构体
    bmp->pDibInfo->bmiHeader.biSize = 40;//结构体大小
    bmp->pDibInfo->bmiHeader.biWidth = MatWidth;//图像宽度
    bmp->pDibInfo->bmiHeader.biHeight = mat->rows;//图像高度
    bmp->pDibInfo->bmiHeader.biPlanes = 1;
    bmp->pDibInfo->bmiHeader.biBitCount = 24;//位深度
    bmp->pDibInfo->bmiHeader.biCompression = 0;
    bmp->pDibInfo->bmiHeader.biSizeImage = MatWidth * 3 * mat->rows;//图片数据大小
    bmp->pDibInfo->bmiHeader.biXPelsPerMeter = 0;     // 水平方向像素/米,分辨率
    bmp->pDibInfo->bmiHeader.biYPelsPerMeter = 0;     // 垂直方向像素/米,分辨率
    bmp->pDibInfo->bmiHeader.biClrUsed = 0;           // BMP图像使用的颜色,0:表示使用全部颜色
    bmp->pDibInfo->bmiHeader.biClrImportant = 0;      // 重要的颜色数,0:所有的颜色都重要,当显卡不能够显示所有颜色时,辅助驱动程序显示颜色

    //创建位图数据指针
    bmp->pBmp = new unsigned char[mat->rows * MatWidth * 3];
    //存储位图数据
    int LineBytes = MatWidth * 3;//计算图片每行的字节宽度
    for (int h = 1; h <= mat->rows; h++)
    {
        for (int w = 0; w < LineBytes; w += 3)
        {
            bmp->pBmp[LineBytes * (mat->rows - h) + w] = mat->at<cv::Vec3b>(h - 1, (int)(w / 3))[0];
            bmp->pBmp[LineBytes * (mat->rows - h) + w + 1] = mat->at<cv::Vec3b>(h - 1, (int)(w / 3))[1];
            bmp->pBmp[LineBytes * (mat->rows - h) + w + 2] = mat->at<cv::Vec3b>(h - 1, (int)(w / 3))[2];
        }
    }

    //更新文件信息头
    BITMAPFILEHEADER* matHead = new BITMAPFILEHEADER;

    matHead->bfType = (WORD)(('M' << 8) | 'B');//填入文件格式
    matHead->bfOffBits = 14 + 40;//填入位图数据偏移量
    matHead->bfSize = 14 + 40 + bmp->pDibInfo->bmiHeader.biSizeImage;//填入文件大小
    matHead->bfReserved1 = 0;//保留用
    matHead->bfReserved2 = 0;//保留用
    bmp->pFileHead = matHead;//更新文件头
    bmp->flag = true;
}
  • 为了防止第二次转换图片时,没有清空上一次的内存空间,所以定义一个清空MyPicture的内存空间的函数
void MyPicture::DeletePicture()
{
    if (bmp->pFileHead != NULL)
    {
        delete bmp->pFileHead;
    }
    if (bmp->pDibInfo != NULL)
    {
        delete bmp->pDibInfo;
    }
    if (bmp->pBmp != NULL)
    {
        delete[] bmp->pBmp;
    }
    bmp->flag = false;
}
  • 有了bitmap图片的数据后,就可以开始显示图片了
void MyBmp::ShowBmp(CDC * dc)
{
    //若已加载好图像,则开始转换为bitmap格式数据
    if (mat_flag)
        MatToBmp();

    //转换完成则显示图片
    if(bmp->flag)//若已加载图片
    //显示缓存区位图
    StretchDIBits
    (
        dc->GetSafeHdc(),//显示的设备上下文句柄
        0,//图像显示位置 X
        0,//图像显示位置 Y
        bmp->pDibInfo->bmiHeader.biWidth,//图像显示宽度
        bmp->pDibInfo->bmiHeader.biHeight,//图像显示高度
        0,//图像缓冲区位置 X
        0,//图像缓冲区位置 Y
        bmp->pDibInfo->bmiHeader.biWidth,//图像缓冲区宽度
        bmp->pDibInfo->bmiHeader.biHeight,//图像缓冲区高度
        bmp->pBmp,//图像缓冲区指针
        bmp->pDibInfo,//图像信息结构体指针
        DIB_RGB_COLORS,//图像显示类型
        SRCCOPY//图像显示方式
    );
}

接下来使用MFC单文档来使用上述接口

  • 首先在文档类中定义自己写好的图片类接口
//导入自己的图像类
#include "MyPicture.h"

class **<em>Doc : public CDocument
{
.....
// 特性
public:
    MyPicture</em> picture;
.....
}
  • 在构造和析构函数中分配图像类内存
//构造函数
****Doc::C****Doc() noexcept
{
    // TODO: 在此添加一次性构造代码
    picture = new MyPicture();
}

//析构函数
*****Doc::~C****Doc()
{
    if (picture != NULL)
    {
        delete picture;
    }
}
  • 重写文档类的OnOpenDocument函数
    2t7K5F.png
BOOL C****Doc::OnOpenDocument(LPCTSTR lpszPathName)
{
    if (!CDocument::OnOpenDocument(lpszPathName))
        return FALSE;

    // TODO:  在此添加您专用的创建代码

    picture->ReadPicture(lpszPathName);

    //刷新视图
    CMiniFrameWnd <em>pMainFrame = (CMiniFrameWnd</em>)AfxGetMainWnd();
    pMainFrame->Invalidate();

    return TRUE;

}
  • 搞定读取图片接口后就可以开始在OnDrow函数中显示图片了
void C****View::OnDraw(CDC* pDC)
{
    C****Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: 在此处为本机数据添加绘制代码
    pDoc->picture->ShowBmp(pDC);
}
  • 最后就是显示结果啦
    2tHSMR.png
    由于显示图片尺寸不固定,所以没显示完,后续可以修改图片尺寸,或修改显示图片接口StretchDIBits中的尺寸来使图片与窗口适配

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注