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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章