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

时间:2022-05-29 12:49:44

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

拍照就需要抓取图像,这里要用到一个不太一样的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 <CamResolutionInfo, CamResolutionInfo&> 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进行摄像头预览并拍照


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