MFC框架下的OpenCV 视频捕获

在MFC框架下,调用开源代码OpenCV中的函数,完成视屏捕获功能。

文档类Doc中完成视频的捕获,在View类中完成视频(分帧)的显示。

如下是Doc类中的主要代码:

void Clzy1_0614Doc::OnOpenCamera()
{
	// TODO: Add your command handler code here
	CvCapture * pCapture = NULL;

	//打开摄像头
	if (! (pCapture = cvCaptureFromCAM(1)))	//USB摄像头;0和-1都出错了,不知何解
	{
		AfxMessageBox(TEXT("打开摄像头失败!"));
		return;
	}
	//逐帧读取视频
	//IplImage *pFrame = NULL;
	//IplImage *pProcess = NULL;
	int nFrmCount = 0;
	while (pFrame = cvQueryFrame(pCapture))
	{
		++ nFrmCount;
		//如果是第一帧,需要申请内存,并初始化
		if (1 == nFrmCount)
		{
			pProcess = cvCreateImage(cvSize(pFrame->width, pFrame->height), pFrame->depth, 1);	//创建灰度图
			//width和height的顺序不要搞反了,否则cvCvtColor转换函数出错
			//pProcess = cvCreateImage(cvGetSize(pFrame), IPL_DEPTH_8U, 1);

			//cvSaveImage("lzy.jpg", pFrame);

			//height和width的顺序不要搞反了,否则cvConvert转换函数出错;与上个完全相反
			pFrmMat = cvCreateMat(pFrame->height, pFrame->width, CV_64FC3);		//创建RGB图CvMat
			pPrcMat = cvCreateMat(pFrame->height, pFrame->width, CV_64FC1);		//创建灰度图CvMat,注意单通道
			
			FMat = cv::Mat(pFrame);
			PMat = cv::Mat(pProcess);
		}

		//cvCvtColor(pFrame, pProcess, CV_RGB2GRAY);	//不改变pFrame及其指向的Frame值
		//cvConvert(pFrame, pFrmMat);
		//cvConvert(pProcess, pPrcMat);

		cvThreshold(pProcess, pProcess, 128, 255.0, CV_THRESH_TOZERO_INV); //CV_THRESH_BINARY

		/***************调用view类中的位图显示函数*****************/
		//获得窗体指针:   
		CMainFrame *pMainFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd;   
		//获得与该窗体符合的视图:   
		Clzy1_0614View *m_pView = (Clzy1_0614View *) pMainFrame->GetActiveView();   
		//调用视图函数:   
		m_pView->OnShowImage2();

		Sleep(30);
		//return;
	}
	//cvReleaseImage(&pProcess);

	//cvReleaseMat(&pFrmMat);
	//cvReleaseMat(&pPrcMat);

	//cvReleaseCapture(&pCapture);
}

如下是View类中的主要代码:(函数功能是用于显示每一帧图像,被Doc类中的代码直接调用)

void Clzy1_0614View::OnShowImage2()
{
	//http://blog.csdn.net/zfdxx369/article/details/8138706
	//获取Doc窗口句柄,参考上面的函数OnDraw
	Clzy1_0614Doc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	cv::Mat pFMat = pDoc->FMat;
	//IplImage* pFrame = cvLoadImage("lzy.bmp");	//用加载本地位图测试
	//cv::Mat pFMat = cv::Mat(pFrame);

	int width = pDoc->pFrame->width;
	int height = pDoc->pFrame->height;
	//int width = pFrame->width;
	//int height = pFrame->height;

	if(pFMat.empty())    
        return;    
  
    CClientDC dc(this);  
    HDC hDC =dc.GetSafeHdc();  
      
    //内存中的图像数据拷贝到屏幕上  
    BYTE *bitBuffer        = NULL;  
    BITMAPINFO *bitMapinfo = NULL;  
      
    int ichannels =pFMat.channels();  
    if( ichannels == 1)  
    {  
        bitBuffer  = new BYTE[40+4*256];   
    }  
    else if( ichannels == 3)  
    {  
        bitBuffer  = new BYTE[sizeof(BITMAPINFO)];   
    }  
    else  
    {  
        return;  
    }  
  
    if(bitBuffer == NULL)  
    {     
        return;  
    }  
  
    bitMapinfo = (BITMAPINFO *)bitBuffer;  
    bitMapinfo->bmiHeader.biSize         = sizeof(BITMAPINFOHEADER);  
    bitMapinfo->bmiHeader.biHeight           = -pFMat.rows;  //如果高度为正的,位图的起始位置在左下角。如果高度为负,起始位置在左上角。  
    bitMapinfo->bmiHeader.biWidth            = pFMat.cols;  
    bitMapinfo->bmiHeader.biPlanes           = 1;      // 目标设备的级别,必须为1     
    bitMapinfo->bmiHeader.biBitCount     = ichannels *8;     // 每个像素所需的位数,必须是1(双色), 4(16色),8(256色)或24(真彩色)之一  
    bitMapinfo->bmiHeader.biCompression      = BI_RGB; //位图压缩类型,必须是 0(不压缩), 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一  
    bitMapinfo->bmiHeader.biSizeImage        = 0;      // 位图的大小,以字节为单位  
    bitMapinfo->bmiHeader.biXPelsPerMeter    = 0;      // 位图水平分辨率,每米像素数  
    bitMapinfo->bmiHeader.biYPelsPerMeter    = 0;      // 位图垂直分辨率,每米像素数  
    bitMapinfo->bmiHeader.biClrUsed          = 0;      // 位图实际使用的颜色表中的颜色数  
    bitMapinfo->bmiHeader.biClrImportant = 0;      // 位图显示过程中重要的颜色数  
  
    if(ichannels == 1)  
    {  
        for(int i=0; i<256; i++)  
        {   //颜色的取值范围 (0-255)  
            bitMapinfo->bmiColors[i].rgbBlue  =bitMapinfo->bmiColors[i].rgbGreen =bitMapinfo->bmiColors[i].rgbRed   =(BYTE) i;  
        }  
  
        bitMapinfo->bmiHeader.biClrUsed = 256;    // 位图实际使用的颜色表中的颜色数  
    }  
    SetStretchBltMode(hDC, COLORONCOLOR);  
  
    StretchDIBits(hDC, 0, 0, width, height, 0, 0, pFMat.cols, pFMat.rows, pFMat.data, bitMapinfo, DIB_RGB_COLORS, SRCCOPY);  
      
    delete []bitBuffer;
}

