基於opencv和mfc的攝像頭采集代碼(GOMFCTemplate2)持續更新


 
      編寫帶界面的圖像處理程序,選擇opencv+mfc是一種很好的選擇;在讀取攝像頭數據方面,網上的方法很多,其中shiqiyu的camerads的方法是較好的。
      基於現有資料,通過在實際項目中的積累,我總結出來一套結合opencv和mfc的攝像頭采集框架。具有以下特點:
      1、基於directshow,兼容性好,速度快。到目前為止,無論是工業相機還是普通相機,沒發現不兼容的;
      2、攝像頭部分通過線程讀取,保證界面的運行流暢;
      3、框架經過多次打磨,已經比較穩定,不會出現異常錯誤;代碼簡潔明了,方便復用。
 
一、代碼解析
       框架為對話框模式代碼生成,加入CameraDS類和CvvImage類。
      
CameraDS是shiqiyu編寫的,主要完成directshow的引入,提供了以下函數。能夠獲得目前相機總數,讀取相機名稱,打開相機以及獲得當前幀的數據等
 
//打開攝像頭,nCamID指定打開哪個攝像頭,取值可以為0,1,2,...
//bDisplayProperties指示是否自動彈出攝像頭屬性頁
//nWidth和nHeight設置的攝像頭的寬和高,如果攝像頭不支持所設定的寬度和高度,則返回false
boolCCameraDS::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);
else
AfxMessageBox(_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
包括填寫combolist控件,為兩個用於顯示的static控件生成對應大小的mat變量等。
3、打開攝像頭,主要就是根據選擇的攝像頭名稱,創建攝像頭線程
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("請確認至少有攝像頭連上了"));
}
}
稍作修改,可以用於多攝像頭,這個是完全沒有問題並且做過實際項目的。
4、關閉攝像頭
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函數,並進行相關的錯誤控制。
二、存在問題
      由於directshow本身是com架構,學習曲線陡峭。目前這個框架還存在至少兩個問題:
      1、攝像頭熟悉配置問題,以及配置的保持問題。目前框架中的攝像頭配置沒有實現, 就是下圖的這種能夠調整攝像頭參數的對話框如何出來。並且上一次的配置數據要能夠保存下來。
      
      2、視頻數據采集問題。
      目前我采用的是xvid的攝像頭數據采集方式,能夠解決問題。但是我認為directshow應該本身就能夠才是攝像頭數據,並且保存為.avi。這個方面還需要繼續研究。
      我會在下一步工作中繼續總結這方面資料,同時非常希望能夠得到高人的指點,感謝!
 
代碼位置 https://github.com/jsxyhelu/GOMfcTemplate2

 [p.s]2016年10月7日 經過對directshow的簡單學習,主要參考了ampcap(vs2012+win7可運行版本 https://git.coding.net/jsxyhelu/AMCa_win7vs2012.git ),解決了攝像頭的屬性操作問題,同時也想辦法解決了分辨率設置問題。在usb攝像頭上和工業攝像頭上測試都沒有問題。

 [p.s]我自己在新裝的機器上測試框架,不會出現需要direct頭文件的情況,但是可能由於操作系統版本不一樣,有一些操作系統還是需要頭文件的。鏈接:https://pan.baidu.com/s/1qXNgzOo   direct 為完整版本下載,如果連接失敗了請及時聯系我。感謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM