DirectShow中利用SampleGrabber捕获摄像头每一帧图像,并转为BMP文件信息写入内存

   关于怎样枚举设备、建立Filter Graph等问题,我就不多说了,说也说不清楚,因为我也是才接触DirectShow。网上这类资料还是很多,百度知道的比我多多了。这里主要介绍一下自己在学习了StillCap例子之后,实现利用SampleGrabber捕获摄像头每一帧图像的过程。过程中遇到的印象深刻的问题会在下一篇博文中介绍。废话不说了,开门见山吧。

    大致思路与代码如下:
1、从ISampleGrabber实例化一个sample grabber,充当一个Transform Filter

[cpp]  view plain  copy
  1. CComPtr<ISampleGrabber> m_pSampleGrabber;  

2、设置相关参数,将Grabber注册并且加入到m_pGB这个Filter Graph中
[cpp]  view plain  copy
  1. //Add SampleGrabber  
  2. // create a sample grabber  
  3. //  
  4. hr = m_pSampleGrabber.CoCreateInstance( CLSID_SampleGrabber );  
  5. if( !m_pSampleGrabber )  
  6. {  
  7. MessageBox(NULL, L"Could not create SampleGrabber (is qedit.dll registered?)", L"", MB_OK);  
  8. return hr;  
  9. }  
  10.   
  11. CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabBase( m_pSampleGrabber );  
  12. // force it to connect to video, 24 bit  
  13. //  
  14. CMediaType VideoType;  
  15. VideoType.SetType( &MEDIATYPE_Video );  
  16. VideoType.SetSubtype( &MEDIASUBTYPE_RGB24 );  
  17. VideoType.SetFormatType(&FORMAT_VideoInfo);  
  18. hr = m_pSampleGrabber->SetMediaType( &VideoType ); // shouldn't fail  
  19. if (FAILED(hr))  
  20. {  
  21. MessageBox(NULL, L"不能初始化SampleGrabber媒体类型。", L"", MB_OK);  
  22. return hr;  
  23. }  
  24. hr = m_pGB->AddFilter( pGrabBase, L"Grabber" );  
  25. if( FAILED( hr ) )  
  26. {  
  27. MessageBox(NULL, L"Could not put sample grabber in graph", L"", MB_OK);  
  28. return hr;  
  29. }  


3、连接成功之后,将SampleGrabber作为一个中间filter,利用CSampleGrabber的实例调用回调函数BufferCB,从m_pBF这个Source Filter中得到摄像头设备捕获的每一帧数据,存为Sample,并且将数据原封不动传入m_pVMR中,用于预览
[cpp]  view plain  copy
  1. hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, m_pBF, pGrabBase, m_pVMR);  
  2.   
  3. if (FAILED(hr))  
  4. {  
  5. MessageBox(NULL, L"Could not put sample into sample grabber", L"", MB_OK);  
  6. return hr;  
  7. }  
  8.   
  9. //Set mCB  
  10. AM_MEDIA_TYPE mt;  
  11. hr = m_pSampleGrabber->GetConnectedMediaType( &mt );  
  12. if ( FAILED( hr) )  
  13. {  
  14. MessageBox( NULL, TEXT("Could not read the connected media type"),L"", MB_OK);  
  15. return hr;  
  16. }  
  17.   
  18.   
  19. VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*) mt.pbFormat;  
  20. mCB.IWidth = vih->bmiHeader.biWidth;  
  21. mCB.IHeight = vih->bmiHeader.biHeight;  
  22. FreeMediaType( mt );  
  23.   
  24.   
  25. hr = RenderFileList(m_pGB,m_pVMR );  
  26.   
  27.   
  28. // don't buffer the samples as they pass through  
  29. //  
  30. hr = m_pSampleGrabber->SetBufferSamples( TRUE);  
  31.   
  32.   
  33. // only grab one at a time, stop stream after  
  34. // grabbing one sample  
  35. //  
  36. hr = m_pSampleGrabber->SetOneShot( FALSE );  
  37.   
  38.   
  39. // set the callback, so we can grab the one sample  
  40. //  
  41. hr = m_pSampleGrabber->SetCallback( &mCB, 1 );  
  42. if (FAILED(hr))  
  43. {  
  44. MessageBox(NULL, L"Could not call callback method BufferCB().", L"", MB_OK);   
  45. return hr;  
  46. }  
  47. pGrabBase.Release();  


