2024年2月21日发(作者:)

在MFC中使用OpenCV

在MFC中使用OpenCV

演示程序CVMFC

本程序是在 MFC 中使用 OpenCV 的演示程序,由3部分组成。

一、Windows 下用 MFC 编制的程序框架

采用设备无关位图DIB实现Windows多文档模式下图像的显示,实现显示的关键函数StretchDIBits的原型如下:

int StretchDIBits(

HDC hdc, // 显示设备句柄

int XDest, // 目标矩形区域左上角X坐标

int YDest, // 目标矩形区域左上角Y坐标

int nDestWidth, // 目标矩形区域宽度

int nDestHeight, // 目标矩形区域高度

int XSrc, // 源矩形区域左上角X坐标

int YSrc, // 源矩形区域左上角Y坐标

int nSrcWidth, // 源矩形区域宽度

int nSrcHeight, // 源矩形区域高度

CONST VOID *lpBits, // 位图的像素存放首地址

CONST BITMAPINFO *lpBitsInfo, // 位图信息存放地址

UINT iUsage, // 位图中的颜色类型,RGB模式用DIB_RGB_COLORS

DWORD dwRop // 像素操作码,简单复制用SRCCOPY

);

由于OpenCV中的位图结构中的像素数据与DIB中的像素具有相同的存储结构,见表1中的像素部分。所以,只要为它构造一个DIB的位图信息就可以调用API函数StretchDIBits实现它的显示了。

表1 DIB位图参数与IplImage结构参数

参 数

宽 度

高 度

像素位数

通道数

(单通道位图) 调色板单元数

DIB (MFC)

biWidth

biHeight

biBitCount (1,4,8,16,24,32) = depth*nChannels

---

IplImage (OpenCV)

width

height

depth (8,16,32,64)

nChannels (1,2,3,4)

二值图像显示为灰阶图像

256色彩色图像显示为真彩色图像

origin (0 顶-左,1 底-左)

0 交叉存取,1 位平面方式

4字节对齐或8字节对齐

widthStep

imageSize

char* imageData

roi

2biBitCount

(2, 16, 256)

底-左

交叉存取 (按像素为单位存放)

4字节对齐

(biWidth*biBitCount+31)/32*4

((biWidth*biBitCount+31)/32*4)* biHeight

BYTE* pBits

---

位图坐标原点

像素分量存放方式

对齐方式 (行像素数据凑整)

每行字节数

像素字节数

像素存放地址

感兴趣区域

1

在MFC中使用OpenCV

表中正体字母部分表示相同的参数,粗体字母表示参数部分相同时的交集,斜体加下划线表示结构特有的参数。

位图的宽度、高度、像素存放首地址、每行字节数、像素总字节数等5个参数在两种结构中相同。

像素位数、通道数、坐标原点位置、像素分量存放方式、对齐方式等5个参数在两种结构中部分相同,使用时可以取其交集,表中用粗体字表示。

有2个参数是两种位图各自独有的,感兴趣区域为IplImage结构所独有,调色板单元为DIB所独有。

从表1中可以看出,除了高精度图像(位深度16,32,64)外,这两种位图结构在图像处理的绝大部分应用中可以通用。

从以上比较中也可看出,IplImage结构适用于高精度处理,并且可以限制处理的区域;而DIB适用于Windows图形操作,并且可以存储低位数图像文件,如每像素一位的二值图像与像素8位的索引图像等。

另一种实现MFC的方法是采用CvvImage类,它有一个特点,就是其成员函数DrawToHDC可将位图全部经缩放后显示到窗口中。这样,虽然能够察看全图,但当位图与窗口的长宽比不一致时会造成图像失真。而采用DIB实现的显示比例可选择为1:1,图像显示没有经过缩放,显示画面按窗口大小进行裁剪,并可使用滑动条选择显示部位,这比较符合图像采集与处理的使用习惯。

二、调用 OpenCV 函数实现处理

使用 OpenCV 函数处理图像在 MFC 环境下显示,实现功能为图像平滑、图像缩小与Canny 边缘检测。根据《学习OpenCV》一书第2章的3个例子(例2-4,2-5,2-6) 改编而成。例2-4 与 2-5 使用例图

,例 2-6 使用例图 。还增加了若干图像处理常用功能,详见下面表2菜单结构列表。

视频播放也用 OpenCV 函数实现,根据《OpenCV教程(基础篇)》中第3章的例 3-5 改编而成,用的是 highgui 函数,测试视频文件为。

