• Open Source Computer Vision Library

在MFC中使用OpenCV

Wikipedia,自由的百科全书

Image:CVMFC-GUI.png

目录

例程下载

例程源代码下载

OpenCV与MFC

OpenCV是计算机视觉自由软件的宝库。但是,由于历史的原因它的软件主要采用类似DOS操作系统的命令行方式,使用十分不便,这也影响了它的推广。如果能将它应用到VC++文档结构中就好了。OpenCV程序在MFC中实现的方法通常是采用CvvImage类,这个类的成员函数DrawToHDC可将位图整体经缩放后显示到视图窗口中,解决了位图的显示问题,也就解决了OpenCV在MFC中的使用问题。但是,也有两个致命的弱点,一是显示方式不合图像处理使用习惯,二是位图必须采用CvvImage类。显示方面,虽然已经能够察看图像,但当位图与窗口的长宽比不一致时会造成图像失真,这是浏览器的显示习惯,并不适用于图像处理应用。图像采集与处理的使用习惯是显示比例1:1,图像未经缩放,显示画面可按窗口大小进行裁剪并可使用滚动条选择显示部位。数据结构方面,采用CvvImage类以后程序中所有位图必须修改成这个类,这对于利用大量OpenCV现成软件来说是十分不方便的。显然,在VC++文档结构中使用OpenCV,其关键还是在于OpenCV位图在MFC中的显示。因此,有必要先比较一下两者的位图结构,然后寻找新的解决方法。

StretchDIBits 函数

由于OpenCV位图结构中的像素数据与DIB中的像素具有类似的存储结构,可以考虑直接用来在视图窗口中显示。知道位图像素的存放地址直接往视图窗口显示的函数虽然不多,但还是有Windows API中的StretchDIBits函数可以利用,由下面列出的StretchDIBits函数原型中可知,只要为它构造一个DIB的位图信息就行了。

int StretchDIBits(
    HDC hdc,                        //  显示设备句柄
    int XDest, YDest, nDestWidth, nDestHeight, //  目标矩形区域参数
    int XSrc, YSrc, nSrcWidth, nSrcHeight,     //  源矩形区域参数
    //  源区域与目标区域参数相同时为 1:1 比例显示
    CONST VOID *lpBits,             //  位图的像素存放首地址
    CONST BITMAPINFO *lpBitsInfo,   //  位图信息存放地址
    UINT iUsage,                    //  位图中的颜色类型,RGB模式用DIB_RGB_COLORS
   DWORD dwRop                     //  像素操作码,简单复制用SRCCOPY
);

为此,演示程序CCVMFC中增加了3个函数,即CtreateMapInfo、imageClone与imageReplace。其中,CtreateMapInfo 函数用于建立OpenCV位图的位图信息,其特点是可以为单通道位图设置黑白灰阶调色板。ImageClone函数使用OpenCV函 数实现位图的复制,自动释放老的指针所指向的存储单元以防止内存泄漏,同时返回的m_dibFlag标志可以用于激发刷新工 作位图workImg的位图信息m_lpBmi (见后面说明)。imageReplace与ImageClone相似,但不建立新位图,只用输入位图替 换输出位图。