4、从ISampleGrabber继承类CSampleGrabber,用于处理捕获的视频帧sample,重写AddRef, Release, QueryInterface, SampleCB和BufferCB关键函数,加入自己的函数FormatImage(用于转化内存数据为bmp文件数据,并存入内存),GetImgFileData(得到内存中bmp文件数据),SetImgFileData(防止内存泄露)
[cpp]  view plain  copy
  1. //SampleGrabber  
  2. // Global data  
  3. #define WM_CAPTURE_BITMAP   WM_APP + 1  
  4. BOOL g_bOneShot=TRUE;  
  5.   
  6.   
  7. // Structures  
  8. typedef struct _callbackinfo   
  9. {  
  10. double dblSampleTime;  
  11. long lBufferSize;  
  12. BYTE *pBuffer;  
  13. BITMAPINFOHEADER bih;  
  14.   
  15.   
  16. } CALLBACKINFO;  
  17.   
  18.   
  19. CALLBACKINFO cb={0};  
  20.   
  21.   
  22. class CSampleGrabberCB : public ISampleGrabberCB   
  23. {  
  24. private:  
  25. LPBITMAPFILEHEADER m_pFileHeader ;    // Bmp文件头  
  26. LPBITMAPINFOHEADER m_pBmpInfo ;     // Bmp信息头指针  
  27.   
  28.   
  29. BYTE* m_pImgFileData;                //Bmp文件数据  
  30.   
  31.   
  32. BOOL m_bViladImage ;  
  33.   
  34.   
  35. LPBYTE m_pBitmapHeader ;  
  36. LPVOID m_pvColorTable ;               //调色板指针  
  37. public:  
  38. // these will get set by the main thread below. We need to  
  39. // know this in order to write out the bmp  
  40. long IWidth;  
  41. long IHeight;  
  42.   
  43.   
  44. CSampleGrabberCB( )  
  45. {   
  46. m_pFileHeader = new BITMAPFILEHEADER ;  
  47. m_pBitmapHeader = new BYTE[sizeof(BITMAPINFOHEADER)] ;  
  48. }     
  49.   
  50.   
  51. // fake out any COM ref counting  
  52. //  
  53. STDMETHODIMP_(ULONG) AddRef() { return 2; }  
  54. STDMETHODIMP_(ULONG) Release() { return 1; }  
  55.   
  56.   
  57. // fake out any COM QI'ing  
  58. //  
  59. STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)  
  60. {  
  61. if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown )   
  62. {  
  63. *ppv = (void *) static_cast<ISampleGrabberCB*> ( this );  
  64. return NOERROR;  
  65. }      
  66. return E_NOINTERFACE;  
  67. }  
  68.   
  69.   
  70. // we don't implement this interface for this example  
  71. //  
  72. STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample )  
  73. {  
  74. return 0;  
  75. }  
  76. // As a workaround, copy the bitmap data during the callback,  
  77. // post a message to our app, and write the data later.  
  78. //  
  79. STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize )  
  80. {  
  81. // this flag will get set to true in order to take a picture  
  82. //  
  83. if( !g_bOneShot )  
  84. return 0;  
  85.   
  86.   
  87. if (!pBuffer)  
  88. {  
  89. return E_POINTER;  
  90. }  
  91.   
  92.   
  93. if( cb.lBufferSize < lBufferSize )  
  94. {  
  95. delete [] cb.pBuffer;  
  96. cb.pBuffer = NULL;  
  97. cb.lBufferSize = 0;  
  98. }  
  99.   
  100.   
  101. // Since we can't access Windows API functions in this callback, just  
  102. // copy the bitmap data to a global structure for later reference.  
  103. cb.dblSampleTime = dblSampleTime;  
  104.   
  105.   
  106. // If we haven't yet allocated the data buffer, do it now.  
  107. // Just allocate what we need to store the new bitmap.  
  108. if (!cb.pBuffer)  
  109. {  
  110. cb.pBuffer = new BYTE[lBufferSize];  
  111. cb.lBufferSize = lBufferSize;  
  112. }  
  113.   
  114.   
  115. if( !cb.pBuffer )  
  116. {  
  117. cb.lBufferSize = 0;  
  118. return E_OUTOFMEMORY;  
  119. }  
  120.   
  121.   
  122. //Get bmp information  
  123. BITMAPINFOHEADER bih;  
  124. memset( &bih, 0, sizeof( bih ) );  
  125. bih.biSize = sizeof( bih );  
  126. bih.biWidth = IWidth;  
  127. bih.biHeight = IHeight;  
  128. bih.biPlanes = 1;  
  129. bih.biBitCount = 24;  
  130. memcpy(&(cb.bih), &bih, sizeof(bih));  
  131.   
  132.   
  133. // Copy the bitmap data into our global buffer  
  134. memcpy(cb.pBuffer, pBuffer, lBufferSize);  
  135. // Post a message to our application, telling it to come back  
  136. // and write the saved data to a bitmap file on the user's disk.  
  137. SendMessage(m_hWnd, WM_CAPTURE_BITMAP, 0, 0L);  
  138. return 0;  
  139. }  
  140.   
  141.   
  142. BOOL FormatImage( BYTE *lpImageData, int nBitCount, int nWidth, int nHeight )   
  143. {  
  144.   
  145. m_bViladImage = FALSE ;  
  146.   
  147.   
  148. int nKlsBmpBitCount ;  
  149. int nImgWidth = nWidth ;  
  150. int nImgHeight = nHeight ;  
  151. if (nBitCount == 8 || nBitCount == 24 || nBitCount == 32)  
  152. {  
  153. nKlsBmpBitCount = nBitCount;  
  154. }  
  155. else  
  156. {  
  157. return m_bViladImage ;  
  158. }  
  159.   
  160.   
  161. int nDataWidth = nKlsBmpBitCount / 8 * nWidth ;  
  162.   
  163.   
  164. nDataWidth = ( nDataWidth % 4 == 0 ) ? nDataWidth : ( ( nDataWidth / 4 + 1 ) * 4 ) ;  
  165.   
  166.   
  167. //m_pFileHeader = new BITMAPFILEHEADER ;  
  168.   
  169.   
  170. m_pFileHeader->bfType      = 0x4d42 ;    
  171. m_pFileHeader->bfSize      = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + nDataWidth*nImgHeight ;  
  172. m_pFileHeader->bfReserved1 = 0 ;  
  173. m_pFileHeader->bfReserved2 = 0 ;  
  174.   
  175.   
  176. if ( nBitCount == 8 )  
  177. {  
  178. int nBmpInfoSize = sizeof(BITMAPFILEHEADER)   
  179. sizeof(BITMAPINFOHEADER)  
  180. + 256 * 4 ;  
  181.   
  182.   
  183. m_pFileHeader->bfOffBits = nBmpInfoSize ;  
  184.   
  185.   
  186. m_pBitmapHeader = new BYTE[nBmpInfoSize] ;  
  187.   
  188.   
  189. m_pBmpInfo = (LPBITMAPINFOHEADER)m_pBitmapHeader ;  
  190.   
  191.   
  192. m_pvColorTable = m_pBitmapHeader + sizeof(BITMAPINFOHEADER) ;  
  193.   
  194.   
  195. LPRGBQUAD pDibQuad = (LPRGBQUAD)(m_pvColorTable) ;  
  196.   
  197.   
  198. for ( int c=0; c<256; ++c )  
  199. {  
  200. pDibQuad[c].rgbRed = c ;  
  201. pDibQuad[c].rgbGreen = c ;  
  202. pDibQuad[c].rgbBlue = c ;  
  203. pDibQuad[c].rgbReserved = 0 ;  
  204. }  
  205. }  
  206. else  
  207. {  
  208. m_pFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) ;  
  209.   
  210.   
  211. //m_pBitmapHeader = new BYTE[sizeof(BITMAPINFOHEADER)] ;  
  212.   
  213.   
  214. m_pBmpInfo = (LPBITMAPINFOHEADER)m_pBitmapHeader ;  
  215. m_pvColorTable = NULL ;  
  216. }  
  217.   
  218.   
  219. m_pBmpInfo->biBitCount      = nKlsBmpBitCount ;   
  220. m_pBmpInfo->biWidth         = nImgWidth ;  
  221. m_pBmpInfo->biHeight        = nImgHeight ;  
  222. m_pBmpInfo->biPlanes        = 1 ;  
  223. m_pBmpInfo->biSize          = sizeof(BITMAPINFOHEADER) ;  
  224. m_pBmpInfo->biSizeImage     = nImgWidth * nImgHeight * nKlsBmpBitCount / 8 ;   
  225. m_pBmpInfo->biClrImportant  = 0 ;  
  226. m_pBmpInfo->biClrUsed       = 0 ;  
  227. m_pBmpInfo->biCompression   = 0 ;  
  228. m_pBmpInfo->biXPelsPerMeter = 0 ;  
  229. m_pBmpInfo->biYPelsPerMeter = 0 ;   
  230.   
  231.   
  232. //m_pImgFileData = new BYTE[nDataWidth*nImgHeight] ;  
  233. SetImgFileData(nDataWidth, nImgHeight);  
  234.   
  235.   
  236. if ( nBitCount == 8 )  
  237. {  
  238. if ( nImgWidth % 4 == 0 )  
  239. {  
  240. memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;  
  241. memcpy( m_pImgFileData, lpImageData, cb.lBufferSize) ;  
  242. }  
  243. else  
  244. {  
  245. memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;  
  246.   
  247.   
  248. for ( int i=0; i<nImgHeight; i++ )  
  249. {  
  250. memcpy( m_pImgFileData + i*nDataWidth, lpImageData + i*nImgWidth, nImgWidth ) ;  
  251. }  
  252. }  
  253. }  
  254. else if ( nBitCount == 24 )  
  255. {  
  256. if ( nImgWidth % 4 == 0 )  
  257. {  
  258. memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;  
  259. memcpy( m_pImgFileData, lpImageData, cb.lBufferSize ) ;  
  260. }  
  261. else  
  262. {  
  263. memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;  
  264.   
  265.   
  266. for ( int i=0; i<nImgHeight; i++ )  
  267. {  
  268. memcpy( m_pImgFileData + i*nDataWidth, lpImageData + i*3*nImgWidth, 3*nImgWidth ) ;  
  269. }  
  270. }  
  271. }  
  272. else if ( nBitCount == 32 )  
  273. {  
  274. memcpy( m_pImgFileData, lpImageData,cb.lBufferSize ) ;  
  275. }  
  276.   
  277.   
  278. m_bViladImage = TRUE ;  
  279.   
  280.   
  281. return m_bViladImage ;  
  282. }  
  283.   
  284.   
  285. BYTE* GetImgFileData()  
  286. {  
  287. return m_pImgFileData;  
  288. }  
  289.   
  290.   
  291. void SetImgFileData(int dataWidth, int dataHeight)  
  292. {  
  293. if (!m_pImgFileData)  
  294. {  
  295. m_pImgFileData = new BYTE[dataWidth*dataHeight];  
  296. }   
  297. else  
  298. {  
  299. delete [] m_pImgFileData;  
  300. m_pImgFileData = NULL;  
  301. m_pImgFileData = new BYTE[dataWidth*dataHeight];  
  302. }  
  303.   
  304. }  
  305. };  
  306.   
  307.   
  308. //  
  309. //  
  310. // This semi-COM object will receive sample callbacks for us  
  311. //  
  312. //  
  313. CSampleGrabberCB mCB;  
  314. //SampleGrabber  

