5

我开发了 DirectShow C++ 应用程序,它成功地将网络摄像头视图预览到提供的窗口中。现在我想从这个实时网络摄像头预览中捕获图像。为此,我使用了图形管理器、ICaptureGraphBuilder2、IMoniker 等。我搜索并找到了以下选项:WIA 和 Sample Grabber。许多人建议使用 SampleGrabber,但根据 MS 的 msdn 文档, SampleGrabber已被弃用,不应使用。而且我不想使用 WIA API。

那么从实时网络摄像头预览中捕获图像的最佳 DirectShow 方法是什么?

4

3 回答 3

4

这是来自 DirectShow.NET 库的 DxSnap 示例的引用:

使用 DirectShow 从捕获设备的 Still 引脚拍摄快照。请注意,MS 鼓励您为此使用 WIA,但如果您想使用 DirectShow 和 C#,请参考以下方法。

请注意,此示例仅适用于将未压缩视频输出为 RBG24 的设备。这将包括大多数网络摄像头,但可能是零电视调谐器。

这是 C# 代码,但你应该明白,因为接口都是一样的。还有其他关于如何Sample Grabber Filter在 C++ 中使用的示例。

Sample Grabber 已被弃用,标头已从几个最新的 SDK 中删除,但运行时组件都存在并且将存在很长时间,否则会破坏大量应用程序(例如托管浏览器中的视频聊天GMail 正在使用 Sample Grabber)。所以基本上 Sample Grabber 仍然是一种从网络摄像头捕获快照的简单方法,或者如果您更喜欢遵循最新的 MS API - 您可能想查看 Media Foundation(2016 年 7 月 9 日更新:新的 Windows Server 安装可能需要一个添加“Media Foundation”和/或“Desktop Experience”功能以使 Media Foundation API 与 DirectShow 和 DirectShow 编辑服务一起可用,Sample Grabber 是其中的一部分。默认安装不提供 qedit。

同样在 C++ 中,您当然不必使用 Sample Grabber Filter。您可以使用 DirectShow BaseClasses 开发自定义过滤器作为自定义转换过滤器或自定义渲染器,它们接受传入的视频源并从 DirectShow 管道导出帧。另一种选择是使用来自旧 SDK 的 Sample Grabber 示例源代码(这不是 OS Sample Grabber 的确切源代码,但它正在做同样的事情)。然而,Windows 附带的 Sample Grabber 仍然是一个不错的选择。

于 2012-07-09T18:20:03.947 回答
1

天啊...这么多虚假信息。如果您在 directshow 图表中进行预览,那么这取决于您要预览的内容。捕获过滤器有 1、2 或 3 个引脚。如果它有 1 个引脚,则很可能是“捕获”引脚(无预览引脚)。为此,如果您想同时捕捉和预览,您应该放入“Smart Tee”过滤器,并将 VMR 连接到预览引脚上,并将“抓帧的东西”从捕捉引脚上连接起来。因为您不想与 DirectShow 的糟糕引脚开始/停止东西混在一起(相反,只是简单地控制整个图形的开始/停止状态)。您不需要使用 SampleGrabber,它是一个非常简单的过滤器,您可以在几个小时内编写它(我应该知道,我是编写它的人)。它' s 只是一个 CTransInPlace 过滤器,您可以为其设置强制媒体类型以使其接受,并且您可以在其上设置回调接口以在收到样本时回调您。实际上,编写一个 NullRenderer 会更简单,它会在收到样本时回调您,您可以很容易地编写它。

如果捕获过滤器有 2 个引脚,它很可能是一个捕获引脚和一个静止引脚。在这种情况下,您仍然需要将 Smart Tee 连接到源的捕获引脚,并且需要从智能三通的预览引脚进行预览,并从智能三通的捕获引脚捕获样本。

(如果您不知道 SmartTee 是什么,它是一个过滤器,可以播放分配器技巧,并且仅在捕获引脚没有被超级阻塞时才将样本发送到预览引脚。它的工作是为 VMR 提供一条路径渲染不会破坏捕获过滤器和捕获过滤器下游过滤器之间的分配器)

如果捕获过滤器同时具有捕获和预览引脚,我想你可以弄清楚你需要做什么。

总之,总结:SampleGrabber 只是一个 CTransInPlaceFilter。您也可以将其编写为 Null Renderer,只需确保在 CheckInputType 中填写一些垃圾内容,并在 DoRenderSample 中回调您的回调。

于 2014-02-13T07:02:57.340 回答
1

Microsoft 网站上列出了如何使用 IVMRWindowlessControl9::GetCurrentImage 捕获帧的示例......这是一种方法:

IBaseFilter*            vmr9ptr; // I'm assuming that you got this pointer already
IVMRWindowlessControl9* controlPtr = NULL;

vmr9ptr->QueryInterface(IID_IVMRWindowlessControl9, (void**)controlPtr);
assert ( controlPtr != NULL );

// Get the current frame
BYTE*   lpDib = NULL;
hr = controlPtr->GetCurrentImage(&lpDib);

// If everything is okay, we can create a BMP
if (SUCCEEDED(hr))
{
    BITMAPINFOHEADER*   pBMIH = (BITMAPINFOHEADER*) lpDib;
    DWORD               bufSize = pBMIH->biSizeImage;

    // Let's create a bmp
    BITMAPFILEHEADER    bmpHdr;
    BITMAPINFOHEADER    bmpInfo;
    size_t              hdrSize     = sizeof(bmpHdr);
    size_t              infSize     = sizeof(bmpInfo);

    memset(&bmpHdr, 0, hdrSize);
    bmpHdr.bfType                   = ('M' << 8) | 'B';
    bmpHdr.bfOffBits                = static_cast<DWORD>(hdrSize + infSize);
    bmpHdr.bfSize                   = bmpHdr.bfOffBits + bufSize;

    // Builder the bit map info.
    memset(&bmpInfo, 0, infSize);
    bmpInfo.biSize                  = static_cast<DWORD>(infSize);
    bmpInfo.biWidth                 = pBMIH->biWidth;
    bmpInfo.biHeight                = pBMIH->biHeight;
    bmpInfo.biPlanes                = pBMIH->biPlanes;
    bmpInfo.biBitCount              = pBMIH->biBitCount;

    // boost::shared_arrays are awesome!
    boost::shared_array<BYTE> buf(new BYTE[bmpHdr.bfSize]);//(lpDib);
    memcpy(buf.get(),                       &bmpHdr,    hdrSize); // copy the header
    memcpy(buf.get() + hdrSize,             &bmpInfo,   infSize); // now copy the info block
    memcpy(buf.get() + bmpHdr.bfOffBits,    lpDib,      bufSize);

    // Do something with your image data ... seriously...
    CoTaskMemFree(lpDib);

} // All done!
于 2012-07-31T01:42:35.433 回答