LPBITMAPINFO  CtreateMapInfo(IplImage* workImg)    //  建立位图信息
{                                           
    BITMAPINFOHEADER BIH={40,1,1,1,8,0,0,0,0,0,0};
    LPBITMAPINFO lpBmi;
    int          wid, hei, bits, colors,i;
    RGBQUAD  ColorTab[256];
    wid =workImg->width;     hei =workImg->height;
    bits=workImg->depth*workImg->nChannels;
    if (bits>8) colors=0;
    else colors=1<<bits;
    lpBmi=(LPBITMAPINFO) malloc(40+4*colors);
    BIH.biWidth   =wid;     BIH.biHeight  =hei;
    BIH.biBitCount=(BYTE) bits;
    memcpy(lpBmi,&BIH,40);                   //  复制位图信息头
    if (bits==8) {                           //  256 色位图
        for (i=0;i<256;i++)  {                //  设置灰阶调色板
           ColorTab[i].rgbRed=ColorTab[i].rgbGreen=ColorTab[i].rgbBlue=(BYTE) i;
       }
       memcpy(lpBmi->bmiColors, ColorTab, 1024);
    }
    return(lpBmi);
}
int  imageClone(IplImage* pi, IplImage** ppo)  //  复制 IplImage 位图 (OpenCV)
{
   if (*ppo)
       cvReleaseImage(ppo);                    //  释放原来位图
    (*ppo) = cvCloneImage(pi);                  //  复制新位图
    return(true);
}

演示程序

演示程序CVMFC采用VC++多文档带滚动条结构,图像的存放与处理则采用OpenCV的位图结构与函数,图像的显示通过建立相应的位图信息m_lpBmi来实现,为了便于管理对m_lpBmi的操作集中在OnDraw程序中。待显示位图结构发生改变时用m_dibFlag标志激发m_lpBmi的刷新。除了文件结构与图像显示外,其余部分基本上都是OpenCV程序。


位图数据:

CVMFCDoc中, pImg (读入图像文件所得原始位图)

CVMFCView中, workImg (工作位图)、saveImg (备份位图)、m_lpBmi (工作位图的位图信息)

CVDSCap中, m_Frame (视频采集所得位图)


视图的显示管理集中在OnDraw函数中:

void CCVMFC0View::OnDraw(CDC* pDC)
{
  ……                             //  其余部分内容
  if (m_dibFlag) {                         //  刷新 DIB位图信息
     if (m_lpBmi)
           free(m_lpBmi);
     m_lpBmi = CtreateMapInfo(workImg);
     m_dibFlag = 0;
  }
  if (workImg) {                            //  刷新视图窗口
     StretchDIBits(pDC->m_hDC, 
                  0, 0, workImg->width, workImg->height,
                  0, 0, workImg->width, workImg->height,
                  workImg->imageData, m_lpBmi, DIB_RGB_COLORS, SRCCOPY);
  }
}

像素数据类型

图像处理主要是对像素数据的处理。需要注意的是,在OpenCV中像素存放地址imageData为char* 类型。因此,在图像处理时必须转换成BYTE* 类型才可以使用。下面以识别图像类型函数imageType为例来作说明。

int  imageType(IplImage* p)         //  识别图像类型
{
   int     i,j,k,bpl,n,pg[256];
   BYTE *buf;
   k=p->nChannels;                    //  1 与 3 分别表示灰阶图像与彩色图像
   if ( k==1 ) {                      //  检查二值图像
       for ( i = 0; i < 256; i++ ) pg[i] = 0;
      buf = (BYTE*) p->imageData;     //  修改像素数据类型
       bpl = p->widthStep;
      for ( i = 0; i < p->height; i++ ) {
          for ( j = 0; j < p->width; j++) pg[buf[j]]++;  //  直方图统计
            buf += bpl;
      }
      for ( i = 0, n = 0; i < 256; i++ ) 
         if (pg[i]) n++;               //  统计使用色阶数
       if (n == 2) k = -1;              //  -1 表示二值图像
   }
   return(k);
}

图像镜像

在位图的像素方面,除了imageData的类型问题外,DIB位图与IplImage结构间还有一个显著的不同,那就是坐标原点位置的不同。前者的坐标原点在位图底部左侧,而后者在顶部左侧。因此,当在OpenCV中需要使用MFC的函数显示时位图应作垂直镜像,反之亦然。典型的例子是在OpenCV中调用DirectShow视频采集程序CameraDS中的获取当前帧函数QueryFrame,其程序如下:

