OpenCV學習筆記(6)基於 VC+OpenCV+DirectShow 的多個攝像頭同步工作

来源:互联网 发布:unity3d破解版下载 编辑:程序博客网 时间:2024/06/01 09:36

http://blog.csdn.net/chenyusiyuan/article/details/4643313

因項目需要采集2個攝像頭的數據進行雙目檢測,一開始采用以下代碼來測試:

#include "stdafx.h"#include <cv.h>#include <cxcore.h>#include <highgui.h>int main(int argc, _TCHAR* argv[]){    CvCapture* capture1 = cvCreateCameraCapture( 0 );    CvCapture* capture2 = cvCreateCameraCapture( 1 );    double w = 320, h = 240;    cvSetCaptureProperty ( capture1, CV_CAP_PROP_FRAME_WIDTH,  w );      cvSetCaptureProperty ( capture1, CV_CAP_PROP_FRAME_HEIGHT, h );    cvSetCaptureProperty ( capture2, CV_CAP_PROP_FRAME_WIDTH,  w );      cvSetCaptureProperty ( capture2, CV_CAP_PROP_FRAME_HEIGHT, h );    cvNamedWindow( "Camera_1", CV_WINDOW_AUTOSIZE );    cvNamedWindow( "Camera_2", CV_WINDOW_AUTOSIZE );    IplImage* frame1;    IplImage* frame2;    int n = 2;    while(1)    {        frame1 = cvQueryFrame( capture1 );        if( !frame1 ) break;        cvShowImage( "Camera_1", frame1 );        frame2 = cvQueryFrame( capture2 );        if( !frame2 ) break;        cvShowImage( "Camera_2", frame2 );        int key = cvWaitKey(30);        if( key == 27 ) break;    }    cvReleaseCapture( &capture1 );    cvReleaseCapture( &capture2 );    cvDestroyWindow( "Camera_1" );    cvDestroyWindow( "Camera_2" );    return 0;}

3

這個程序在使用不同類型的攝像頭時,例如我使用一個普通的網絡攝像頭,另外一個是手機上的攝像頭(這款手機具有網絡攝像頭功能),這樣的話程序就能正常運行;但如果攝像頭是相同類型時,就只能讀取其中一個攝像頭的數據了,第二個窗口則是一片灰色。查閱開發文檔資料得知 cvCreateCameraCapture(int index) 函數可以選擇攝像頭,但實際測試發現 cvCreateCameraCapture 只接受 –1 和 0 兩種參數,其他值,如1,2,101,102,201,202...全都無法正確的切換到第二個接入的攝像頭。如果兩個 capture 都使用 cvCreateCameraCapture(-1),是可以切換到第二個攝像頭,但當第二次執行 cvCreateCameraCapture() 函數時,會強行彈出選擇攝像頭的對話框要你手動選擇,而且以後再添加攝像頭的話,還得修改代碼重新build,實際項目中肯定不能這樣處理。在OpenCV中文論壇上找到的解釋是,如果攝像頭的名稱是「USB視頻設備 #*」,則 OpenCV  只能讀取其中一個的數據。

查閱opencv的cvcam官方文檔,找到一些資料: 

/*Begin work with cvcam, you can select single or multiple cameras in 2 ways. The first is using a camera selection dialog with cvcamSelectCamera. See an example below: */ //Prototype /*Pops up a camera(s) selection dialog Return value - number of cameras selected (0,1 or 2); Argument: an array of selected cameras numbers NULL if none selected. Should be released with free() when not needed. if NULL passed, not used. */ CVCAM_API int cvcamSelectCamera(int** out); Function ThatSelectsCamera() {  int* out;  int nselected = cvcamSelectCamera(&out);  if(nselected>0)     printf("the 1-st selected camera is camera number %d", out[0]);  if(nselected == 2)     printf("the 2-nd selected camera is camera number %d", out[1]);  free(out);  return; } /*Note: if you don』t need selected cameras numbers, simply call cvcamSelectCamera(NULL) Note2: Linux version of cvcam currently has no implementation of cvcamSelectCamera. *///The second, non-dialog way is to use CVCAM_PROP_ENABLE property like this:int desiredcamera = 0;//for example cvcamSetProperty(desiredcamera, CVCAM_PROP_ENABLE,CVCAMTRUE); 

根據上述說明,我找到了下面這段對應的代碼,不過應該是用 VC6+OpenCV1.0 寫的,在我的機子上(VS2008+OpenCV2.0)運行不了,不能驗證是否有效,不過還是貼出來供大家討論:

