使用DirectShow SDK调用PS3eye摄像头时QueryFrame返回黑屏求助
首先,你的代码里有几个关键问题导致了黑屏,我们一步步拆解:
1. 每次抓帧都启停Graph并等待完成是核心问题
你在QueryFrame()里每次都调用m_pMediaControl->Run(),然后立刻用WaitForCompletion(INFINITE)等待整个Graph运行结束——这完全误解了DirectShow Graph的运行逻辑!WaitForCompletion(INFINITE)是用来等待整个播放流程(比如播放一个视频文件到结尾)结束的,而摄像头是持续输出帧的设备,这个调用会让Graph一直等到摄像头停止输出,此时你再去拿缓冲区,要么是空的,要么是早就过时的数据,自然显示黑屏。
而且你每次调用都重新QueryInterface获取IMediaControl,用完就Release,这不仅效率极低,还会导致Graph的状态混乱。
2. Sample Grabber的使用方式不对
你用GetCurrentBuffer主动去拉取帧数据,这种方式并不适合持续的视频流捕获。DirectShow的Sample Grabber更推荐用回调函数的方式来接收帧数据,这样能保证拿到最新的有效帧,而不是在不确定的时机去主动读取缓冲区。
另外,还要确认你是否给Sample Grabber设置了正确的输出格式——PS3Eye默认输出的是YUY2格式,而你的代码直接把数据写到3通道的BGR图像里,格式不匹配也会导致黑屏或者花屏。
修复步骤与修改后的代码示例
第一步:调整MediaControl的初始化与使用
在摄像头初始化的Open()函数里就获取IMediaControl并保存,只启动一次Graph:
// 初始化时的代码(比如在CCameraDS::Open里) HRESULT hr; hr = m_pGraph->QueryInterface(IID_IMediaControl, (void**)&m_pMediaControl); if (SUCCEEDED(hr)) { hr = m_pMediaControl->Run(); if (FAILED(hr)) { // 处理启动失败的情况 m_pMediaControl->Release(); m_pMediaControl = nullptr; } }
第二步:修改QueryFrame函数,去掉错误的启停逻辑
移除每次抓帧时的Run/Stop和WaitForCompletion,改用更合理的方式获取帧:
IplImage* CCameraDS::QueryFrame() { if (!m_IsOpen || !m_pMediaControl || !m_pSampleGrabber) { return NULL; } HRESULT hr; long size = 0; // 先获取当前缓冲区大小 hr = m_pSampleGrabber->GetCurrentBuffer(&size, NULL); if (FAILED(hr)) { return NULL; } // 如果缓冲区大小变化,重新创建图像 if (size != m_nBufferSize) { if (m_pFrame) cvReleaseImage(&m_pFrame); m_nBufferSize = size; // 这里要确保格式是RGB24,所以图像是3通道 m_pFrame = cvCreateImage(cvSize(m_nWidth, m_nHeight), IPL_DEPTH_8U, 3); if (!m_pFrame) { return NULL; } } // 获取缓冲区数据 hr = m_pSampleGrabber->GetCurrentBuffer(&m_nBufferSize, (long*)m_pFrame->imageData); if (FAILED(hr)) { return NULL; } // 翻转图像(根据你的需求) cvFlip(m_pFrame); return m_pFrame; }
第三步:给Sample Grabber设置正确的输出格式
在构建Graph的时候,给Sample Grabber设置RGB24格式,避免格式不匹配:
// 在构建Sample Grabber的时候添加 hr = m_pSampleGrabber->SetMediaType(&MEDIASUBTYPE_RGB24); if (FAILED(hr)) { // 处理错误 }
进阶优化:改用Sample Grabber回调(更可靠)
如果你想更稳定地捕获帧,推荐实现ISampleGrabberCB接口,在回调里接收帧数据:
class CSampleGrabberCB : public ISampleGrabberCB { private: long m_cRef; IplImage* m_pFrame; long m_nBufferSize; int m_nWidth, m_nHeight; public: CSampleGrabberCB(int width, int height) : m_cRef(1), m_pFrame(NULL), m_nBufferSize(0), m_nWidth(width), m_nHeight(height) {} ~CSampleGrabberCB() { if (m_pFrame) cvReleaseImage(&m_pFrame); } // IUnknown接口实现 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown || riid == IID_ISampleGrabberCB) { *ppv = static_cast<ISampleGrabberCB*>(this); AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) Release() { ULONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) delete this; return cRef; } // ISampleGrabberCB接口实现 STDMETHODIMP SampleCB(double SampleTime, IMediaSample* pSample) { // 在这里处理帧数据 BYTE* pBuffer; pSample->GetPointer(&pBuffer); long size = pSample->GetSize(); // 转换为IplImage if (!m_pFrame || size != m_nBufferSize) { if (m_pFrame) cvReleaseImage(&m_pFrame); m_nBufferSize = size; m_pFrame = cvCreateImage(cvSize(m_nWidth, m_nHeight), IPL_DEPTH_8U, 3); } memcpy(m_pFrame->imageData, pBuffer, size); cvFlip(m_pFrame); return S_OK; } STDMETHODIMP BufferCB(double SampleTime, BYTE* pBuffer, long BufferLen) { // 可以留空,或者和SampleCB类似处理 return S_OK; } IplImage* GetFrame() { // 返回当前帧(注意线程安全,可加锁保护) return m_pFrame; } };
然后在构建Graph的时候设置回调:
CSampleGrabberCB* pCB = new CSampleGrabberCB(m_nWidth, m_nHeight); hr = m_pSampleGrabber->SetCallback(pCB, 1); // 1表示用SampleCB回调
这样你的QueryFrame()就可以直接调用pCB->GetFrame()来获取最新帧,稳定性会高很多。
额外注意点
- 确保你的Graph构建正确,Sample Grabber已经连接到摄像头的输出引脚
- 检查PS3Eye的驱动是否安装正确(虽然AMcap能识别,但DirectShow的Graph可能需要特定的引脚配置)
- 处理线程安全问题:如果回调在后台线程运行,访问帧数据时要加锁
内容的提问来源于stack exchange,提问作者wen dai