IplImage* CCameraDS::QueryFrame()
{
  ……
  m_pSampleGrabber->GetCurrentBuffer(&m_nBufferSize, (long*) m_pFrame->imageData);
  cvFlip(m_pFrame);
  return m_pFrame;
}

演示程序CVMFC的图像输入输出采用OpenCV的cvLoadImage与cvSaveImage函数实现,而显示采用Windows API中的 StretchDIBits函数。为了能正常工作,图像读入后需作垂直镜像,图像存盘前也需作垂直镜像。也就是说,内存中存放的是 经过垂直镜像的OpenCV位图。同样,因为结构相同,它也是DIB位图的像素数据。

BOOL CCVMFCDoc::Load(IplImage** pp,LPCTSTR csFileName)
{
  IplImage* pImg=NULL;
  pImg = cvLoadImage(csFileName,-1);    //  读图像文件
   if (!pImg) return(false);
   cvFlip(pImg);                        //  使与 DIB 像素结构一致
   if (*pp)  
        cvReleaseImage(pp);
   (*pp)=pImg;
   m_Display=0;
   return(true);
}
BOOL CCVMFCDoc::Save(LPCTSTR csFileName,IplImage* pImg)
{
  int   bl;
  cvFlip(pImg);                           //  恢复原 OpenCV 位图结构
   bl=cvSaveImage(csFileName,pImg);        //  图像存盘
   return(bl);
}

对于大多数的图像处理算法来说位图的镜像与否没有什么影响。但是,对于某些OpenCV函数,例如涉及旋转方向以及需要往IplImage结构的位图上绘制图形、显示文字时就会使位置出错。这时就需要与存盘时一样先作垂直镜像,旋转角度反向,操作结束返回Windows视图显示处理结果时再转换回来。

驱动模式与人机交互

众所周知,DOS操作系统采用过程驱动与文本模式,Windows操作系统采用事件驱动与图形模式,它们分别使用键盘与鼠标器作为主要输入工具。OpenCV编程环境基于DOS操作系统,因此显示图像需要调用cvNamedWindow函数建立专门的窗口,然后调用cvShowImage函数进行显示。画面的保持则使用cvWaitKey(0)语句,或使用内含cvWaitKey语句的死循环来实现。为了避免出现死机故障,出现OpenCV函数所开窗口时,务必使用ESCAPE键关闭窗口并退出。注意:菜单中带 (ESC) 字样的命令必须使用ESCAPE键退出。

同时,OpenCV编程环境人机交互的功能有限,只有鼠标、键盘与滑动条。其中,鼠标与滑动条属于事件驱动,而键盘属于过程驱动。由于没有菜单功能,只能使用键盘命令控制程序走向并等待键盘输入选择不同功能。

在驱动模式方面为了便于比较,提供了两个[点集凸包]命令,一个采用过程驱动,另一个采用事件驱动,不妨比较具体功能及其实现的程序。

程序移植例

演示程序CVMFC中待处理图像与处理结果都放在工作位图workImg中。同时,工作位图workImg也是窗口画面显示的内容。所以,处理程序中仅源图像的获取与结果图像的显示与OpenCV中的程序有所不同,中间处理部分可以完全相同,现以《学习OpenCV》一书中的例2-6为例来作修改说明。