#include <cvcam.h>#include <cv.h>#include <highgui.h>#include "stdio.h"#include <windows.h>void StereoCallback(IplImage *frame1,IplImage *frame2);void onMouse(int Event,int x,int y,int flags,void *param);IplImage *image1,*image2;char *strleft[4]={"left1.bmp","left2.bmp","left3.bmp","left4.bmp"};char *strright[4]={"right1.bmp","right2.bmp","right3.bmp","right4.bmp"};void main(){    HWND CaptureWindow1=0;    HWND CaptureWindow2=0;    //int ncams=cvcamGetCamerasCount(); //獲取攝像頭的個數//用對話框的形式來選取攝像頭    int *CameraNumber;    int nSelected = cvcamSelectCamera(&CameraNumber);/* //灰色圖像    image1=cvCreateImage(cvSize(320,240),IPL_DEPTH_8U,1);    image2=cvCreateImage(cvSize(320,240),IPL_DEPTH_8U,1);*///彩色圖像    image1=cvCreateImage(cvSize(320,240),IPL_DEPTH_8U,3);    image2=cvCreateImage(cvSize(320,240),IPL_DEPTH_8U,3);//初始化兩個攝像頭        cvNamedWindow("cvcam1 Window",1);        CaptureWindow1=(HWND)cvGetWindowHandle("cvcam1 Window");        cvcamSetProperty(CameraNumber[0], CVCAM_PROP_ENABLE, CVCAMTRUE);        cvcamSetProperty(CameraNumber[0], CVCAM_PROP_RENDER, CVCAMTRUE);        cvcamSetProperty(CameraNumber[0], CVCAM_PROP_WINDOW, &CaptureWindow1);        cvSetMouseCallback("cvcam1 Window",onMouse,0);            cvNamedWindow("cvcam2 Window",1);        CaptureWindow2=(HWND)cvGetWindowHandle("cvcam2 Window");        cvcamSetProperty(CameraNumber[1], CVCAM_PROP_ENABLE, CVCAMTRUE);        cvcamSetProperty(CameraNumber[1], CVCAM_PROP_RENDER, CVCAMTRUE);        cvcamSetProperty(CameraNumber[1], CVCAM_PROP_WINDOW, &CaptureWindow2);//讓兩個攝像頭同步            cvcamSetProperty(CameraNumber[0], CVCAM_STEREO_CALLBACK,(void *)&StereoCallback);    //啟動程序    cvcamInit();    cvcamStart();    cvWaitKey(0);    cvcamStop();    cvcamExit();    free(CameraNumber);    cvDestroyWindow("cvcam1 Window");    cvDestroyWindow("cvcam2 Window");}void StereoCallback(IplImage* frame1,IplImage *frame2){/*   //把圖像轉換成灰度圖並保存到image中    cvCvtColor(frame1,image1,CV_RGB2GRAY);    cvCvtColor(frame2,image2,CV_RGB2GRAY);    *///拷貝圖像到全局變量image中 該函數這樣用存在問題     cvCopy(frame1,image1);    cvCopy(frame2,image2);//    image1=cvCloneImage(frame1);//    image2=cvCloneImage(frame2);    //對截取的圖像翻轉    cvFlip(image1,image1,0);    cvFlip(image2,image2,0);}void onMouse(int Event,int x,int y,int flags,void *param){        static int num=0;        if(Event==CV_EVENT_LBUTTONDOWN)        {        if(num==4)num=0;//只是固定定義了保存4張圖片,為了不讓程序非法而設置的復原            cvcamPause();            //圖像保存        cvSaveImage(strleft[num],image1);            cvSaveImage(strright[num],image2);        //    cvSaveImage("left.bmp",image1);        //    cvSaveImage("right.bmp",image2);        }        if(Event==CV_EVENT_RBUTTONDOWN)        {            cvcamResume();            num++;        }        }