5、得到Sample之后,放入到回调函数BufferCB中进行处理,从而得到必要的cb.pBuffer数据块,并将大小,图像的头信息bih存入结构体cb中
[cpp]  view plain  copy
  1. //From Step 3  
  2. // set the callback, so we can grab the one sample  
  3. //  
  4. hr = m_pSampleGrabber->SetCallback( &mCB, 1 );  
  5.   
  6. //From BufferCB  
  7. //Get bmp information  
  8. BITMAPINFOHEADER bih;  
  9. memset( &bih, 0, sizeof( bih ) );  
  10. bih.biSize = sizeof( bih );  
  11. bih.biWidth = IWidth;  
  12. bih.biHeight = IHeight;  
  13. bih.biPlanes = 1;  
  14. bih.biBitCount = 24;  
  15. memcpy(&(cb.bih), &bih, sizeof(bih));  
  16.   
  17.   
  18. // Copy the bitmap data into our global buffer  
  19. memcpy(cb.pBuffer, pBuffer, lBufferSize);  

以下两步均在使用数据的函数中实现,一般是在同一函数中使用  
6、得到cb中的pBuffer数据和相关信息,存入指定内存
[cpp]  view plain  copy
  1. BYTE* pImageData = new BYTE[cb.lBufferSize];  
  2. CopyMemory(pImageData, cb.pBuffer, cb.lBufferSize);  