以上代码完成后,可以实时显示摄像头的视场。

但是由于在Doc类中视频捕获采用了while循环,故一直在此循环中,不能跳出做其他事情。所以考虑用多线程编程,为此视频捕获单独开一个线程。



//**********2014/08/04*****lzy*****修改**************//

采用多线程编程之后,可以解决以上问题。

如下代码中,加入创建线程函数AfxBeginThread。

但是又会产生新的问题:当窗口关闭后,这个线程没有退出(表现在调试没有结束,任务管理器线程还存在)。

尝试解决方法:1. 在Doc类的析构函数里改变某一个全局变量的值,在循环获取图像帧函数里判断,决定是否执行后面的动作。尝试不能解决。

2.在Doc类的析构函数里给线程发送WM_QUIT消息,PostThreadMessage(ThreadID,WM_QUIT,0,0),尝试有时可以退出,有时不能退出。消息有时执行有时没有被执行??工作者线程不是没有消息队列吗??还是不了解什么原因。为啥我这个新手编的程序各种奇葩错误?!PS:看来编程还得一步一步来,一下子迈大了,会发生各种奇葩错误。

3. 在View类中截获WM_QUIT消息(类向导添加WM_QUIT的消息处理函数)。在里面改变标志位。

期间又产生了溢出,原因是:在Doc类中,循环获取图像帧并显示在View类的窗口中时,调用了全局函数并直接(未经过判断)获取窗口句柄,而此时窗口可能已经销毁(标志位在while判断时没有改变,但在执行while里面的时候,可能关闭窗口并且改变标志位,然后自然窗口已销毁,强制获取句柄肯定出错)。然后干脆把在Doc类中干的事全部移到View类算了,反正数据不多,就一帧图像。


void Clzy1_0614View::OnClose()
{
	// TODO: Add your message handler code here and/or call default
	Flag_closeWindow = true;
	Sleep(500);
	CView::OnClose();
}


Clzy1_0614Doc::~Clzy1_0614Doc()
{
	//Flag_closeWindow = true;	//窗口关闭,变量值改变,以便线程中判断是否退出,但实验不可行
	//PostThreadMessage(ThreadID, WM_QUIT, 0, 0); //关闭窗口后,在析构函数中发送线程退出消息,不太可行
	//Sleep(200);
}


void Clzy1_0614Doc::OnOpenCamera()
{
	//static UINT (*Clzy1_0614Doc::pf)(LPVOID pPARAM); //pf为函数指针
	
	//创建多线程
	CWinThread* pThread;
	pThread = AfxBeginThread( pfnThreadProc, NULL); 
	ThreadID = pThread->m_nThreadID;  //全局变量保存线程ID,在关闭线程时用到
}

UINT __cdecl pfnThreadProc( LPVOID pParam )
{
	//逐帧读取视频
	CvCapture * pCapture = NULL;

	//打开摄像头
	if (! (pCapture = cvCaptureFromCAM(1)))	//USB摄像头;0和-1都出错了,不知何解
	{
		AfxMessageBox(TEXT("打开摄像头失败!"));
		return 0;
	}

	IplImage *pFrame = NULL;
	int nFrmCount = 0;

	cv::Mat FMat;

	while (false == Flag_closeWindow)	//循环进入之前,查看是否有窗口关闭消息
	{
		pFrame = cvQueryFrame(pCapture);
		if (!pFrame)
			break;

		++ nFrmCount;
		//如果是第一帧,需要申请内存,并初始化
		if (1 == nFrmCount)
		{
			CvMat* pFrmMat = cvCreateMat(pFrame->height, pFrame->width, CV_64FC3);		//创建RGB图CvMat
			FMat = cv::Mat(pFrame);
		}


		//通过设定标志位,在Doc析构函数里面改变标志位,这样并没有成功实现;
		//而用发送消息的方法可行:在Doc析构函数里面,发送线程退出消息(后来证实:有时可行,有时不可行)
		//http://zhidao.baidu.com/link?url=EdCa-FLbMjsaNfr4mtNikcif78l17SQB6QLPfsXANZ43p0QMVez6mNOafKGZbEUiKdFYjhl9wy8SfpqgdYDSKq


		//以下获取窗口句柄时,窗口可能已经销毁,再强制获取句柄可能产生溢出错误;本例中,已经产生这样的错误。
		//避免产生过多错误,干脆将获取摄像头图像的以上函数都放入CView类中。

		/***************调用view类中的位图显示函数*****************/
		//获得窗体指针:   
		//CMainFrame *pMainFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd;   
		//获得与该窗体符合的视图:   
		//Clzy1_0614View *m_pView = (Clzy1_0614View *) pMainFrame->GetActiveView();   
		//调用视图函数:   
		//m_pView->OnShowImage2(FMat);
	}
	return 0;
}



发布了28 篇原创文章 · 获赞 7 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章