例图文件都在 image 目录下。

三、用 DirectShow 编制的视频采集程序

采用 DirectShow 实现视频采集,其优点是支持高分辨率图像采集,最高分辨率由所用的摄像头决定,如罗技130万像素USB摄像头C300最高分辨率可达1280*1024,在MPEG模式下采集速度为每秒15帧。使用时CPU 的时间占用率在15~30%之间。

通过多图像平均可以采集得到质量不错的图像,经过4幅图像平均采集所得图像的信噪比可提高一倍。由于需要连续采集多帧图像,约需0.25秒,故适宜采集静态或缓慢移动对象的图像。

视频采集功能调用 OpenCV China 网站下载的软件 camerads 中的函数实现。

主要内容

下面列出stdafx.h尾部集中的几个头文件,程序结构由此可见一斑。

#include "CVMFC.h" // 窗口管理

#include "cv.h" // OpenCV 头文件

#include "highgui.h"

#include "CameraDS.h" // DirectShow(基于OpenCV)

#include "CVDSCap.h" // 视频采集接口

2

在MFC中使用OpenCV

CVMFC的菜单结构

程序分为文件、点处理、邻域处理、二值图像处理、形态学处理、综合处理例与视频采集播放等七个菜单条共56个功能,近半数功能由参考文献上的程序修改而成,为了便于找到相应参考材料进行对照,在程序中附上了它们的出处。整个程序的菜单结构见表2。

表2 CVMFC菜单结构

文件

打开图像

恢复图像

关闭当前窗口

保存当前位图

最近文件

恢复原始图像

当前画面存盘

退出

点处理

彩色变灰阶

图像反相

垂直镜像

水平镜像

180 度旋转

30 度旋转

亮度变换

图像直方图

直方图均衡化

邻域处理

邻域平均

Gauss 滤波

中值滤波

Sobel 算法

Laplace算法

二值图像处理

选择阈值

自适应阈值法

全局阈值法

外接矩形

最小面积矩形

点集凸包

区域凸包

轮廓跟踪

距离变换

形态学处理

腐蚀

膨胀

开运算

闭运算

形态学梯度

顶帽变换

波谷检测

综合处理例

图像缩小

径向梯度

Canny 算法

种子填充

金字塔图像分割

椭圆曲线拟合

Snake 原理

分水岭原理

角点检测

视频采集播放

启动摄像头

打开 AVI 文件

视频解冻

视频冻结

多图像平均

关闭视频

选择分辨率

动态边缘检测

L_K光流跟踪

典型例图

各类功能都有一定的特异性,为了突出各功能的实效,建议初次使用时先用表3中列出的例图进行处理。这些例图在程序包的Image目录下,表中也对一些功能作了简要的说明。

表3 功能的典型例图

功能名称

外接矩形 (BoundingRect)

最小面积矩形 (MinAreaRect2)

区域凸包 (ConvexHull2)

轮廓跟踪 (FindContours)

距离变换 (DistTransform)

顶帽变换

波谷检测

图像缩小

径向梯度

Canny 算法

种子填充 (FloodFill)

金字塔图像分割 (PyrSegmentation)

椭圆曲线拟合 (FitEllipse)

Snake原理 (SnakeImage)

分水岭原理 (Watershed)

角点检测 (GoodFeaturesToTrack)

打开 AVI 文件

、 适用各类二值图像,使用范围较宽

典型例图

简要说明

仅适用于单连通区域,

不能处理多连通区域与孔边界

、 图像反相后也可进行处理,得到不同结果

为灰值形态学运算,波谷检测又称黑顶帽变换

图像反相后处理与另一方法直接处理等价

鼠标选种子,命令键C、M、R、S、F、G、8、4,ESC

滑动条调节参数,任意键退出

滑动条调节参数,任意键退出

滑动条调节参数,ESC 退出

鼠标绘制区域标记,R 清除,W或Enter 分割,ESC 退出

滑动条调节参数,鼠标确定检测范围,任意键退出

不能播放用Mpeg压缩的视频文件

程序结构

程序采用VC++多文档结构,图像的存放与处理则采用OpenCV的结构与函数,图像的显示采用位图信息m_lpBmi实现,为了便于管理对m_lpBmi的操作集中在OnDraw程序中。待显示位图结构发生改变3

在MFC中使用OpenCV

时用m_dibFlag标志激发m_lpBmi的刷新。除了文件结构与图像显示外,其余部分基本上是OpenCV程序。

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

