由于工作中要做立体视觉相关开发,使用到摄像头的图像采集,而使用了于老师基于Directshow的CameraDS类, 但遇到使用明美某款摄像头是,QueryFrame()总是抓到同一帧画面,不会更新,细看CameraDS,发现使用的是DS的缓冲区模式,估计是该摄像头对于缓冲区模式支持存在问题。于是,我尝试使用回调模式,由于回调也存在两种小的模式,即SetOneShot(FALSE or TRUE),这两种模式。SetOneShot(FALSE),只用Run()一次即可。如果SetOneShot(TRUE)的话,也可以每请求一帧Run()一次,然后WaitForCompletion,再从回调函数的buffer中取出数据,这种其实和缓冲区 模式一样,只不过缓冲区变成了在回调函数中。
下面是模式用于回调抓图的类,实现了ISampleGrabberCB的接口
class CSampleGrabberCB : public ISampleGrabberCB
{
public:
long lWidth ;
long lHeight ;
BOOL bGrabVideo ;
// add yelei
BOOL bReadyGO;
double dblSampleTime;
BYTE *pBuffer;
long lBufferSize;
public:
CSampleGrabberCB(){
lWidth = 0 ;
lHeight = 0 ;
bGrabVideo = FALSE ;
bReadyGO = FALSE;
pBuffer = NULL;
}
~CSampleGrabberCB(){
if (pBuffer) {
delete []pBuffer;
pBuffer = NULL;
}
}
STDMETHODIMP_(ULONG) AddRef() { return 2; }
STDMETHODIMP_(ULONG) Release() { return 1; }
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) {
if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown ){
*ppv = static_cast<void *>( this );
return NOERROR;
}
return E_NOINTERFACE;
}
STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample ) {
return 0;
}
STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize ) {
if (!pBuffer) return E_POINTER;
if (!this->pBuffer) this->pBuffer = new BYTE[lBufferSize];
this->dblSampleTime = dblSampleTime;
this->lBufferSize = lBufferSize;
if(!bGrabVideo) {
bReadyGO = FALSE; //确保原子操作
memcpy(this->pBuffer, pBuffer, lBufferSize);
bReadyGO = TRUE;
}
// 只使用标志位的话,你没法保证一个线程在对标志位做判断和修改
// 标志位这两个操作之间不会有其它的线程修改标志位。所以需要有
// 一个原子操作去一次性的互斥的完成判断和修改这两个操作。但这
// 里只有一个线程会执行该段代码,所以标志位足矣
return 0;
}
};
打开Camera时,设置回调模式
m_pSampleGrabber->SetBufferSamples(FALSE);
m_pSampleGrabber->SetOneShot(FALSE);
m_pCB->lWidth = m_nWidth = nWidth;
m_pCB->lHeight = m_nHeight = nHeight;
hr = m_pSampleGrabber->SetCallback(m_pCB, 1);
m_pMediaControl->Run();
最后抓图时
static double lastSampleTime=0;
if (lastSampleTime == m_pCB->dblSampleTime)
return NULL; //若时间戳一样,说明和上一帧是同一帧,则返回NULL
if (m_pCB->lBufferSize == 0)
return NULL;
if ( m_nBufferSize != m_pCB->lBufferSize) {
if (m_pFrame)
cvReleaseImage(&m_pFrame);
m_nBufferSize = m_pCB->lBufferSize;
m_pFrame = cvCreateImage(cvSize(m_nWidth, m_nHeight), IPL_DEPTH_8U, 3);
}
lastSampleTime = m_pCB->dblSampleTime;
m_pCB->bGrabVideo = TRUE;
while (!m_pCB->bReadyGO) {
Sleep(0); // 切到下一个时间片,确保复制(原子)操作完成
}
memcpy(m_pFrame->imageData, (char*)m_pCB->pBuffer, m_nBufferSize);
m_pCB->bGrabVideo = FALSE;
至此,可以使用DS的回调模式来抓图了,操作方式跟缓冲区模式一样,只是为了支持某些不支持缓冲区模式的相机。