void CCVMFC0View::OnCannyBorddetec()       //  Canny 边缘检测
{                         //  根据《学习OpenCV》例 2-6 改编
 //  定义工作位图
  IplImage* pImage;
  IplImage* pImg8u = NULL;
  IplImage* pImg8uSmooth = NULL;
  IplImage* pImgCanny = NULL;
  //**  输入待处理图像  **                //  修改部分 1
  pImage = workImg;
  //  建立辅助位图
  pImg8u =cvCreateImage(cvGetSize(pImage),IPL_DEPTH_8U,1);
  pImg8uSmooth=cvCreateImage(cvGetSize(pImage),IPL_DEPTH_8U,1);
  pImgCanny =cvCreateImage(cvGetSize(pImage),IPL_DEPTH_8U,1);
  //  图像处理
  cvCvtColor(pImage, pImg8u,CV_BGR2GRAY);
  cvSmooth(pImg8u, pImg8uSmooth,CV_GAUSSIAN,3,0,0);
  cvCanny(pImg8uSmooth, pImgCanny,100,200,3);
  //  释放位图
  cvReleaseImage(&pImg8u);
  cvReleaseImage(&pImg8uSmooth);
  //**  输出处理结果  **                  //  修改部分 2
  m_dibFlag = ImageReplace(pImgCanny, &workImg);
  //**  设置标志及刷新窗口  ** 
  m_ImageType=1;
  Invalidate();
}

形参书写顺序

演示程序CVMFC是Windows API系统与OpenCV两种体系函数的混合使用,而它们的函数参数的书写习惯是不同的,极容易引起混淆。这里作一简要说明,请看下面例子:

目标参数Dest <-- 源参数Src

C:
memcpy( pDest, pSrc, size);
Windows API:
BitBlt( hDestDC, 0, 0, wid, hei, hSrcDC, 0, 0, SRCCOPY);
VC++:
pDestDC->BitBlt( 0, 0, p->wid, p->hei, pSrcDC, 0, 0, SRCCOPY);

源参数Src --> 目标参数Dest

OpenCV:
cvCvtColor( pSrcImg, pDestcImg, CV_BGR2GRAY);
cvSmooth( pSrcImg, pDestcImg, CV_GAUSSIAN, 3, 0, 0);

演示程序中除了OpenCV函数采用后一书写形式外,其他函数都采用前一书写形式,千万注意不能混淆。OpenCV函数很 容易辨认,都以前缀cv-开头,如cvCvtColor、cvSmooth。新增函数ImageClone与imageReplace因为调用OpenCV函数, 故也采用后一书写形式。

CVMFC 1.1版菜单

[文件]:打开图像、恢复图像、关闭当前窗口、保存当前位图、恢复原始图像、当前画面存盘、退出

[点处理]:彩色变灰阶、图像反相、垂直镜像、水平镜像、180 度旋转、30 度旋转、仿射变换、透视变换、亮度变换、灰阶图像直方图、直方图均衡化

[邻域处理]:邻域平均、Gauss 滤波、中值滤波、Sobel 边缘检测、Laplace 边缘检测

[二值化]:选择阈值、选择阈值(原位图)、自适应阈值法、基本全局阈值法

[二值图像处理]:点集最小区域、外接矩形、最小面积矩形、多边形逼近、点集凸包、点集凸包 (事件驱动)、区域凸包、区域凹差、轮廓跟踪、距离变换

[形态学处理]:腐蚀、膨胀、开运算、闭运算、形态学梯度、顶帽变换、波谷检测

[彩色图像处理]:RGB 分量、HSV 分量、Lab 分量、RGB 分量 (C)、XYZ 分量、YCrCb 分量、Luv 分量、二维直方图、邻域平均、Gauss 滤波、中值滤波、Sobel 边缘检测、Laplace 边缘检测

[综合处理例]:图像缩小、径向梯度、Canny 算法、Hough 变换 (直线)、"Hough 变换 (圆)、平行四边形检测、连通区域填充、金字塔法图像分割、椭圆曲线拟合、Snake 原理、分水岭原理、角点检测、点集聚类、分割二维点集、旋转点跟踪、人脸检测

[动态检测]:动态边缘检测、L_K 算法光流跟踪、背景建模检测、运动目标检测、彩色目标跟踪、人脸检测二

[视频采集播放]:启动摄像头、打开 AVI 文件、视频解冻、视频冻结、多图像平均、关闭视频、选择采样分辨率

[图形及其他]:绘制图形、绘制 Delaunay 图形、极坐标变换、DFT

Views
Personal tools