在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;
}