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