您的位置:首页 > 编程语言 > C语言/C++

Visual C++编程实现摄像头视频捕捉

2014-04-18 14:39 351 查看
  摘要:本文主要讲述用Directshow进行视频捕捉(捕捉静态图像)的编程思路,并提供针对摄像头编程的一个视频捕捉类CcaptureVideo和一个示例。

  前言

  DirectShow是微软公司提供的一套在Windows平台上进行流媒体处理的开发包,与DirectX开发包一起发布。DirectShow为多媒体流的捕捉和回放提供了强有力的支持。用DirectShow开发应用程序,我们可以很方便地从支持WDM驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。

  DirectShow是基于COM的,为了编写DirectShow应用程序,需要了解COM客户程序编写的基础知识。DirectShow提供了大量的接口,但在编程中发现还是不够方便,如果能构建一个视频捕捉类把常用的一些动作封装起来,那么就更方便了。

  编程思路

  为了更加容易建立视频捕捉应用程序,DirectShow提供了一个叫做Capture Graph Builder的对象,Capture Graph Builder提供IcaptureGraphBuilder2接口,该接口可以建立和控制Capture Graph。

  建立视频捕捉程序,必须首先获取并初始化IcaptureGraphBuilder2接口,然后选择一个适当的视频捕捉设备。选择好设备后,为该设备创建Capture filter,然后调用AddFilter把Capture filter添加到Filter Graph。

  如果仅仅希望用摄像头来进行实时监控的话,只需要在上面的基础上调用ICaptureGraphBuilder2::RenderStream就可以了:

  ICaptureGraphBuilder2 *pBuild; // Capture Graph Builder

  //省略初始化部分代码

  IBaseFilter *pCap; // Video capture filter.

  //省略初始化和添加到Filter Graph部分代码

  pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, NULL);

  DirectShow提供了一个捕捉静态图像的方法:使用Sample Grabber filter。依次按照以下三个步骤就可以了:

  第一步, 定义一个类实现Sample Grabber的回调接口IsampleGrabberCB:

  class CSampleGrabberCB : public ISampleGrabberCB

  {

  //在后面提供的类中具体完成

  }

  CSampleGrabberCB mCB;

  第二步、调用RenderStream依次把Still pin、Sample Grabber和系统默认Renderer Filter连接起来。

  第三步、配置Sample Grabber以捕获数据。

  视频捕捉类CCaptureVideo的具体实现

  // CCaptureVideo视频捕捉类头文件

  /////////////////////////////////////////////////////////////////////

  #if !defined(AFX_CAPTUREVIDEO_H__F5345AA4_A39F_4B07_B843_3D87C4287AA0__INCLUDED_)

  #define AFX_CAPTUREVIDEO_H__F5345AA4_A39F_4B07_B843_3D87C4287AA0__INCLUDED_

  /////////////////////////////////////////////////////////////////////

  // CaptureVideo.h : header file

  /////////////////////////////////////////////////////////////////////

  #if _MSC_VER > 1000

  #pragma once

  #endif // _MSC_VER > 1000

  #include <atlbase.h>

  #include <windows.h>

  #include <dshow.h>

  #ifndef SAFE_RELEASE

  #define SAFE_RELEASE( x ) \

  if ( NULL != x ) \

  { \

  x->Release( ); \

  x = NULL; \

  }

  #endif

  class CSampleGrabberCB;

  class CCaptureVideo : public CWnd

  {

  friend class CSampleGrabberCB;

  public:

  void GrabOneFrame(BOOL bGrab);

  HRESULT Init(int iDeviceID,HWND hWnd);

  int EnumDevices(HWND hList);

  CCaptureVideo();

  virtual ~CCaptureVideo();

  private:

  HWND m_hWnd;

  IGraphBuilder *m_pGB;

  ICaptureGraphBuilder2* m_pCapture;

  IBaseFilter* m_pBF;

  IMediaControl* m_pMC;

  IVideoWindow* m_pVW;

  CComPtr<ISampleGrabber> m_pGrabber;

  protected:

  void FreeMediaType(AM_MEDIA_TYPE& mt);

  bool BindFilter(int deviceId, IBaseFilter **pFilter);

  void ResizeVideoWindow();

  HRESULT SetupVideoWindow();

  HRESULT InitCaptureGraphBuilder();

  };

  #endif // !defined(AFX_CAPTUREVIDEO_H__F5345AA4_A39F_4B07_B843_3D87C4287AA0__INCLUDED_)

  //-------------------------------------------------------------------

  // CCaptureVideo视频捕捉类实现文件CaptureVideo.cpp

  //-------------------------------------------------------------------

  // CaptureVideo.cpp: implementation of the CCaptureVideo class.

  //

  /////////////////////////////////////////////////////////////////////

  #include "stdafx.h"

  #include "CaptureVideo.h"

  #ifdef _DEBUG

  #undef THIS_FILE

  static char THIS_FILE[]=__FILE__;

  #define new DEBUG_NEW

  #endif

  BOOL bOneShot=FALSE;//全局变量

  class CSampleGrabberCB : public ISampleGrabberCB

  {

  public:

  long lWidth;

  long lHeight;

  TCHAR m_szFileName[MAX_PATH];// 位图文件名称

  CSampleGrabberCB( ){

  strcpy(m_szFileName, "c:\\donaldo.bmp");

  }

  STDMETHODIMP_(ULONG) AddRef() { return 2; }

  STDMETHODIMP_(ULONG) Release() { return 1; }

  STDMETHODIMP QueryInterface(REFIID riid, void ** ppv){

  if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown ){

  *ppv = (void *) static_cast<ISampleGrabberCB*> ( this );

  return NOERROR;

  }

  return E_NOINTERFACE;

  }

  STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample ){

  return 0;

  }

  STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize ){

  if( !bOneShot )return 0;

  if (!pBuffer)return E_POINTER;

  SaveBitmap(pBuffer, lBufferSize);

  bOneShot = FALSE;

  return 0;

  }

  //创建位图文件

  BOOL SaveBitmap(BYTE * pBuffer, long lBufferSize )

  {

  HANDLE hf = CreateFile(

  m_szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,

  CREATE_ALWAYS, NULL, NULL );

  if( hf == INVALID_HANDLE_VALUE )return 0;

  // 写文件头

  BITMAPFILEHEADER bfh;

  memset( &bfh, 0, sizeof( bfh ) );

  bfh.bfType = ’MB’;

  bfh.bfSize = sizeof( bfh ) + lBufferSize + sizeof( BITMAPINFOHEADER );

  bfh.bfOffBits = sizeof( BITMAPINFOHEADER ) + sizeof( BITMAPFILEHEADER );

  DWORD dwWritten = 0;

  WriteFile( hf, &bfh, sizeof( bfh ), &dwWritten, NULL );

  // 写位图格式

  BITMAPINFOHEADER bih;

  memset( &bih, 0, sizeof( bih ) );

  bih.biSize = sizeof( bih );

  bih.biWidth = lWidth;

  bih.biHeight = lHeight;

  bih.biPlanes = 1;

  bih.biBitCount = 24;

  WriteFile( hf, &bih, sizeof( bih ), &dwWritten, NULL );

  // 写位图数据

  WriteFile( hf, pBuffer, lBufferSize, &dwWritten, NULL );

  CloseHandle( hf );

  return 0;

  }

  };

  CSampleGrabberCB mCB;

  //////////////////////////////////////////////////////////////////////

  // Construction/Destruction

  //////////////////////////////////////////////////////////////////////

  CCaptureVideo::CCaptureVideo()

  {

  //COM Library Intialization

  if(FAILED(CoInitialize(NULL))) /*, COINIT_APARTMENTTHREADED)))*/

  {

  AfxMessageBox("CoInitialize Failed!\r\n");

  return;

  }

  m_hWnd = NULL;

  m_pVW = NULL;

  m_pMC = NULL;

  m_pGB = NULL;

  m_pCapture = NULL;

  }

  CCaptureVideo::~CCaptureVideo()

  {

  // Stop media playback

  if(m_pMC)m_pMC->Stop();

  if(m_pVW){

  m_pVW->put_Visible(OAFALSE);

  m_pVW->put_Owner(NULL);

  }

  SAFE_RELEASE(m_pCapture);

  SAFE_RELEASE(m_pMC);

  SAFE_RELEASE(m_pGB);

  SAFE_RELEASE(m_pBF);

  CoUninitialize( );

  }

  int CCaptureVideo::EnumDevices(HWND hList)

  {

  if (!hList)

  return -1;

  int id = 0;

  //枚举视频扑捉设备

  ICreateDevEnum *pCreateDevEnum;

  HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,IID_ICreateDevEnum, (void**)&pCreateDevEnum);

  if (hr != NOERROR)return -1;

  CComPtr<IEnumMoniker> pEm;

  hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0);

  if (hr != NOERROR)return -1;

  pEm->Reset();

  ULONG cFetched;

  IMoniker *pM;

  while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)

  {

  IPropertyBag *pBag;

  hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);

  if(SUCCEEDED(hr))

  {

  VARIANT var;

  var.vt = VT_BSTR;

  hr = pBag->Read(L"FriendlyName", &var, NULL);

  if (hr == NOERROR)

  {

  TCHAR str[2048];

  id++;

  WideCharToMultiByte(CP_ACP,0,var.bstrVal, -1, str, 2048, NULL, NULL);

  ::SendMessage(hList, CB_ADDSTRING, 0,(LPARAM)str);

  SysFreeString(var.bstrVal);

  }

  pBag->Release();

  }

  pM->Release();

  }

  return id;

  }

  HRESULT CCaptureVideo::Init(int iDeviceID, HWND hWnd)

  {

  HRESULT hr;

  hr = InitCaptureGraphBuilder();

  if (FAILED(hr)){

  Af
c4b3
xMessageBox("Failed to get video interfaces!");

  return hr;

  }

  // Bind Device Filter. We know the device because the id was passed in

  if(!BindFilter(iDeviceID, &m_pBF))return S_FALSE;

  hr = m_pGB->AddFilter(m_pBF, L"Capture Filter");

  // hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,

  // m_pBF, NULL, NULL);

  // create a sample grabber

  hr = m_pGrabber.CoCreateInstance( CLSID_SampleGrabber );

  if( !m_pGrabber ){

  AfxMessageBox("Fail to create SampleGrabber, maybe qedit.dll is not registered?");

  return hr;

  }

  CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabBase( m_pGrabber );

  //设置视频格式

  AM_MEDIA_TYPE mt;

  ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));

  mt.majortype = MEDIATYPE_Video;

  mt.subtype = MEDIASUBTYPE_RGB24;

  hr = m_pGrabber->SetMediaType(&mt);

  if( FAILED( hr ) ){

  AfxMessageBox("Fail to set media type!");

  return hr;

  }

  hr = m_pGB->AddFilter( pGrabBase, L"Grabber" );

  if( FAILED( hr ) ){

  AfxMessageBox("Fail to put sample grabber in graph");

  return hr;

  }

  // try to render preview/capture pin

  hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,m_pBF,pGrabBase,NULL);

  if( FAILED( hr ) )

  hr = m_pCapture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,m_pBF,pGrabBase,NULL);

  if( FAILED( hr ) ){

  AfxMessageBox("Can’t build the graph");

  return hr;

  }

  hr = m_pGrabber->GetConnectedMediaType( &mt );

  if ( FAILED( hr) ){

  AfxMessageBox("Failt to read the connected media type");

  return hr;

  }

  VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*) mt.pbFormat;

  mCB.lWidth = vih->bmiHeader.biWidth;

  mCB.lHeight = vih->bmiHeader.biHeight;

  FreeMediaType(mt);

  hr = m_pGrabber->SetBufferSamples( FALSE );

  hr = m_pGrabber->SetOneShot( FALSE );

  hr = m_pGrabber->SetCallback( &mCB, 1 );

  //设置视频捕捉窗口

  m_hWnd = hWnd ;

  SetupVideoWindow();

  hr = m_pMC->Run();//开始视频捕捉

  if(FAILED(hr)){AfxMessageBox("Couldn’t run the graph!");return hr;}

  return S_OK;

  }

  bool CCaptureVideo::BindFilter(int deviceId, IBaseFilter **pFilter)

  {

  if (deviceId < 0)

  return false;

  // enumerate all video capture devices

  CComPtr<ICreateDevEnum> pCreateDevEnum;

  HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,

  IID_ICreateDevEnum, (void**)&pCreateDevEnum);

  if (hr != NOERROR)

  {

  return false;

  }

  CComPtr<IEnumMoniker> pEm;

  hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0);

  if (hr != NOERROR)

  {

  return false;

  }

  pEm->Reset();

  ULONG cFetched;

  IMoniker *pM;

  int index = 0;

  while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK, index <= deviceId)

  {

  IPropertyBag *pBag;

  hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);

  if(SUCCEEDED(hr))

  {

  VARIANT var;

  var.vt = VT_BSTR;

  hr = pBag->Read(L"FriendlyName", &var, NULL);

  if (hr == NOERROR)

  {

  if (index == deviceId)

  {

  pM->BindToObject(0, 0, IID_IBaseFilter, (void**)pFilter);

  }

  SysFreeString(var.bstrVal);

  }

  pBag->Release();

  }

  pM->Release();

  index++;

  }

  return true;

  }

  HRESULT CCaptureVideo::InitCaptureGraphBuilder()

  {

  HRESULT hr;

  // 创建IGraphBuilder接口

  hr=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGB);

  // 创建ICaptureGraphBuilder2接口

  hr = CoCreateInstance (CLSID_CaptureGraphBuilder2 , NULL, CLSCTX_INPROC,

  IID_ICaptureGraphBuilder2, (void **) &m_pCapture);

  if (FAILED(hr))return hr;

  m_pCapture->SetFiltergraph(m_pGB);

  hr = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC);

  if (FAILED(hr))return hr;

  hr = m_pGB->QueryInterface(IID_IVideoWindow, (LPVOID *) &m_pVW);

  if (FAILED(hr))return hr;

  return hr;

  }

  HRESULT CCaptureVideo::SetupVideoWindow()

  {

  HRESULT hr;

  hr = m_pVW->put_Owner((OAHWND)m_hWnd);

  if (FAILED(hr))return hr;

  hr = m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN);

  if (FAILED(hr))return hr;

  ResizeVideoWindow();

  hr = m_pVW->put_Visible(OATRUE);

  return hr;

  }

  void CCaptureVideo::ResizeVideoWindow()

  {

  if (m_pVW){

  //让图像充满整个窗口

  CRect rc;

  ::GetClientRect(m_hWnd,&rc);

  m_pVW->SetWindowPosition(0, 0, rc.right, rc.bottom);

  }

  }

  void CCaptureVideo::GrabOneFrame(BOOL bGrab)

  {

  bOneShot = bGrab;

  }

  void CCaptureVideo::FreeMediaType(AM_MEDIA_TYPE& mt)

  {

  if (mt.cbFormat != 0) {

  CoTaskMemFree((PVOID)mt.pbFormat);

  // Strictly unnecessary but tidier

  mt.cbFormat = 0;

  mt.pbFormat = NULL;

  }

  if (mt.pUnk != NULL) {

  mt.pUnk->Release();

  mt.pUnk = NULL;

  }

  }

  如何使用视频捕捉类CCaptureVideo

  构建CCaptureVideo类以后,使用就方便多了,我们在编程中只需要是要下面三个类成员函数就可以实现用摄像头进行视频捕捉:

  ①int EnumDevices(HWND hList); //hList是下拉列表框的句柄,本函数用于枚举当前系统安装的所有视频捕捉设备

  ②HRESULT Init(int iDeviceID,HWND hWnd);//iDeviceID是视频捕捉设备序号,hWnd是视频捕捉窗口的句柄

  ③void GrabOneFrame(BOOL bGrab);//调用GrabOneFrame(true)就可以捕获当前的静态图像并保存到硬盘上

  具体示例:用MFC AppWizard(exe)创建一个对话框应用程序,取名为ds,给对话框添加一个下拉列表框(IDC_COMBO1)、两个按钮(IDC_PHOTO、IDC_HAVEALOOK)和一个Picture控件(ID:IDC_STATIC_SCREEN,Type: Rectangle,Color:Gray)。

  1、使用向导添加成员变量

  CStatic m_staticScreen; // IDC_STATIC_SCREEN

  CComboBox m_ListCtrl; // IDC_COMBO1

  CCaptureVideo m_cap;

  2、为BOOL CDsDlg::OnInitDialog()添加如下代码:

  // TODO: Add extra initialization here

  m_cap.EnumDevices (m_ListCtrl);

  m_ListCtrl.SetCurSel (0);

  3、为确定按钮添加代码如下:

  void CDsDlg::OnOK()

  {

  //只需要四行代码就可以进行视频捕捉了

  UpdateData();

  HWND hWnd = m_staticScreen.GetSafeHwnd() ;

  HRESULT hr = m_cap.Init(m_ListCtrl.GetCurSel (),hWnd);

  GetDlgItem(IDOK)->EnableWindow(FALSE);

  }

  4、如果希望捕捉静态图像,为照相按钮添加如下代码:

  void CDsDlg::OnPhoto()

  {

  m_cap.GrabOneFrame(true);

  }

  运行程序时,选定摄像头后只需要按确定就可以了。

  结束语

  本文提供的视频捕捉类CcaptureVideo和示例,在Win2K + DirectX9 SDK + VC6 环境下调试通过。注意:编译时需要Strmiids.lib Quartz.lib两个库文件(DirectX9 SDK自带)。

  分享到: QQ空间 新浪微博 腾讯微博 微信 更多 0

  蓝客门户

  今日更新

  如果你想学好编程!就一定要看这个!对无论有多厉害的你有很多帮助!

  HTC王雪红:2014首先要解决供应链问题

  为单手操作而生 IUNI OS正式开启公测

  1600万像素骁龙800芯 IUNI手机配置曝光

  或支持128GB扩展 HTC All New One将至

  索尼Z3或于八月发布 Z2只是半年旗舰机

  或搭载64位八核芯 Nexus 6发布时间曝光

  只要50美元 谷歌Ara模块化手机明年推出

  双后置摄像头 HTC M8真机谍照再度曝光

  售价或为2000元 HTC Desire 816将上市

  头条新闻

  本类焦点

  本类推荐

  如果你想学好编程!就一定要看这个!对无论有多厉害的你有很多帮助!

  只要50美元 谷歌Ara模块化手机明年推出

  售价或为2000元 HTC Desire 816将上市

  病毒预报(2013.10.14-2013.10.20)

  病毒监测周报 (2013.10.20-2013.10.26)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: