在之前的博客中我們已經實現讀取用戶選定的文件夾,並將其路徑保存在相應的變量中,在這篇博文中我們將介紹如何借助CvvImage類將圖片顯示在picture控件中,並自動讀取文件夾下的其他圖片。
一、添加“下一張”按鈕
由於我們需要讀取文件夾下的所有圖像文件,而非某一張文件,因此有必要添加一個按鈕來進行控制,具體功能就是:每單擊一次這個按鈕,程序就會自動讀取下一張圖片並顯示在界面上。由於之前已經詳細介紹了MFC中添加Button控件的方式,這裡不再贅述。添加一個按鈕,命名為“下一張”,將ID更改為IDC_BUTTON_NextImage:
二、編寫遍歷函數
在上一篇博客中我們提到,在選中文件夾之後,程序會將文件夾的路徑保存在m_Path變量之中。接下來我們就借助這個變量來進一步遍歷其路徑下的圖像文件。這裡我們專門編寫一個函數來實現“遍歷下一張圖片”的功能,命名為GetNextBigImg。因此,需要向CGenderRecognitionMFCDlg類中添加這個成員函數。在類視圖中右擊相應的類,在快捷菜單中選擇“添加->添加函數”,輸入函數的屬性:
GetNextBigImg()函數主要承擔著一下幾個任務:
1、開始遍歷
這裡將GetNextBigImg()放在OnBnClickedButtonImagefile()函數中的末尾部分進行調用,用以在單擊“圖片文件夾”按鈕讀取文件夾信息之後啟用文件讀取程序。
2、從當前目錄路徑下讀入一個文件
這裡讀取文件主要通過readdir函數來完成,考慮到用戶可能會選擇一個空文件夾,因此這裡需要對讀取操作進行一次判斷:
if (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL) { }
readdir()函數能夠實現對當前目錄結構(m_pDir)中的文件的無重復順序讀取,即每次讀取完成後都會自動移到下一個待讀取的文件,與指針的機制類似,readdir()函數包含在dirent.h頭文件中,之前已經添加並包含完畢。此時,m_pEnt變量中保存了文件名稱:
3、判斷是否為圖像文件
這裡采用strstr()函數來判斷文件名中是否包含對應的擴展名字符串,這裡默認的圖像格式有四種:jpg ,bmp,png:
if (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL) { /**********判斷是否為圖像文件**********/ char* jpg = strstr(m_pEnt->d_name,".jpg"); char* bmp = strstr(m_pEnt->d_name,".bmp"); char* png = strstr(m_pEnt->d_name,".png"); }
至於“m_pEnt->d_name”這種調用格式,在dirent.h頭文件中有著明確定義,有疑問的話可以查閱相關文件。接下裡通過判斷jpg、bmp、png這幾個變量是否為空來確定文件是否是圖像文件:
if (m_pDir && (m_pEnt = readdir(m_pDir)) != NULL) { /**********判斷是否為圖像文件**********/ char* jpg = strstr(m_pEnt->d_name,".jpg"); char* bmp = strstr(m_pEnt->d_name,".bmp"); char* png = strstr(m_pEnt->d_name,".png"); if (jpg == NULL && bmp == NULL && png == NULL) //如果該文件不是圖像文件 { GetNextBigImg(); } else { /**********顯示該圖片**********/ } }
注意這裡采用了一種遞歸的方式來實現非圖像文件的輪詢,即當前文件被判定為非圖像文件時(jpg、bmp、png均為空),則調用自身GetNextBigImg(),也就意味著再次執行一遍readdir()函數,使得文件指針後移意味,層層遞歸實現最終的文件遍歷;相應的,如果當前文件為三種圖像文件中的一種,則將當前圖片繪制到picture控件中,接下來編寫繪制圖像的代碼。
4、繪制圖像至picture控件
此時該輪到CvvImage大顯身手了。在此之前,我們需要先為picture控件關聯一個CRect類型的矩形變量,這個變量將用來保存picture控件在客戶區所處的位置。首先,為CGenderRecognitionMFCDlg類添加成員變量m_PicCtlRect:
然後,再添加一個HDC(句柄)變量m_pPicCtlHdc,用於保存控件的句柄:
然後在CGenderRecognitionMFCDlg的對話框初始化函數OnInitDialog()中編寫兩行代碼,將控件、句柄、位置信息這三個變量相互關聯起來:
/*********初始化picture控件**********/ m_pPicCtlHdc = GetDlgItem(IDC_PICTURE)->GetDC()->GetSafeHdc(); //返回控件句柄 GetDlgItem(IDC_PICTURE)->GetClientRect(m_PicCtlRect); //關聯控件位置
將這兩句代碼添加到OnInitDialog()末尾即可,這裡有三個問題需要強調:
(1)為什麼需要用到句柄和CRect變量?原因很簡單,CvvImage類的要求。這裡我們介紹一個查看函數形參的小技巧,即在函數名的括號中輸入一個逗號,VS就會自動給出函數的形參格式:
可見,DrawToHDC這個函數需要兩個參數,一個是HDC類型的,一個是RECT*類型的。
(2)如何快速查找類的成員函數?最直接的方法就是通過類視圖,單擊對應的類來進行浏覽即可:
當然,通過上方的搜索欄也是可以的。
(3)OnInitDialog函數。這個函數在程序開始構造MFC框架時執行,因此有關控件的初始化操作都應該在這個函數中進行,而非構造函數。
此時准備工作已經完成,可以為GetNextBigImg()函數添加正式的顯示代碼了:
/**********顯示該圖片**********/ IplImage* imageSrc; CvvImage imageSrcCvvImg; char imageFullName [500]; //保存圖像文件的全路徑 sprintf_s(imageFullName,"%s%s",m_ImageDir,m_pEnt->d_name); //拼出文件全路徑 imageSrc = cvLoadImage(imageFullName); imageSrcCvvImg.CopyOf(imageSrc); imageSrcCvvImg.DrawToHDC(m_pPicCtlHdc,m_PicCtlRect); cvReleaseImage(&imageSrc);
此時,運行程序,通過“圖像文件夾按鈕”,選擇一個含有圖片文件的文件夾,程序正常顯示圖片:
5、添加“下一張”功能
接下來我們為界面中的“下一張”按鈕指定其功能。雙擊“下一張”按鈕,添加響應函數:
由於之前我們已經將圖片輪詢、顯示操作封裝在了GetNextBigImg()函數中,在這裡我們只需調用一把這個函數即可實現“下一張”的功能:
void CGenderRecognitionMFCDlg::OnBnClickedButtonNextimage() { GetNextBigImg(); // TODO: 在此添加控件通知處理程序代碼 }
OK,大功告成。
三、總結
經過這篇博文,我們的MFC框架已經具備了基本的圖像顯示功能,在下一篇博文中我們將向其中添加人臉檢測的功能。這裡有幾個問題需要注意。
1、OpenCv2.x關於圖片顯示的問題
大家留心觀察會發現,這裡用到的CvvImage方法是完全基於OpenCv1.x的,用IplImage變量來表示圖片。
2、遞歸層數的問題
這裡GetNextBigImg()函數存在一個遞歸調用的過程,存在遞歸就需要考慮遞歸深度的問題。這裡每遍歷到一個非圖像文件,遞歸的深度就增加一層,如果超過規定的遞歸深度,程序就會崩潰,從這個角度來講通過遞歸的方法來輪詢圖像文件和非圖像文件,是存在嚴重BUG隱患的,只要文件夾下有足夠多的非圖像文件,程序必然會因為無限遞歸而崩潰,相信大家有能力找到其他更安全的方法來解決這個問題。