基於opencv和mfc的攝像頭採集框架(GOMFCTemplate2)
2、攝像頭部分通過線程讀取,保證界面的運行流暢;
3、框架經過多次打磨,已經比較穩定,不會出現異常錯誤;代碼簡潔明了,方便復用。一、代碼解析 框架為對話框模式代碼生成,加入CameraDS類和CvvImage類。CameraDS是shiqiyu編寫的,主要完成directshow的引入,提供了以下函數。能夠獲得目前相機總數,讀取相機名稱,打開相機以及獲得當前幀的數據等
//打開攝像頭,nCamID指定打開哪個攝像頭,取值可以為0,1,2,...//bDisplayProperties指示是否自動彈出攝像頭屬性頁//nWidth和nHeight設置的攝像頭的寬和高,如果攝像頭不支持所設定的寬度和高度,則返回falseboolCCameraDS::OpenCamera(int nCamID,bool bDisplayProperties=true,int nWidth=320,int nHeight=240);//關閉攝像頭,析構函數會自動調用這個函數voidCloseCamera();//返回攝像頭的數目//可以不用創建CCameraDS實例,採用int c=CCameraDS::CameraCount();得到結果。staticintCameraCount();//根據攝像頭的編號返回攝像頭的名字//nCamID: 攝像頭編號//sName: 用於存放攝像頭名字的數組//nBufferSize: sName的大小//可以不用創建CCameraDS實例,採用CCameraDS::CameraName();得到結果。staticintCCameraDS::CameraName(int nCamID,char* sName,int nBufferSize);//返回圖像寬度intGetWidth(){return m_nWidth;}//返回圖像高度intGetHeight(){return m_nHeight;}//抓取一幀,返回的IplImage不可手動釋放!//返回圖像數據的為RGB模式的Top-down(第一個位元組為左上角像素),即IplImage::origin=0(IPL_ORIGIN_TL)IplImage*QueryFrame();voidDisplayPinProperties(void);
CvvImage類是Opencv自己提供的,這裡使用它的主要目的是講mat對象畫到mfc的控制項中去
CvvImage cimg;IplImage cpy = dst;cimg.CopyOf(&cpy );// 複製圖片cimg.DrawToHDC( hDC,&rect );// 將圖片繪製到顯示控制項的指定區域內
在GOMfcTemplate2Dlg中是主要代碼,分為以下幾個部分。這塊的東西主要是我自己總結的。
1、攝像頭顯示循環,是單獨的線程//攝像頭顯示循環DWORD WINAPI CaptureThread(LPVOID lpParameter){CGOMfcTemplate2Dlg* pDlg =(CGOMfcTemplate2Dlg*)lpParameter;while(true){IplImage* queryframe = pDlg->cameraDs.QueryFrame();Mat matframe(queryframe);//iplimage到Mat轉化if(pDlg->b_closeCam)//退出循環break;if(pDlg->b_takeApic ){pDlg->b_takeApic =false;pDlg->m_mainframe = matframe;Sleep(500);}pDlg->showImage(matframe,IDC_CAM);}return0;}
這個線程函數,在創建的時候讀取主Dlg的指針為參數,這樣能夠進行線程間通信。它主要完成兩項工作,一個是通過camerads的QueryFrame函數讀取當前的圖像並傳遞給主線程;一個是判斷b_closeCam和b_taleApic兩個控制變數是否為true並進行相關操作。
目前的線程間通信採用的變數共享的方式,由於在攝像頭線程中是寫變數,在主線程中是讀變數,一般不會衝突。但是如果攝像頭很多或者實時性非常高,還是應該採用postmessage的方式通信。
2、initdialog中,對界面控制項進行初始化m_nCamCount =CCameraDS::CameraCount();//攝像頭總數//獲得攝像頭數目char camera_name[1024];char istr[25];for(int i=0; i < m_nCamCount; i++){int retval =CCameraDS::CameraName(i, camera_name,sizeof(camera_name));sprintf_s(istr," # %d", i);strcat_s(camera_name,istr );CString camstr = camera_name;if(retval >0)m_CBNCamList.AddString(camstr);elseAfxMessageBox(_T("不能獲取攝像頭的名稱"));}//初始化顯示控制項CRect rect;GetDlgItem(IDC_CAM)->GetClientRect(&rect);m_mainframe =Mat::zeros(rect.Height(),rect.Width(),CV_8UC3);GetDlgItem(IDC_PIC)->GetClientRect(&rect);m_takepic =Mat::zeros(rect.Height(),rect.Width(),CV_8UC3);return TRUE;// 除非將焦點設置到控制項,否則返回 TRUE
voidCGOMfcTemplate2Dlg::OnBnClickedBtnOpencam(){if(m_nCamCount>=1)//開視頻捕獲線程{HANDLE hThread = NULL;DWORD dwThreadID =0;OnBnClickedBtnClosecam();//首先關閉現有攝像頭bool bret = cameraDs.OpenCamera(m_iCamNum,false,640,480);//嘗試打開攝像頭if(bret){b_closeCam =false;hThread =CreateThread(NULL,0,CaptureThread,this,0,&dwThreadID);}}else{AfxMessageBox(_T("請確認至少有攝像頭連上了"));}}
voidCGOMfcTemplate2Dlg::OnBnClickedBtnClosecam(){//嘗試關閉攝像頭b_closeCam =true;Sleep(100);cameraDs.CloseCamera();}
傳遞控制變數到攝像頭線程,並且調用camerads的closecamera函數關閉攝像頭;
5、採集圖片
voidCGOMfcTemplate2Dlg::OnBnClickedBtnTakepic(){b_takeApic =true;Sleep(100);if(m_mainframe.rows >0){showImage(m_mainframe,IDC_PIC);}}
傳遞控制變數到攝像頭線程,並且顯示圖片到控制項。
6、顯示圖像函數,為了方便地講mat對象顯示到mfc的控制項上,編寫圖像實現函數voidCGOMfcTemplate2Dlg::showImage(Mat& src, UINT ID){if(src.empty())return;CRect rect;Mat dst = src.clone();GetDlgItem(ID)->GetClientRect(&rect );// 獲取控制項尺寸位置if(dst.channels()==1)cvtColor(dst, dst, CV_GRAY2BGR);CDC* pDC =GetDlgItem( ID )->GetDC();HDC hDC = pDC ->GetSafeHdc();// 獲取 HDC(設備句柄) 來進行繪圖操作CvvImage cimg;IplImage cpy = dst;cimg.CopyOf(&cpy );// 複製圖片cimg.DrawToHDC( hDC,&rect );// 將圖片繪製到顯示控制項的指定區域內ReleaseDC( pDC );}
主要就是調用cvvimage的drawtohdc函數,並進行相關的錯誤控制。
7、攝像頭參數配置,通過調用directshow介面,實現攝像頭參數在線調整 void CCameraDS::DisplayFilterProperties(){ CComPtr<ISpecifyPropertyPages> pPages; CAUUID cauuid; HRESULT hr = m_pDeviceFilter->QueryInterface(IID_ISpecifyPropertyPages,(void**)&pPages); if (SUCCEEDED(hr)) { hr = pPages->GetPages(&cauuid); if(hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, L"Property Sheet", 1, (IUnknown **)(&m_pDeviceFilter.p), cauuid.cElems, cauuid.pElems, 0, 0, NULL); } }}
8、攝像頭解析度設置,需要重啟攝像頭
void CGOMfcTemplate2Dlg::OnBnClickedBtnRatio(){ OnBnClickedBtnClosecam(); if (m_nCamCount>=1)//開視頻捕獲線程 { HANDLE hThread = NULL; DWORD dwThreadID = 0; OnBnClickedBtnClosecam();//首先關閉現有攝像頭 bool bret = cameraDs.OpenCamera(m_iCamNum,true,640,480); //嘗試打開攝像頭 if (bret) { b_closeCam = false; hThread = CreateThread(NULL, 0, CaptureThread, this, 0, &dwThreadID); } } else { AfxMessageBox(_T("請確認至少有攝像頭連上了")); }}
代碼位置 MFCTemplate2
參考資料1 、能夠在win7下運行ampcap
2、opencvcn
我在博客園的博客是 http://jsxyhelu.cnblogs.com ,還有其他一些關於圖像處理的知識,有興趣歡迎來踩踩!歡迎交流討論!
推薦閱讀:
※Spy++是如何獲取發往窗口的消息的?
※MFC程序員的前途和出路是什麼?
※做PC的客戶端軟體開發框架選擇,sciter vs QT,用哪個好點?
※現在(2013年12月)學習 MFC 的意義是什麼?