DirectShow ISampleGrabber:采样图像上下颠倒且颜色通道反转

5
我必须使用MS DirectShow从相机中捕获视频帧(我只想要原始像素数据)。 我能够构建Graph/Filter网络(捕获设备过滤器和ISampleGrabber),并实现回调(ISampleGrabberCB)。我接收到适当大小的样本。 但是,它们总是颠倒的(垂直翻转,而不是旋转),颜色通道是BGR顺序(而不是RGB)。 我尝试将BITMAPINFOHEADER中的biHeight字段设置为正值和负值,但它没有任何效果。根据MSDN文档,ISampleGrapper::SetMediaType()无论如何都忽略视频数据的格式块。 这是我看到的内容(使用不同的相机录制,而不是DS),以及DirectShow ISampleGrabber给我的内容:所谓的“RGB”实际上是红色,绿色和蓝色。 以下是我正在使用的代码示例,稍微简化:
// Setting the media type...
AM_MEDIA_TYPE*   media_type   = 0 ;
this->ds.device_streamconfig->GetFormat(&media_type); // The IAMStreamConfig of the capture device
// Find the BMI header in the media type struct
BITMAPINFOHEADER* bmi_header;
if (media_type->formattype != FORMAT_VideoInfo) {
    bmi_header = &((VIDEOINFOHEADER*)media_type->pbFormat)->bmiHeader;
} else if (media_type->formattype != FORMAT_VideoInfo2) {
    bmi_header = &((VIDEOINFOHEADER2*)media_type->pbFormat)->bmiHeader;
} else {
    return false;
}
// Apply changes
media_type->subtype  = MEDIASUBTYPE_RGB24;
bmi_header->biWidth  = width;
bmi_header->biHeight = height;
// Set format to video device
this->ds.device_streamconfig->SetFormat(media_type);
// Set format for sample grabber
// bmi_header->biHeight = -(height); // tried this for either and both interfaces, no effect
this->ds.sample_grabber->SetMediaType(media_type);

// Connect filter pins
IPin* out_pin= getFilterPin(this->ds.device_filter, OUT,  0); // IBaseFilter interface for the capture device
IPin* in_pin = getFilterPin(this->ds.sample_grabber_filter,  IN,  0); // IBaseFilter interface for the sample grabber filter
out_pin->Connect(in_pin, media_type);

// Start capturing by callback
this->ds.sample_grabber->SetBufferSamples(false);
this->ds.sample_grabber->SetOneShot(false);
this->ds.sample_grabber->SetCallback(this, 1);
// start recording
this->ds.media_control->Run(); // IMediaControl interface

我正在检查每个函数的返回类型,没有收到任何错误。

非常感谢您提供的任何提示或想法。

我已经尝试过以下方法:

  1. 为捕获设备过滤器或样本抓取器或两者或两者都设置biHeight字段为负值-没有任何效果。

  2. 使用IGraphBuilder连接引脚-同样的问题。

  3. 在更改媒体类型之前连接引脚-同样的问题。

  4. 通过再次查询过滤器来检查媒体类型是否实际应用-但显然已经应用或至少已存储。

  5. 将图像解释为完全字节反转(最后一个字节先,第一个字节后)-则会水平翻转。

  6. 检查视频摄像头是否存在问题-当我使用VLC(DirectShow捕获)进行测试时,它看起来是正常的。


2
我猜想当你从Sample Grabber获取数据时,你错误地处理了行的顺序。通常情况下,它是从底部到顶部排列的,而你却按相反的顺序处理行,因此出现了问题。 - Roman R.
Roman,谢谢你的回复,但是不可能以正常的行顺序(从顶部开始)接收帧吗?我认为相机也不会以这种方式发送它们。 这也无法解释BRG颜色通道翻转。 由于代码将来也应该适用于其他相机,因此我希望能够弄清楚发生了什么... - Makx
1
“正常”的Windows RGB顺序是从底部到顶部。一些组件可以反转它,但这是一个脆弱的假设。更加稳健的方法是让它保持原始顺序或者强制从底部到顶部。然后,如果需要,可以处理行的实际顺序或自己反转行。我想相机不会让你失望,而且你的代码片段也没有说服我你在Sample Grabber缓冲区中将其置于顶部。 - Roman R.
罗曼,再次感谢你的回答。 “你的代码片段并没有说服我你是从上到下在Sample Grabber缓冲区中完成的” - 这正是问题所在:我试图从“上到下”进行操作,但是我无法让它正常工作。最理想的解决方案是捕获设备立即以这种方式捕获,以避免不必要的翻转。我尝试设置biHeight为负值,根据Windows的说法应该会产生效果,但是它并没有起作用。 另外:我仍然困扰于BGR颜色通道翻转,这似乎只发生在我身上。你能想象出任何原因吗? - Makx
2
大多数捕获设备和转换滤镜都无法进行完整的RGB处理,只有少数滤镜可以实现。 - Roman R.
2个回答

0

我对此的快速解决方案:

void Camera::OutputCallback(unsigned char* data, int len, void *instance_)
{
    Camera *instance = reinterpret_cast<Camera*>(instance_);

    int j = 0;
    for (int i = len-4; i > 0; i-=4)
    {
        instance->buffer[j] = data[i];
        instance->buffer[j + 1] = data[i + 1];
        instance->buffer[j + 2] = data[i + 2];
        instance->buffer[j + 3] = data[i + 3];
        j += 4;
    }

    Transport::RTPPacket packet;

    packet.payload = instance->buffer;
    packet.payloadSize = len;

    instance->receiver->Send(packet);
}

在 RGB32 颜色空间中是正确的,对于其他颜色空间,需要进行代码修正


0

我注意到在使用I420色彩空间时,转向会消失。

此外,大多数当前的编解码器(VP8)都是以原始I/O I420色彩空间格式使用的。

我编写了一个简单的镜像帧函数,使用I420色彩空间。

void Camera::OutputCallback(unsigned char* data, int len, uint32_t timestamp, void *instance_)
{
    Camera *instance = reinterpret_cast<Camera*>(instance_);

    Transport::RTPPacket packet;

    packet.rtpHeader.ts = timestamp;

    packet.payload = data;
    packet.payloadSize = len;

    if (instance->mirror)
    {
        Video::ResolutionValues rv = Video::GetValues(instance->resolution);
        int k = 0;

        // Chroma values
        for (int i = 0; i != rv.height; ++i)
        {
            for (int j = rv.width; j != 0; --j)
            {
                int l = ((rv.width * i) + j);
                instance->buffer[k++] = data[l];
            }
        }

        // U values
        for (int i = 0; i != rv.height/2; ++i)
        {
            for (int j = (rv.width/2); j != 0; --j)
            {
                int l = (((rv.width / 2) * i) + j) + rv.height*rv.width;
                instance->buffer[k++] = data[l];
            }
        }

        // V values
        for (int i = 0; i != rv.height / 2; ++i)
        {
            for (int j = (rv.width / 2); j != 0; --j)
            {
                int l = (((rv.width / 2) * i) + j) + rv.height*rv.width + (rv.width/2)*(rv.height/2);
                if (l == len)
                {
                    instance->buffer[k++] = 0;
                }
                else
                {
                    instance->buffer[k++] = data[l];
                }
            }
        }

        packet.payload = instance->buffer;
    }

    instance->receiver->Send(packet);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接