Opencv学习笔记 Opencv+directshow+mfc
因项目需要采集2个摄像头的数据进行双目检测,一开始采用以下代码来测试:
#include \"stdafx.h\"
#include 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; } 这个程序在使用不同类型的摄像头时,例如我使用一个普通的网络摄像头,另外一个是手机上的摄像头(这款手机具有网络摄像头功能),这样的话程序就能正常运行;但如果摄像头是相同类型时,就只能读取其中一个摄像头的数据了,第二个窗口则是一片灰色。查阅开发文档资料得知 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 #include 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\ // cvSaveImage(\"right.bmp\ } 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。 利用该方案提供的例程,结合上一篇笔记中单窗口显示多个视频子图像的程序,就实现了读取两个摄像头的数据、并进行实时边缘检测的功能,主函数代码如下: ////////////////////////////////////////////////////////////////////// // 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 // 单窗口显示多幅图像的函数 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 。 因篇幅问题不能全部显示,请点此查看更多更全内容