CVMFCView中 workImg (工作位图)、saveImg (备份位图)

m_lpBmi (工作位图的位图信息)

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

视图的显示:

void CCVMFC0View::OnDraw(CDC* pDC)

{

CCVMFCDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

if (pDoc->pImg!=NULL) {

CSize sizeTotal;

sizeTotal = CSize(pDoc->pImg->width,pDoc->pImg->height);

SetScrollSizes(MM_TEXT, sizeTotal);

if (pDoc->m_Display==0) { // 输入图像尚未显示

newCloneImage(pDoc->pImg, &saveImg); // 复制到备份位图

m_dibFlag=newCloneImage(saveImg, &workImg); // 复制到工作位图

m_ImageType=pDoc->pImg->nChannels; // 设置标志

m_SaveFlag=m_ImageType;

pDoc->m_Display=1;

}

}

if (m_dibFlag) { // 刷新 DIB位图信息

if (m_lpBmi)

free(m_lpBmi);

m_lpBmi = CtreateMapInfo(workImg, m_dibFlag);

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);

}

}

// 有磁盘输入图像

CCVMFC模块中增加了4个函数,即CtreateMapInfo、imageType、imageClone与imageReplace。CtreateMapInfo函数用于建立工作位图workImg的位图信息m_lpBmi,其特点是可以为单通道位图设置两种调色板,即黑白灰阶调色板与VGA默认调色板。因为在OpenCV中二值图像显示为灰阶图像,imageType函数可从单通道位图中识别出二值位图。ImageClone与imageReplace函数使用OpenCV函数实现位图的复制,自动释放老的指针所指向的存储单元以防止内存泄漏,同时返回的m_dibFlag标志可以用于激发刷新4

在MFC中使用OpenCV

工作位图workImg的位图信息m_lpBmi。imageReplace与ImageClone相似,但不建立新位图,只用输入位图替换输出位图。

LPBITMAPINFO CtreateMapInfo(IplImage* workImg, int flag)

{ // 建立位图信息

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<

lpBmi=(LPBITMAPINFO) malloc(40+4*colors);

h =wid;

ht =hei;

ount=(BYTE) bits;

memcpy(lpBmi,&BIH,40); // 复制位图信息头

if (bits==8) { // 256 色位图

if (flag==1) { // 设置灰阶调色板

for (i=0;i<256;i++) {

ColorTab[i].rgbRed=ColorTab[i].rgbGreen=ColorTab[i].rgbBlue=(BYTE) i;

}

memcpy(lpBmi->bmiColors, ColorTab, 1024);

}

else if (flag==2) { // 设置默认调色板

memcpy(lpBmi->bmiColors, VgaDefPal, 1024);

}

}

return(lpBmi);

}

int imageType(IplImage* p) // 识别二值图像

{

int i,j,k,bpl,n,pg[256];

BYTE *buf;

k=p->nChannels;

if (k==1) { // 检查二值图像

for (i=0;i<256;i++) pg[i]=0;

buf=(BYTE*) p->imageData; bpl=p->widthStep;

for (i=0;iheight;i++) {

for (j=0;jwidth;j++) pg[buf[j]]++; // 直方图统计

buf+=bpl;

5

在MFC中使用OpenCV

}

for (i=0,n=0;i<256;i++) {

if (pg[i]) n++;

}

if (n==2) k=-1; // 二值图像

}

return(k);

}

int imageClone(IplImage* pi, IplImage** ppo) // 复制 IplImage 位图 (OpenCV)

{

if (*ppo)

cvReleaseImage(ppo); // 释放原来位图

(*ppo) = cvCloneImage(pi); // 复制新位图

return(1);

}

int imageReplace(IplImage* pi, IplImage** ppo) // 替换位图 (OpenCV)

{

if (*ppo)

cvReleaseImage(ppo); // 释放原来位图

(*ppo) = pi; // 位图换名

return(1);

}

像素数据地址的类型

从表1中可见,在OpenCV中像素存放地址imageData为char* 类型。因此,在图像处理时就必须转换为BYTE* 类型才可以使用。例如,在上面的imageType函数中统计图像直方图非使用这种转换不可。

另外,图像类型参数m_ImageType用于菜单功能的使能(Enable),程序中用到了4种不同类型的图像,即真彩色图像、灰阶图像、二值图像与伪彩色图像,它们的m_ImageType值分别定义为3,1,-1,-2。

