3.使用DShow进行摄像头预览并拍照

上一篇讲了怎么采集摄像头图像并预览,本篇主要讲预览的同时怎么拍照。

拍照就需要抓取图像,这里要用到一个不太一样的Filter,叫SampleGrabber Filter,通过这个Filter可以获取到ISampleGrabber接口,通过这个接口就可以设置抓取什么样的视频。对于这个接口获取采集到的每一帧的信息,我们可以对其进行处理,可以拿来显示,也可以用来生成图片。下面来一步一步做做看。

首先,我新定义了一个结构,用来存采集设备支持的所有分辨率,ASCamResolutionInfoArray m_arrCamResolutionArr; 它的结构定义如下:

struct CamResolutionInfo
{
	int nWidth;		//分辨率宽
	int nHeight;		//分辨率高
	int nResolutionIndex;	//分辨率序号

	CamResolutionInfo()
	{
		nWidth = 640;
		nHeight = 480;
		nResolutionIndex = -1;
	};
	
	CamResolutionInfo(const CamResolutionInfo &other)
	{
		*this = other;
	};
	
	CamResolutionInfo& operator = (const CamResolutionInfo& other)
	{
		nWidth = other.nWidth;
		nHeight = other.nHeight;
		nResolutionIndex = other.nResolutionIndex;
		return *this;
	};
};
typedef CArray  ASCamResolutionInfoArray;
然后获取采集设备支持的所有分辨率,代码如下:

void CGetDeviceInfoDlg::GetVideoResolution()
{
	if (m_pCapture)
	{
		m_arrCamResolutionArr.RemoveAll();
		m_cbxResolutionCtrl.ResetContent();
		IAMStreamConfig *pConfig = NULL;  
		//&MEDIATYPE_Video,如果包括其他媒体类型,第二个参数设置为0
		HRESULT hr = m_pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, 
										m_pVideoFilter, IID_IAMStreamConfig, (void **)&pConfig);

		int iCount = 0, iSize = 0;
		hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);
		// Check the size to make sure we pass in the correct structure.
		if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS))
		{
			// Use the video capabilities structure.
			for (int iFormat = 0; iFormat < iCount; iFormat++)
			{
				VIDEO_STREAM_CONFIG_CAPS scc;
				AM_MEDIA_TYPE *pmtConfig = NULL;
				hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
				if (SUCCEEDED(hr))
				{
					//(pmtConfig->subtype == MEDIASUBTYPE_RGB24) &&
					if ((pmtConfig->majortype == MEDIATYPE_Video) &&
						(pmtConfig->formattype == FORMAT_VideoInfo) &&
						(pmtConfig->cbFormat >= sizeof (VIDEOINFOHEADER)) &&
						(pmtConfig->pbFormat != NULL))
					{
						VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmtConfig->pbFormat;
						// pVih contains the detailed format information.
						LONG lWidth = pVih->bmiHeader.biWidth;
						LONG lHeight = pVih->bmiHeader.biHeight;
						BOOL bFind = FALSE;
						//是否已经存在这个分辨率,不存在就加入array
						for (int n=0; n < m_arrCamResolutionArr.GetSize(); n++)
						{
							CamResolutionInfo sInfo = m_arrCamResolutionArr.GetAt(n);
							if (sInfo.nWidth == lWidth && sInfo.nHeight == lHeight)
							{
								bFind = TRUE;
								break;
							}
						}
						if (!bFind)
						{
							CamResolutionInfo camInfo;
							camInfo.nResolutionIndex = iFormat;
							camInfo.nWidth = lWidth;
							camInfo.nHeight = lHeight;
							m_arrCamResolutionArr.Add(camInfo);

							CString strFormat = _T("");
							strFormat.Format(_T("%d * %d"), lWidth, lHeight);
							m_cbxResolutionCtrl.AddString(strFormat);
						}
					}

					// Delete the media type when you are done.
					FreeMediaType(pmtConfig);
				}
			}
		}
		if (m_cbxResolutionCtrl.GetCount() > 0)
		{
			m_cbxResolutionCtrl.SetCurSel(0);
		}
	}
}

void CGetDeviceInfoDlg::FreeMediaType(AM_MEDIA_TYPE *pmt)
{
	if (pmt == NULL)
	{
		return;
	}

    if (pmt->cbFormat != 0) 
    {
        CoTaskMemFree((PVOID)pmt->pbFormat);
        // Strictly unnecessary but tidier
        pmt->cbFormat = 0;
        pmt->pbFormat = NULL;
    }
    
    if (pmt->pUnk != NULL) 
    {
        pmt->pUnk->Release();
        pmt->pUnk = NULL;
    }
} 
获取到所有分辨率后,选择一个,其实就得到选择的分辨率序号,在后面的代码中对其进行设置。代码如下:

		//设置视频分辨率、格式
		IAMStreamConfig *pConfig = NULL;  
		m_pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, 
							m_pVideoFilter, IID_IAMStreamConfig, (void **) &pConfig);

		AM_MEDIA_TYPE *pmt = NULL; 
		VIDEO_STREAM_CONFIG_CAPS scc;
		pConfig->GetStreamCaps(nResolutionIndex, &pmt, (BYTE*)&scc);  //nResolutionIndex就是选择的分辨率序号

		pConfig->SetFormat(pmt);

