OpenCV 2.2 以及后面的版本取消掉了 CvvImage.h 和CvvImage.cpp 两个文件,直接导致了苦逼的程序员无法调用里面的显示函数来将图片显示到 MFC 的 Picture Control 控件中。为此,网上很多人表示只要将那两个文件人为的提取出来然后放到工程里面就解决问题了,也提供了两个文件的下载,但是这麻烦不说,还会导致一些奇奇怪怪的报错(至少本人是这样的,很崩溃!)。所以在了解了一些gdi绘图之后结合网上的代码写了如下的函数,只需调用就可以将OpenCV的图片显示在上面了(只支持三通道不支持单通道),初步测试效率跟原来两个文件差不多,如果有大神请帮我完善这份代码!
配置好 OpenCV 后,在文件头部添加如下一行代码:
#define WIDTHBYTES(bits) (((bits)+31)/32*4)//用于使图像宽度所占字节数为4byte的倍数
void drawpic(IplImage* img, unsigned int id);//绘图到 MFC 的 Picture Control 控件相关函数,参数一为 OpenCV 的图像数据结构类,参数二为 Picture Control 控件的id
定义函数如下:
void CMFCDrawDlg::drawpic(IplImage* img, unsigned int id)//CMFCDrawDlg为对话框类名 { BITMAPINFO *pbmi;//位图信息 BYTE *bmibuf;//位图信息空间 BYTE *g_pBits; HDC g_hMemDC; HBITMAP g_hBmp; CDC *pDC; CStatic *pic; int width, height; CRect rect; //位图信息初始化 bmibuf = new BYTE[sizeof(BITMAPINFO)+256 * sizeof(RGBQUAD)]; memset(bmibuf, 0, sizeof(bmibuf)); pbmi = (BITMAPINFO*)bmibuf; pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = img->width; pbmi->bmiHeader.biHeight = img->height; pbmi->bmiHeader.biPlanes = 1; pbmi->bmiHeader.biBitCount = 24; pbmi->bmiHeader.biCompression = BI_RGB; //获得设备DC和显示宽高 pDC = GetDlgItem(id)->GetDC(); pic = (CStatic*)GetDlgItem(id); pic->GetClientRect(&rect); width = rect.Width(); height = rect.Height(); g_hMemDC = CreateCompatibleDC(pDC->m_hDC);//创建兼容设备环境的内存DC g_hBmp = CreateDIBSection(g_hMemDC, pbmi, DIB_RGB_COLORS, (void**)&g_pBits, 0, 0);//创建应用程序可以直接写入的、与设备无关的位图 //修改图像内容:g_pBits int l_width = WIDTHBYTES(img->width* pbmi->bmiHeader.biBitCount); for (int row = 0; row < img->height; row++) memcpy(&g_pBits[row*l_width], &img->imageData[(img->height - row - 1)*l_width], l_width); SelectObject(g_hMemDC, g_hBmp);//将位图对象选入g_hMemDC内存DC中 //拉伸绘图 TransparentBlt(pDC->m_hDC, 0, 0, width, height, g_hMemDC, 0, 0, img->width, img->height, RGB(1, 0, 0));//RGB值原本设置为(0,0,0),不过似乎在这里绘图会有bug,是故改为(1,0,0) //释放内存资源 ReleaseDC(pDC); DeleteDC(g_hMemDC); DeleteObject(pic); DeleteObject(g_hBmp); }在需要将图片显示到 Picture Control 控件的地方添加如下代码即可:
char *filename = "pic1.jpg";//图像路径 IplImage* img = cvLoadImage(filename); drawpic(img, IDC_STATIC_SHOW);//IDC_STATIC_SHOW 为Picture Control 控件的ID cvReleaseImage(&img);
离最开始写这篇文章已经很久了,由于最近需要进行绘图方面的工作且该工作要求绘图效率很高,是故又回过头来看这份东西,发现了一些错误,也把错误的地方修改掉了。相信有GDI绘图基础的人应该早就看出来了,实在惭愧。
对于上面的那份代码,是可以适用于一般情况的绘图工作的,然而如果需要进一步优化性能还是大有可为的,比如将程序拆分成“初始化”,“主体部分”,“内存释放”三个部分,便不用每次显示图片都进行初始化和内存释放,会进一步的提高程序的效率。下面将修改版本的代码也放上来。
声明一个类用以存放位图和设备环境等相关信息:
class drawBitmapobj { public: //位图对象 BITMAPINFO *pbmi;//位图信息 BYTE *bmibuf;//位图信息空间 BYTE *g_pBits; HBITMAP g_hBmp; CDC *pDC; CRect rect; CStatic *pic; HDC g_hMemDC; };在对话框类中声明函数:
void drawpicinit(IplImage* img, unsigned int id, drawBitmapobj &mybmpobj);//初始化函数,参数一为 OpenCV的图像数据结构类,参数二为控件id,参数三为位图和设备信息对象 void drawpic(IplImage* img, drawBitmapobj &mybmpobj);//绘图到MFC的 Picture Control 控件相关函数,参数一为 OpenCV的图像数据结构类,参数二为位图和设备信息对象 void drawrelease(drawBitmapobj &mybmpobj);//释放绘图对象因为涉及到位图,需要四字节对齐,所以将该公式也放在需要用到的文件头部:
#define WIDTHBYTES(bits) (((bits)+31)/32*4)//用于使图像宽度所占字节数为4byte的倍数
分别定义三个函数:
void CMFCDrawDlg::drawpicinit(IplImage* img, unsigned int id, drawBitmapobj &mybmpobj) { //位图信息初始化 mybmpobj.bmibuf = new BYTE[sizeof(BITMAPINFO)+256 * sizeof(RGBQUAD)]; memset(mybmpobj.bmibuf, 0, sizeof(mybmpobj.bmibuf)); mybmpobj.pbmi = (BITMAPINFO*)mybmpobj.bmibuf; mybmpobj.pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); mybmpobj.pbmi->bmiHeader.biWidth = img->width; mybmpobj.pbmi->bmiHeader.biHeight = img->height; mybmpobj.pbmi->bmiHeader.biPlanes = 1; mybmpobj.pbmi->bmiHeader.biBitCount = 24; mybmpobj.pbmi->bmiHeader.biCompression = BI_RGB; mybmpobj.pDC = GetDlgItem(id)->GetDC(); mybmpobj.pic = (CStatic*)GetDlgItem(id); mybmpobj.pic->GetClientRect(&mybmpobj.rect); mybmpobj.g_hMemDC = CreateCompatibleDC(mybmpobj.pDC->m_hDC);//创建兼容设备环境的内存DC mybmpobj.g_hBmp = CreateDIBSection(mybmpobj.g_hMemDC, mybmpobj.pbmi, DIB_RGB_COLORS, (void**)&mybmpobj.g_pBits, 0, 0);//创建应用程序可以直接写入的、与设备无关的位图 } void CMFCDrawDlg::drawpic(IplImage* img, drawBitmapobj &mybmpobj)//CMFCOpenCVShowDlg 为对话框类名 { //修改图像内容:g_pBits //这里这么做一则为BMP图像的四字节对齐机制,二则是因为BMP图像是从图像的左下角开始算起的,如果直接拷贝会导致图像上下颠倒 int l_width = WIDTHBYTES(img->width* mybmpobj.pbmi->bmiHeader.biBitCount); for (int row = 0; row < img->height; row++) memcpy(&mybmpobj.g_pBits[row*l_width], &img->imageData[(img->height - row - 1)*l_width], l_width); SelectObject(mybmpobj.g_hMemDC, mybmpobj.g_hBmp);//将位图对象选入g_hMemDC内存DC中 //拉伸绘图 TransparentBlt(mybmpobj.pDC->m_hDC, 0, 0, mybmpobj.rect.Width(), mybmpobj.rect.Height(), mybmpobj.g_hMemDC, 0, 0, img->width, img->height, RGB(1, 0, 0));//RGB值原本设置为(0,0,0),不过似乎在这里绘图会有bug,是故改为(1,0,0) } void CMFCDrawDlg::drawrelease(drawBitmapobj &mybmpobj) { //释放内存资源 delete[]mybmpobj.bmibuf; DeleteDC(mybmpobj.g_hMemDC); DeleteObject(mybmpobj.pic); DeleteObject(mybmpobj.g_hBmp); ReleaseDC(mybmpobj.pDC); }
做完上面那些就OK了,调用的方式如下:
drawBitmapobj mybmpobj; char *filename = "1.jpg";//图像路径 IplImage* img = cvLoadImage(filename); drawpicinit(img, IDC_STATIC_SHOW, mybmpobj); long begintime = clock(); int i = 8; while (i--) { drawpic(img, mybmpobj);//IDC_STATIC_SHOW 为Picture Control 控件的ID } printf("消耗时间:%dms\n", (clock() - begintime)/8); cvReleaseImage(&img); drawrelease(mybmpobj);
当然,也可以进一步直接将其封装成一个类
#include "stdafx.h" #include "afxdialogex.h" /**************************** **名字:绘制图像类 **功能:Opencv输入的IplImage图像对象绘制图像到指定窗体的控件中 **解释:showWnd为指定窗体,id为指定窗体id,img为输入图像对象 **作者:weixinhum **时间:2015/10/29 ****************************/ class PaintImgToScreenControls : public CDialogEx { public: //位图对象 BITMAPINFO *pbmi;//位图信息 BYTE *bmibuf;//位图信息空间 BYTE *g_pBits; HBITMAP g_hBmp; CDC *pDC; CRect rect; CStatic *pic; HDC g_hMemDC; void drawpicinit(IplImage* img, unsigned int id, CWnd* showWnd);//初始化函数,参数一为 OpenCV的图像数据结构类,参数二为控件id void drawpic(IplImage* img);//绘图到MFC的 Picture Control 控件相关函数,参数一为 OpenCV的图像数据结构类 void drawrelease();//释放绘图对象 CWnd* mShowWnd; }; #define WIDTHBYTES(bits) (((bits)+31)/32*4)//用于使图像宽度所占字节数为4byte的倍数 void PaintImgToScreenControls::drawpicinit(IplImage* img, unsigned int id, CWnd* showWnd) { //位图信息初始化 bmibuf = new BYTE[sizeof(BITMAPINFO)+256 * sizeof(RGBQUAD)]; memset(bmibuf, 0, sizeof(bmibuf)); pbmi = (BITMAPINFO*)bmibuf; pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = img->width; pbmi->bmiHeader.biHeight = img->height; pbmi->bmiHeader.biPlanes = 1; pbmi->bmiHeader.biBitCount = 24; pbmi->bmiHeader.biCompression = BI_RGB; mShowWnd = showWnd; pDC = mShowWnd->GetDlgItem(id)->GetDC(); pic = (CStatic*)mShowWnd->GetDlgItem(id); pic->GetClientRect(&rect); g_hMemDC = CreateCompatibleDC(pDC->m_hDC);//创建兼容设备环境的内存DC g_hBmp = CreateDIBSection(g_hMemDC, pbmi, DIB_RGB_COLORS, (void**)&g_pBits, 0, 0);//创建应用程序可以直接写入的、与设备无关的位图 } void PaintImgToScreenControls::drawpic(IplImage* img)//CMFCOpenCVShowDlg 为对话框类名 { //修改图像内容:g_pBits //这里这么做一则为BMP图像的四字节对齐机制,二则是因为BMP图像是从图像的左下角开始算起的,如果直接拷贝会导致图像上下颠倒 int l_width = WIDTHBYTES(img->width* pbmi->bmiHeader.biBitCount); for (int row = 0; row < img->height; row++) memcpy(&g_pBits[row*l_width], &img->imageData[(img->height - row - 1)*l_width], l_width); SelectObject(g_hMemDC, g_hBmp);//将位图对象选入g_hMemDC内存DC中 //拉伸绘图 TransparentBlt(pDC->m_hDC, 0, 0, rect.Width(), rect.Height(), g_hMemDC, 0, 0, img->width, img->height, RGB(1, 0, 0));//RGB值原本设置为(0,0,0),不过似乎在这里绘图会有bug,是故改为(1,0,0) } void PaintImgToScreenControls::drawrelease() { //释放内存资源 delete[]bmibuf; DeleteDC(g_hMemDC); DeleteObject(pic); DeleteObject(g_hBmp); mShowWnd->ReleaseDC(pDC); }然后调用
PaintImgToScreenControls paintObj; char *filename = "pano.jpg";//图像路径 IplImage* img = cvLoadImage(filename); paintObj.drawpicinit(img, IDC_STATIC_PANO, this); int i = 8; while (i--) { paintObj.drawpic(img);//IDC_STATIC_SHOW 为Picture Control 控件的ID } cvReleaseImage(&img); paintObj.drawrelease();