双操作数函数的参数顺序

本程序是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:

6

在MFC中使用OpenCV

cvCvtColor( pSrcImg, pDestcImg, CV_BGR2GRAY);

cvSmooth( pSrcImg, pDestcImg, CV_GAUSSIAN, 3, 0, 0);

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

图像处理程序的修改

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

《学习OpenCV》例2-6原始程序:

int main(int argc, char* argv[]) // Canny 边缘检测

{

IplImage* pImage = NULL;

IplImage* pImgCanny = NULL;

IplImage* pImg8u = NULL;

IplImage* pImg8uSmooth = NULL;

pImage = cvLoadImage(argv[1]); // 输入待处理图像

if (!pImage) return 0;

cvNamedWindow("Example6-in",CV_WINDOW_AUTOSIZE);

cvShowImage("Example6-in",pImage);

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);

cvNamedWindow("Example6-out",CV_WINDOW_AUTOSIZE);

cvShowImage("Example6-out", pImgCanny); // 显示处理结果

cvWaitKey(0);

cvReleaseImage(&pImage);

cvReleaseImage(&pImg8u);

cvReleaseImage(&pImg8uSmooth);

cvReleaseImage(&pImgCanny);

cvDestroyWindow("Example6-in");

cvDestroyWindow("Example6-out");

return 0;

}

7

在MFC中使用OpenCV

在CVMFC程序中修改为:

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);

//** 输出处理结果 ** // 修改部分 2

m_dibFlag = ImageReplace(pImgCanny, &workImg);

// cvReleaseImage(&workImg); // 上面一条语句相当于删除的这3条语句

// workImg = pImgCanny; // 替换workImg位图

// m_dibFlag = 1; // 设置激发DIB位图信息刷新的标志

// 释放位图

cvReleaseImage(&pImg8u);

cvReleaseImage(&pImg8uSmooth);

//** 设置标志及刷新窗口 ** // 修改部分 3

m_ImageType=1;

Invalidate();

}

轮廓跟踪与距离变换

轮廓跟踪与距离变换是OpenCV中很有特色的两个函数,但是它们目前的演示程序却并不理想。轮廓跟踪的例图太简单,绘制轮廓的笔划太粗,十分粗糙。本演示程序包中提供二值图像可以很好地显示跟踪结果,此图还可黑白反相后使用。距离变换中原演示程序距离值使用的颜色层次模糊不清,滑动条的使用又模糊了距离变换的焦点,也可以利用此图结合VGA默认调色板改进演示效果。距离变换本身为内向距离变换,图像反相后再作距离变换可得到它的外向距离变换。

8

在MFC中使用OpenCV

VGA默认调色板

轮廓跟踪与距离变换的演示中都使用了彩色画面。轮廓跟踪用不同颜色显示轮廓,距离变换采用不同颜色表示不同的距离值。VGA 256色显示模式下定义了一个默认调色板,提供一组典型的颜色组合,可满足大多数场合的使用要求,见表4。用户若无特殊需要,可直接用它来绘制图形图像。本演示程序用NextColor函数在轮廓跟踪与距离变换中利用了其中228种(1~15中的12种加上32~247之间的216种)彩色值来设置颜色,使用方便、色差明显。参数step用于选择色差,step取1 时有228种彩色可选,step取4 时有66种彩色可选。轮廓跟踪的结果用3通道图像使用228种颜色通过绘制图形实现显示,距离变换的结果用单通道图像66种颜色循环使用通过像素赋值实现显示。

表4 VGA默认调色板

序号

0 ~ 7

8 ~ 15

16 ~ 31

32 ~ 103

104 ~ 175

176 ~ 247

248 ~ 255

int NextColor(int start, int k, int step) // 下一彩色号 递增步长 step 可取 1 或 4

{ // step=1 时有228种彩色可选,step=4 时有66种彩色可选

k++; // 修改颜色号

if (k==7) k+=2; // 跳过默认调色板7、8号颜色

else if (k==15) k+=17; // 跳过默认调色板15~31号颜色

if (k>32) k+=step-1;

if (k>247) k=start; // 超过默认调色板248号颜色,从头开始循环

return(k);

}

颜色数

8

8

16

72

72

72

8

内容

黑、蓝、绿、青、红、品红、褐色、淡灰

深灰、亮蓝、亮绿、亮青、亮红、亮品红、黄、白

灰阶,从黑到白16个等级

