[編輯] 例程下載[編輯] OpenCV與MFCOpenCV是計(jì)算機(jī)視覺(jué)自由軟件的寶庫(kù),。但是,,由于歷史的原因它的軟件主要采用類似DOS操作系統(tǒng)的命令行方式,使用十分不便,,這也影響了它的推廣,。如果能將它應(yīng)用到VC++文檔結(jié)構(gòu)中就好了。OpenCV程序在MFC中實(shí)現(xiàn)的方法通常是采用CvvImage類,,這個(gè)類的成員函數(shù)DrawToHDC可將位圖整體經(jīng)縮放后顯示到視圖窗口中,,解決了位圖的顯示問(wèn)題,也就解決了OpenCV在MFC中的使用問(wèn)題,。但是,,也有兩個(gè)致命的弱點(diǎn),一是顯示方式不合圖像處理使用習(xí)慣,,二是位圖必須采用CvvImage類,。顯示方面,雖然已經(jīng)能夠察看圖像,,但當(dāng)位圖與窗口的長(zhǎng)寬比不一致時(shí)會(huì)造成圖像失真,,這是瀏覽器的顯示習(xí)慣,并不適用于圖像處理應(yīng)用。圖像采集與處理的使用習(xí)慣是顯示比例1:1,,圖像未經(jīng)縮放,,顯示畫(huà)面可按窗口大小進(jìn)行裁剪并可使用滾動(dòng)條選擇顯示部位,。數(shù)據(jù)結(jié)構(gòu)方面,,采用CvvImage類以后程序中所有位圖必須修改成這個(gè)類,這對(duì)于利用大量OpenCV現(xiàn)成軟件來(lái)說(shuō)是十分不方便的,。顯然,,在VC++文檔結(jié)構(gòu)中使用OpenCV,其關(guān)鍵還是在于OpenCV位圖在MFC中的顯示,。因此,,有必要先比較一下兩者的位圖結(jié)構(gòu),然后尋找新的解決方法,。 [編輯] StretchDIBits 函數(shù)由于OpenCV位圖結(jié)構(gòu)中的像素?cái)?shù)據(jù)與DIB中的像素具有類似的存儲(chǔ)結(jié)構(gòu),,可以考慮直接用來(lái)在視圖窗口中顯示。知道位圖像素的存放地址直接往視圖窗口顯示的函數(shù)雖然不多,,但還是有Windows API中的StretchDIBits函數(shù)可以利用,,由下面列出的StretchDIBits函數(shù)原型中可知,只要為它構(gòu)造一個(gè)DIB的位圖信息就行了,。 int StretchDIBits( HDC hdc, // 顯示設(shè)備句柄 int XDest, YDest, nDestWidth, nDestHeight, // 目標(biāo)矩形區(qū)域參數(shù) int XSrc, YSrc, nSrcWidth, nSrcHeight, // 源矩形區(qū)域參數(shù) // 源區(qū)域與目標(biāo)區(qū)域參數(shù)相同時(shí)為 1:1 比例顯示 CONST VOID *lpBits, // 位圖的像素存放首地址 CONST BITMAPINFO *lpBitsInfo, // 位圖信息存放地址 UINT iUsage, // 位圖中的顏色類型,,RGB模式用DIB_RGB_COLORS DWORD dwRop // 像素操作碼,簡(jiǎn)單復(fù)制用SRCCOPY ); 為此,,演示程序CCVMFC中增加了3個(gè)函數(shù),,即CtreateMapInfo、imageClone與imageReplace,。其中,,CtreateMapInfo 函數(shù)用于建立OpenCV位圖的位圖信息,其特點(diǎn)是可以為單通道位圖設(shè)置黑白灰階調(diào)色板,。ImageClone函數(shù)使用OpenCV函 數(shù)實(shí)現(xiàn)位圖的復(fù)制,,自動(dòng)釋放老的指針?biāo)赶虻拇鎯?chǔ)單元以防止內(nèi)存泄漏,同時(shí)返回的m_dibFlag標(biāo)志可以用于激發(fā)刷新工 作位圖workImg的位圖信息m_lpBmi (見(jiàn)后面說(shuō)明),。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); // 復(fù)制位圖信息頭 if (bits==8) { // 256 色位圖 for (i=0;i<256;i++) { // 設(shè)置灰階調(diào)色板 ColorTab[i].rgbRed=ColorTab[i].rgbGreen=ColorTab[i].rgbBlue=(BYTE) i; } memcpy(lpBmi->bmiColors, ColorTab, 1024); } return(lpBmi); } int imageClone(IplImage* pi, IplImage** ppo) // 復(fù)制 IplImage 位圖 (OpenCV) { if (*ppo) cvReleaseImage(ppo); // 釋放原來(lái)位圖 (*ppo) = cvCloneImage(pi); // 復(fù)制新位圖 return(true); } [編輯] 演示程序演示程序CVMFC采用VC++多文檔帶滾動(dòng)條結(jié)構(gòu),,圖像的存放與處理則采用OpenCV的位圖結(jié)構(gòu)與函數(shù),圖像的顯示通過(guò)建立相應(yīng)的位圖信息m_lpBmi來(lái)實(shí)現(xiàn),,為了便于管理對(duì)m_lpBmi的操作集中在OnDraw程序中,。待顯示位圖結(jié)構(gòu)發(fā)生改變時(shí)用m_dibFlag標(biāo)志激發(fā)m_lpBmi的刷新。除了文件結(jié)構(gòu)與圖像顯示外,其馀部分基本上都是OpenCV程序,。
CVMFCDoc中,, pImg (讀入圖像文件所得原始位圖) CVMFCView中, workImg (工作位圖),、saveImg (備份位圖),、m_lpBmi (工作位圖的位圖信息) CVDSCap中, m_Frame (視頻采集所得位圖)
void CCVMFC0View::OnDraw(CDC* pDC) { …… // 其馀部分內(nèi)容 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); } } [編輯] 像素?cái)?shù)據(jù)類型圖像處理主要是對(duì)像素?cái)?shù)據(jù)的處理,。需要注意的是,,在OpenCV中像素存放地址imageData為char* 類型。因此,,在圖像處理時(shí)必須轉(zhuǎn)換成BYTE* 類型才可以使用,。下面以識(shí)別圖像類型函數(shù)imageType為例來(lái)作說(shuō)明。 int imageType(IplImage* p) // 識(shí)別圖像類型 { 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; // 修改像素?cái)?shù)據(jù)類型 bpl = p->widthStep; for ( i = 0; i < p->height; i++ ) { for ( j = 0; j < p->width; j++) pg[buf[j]]++; // 直方圖統(tǒng)計(jì) buf += bpl; } for ( i = 0, n = 0; i < 256; i++ ) if (pg[i]) n++; // 統(tǒng)計(jì)使用色階數(shù) if (n == 2) k = -1; // -1 表示二值圖像 } return(k); } [編輯] 圖像鏡像在位圖的像素方面,,除了imageData的類型問(wèn)題外,,DIB位圖與IplImage結(jié)構(gòu)間還有一個(gè)顯著的不同,那就是坐標(biāo)原點(diǎn)位置的不同,。前者的坐標(biāo)原點(diǎn)在位圖底部左側(cè),,而后者在頂部左側(cè)。因此,,當(dāng)在OpenCV中需要使用MFC的函數(shù)顯示時(shí)位圖應(yīng)作垂直鏡像,,反之亦然。典型的例子是在OpenCV中調(diào)用DirectShow視頻采集程序CameraDS中的獲取當(dāng)前幀函數(shù)QueryFrame,,其程序如下: IplImage* CCameraDS::QueryFrame() { …… m_pSampleGrabber->GetCurrentBuffer(&m_nBufferSize, (long*) m_pFrame->imageData); cvFlip(m_pFrame); return m_pFrame; } 演示程序CVMFC的圖像輸入輸出采用OpenCV的cvLoadImage與cvSaveImage函數(shù)實(shí)現(xiàn),,而顯示采用Windows API中的 StretchDIBits函數(shù)。為了能正常工作,,圖像讀入后需作垂直鏡像,,圖像存盤(pán)前也需作垂直鏡像。也就是說(shuō),,內(nèi)存中存放的是 經(jīng)過(guò)垂直鏡像的OpenCV位圖,。同樣,因?yàn)榻Y(jié)構(gòu)相同,,它也是DIB位圖的像素?cái)?shù)據(jù),。 BOOL CCVMFCDoc::Load(IplImage** pp,LPCTSTR csFileName) { IplImage* pImg=NULL; pImg = cvLoadImage(csFileName,-1); // 讀圖像文件 if (!pImg) return(false); cvFlip(pImg); // 使與 DIB 像素結(jié)構(gòu)一致 if (*pp) cvReleaseImage(pp); (*pp)=pImg; m_Display=0; return(true); } BOOL CCVMFCDoc::Save(LPCTSTR csFileName,IplImage* pImg) { int bl; cvFlip(pImg); // 恢復(fù)原 OpenCV 位圖結(jié)構(gòu) bl=cvSaveImage(csFileName,pImg); // 圖像存盤(pán) return(bl); } 對(duì)于大多數(shù)的圖像處理算法來(lái)說(shuō)位圖的鏡像與否沒(méi)有什么影響。但是,,對(duì)于某些OpenCV函數(shù),,例如涉及旋轉(zhuǎn)方向以及需要往IplImage結(jié)構(gòu)的位圖上繪制圖形、顯示文字時(shí)就會(huì)使位置出錯(cuò),。這時(shí)就需要與存盤(pán)時(shí)一樣先作垂直鏡像,,旋轉(zhuǎn)角度反向,,操作結(jié)束返回Windows視圖顯示處理結(jié)果時(shí)再轉(zhuǎn)換回來(lái)。 [編輯] 驅(qū)動(dòng)模式與人機(jī)交互眾所周知,,DOS操作系統(tǒng)采用過(guò)程驅(qū)動(dòng)與文本模式,,Windows操作系統(tǒng)采用事件驅(qū)動(dòng)與圖形模式,它們分別使用鍵盤(pán)與鼠標(biāo)器作為主要輸入工具,。OpenCV編程環(huán)境基于DOS操作系統(tǒng),,因此顯示圖像需要調(diào)用cvNamedWindow函數(shù)建立專門(mén)的窗口,然后調(diào)用cvShowImage函數(shù)進(jìn)行顯示,。畫(huà)面的保持則使用cvWaitKey(0)語(yǔ)句,,或使用內(nèi)含cvWaitKey語(yǔ)句的死循環(huán)來(lái)實(shí)現(xiàn),。為了避免出現(xiàn)死機(jī)故障,,出現(xiàn)OpenCV函數(shù)所開(kāi)窗口時(shí),務(wù)必使用ESCAPE鍵關(guān)閉窗口并退出,。注意:菜單中帶 (ESC) 字樣的命令必須使用ESCAPE鍵退出,。 同時(shí),OpenCV編程環(huán)境人機(jī)交互的功能有限,,只有鼠標(biāo),、鍵盤(pán)與滑動(dòng)條。其中,,鼠標(biāo)與滑動(dòng)條屬于事件驅(qū)動(dòng),,而鍵盤(pán)屬于過(guò)程驅(qū)動(dòng)。由于沒(méi)有菜單功能,,只能使用鍵盤(pán)命令控制程序走向并等待鍵盤(pán)輸入選擇不同功能,。 在驅(qū)動(dòng)模式方面為了便于比較,提供了兩個(gè)[點(diǎn)集凸包]命令,,一個(gè)采用過(guò)程驅(qū)動(dòng),,另一個(gè)采用事件驅(qū)動(dòng),不妨比較具體功能及其實(shí)現(xiàn)的程序,。 [編輯] 程序移植例演示程序CVMFC中待處理圖像與處理結(jié)果都放在工作位圖workImg中,。同時(shí),工作位圖workImg也是窗口畫(huà)面顯示的內(nèi)容,。所以,,處理程序中僅源圖像的獲取與結(jié)果圖像的顯示與OpenCV中的程序有所不同,中間處理部分可以完全相同,,現(xiàn)以《學(xué)習(xí)OpenCV》一書(shū)中的例2-6為例來(lái)作修改說(shuō)明,。 void CCVMFC0View::OnCannyBorddetec() // Canny 邊緣檢測(cè) { // 根據(jù)《學(xué)習(xí)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); //** 輸出處理結(jié)果 ** // 修改部分 2 m_dibFlag = ImageReplace(pImgCanny, &workImg); //** 設(shè)置標(biāo)志及刷新窗口 ** m_ImageType=1; Invalidate(); } [編輯] 形參書(shū)寫(xiě)順序演示程序CVMFC是Windows API系統(tǒng)與OpenCV兩種體系函數(shù)的混合使用,而它們的函數(shù)參數(shù)的書(shū)寫(xiě)習(xí)慣是不同的,,極容易引起混淆,。這里作一簡(jiǎn)要說(shuō)明,,請(qǐng)看下面例子: 目標(biāo)參數(shù)Dest <-- 源參數(shù)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); 源參數(shù)Src --> 目標(biāo)參數(shù)Dest OpenCV: cvCvtColor( pSrcImg, pDestcImg, CV_BGR2GRAY); cvSmooth( pSrcImg, pDestcImg, CV_GAUSSIAN, 3, 0, 0); 演示程序中除了OpenCV函數(shù)采用后一書(shū)寫(xiě)形式外,其他函數(shù)都采用前一書(shū)寫(xiě)形式,,千萬(wàn)注意不能混淆,。OpenCV函數(shù)很 容易辨認(rèn),都以前綴cv-開(kāi)頭,,如cvCvtColor,、cvSmooth。新增函數(shù)ImageClone與imageReplace因?yàn)檎{(diào)用OpenCV函數(shù),, 故也采用后一書(shū)寫(xiě)形式,。 [編輯] CVMFC 1.1版菜單[文件]:打開(kāi)圖像、恢復(fù)圖像,、關(guān)閉當(dāng)前窗口,、保存當(dāng)前位圖、恢復(fù)原始圖像,、當(dāng)前畫(huà)面存盤(pán),、退出 [點(diǎn)處理]:彩色變灰階、圖像反相,、垂直鏡像,、水平鏡像、180 度旋轉(zhuǎn),、30 度旋轉(zhuǎn),、仿射變換、透視變換,、亮度變換,、灰階圖像直方圖、直方圖均衡化 [鄰域處理]:鄰域平均,、Gauss 濾波,、中值濾波、Sobel 邊緣檢測(cè),、Laplace 邊緣檢測(cè) [二值化]:選擇閾值,、選擇閾值(原位圖)、自適應(yīng)閾值法,、基本全局閾值法 [二值圖像處理]:點(diǎn)集最小區(qū)域,、外接矩形、最小面積矩形,、多邊形逼近,、點(diǎn)集凸包、點(diǎn)集凸包 (事件驅(qū)動(dòng)),、區(qū)域凸包,、區(qū)域凹差,、輪廓跟蹤、距離變換 [形態(tài)學(xué)處理]:腐蝕,、膨脹,、開(kāi)運(yùn)算、閉運(yùn)算,、形態(tài)學(xué)梯度,、頂帽變換、波谷檢測(cè) [彩色圖像處理]:RGB 分量,、HSV 分量,、Lab 分量、RGB 分量 (C),、XYZ 分量,、YCrCb 分量、Luv 分量,、二維直方圖,、鄰域平均,、Gauss 濾波,、中值濾波、Sobel 邊緣檢測(cè),、Laplace 邊緣檢測(cè) [綜合處理例]:圖像縮小,、徑向梯度、Canny 算法,、Hough 變換 (直線),、"Hough 變換 (圓)、平行四邊形檢測(cè),、連通區(qū)域填充,、金字塔法圖像分割、橢圓曲線擬合,、Snake 原理,、分水嶺原理、角點(diǎn)檢測(cè),、點(diǎn)集聚類,、分割二維點(diǎn)集、旋轉(zhuǎn)點(diǎn)跟蹤,、人臉檢測(cè) [動(dòng)態(tài)檢測(cè)]:動(dòng)態(tài)邊緣檢測(cè),、L_K 算法光流跟蹤、背景建模檢測(cè),、運(yùn)動(dòng)目標(biāo)檢測(cè),、彩色目標(biāo)跟蹤,、人臉檢測(cè)二 [視頻采集播放]:?jiǎn)?dòng)攝像頭、打開(kāi) AVI 文件,、視頻解凍,、視頻凍結(jié)、多圖像平均,、關(guān)閉視頻,、選擇采樣分辨率 [圖形及其他]:繪制圖形、繪制 Delaunay 圖形,、極坐標(biāo)變換,、DFT |
|