在論壇上找了很久,最終找到了解決辦法,即利用於仕琪老師提供的DirectShow視頻采集方案(http://www.opencv.org.cn/index.php/%E4%BD%BF%E7%94%A8DirectShow%E9%87%87%E9%9B%86%E5%9B%BE%E5%83%8F)。該方案介紹的CCameraDS類調用采集函數可直接返回IplImage,使用更方便,且集成了DirectShow,勿需安裝龐大的DirectX/Platform SDK。

4

利用該方案提供的例程,結合上一篇筆記中單窗口顯示多個視頻子圖像的程序,就實現了讀取兩個攝像頭的數據、並進行實時邊緣檢測的功能,主函數代碼如下:

//////////////////////////////////////////////////////////////////////// Multiple Cameras Capture using DirectShow// Author: Yuhua Zou// Thanks to://      Shiqi Yu (shiqi.yu@gmail.com)//        HardyAI@OpenCV China//        flymanbox@OpenCV China (for his contribution to function CameraName, and frame width/height setting)// Last modification: October 8, 2009////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 使用說明://  在 VC6 開發環境下的使用說明://   1. 將CameraDS.h CameraDS.cpp以及目錄DirectShow復制到你的項目中//   2. 菜單 Project->Settings->Settings for:(All configurations)->C/C++->Category(Preprocessor)->Additional include directories//      設置為 DirectShow/Include//   3. 菜單 Project->Settings->Settings for:(All configurations)->Link->Category(Input)->Additional library directories//      設置為 DirectShow/Lib//  在 VS2005/2008 開發環境下的使用說明://   1. 將CameraDS.h CameraDS.cpp復制到你的項目中//   2. 將DirectShow復制到你的opencv根目錄下,菜單 工具->選項->項目和解決方案->vc++目錄,把..(你的opencv安裝目錄)/DirectShow/Include添加到//     「引用文件」中$(VCInstallDir)PlatformSDK/include和$(FrameworkSDKDir)include下面任意位置//   3. 菜單 工具->選項->項目和解決方案->vc++目錄,把..(你的opencv安裝目錄)/DirectShow/Lib添加到「庫文件」下面。也可參考使用說明3。//////////////////////////////////////////////////////////////////////#include "stdafx.h"#include "camerads.h"#include <cv.h>#include <cxcore.h>#include <highgui.h>#include <stdio.h>#include <stdarg.h>#include <time.h>// 單窗口顯示多幅圖像的函數void cvShowMultiImages(char* title, int nArgs, ...) {   // 略,詳見學習筆記(5)}int main( int argc, char** argv ) {    int cam_count;    //僅僅獲取攝像頭數目    cam_count = CCameraDS::CameraCount();    printf("There are %d cameras./n", cam_count);    //獲取所有攝像頭的名稱    for(int i=0; i < cam_count; i++)    {        char camera_name[1024];          int retval = CCameraDS::CameraName(i, camera_name, sizeof(camera_name) );        if(retval >0)            printf("Camera #%d's Name is '%s'./n", i, camera_name);        else            printf("Can not get Camera #%d's name./n", i);    }    if(cam_count==0)        return -1;    // 創建2個攝像頭類    CCameraDS camera1;    CCameraDS camera2;            //打開第一個攝像頭    //if(! camera.OpenCamera(0, true)) //彈出屬性選擇窗口    if(! camera1.OpenCamera(0, false, 320,240)) //不彈出屬性選擇窗口,用代碼制定圖像寬和高    {        fprintf(stderr, "Can not open camera./n");        return -1;    }    //打開第二個攝像頭    camera2.OpenCamera(1, false, 320,240);            cvNamedWindow("Multiple Cameras");    // 初始化在子圖像中顯示字符的字體格式    CvFont tFont;    cvInitFont(&tFont,  CV_FONT_HERSHEY_COMPLEX, 0.5f,0.7f,0,1,8);       char cam1str[] = "Camera #1";    char cam2str[] = "Camera #2";    // 為讀取系統時間信息分配內存    char timestr[25];    memset(timestr, 0, 25 * sizeof(char));           while(1)    {        //獲取一幀        IplImage *pFrame1 = camera1.QueryFrame();        IplImage *pFrame2 = camera2.QueryFrame();                // 獲取當前幀的灰度圖        IplImage* frame_gray_1 = cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,1);        IplImage* frame_gray_2 = cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,1);        cvCvtColor(pFrame1,frame_gray_1,CV_RGB2GRAY);        cvCvtColor(pFrame2,frame_gray_2,CV_RGB2GRAY);              // 對灰度圖像進行Canny邊緣檢測        // 然後將圖像通道數改為三通道        IplImage* frame_canny_1 = cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,1);        IplImage* frame_canny_2 = cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,1);        IplImage* frame1 = cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,pFrame1->nChannels);        IplImage* frame2 = cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,pFrame2->nChannels);        cvCanny(frame_gray_1,frame_canny_1,20,75,3);        cvCanny(frame_gray_2,frame_canny_2,20,75,3);        cvCvtColor(frame_canny_1,frame1,CV_GRAY2BGR);        cvCvtColor(frame_canny_2,frame2,CV_GRAY2BGR);                // 獲取系統時間信息        time_t rawtime;         struct tm* timeinfo;         rawtime = time( NULL );         timeinfo = localtime( &rawtime );         char* p = asctime( timeinfo );              // 字符串 p 的第25個字符是換行符 '/n'        // 但在子圖像中將亂碼顯示        // 故僅讀取 p 的前 24 個字符        for (int i = 0; i < 24; i++)        {            timestr[i] = *p;            p++;        }        p = NULL;                // 在每個子圖像上顯示攝像頭序號以及系統時間信息        cvPutText( pFrame1, cam1str, cvPoint(95,15), &tFont,  CV_RGB(255,0,0) );        cvPutText( pFrame2, cam2str, cvPoint(95,15), &tFont,  CV_RGB(255,0,0) );        cvPutText( frame1,  cam1str, cvPoint(95,15), &tFont,  CV_RGB(255,0,0) );        cvPutText( frame2,  cam2str, cvPoint(95,15), &tFont,  CV_RGB(255,0,0) );        cvPutText( pFrame1, timestr, cvPoint(5,225), &tFont,  CV_RGB(255,0,0) );        cvPutText( pFrame2, timestr, cvPoint(5,225), &tFont,  CV_RGB(255,0,0) );        cvPutText( frame1,  timestr, cvPoint(5,225), &tFont,  CV_RGB(255,0,0) );        cvPutText( frame2,  timestr, cvPoint(5,225), &tFont,  CV_RGB(255,0,0) );                // 顯示實時的攝像頭視頻        cvShowMultiImages( "Multiple Cameras", 4, pFrame1, pFrame2, frame1, frame2 );              //cvWaitKey(33);        int key = cvWaitKey(33);        if( key == 27 ) break;        cvReleaseImage(&frame1);        cvReleaseImage(&frame2);        cvReleaseImage(&frame_gray_1);        cvReleaseImage(&frame_gray_2);        cvReleaseImage(&frame_canny_1);          cvReleaseImage(&frame_canny_2);     }        camera1.CloseCamera(); //可不調用此函數,CCameraDS析構時會自動關閉攝像頭    camera2.CloseCamera();    cvDestroyWindow("Multiple Cameras");    return 0;}

在 Project -> Properties -> Configuration Properties -> Linker -> Input 的 Additional Dependencies 中,需要添加以下庫文件: 
odbc32.lib 
odbccp32.lib 
cv200.lib 
cxcore200.lib 
highgui200.lib

在編譯以上程序時,可能會出現以下幾種錯誤(參見 http://topic.csdn.net/u/20081022/12/30fb745f-332b-42f7-bbee-02a760c48132.html):

1> ../../../winnt.h(222) : error C4430: missing type specifier - int assumed. Note: C++ does not support  
2> ../../../winnt.h(222) : error C2146: syntax error : missing ';' before identifier 'PVOID64' 
3> ../../../winnt.h(5940) : error C2146: syntax error : missing ';' before identifier 'Buffer'

對於第1類錯誤,可以用wd4430來解決,具體的在Project -> Properties -> Configuration Properties -> Linker -> Command Line的 Additional Options 中添加 『/wd4430』  即可。

對於第2類錯誤,一般可通過調整 DirectShow/Include 在 Tools -> Options -> Projects and Solutions -> VC++ Directories -> Show Directories for –> Include Files 中的位置(把它下移到最下面),然後把 Project -> Properties -> Configuration Properties –> C/C++ 中的 Additional Include Directories 裡面的內容(../../../../include)刪掉,重新編譯,PVOID64的錯誤就會消失,原因如下:

POINTER_64 是一個宏,在64位編譯下起作用,它包含在SDK目錄下的BASETSD.H中(Microsoft Visual Studio 8/VC/PlatformSDK/Include/basetsd.h(23):#define POINTER_64 __ptr64),但DXSDK自己也帶了一個basetsd.h,裡面沒有定義POINTER_64,從而導致出錯,只需要改變 include files 的優先級即可。

當然,也可以改寫 winnt.h 中的代碼,在下面這兩行: 
typedef  void  *PVOID;  
typedef  void  *POINTER_64  PVOID64; 
之前增加一行: 
#define  POINTER_64  __ptr64 
不過最好不要輕易改寫 winnt.h 。


原创粉丝点击