高亮彩色 (高饱和、中饱和、低饱和各24种色调)

中亮彩色 (高饱和、中饱和、低饱和各24种色调)

低亮彩色 (高饱和、中饱和、低饱和各24种色调)

未定义

位图的坐标原点

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

IplImage* CCameraDS::QueryFrame()

{

……

m_pSampleGrabber->GetCurrentBuffer(&m_nBufferSize, (long*)m_pFrame->imageData);

cvFlip(m_pFrame);

return m_pFrame;

}

本程序图像的输入输出采用OpenCV的cvLoadImage与cvSaveImage函数实现,而显示采用Windows

9

在MFC中使用OpenCV

API中的StretchDIBits函数。为了正常工作,也需要作垂直镜像。

BOOL CCVMFCDoc::Load(IplImage** pp,LPCTSTR csFileName)

{

IplImage* pImg = NULL;

pImg = cvLoadImage(csFileName,-1); // 读图像文件(DSCV)

if (!pImg) return(false);

cvFlip(pImg); // 与 DIB 像素结构一致

return(true);

}

BOOL CCVMFCDoc::Save(LPCTSTR csFileName,IplImage* pImg)

{

cvFlip(pImg); // 恢复原 OpenCV 位图结构

bl = cvSaveImage(csFileName,pImg); // 图像存盘

return(bl);

}

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

void CCVMFCView::OnRotation30() // 图像30 度旋转

{

int angle = 30; // 定义旋转角

pImage = workImg;

pImgRotation = cvCloneImage(workImg);

angle=-angle; // 旋转方向反转

// 图像旋转

cvNamedWindow("Rotation Image");

cvFlip(pImgRotation);

cvShowImage("Rotation Image",pImgRotation); // 在OpenCV窗口显示旋转结果

}

void CCVMFCView::OnAreaRect() // 外接矩形 (单连通)

{

image = cvCloneImage( workImg );

cvFlip(image);

// OpenCV图形操作

cvCircle(image, pt, 2, CV_RGB( 0, 255, 0 ), CV_FILLED );

cvRectangle(image, cvPoint(pt1.x,pt1.y), cvPoint(pt2.x,pt2.y), CV_RGB(0,255,0), 1, 8, 0);

......

cvFlip(image);

m_dibFlag=imageReplace(image, &workImg); // 转主窗口显示处理结果

}

10

在MFC中使用OpenCV

受到这种影响的程序有:轮廓跟踪、点集凸包、区域凸包、外接矩形、最小外接矩形、椭圆曲线拟合、Snake 原理等。下面程序中需要删除cvFlip语句也是这个道理,IplImage结构与DIB位图在Y轴上的增量方向是相反的。

人机交互

众所周知,DOS操作系统采用文本模式与过程驱动,Windows操作系统采用图形模式与事件驱动。因此,在DOS操作系统下显示图像需要设置图形显示模式,或打开专门的显示图像的窗口来显示图像。图像处理专家远在Windows出现前就已开展工作。因此,养成了DOS操作系统的工作习惯,OpenCV的许多应用实例中遗留了DOS命令行编程习惯。如使用等待键盘输入函数cvWaitKey停住显示画面,并用字母输入改变程序走向等。综合处理例中有多个功能,如椭圆曲线拟合、Snake 原理与分水岭原理使用了滑动条。这时由于有两个窗口——视图窗口与弹出窗口,退出时应先退出弹出窗口,然后再关闭视图窗口。操作顺序颠倒会造成系统错误,弹出窗口通常采用按ESCAPE键退出。

为了避免出现故障,出现OpenCV函数所开窗口时,务必使用ESCAPE键退出,并关闭该窗口。菜单中带 (ESC) 字样的命令一定得用ESCAPE键退出。

为了便于比较,提供了两个[点集凸包]命令,一个采用过程驱动,一个采用事件驱动,不妨比较一下实际功能及其实现程序。

此外,分水岭原理程序原来在主窗口中有如下文字提示:

printf( "Hot keys: n"

"tESC - quit the programn"

"tr - restore the original imagen"

"tw or ENTER - run watershed algorithmn"

"tt(before running it, roughly mark the areas on the image)n"

"t (before that, roughly outline several markers on the image)n" );

指示按ESC键退出程序,按R键恢复原始图像,按W或ENTER键运行分水岭算法程序。改编程序中没有将这段文字显示出来,使用时请特别注意。