这些都做好后,就开始创建SampleGrabber Filter,代码如下:

	//创建视频捕捉实例 
	HRESULT hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&m_pGrabberFilter); 
	if (m_pGrabberFilter == NULL)
	{
		MessageBox(_T("获取m_pGrabberFilter失败"), _T("提示"));
		return;
	}
	//将视频捕捉过滤器加入图表
	hr = m_pGraphBuilder->AddFilter(m_pGrabberFilter, L"Grabber");
	if(S_OK != hr)
	{
		MessageBox(_T("Fail to put sample grabber in graph"));
		return;
	}
再获取ISampleGrabber接口,并设置要抓取的视频

m_pGrabberFilter->QueryInterface(IID_ISampleGrabber, (void **)&m_pGrabber);
		pmt->majortype = MEDIATYPE_Video;
		pmt->subtype = MEDIASUBTYPE_RGB24;  //抓取24位位图
		HRESULT hr = m_pGrabber->SetMediaType(pmt);
		if(FAILED(hr))
		{
			AfxMessageBox(_T("Fail to set media type!"));
			return;
		}
接下来一步非常重要,设置是否缓存,如果设置了缓存,则可以用接口获取到缓存数据,并生成图片,如果不设置缓存,则需要设置回调函数,在回调函数中获取到帧的数据,并生成图片,两种方法都可以,根据实际需要来选择。我这里就用缓存的方式,设置方法如下:

//是否缓存数据,缓存的话,可以给后面做其他处理,不缓存的话,图像处理就放在回调中
m_pGrabber->SetBufferSamples( TRUE ); 

如果用设置回调的方法,请调用m_pGrabber->SetCallback(...,...),第一个参数就是回调函数的名称,是一个结构的实例,这个结构里有两个虚函数:SampleCB和BufferCB,所以你需要写一个类继承这个结构,然后重载这两个函数,看两个函数的参数,就知道怎么处理 了。

接下来就是链接Filter,代码如下:

m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, m_pVideoFilter, m_pGrabberFilter, NULL);

后面就是设置预览窗口,然后拍照,拍照代码如下:

void CGetDeviceInfoDlg::OnBnClickedBtnTakepic()
{
	//获取抓取buffer大小
	long nBufferSize = 0; 
	HRESULT hr = m_pGrabber->GetCurrentBuffer(&nBufferSize, NULL); 
	if(FAILED(hr))
	{
		AfxMessageBox(_T("Get BufferSize failed"));
		return;
	}

	//获取抓取的data数据
	BYTE *pBuffer = new BYTE[nBufferSize]; 
	hr = m_pGrabber-> GetCurrentBuffer(&nBufferSize, (long*)pBuffer); 
	if(FAILED(hr))
	{
		AfxMessageBox(_T("Get BufferData failed"));
	}

	AM_MEDIA_TYPE mt;
    hr = m_pGrabber->GetConnectedMediaType(&mt);

    VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*) mt.pbFormat;
    int nWidth  = vih->bmiHeader.biWidth;
    int nHeight = vih->bmiHeader.biHeight;
    FreeMediaType(&mt);

	//生成图片
	CTime cTime = CTime::GetCurrentTime();
	CString strTime = cTime.Format(_T("%Y%m%d_%H%M%S.bmp"));
	CString strFullPath = _T("");
	strFullPath.Format(_T("D:\\JYSoft\\temp\\%s"), strTime);

    //文件头
    BITMAPFILEHEADER bfh;
    memset(&bfh, 0, sizeof(bfh));
    bfh.bfType = 'MB';
    bfh.bfSize = sizeof(bfh) + nBufferSize + sizeof(BITMAPINFOHEADER);
    bfh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);

    //格式信息
    BITMAPINFOHEADER bih;
    memset(&bih, 0, sizeof(bih));
    bih.biSize = sizeof(bih);
    bih.biWidth = nWidth;
    bih.biHeight = nHeight;
    bih.biPlanes = 1;
    bih.biBitCount = 24;
	bih.biCompression = BI_RGB;   //非压缩  

	CFile file;
	if(file.Open(strFullPath, CFile::modeWrite | CFile::modeCreate))  
	{
		//写入文件      
		file.Write((LPSTR)&bfh,sizeof(BITMAPFILEHEADER));  
		file.Write((LPSTR)&bih,sizeof(BITMAPINFOHEADER));  
		file.Write(pBuffer, nBufferSize);  
		file.Close();  
	} 

	//用完释放
	delete[] pBuffer;
}

拍照生成bmp图片,最后的界面如下:

3.使用DShow进行摄像头预览并拍照_第1张图片


具体工程代码,请到这里下载:工程代码下载
如有什么问题,请多指教。




你可能感兴趣的:(DShow)