7、,利用FormatImage对内存数据进行转化,得到bmp数据,并存入指定内存,通过GetImgFileData得到bmp数据,放入到压缩函数中进行压缩,放入公共内存中,传入到虚拟摄像头中。

[cpp]  view plain  copy
  1. if (mCB.FormatImage(pImageData, 24,  cb.bih.biWidth, cb.bih.biHeight))  
  2. {  
  3. //此函数用于转化m_pImgFileData为指定长宽的bmp图片信息,没有需求就不用了  
  4. ScaleRgb24BmpData(TransformBuffer, 320, 240,( mCB.GetImgFileData()),cb.bih.biWidth, cb.bih.biHeight);  
  5. ZeroMemory(m_pData, BUFFER_SIZE);  
  6. //写入共享内存  
  7. CopyMemory(m_pData, TransformBuffer, BUFFER_SIZE);  
  8. }  
  9. //to free memory  
  10. delete pImageData;  
[cpp]  view plain  copy
  1. //END ICTwangbiao  
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的C++程序,它可以利用DirectShow控制四个摄像头,分别拍照并保存图像: ``` #include <Windows.h> #include <dshow.h> #include <iostream> #include <string> #pragma comment(lib, "strmiids.lib") using namespace std; // 四个摄像头的设备路径 string camera1_path = "video=USB Camera 1"; string camera2_path = "video=USB Camera 2"; string camera3_path = "video=USB Camera 3"; string camera4_path = "video=USB Camera 4"; // 文件名前缀 string filename_prefix = "camera"; // 文件名后缀 string filename_suffix = ".bmp"; // 拍照并保存图像 void captureImage(string camera_path, int camera_id) { // 创建Graph对象 IGraphBuilder* pGraph = NULL; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph); // 创建Capture Graph Builder对象 ICaptureGraphBuilder2* pBuilder = NULL; CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuilder); // 为Capture Graph Builder对象设置Graph对象 pBuilder->SetFiltergraph(pGraph); // 创建摄像头过滤器 IBaseFilter* pCameraFilter = NULL; pGraph->AddSourceFilter(camera_path.c_str(), NULL, &pCameraFilter); // 创建Sample Grabber过滤器 IBaseFilter* pGrabberFilter = NULL; CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pGrabberFilter); // 设置Sample Grabber参数 ISampleGrabber* pGrabber = NULL; pGrabberFilter->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber); pGrabber->SetOneShot(TRUE); pGrabber->SetBufferSamples(TRUE); // 将过滤器添加到Graph pGraph->AddFilter(pGrabberFilter, NULL); // 连接过滤器 IPin* pCameraOut = NULL; IPin* pGrabberIn = NULL; pBuilder->FindPin(pCameraFilter, PINDIR_OUTPUT, NULL, NULL, FALSE, 0, &pCameraOut); pBuilder->FindPin(pGrabberFilter, PINDIR_INPUT, NULL, NULL, FALSE, 0, &pGrabberIn); pGraph->Connect(pCameraOut, pGrabberIn); // 运行Graph IMediaControl* pControl = NULL; pGraph->QueryInterface(IID_IMediaControl, (void**)&pControl); pControl->Run(); // 等待拍照完成 long evCode; pControl->WaitForCompletion(-1, &evCode); // 保存图像 AM_MEDIA_TYPE mt; pGrabber->GetConnectedMediaType(&mt); BITMAPINFOHEADER* bmiHeader = (BITMAPINFOHEADER*)mt.pbFormat; long imageSize = bmiHeader->biSizeImage; BYTE* pBuffer = new BYTE[imageSize]; pGrabber->GetCurrentBuffer(&imageSize, (long*)pBuffer); string filename = filename_prefix + to_string(camera_id) + filename_suffix; HANDLE hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwBytesWritten; WriteFile(hFile, pBuffer, imageSize, &dwBytesWritten, NULL); CloseHandle(hFile); // 释放资源 delete[] pBuffer; pControl->Stop(); pGrabber->Release(); pGrabberFilter->Release(); pCameraFilter->Release(); pBuilder->Release(); pGraph->Release(); } int main() { // 拍照并保存四个摄像头图像 captureImage(camera1_path, 1); captureImage(camera2_path, 2); captureImage(camera3_path, 3); captureImage(camera4_path, 4); return 0; } ``` 注意:由于每个摄像头的设备路径可能不同,需要根据实际情况修改代码的`camera1_path`、`camera2_path`、`camera3_path`和`camera4_path`的值,确保程序能够正确识别摄像头。此外,需要将程序编译为32位应用程序,以便与DirectShow框架兼容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值