Snake 原理与分水岭原理功能需要主、辅两个窗口画面的联动,由于在程序循环中无法用Invalidate函数直接刷新主窗口画面,更新的画面只能画上去。如在Snake 原理中用下列程序段实现:

imageClone(image2, &workImg);

cvFlip(workImg);

free(m_lpBmi);

m_lpBmi=CtreateMapInfo(workImg, m_dibFlag);

CClientDC dc(this);

StretchDIBits(dc.m_hDC, 0, 0, workImg->width, workImg->height, // 刷新主窗口

0, 0, workImg->width, workImg->height,

workImg->imageData, m_lpBmi, DIB_RGB_COLORS, SRCCOPY);

由于Snake 原理的原始图像是灰阶图像,而显示画面为真彩色图像,因此刷新画面时需要修改DIB的位图信息。这是本程序中除了OnDraw外唯一一处修改位图信息m_lpBmi的地方。

OpenCV编程环境人机交互的功能有限,只有鼠标、键盘与滑动条。分水岭原理例中这3种功能都已经出现,对于初学者而言是一个很好的学习范例。

11

在MFC中使用OpenCV

绘图功能

在OpenCV中可直接往位图上绘制图形,单它的绘图功能较弱,只有画线、画矩形、画圆和文字显示功能。但是却有一个Windows API中所没有的特殊功能,即它可以画轴线倾斜的椭圆,见椭圆曲线拟合功能。不少功能中有画线、画矩形、画圆的例子,如外接矩形、最小面积矩形、区域凸包与轮廓跟踪。轮廓跟踪功能中则是显示文字的例子。

数据结构

OpenCV中数据结构异常复杂,对于初学者来说,通过实例学习是一个便捷的好方法。演示程序中,30度旋转是矩阵的应用实例,轮廓跟踪是序列的应用实例。

体会与经验

在编制最小外接矩形程序时发现,手册上关于CVBox2D结构的说明是错误的。其中成员是以角度为单位计算的,而不是用弧度为单位。另外,第一条边指的是外接矩形的长边,总为。所谓夹角是指这条长边与水平方向间的夹角。两幅例图与正好一个矮胖一个高瘦,但它们的长边都是。所以,没有经过实际调试这是根本体会不到、发现不了的。

在程序中尽量避免频繁地释放与建立位图,在调试Snake 原理程序时发现每执行两次后程序就出现故障了。怀疑是释放与建立位图过于频繁,将程序改为开始时就建立所有4个需要的位图,以后采用复制函数cvCopy来恢复原始位图。这样修改后问题就解决了。在OnDraw程序的修改中也有过这样的体会,现在显示所需的位图信息只需在开始时生成一次,不仅提高了效率,又“少做少错”,使程序更为可靠。

程序中的修改

程序仅作两处修改,为便于区别改名。修改如下:

一、包含文件

//#include "CameraDS.h" // 放到stdafx.h中

#include "stdafx.h" // VC++编程要求

二、垂直镜像

// cvFlip(m_pFrame); // 垂直镜像

OpenCV系统的DLL

为了便于在未安装OpenCV的计算机上运行演示程序CVMFC,本程序包中附上了相应的DLL。

致谢

本人虽然从事图像处理工作多年,也有一些由TC编程向VC++编程转变的体会与经验,但是对于OpenCV来说毕竟还是个初学者。学习过程中国内仅有的三本有关OpenCV的著作都找来看了,受益非浅、深受启发。本程序中的不少例子是由这些书上的例子修改、补充、整理而成的。另外,也从OpenCV China

网站获得了不少有关OpenCV的知识与信息,同时下载了所需的软件。对于这几本书的作者、译者与网站工作人员在此表示由衷的感谢。

陆宗骐 2010.9.18

12

在MFC中使用OpenCV

参考文献:

位图、图像的采集与处理:

陆宗骐. C/C++图像处理编程. 北京: 清华大学出版社, 2005.

陆宗骐, 金登男. Visual C++.Net图像处理编程. 北京: 清华大学出版社, 2006.

OpenCV:

刘瑞祯, 于仕琪. OpenCV教程(基础篇) . 北京: 北京航空航天大学出版社, 2007.

陈胜勇, 刘盛等. 基于OpenCV的计算机视觉技术实现. 北京: 科学出版社, 2008.

Gary Bradski, Adrian Kaehler. 学习OpenCV. 北京: 清华大学出版社, 2009.

DirectShow编程:

于仕琪. . OpenCV China 网站.

13