OpenCv读/写视频色差

axe*_*het 3 c++ video opencv

我试图简单地使用 openCV 打开视频,处理帧并将处理后的帧写入新的视频文件。

我的问题是,即使我根本不处理帧(只是打开视频,使用 VideoCapture 读取帧并使用 VideoWriter 将它们写入新文件),输出文件看起来比输入更“绿色”。

例子

可以在任何 openCV 教程中找到执行此操作的代码,没什么特别的。

我在Windows 10上使用openCV c++ 4.4.0。我通过opencv_videoio_ffmpeg440_64.dll将openCV与ffmpeg一起使用。输入视频是mp4。我使用 huffyuv 编解码器将输出编写为 .avi :

m_video_writer.reset(new cv::VideoWriter(m_save_video_path.toStdString(), cv::VideoWriter::fourcc('H', 'F', 'Y', 'U'), // lossless compression
            m_model->getFps(), cv::Size(m_frame_size.width(), m_frame_size.height())));
Run Code Online (Sandbox Code Playgroud)

我尝试了许多其他编解码器,问题仍然存在。

像素的差异很小,值不是恒定的,但总是以相同的方式变化:蓝色通道较低,红色和绿色通道较高。

奇怪的事实:当我用 opencv 打开输入或输出视频时,矩阵实际上完全相同。所以我想问题出在阅读上?

以下是使用 Windows Media Playre (MPC-HC) 导出的每个视频文件的属性。

原来的

VS

输出

我应该调查什么?谢谢 !!

完整代码在这里(复制我视频的前 100 帧):

VideoCapture original("C:/Users/axelle/Videos/original.MP4");

    int frame_height = original.get(CAP_PROP_FRAME_HEIGHT);
    int frame_width = original.get(CAP_PROP_FRAME_WIDTH);
    int fps = original.get(CAP_PROP_FPS);

    VideoWriter output("C:/Users/axelle/Videos/output.avi", VideoWriter::fourcc('H', 'F', 'Y', 'U'),
        fps, cv::Size(frame_width, frame_height));

    int count = 0;
    while (count < 100)
    {
        count++;

        Mat frame;
        original >> frame;
        if (frame.empty())
        {
            break;
        }

        //imshow("test", frame);
        //waitKey(0);

        output.write(frame);
    }

    original.release();
    output.release();
Run Code Online (Sandbox Code Playgroud)

注意:颜色的差异已经可以在 imshow 中看到。

Rot*_*tem 6

VideoCapture使用 FFmpeg 后端读取视频帧时,OpenCV 存在错误。

当 H.264 视频流标记为BT.709颜色标准时,该错误会导致“颜色偏移”。


这个主题太重要了,不能不回答……
这篇文章的重要部分是重现问题,并证明问题是真实的。

我找到的解决方案是选择 GStreamer 后端而不是 FFmpeg 后端。建议的解决方案有缺点(例如需要构建支持 GStreamer 的 OpenCV)。

笔记:

  • 在 Windows 10 下使用 OpenCV 4.53 可以重现该问题。
    在 Ubuntu 18.04 下(在 Python 中使用 OpenCV)也可以重现该问题。
    该问题适用于 BT.709 颜色标准的“全范围”和“有限范围”。

构建合成视频模式来重现问题:
我们可以使用FFmpeg命令行工具创建一个合成视频用作输入。
以下命令生成具有 H.264 编解码器和 BT.709 颜色标准的 MP4 视频文件:

ffmpeg -y -f lavfi -src_range 1 -color_primaries bt709 -color_trc bt709 -colorspace bt709 -i testsrc=size=192x108:rate=1:duration=5 -vcodec libx264 -crf 17 -pix_fmt yuv444p -dst_range 1 -color_primaries bt709 -color_trc bt709 -colorspace bt709 -bsf:v h264_metadata=video_full_range_flag=1:colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 bt709_full_range.mp4
Run Code Online (Sandbox Code Playgroud)
  • 上面的命令使用 yuv444p 像素格式(而不是 yuv420p)来获得更纯的颜色。
  • 参数-bsf:v h264_metadata=video_full_range_flag=1:colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1使用比特流过滤器将 H.264 流标记为“全范围”BT.709。

使用MediaInfo工具,我们可以查看以下颜色特征:

ffmpeg -y -f lavfi -src_range 1 -color_primaries bt709 -color_trc bt709 -colorspace bt709 -i testsrc=size=192x108:rate=1:duration=5 -vcodec libx264 -crf 17 -pix_fmt yuv444p -dst_range 1 -color_primaries bt709 -color_trc bt709 -colorspace bt709 -bsf:v h264_metadata=video_full_range_flag=1:colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 bt709_full_range.mp4
Run Code Online (Sandbox Code Playgroud)

使用 OpenCV 捕获视频

以下 C++ 代码抓取第一帧,并将其保存到1.png图像文件:

#include "opencv2/opencv.hpp"

void main()
{   
    cv::VideoCapture cap("bt709_full_range.mp4");

    cv::Mat frame;    
    cap >> frame;

    cv::imwrite("1.png", frame);

    cap.release();    
}
Run Code Online (Sandbox Code Playgroud)

我们还可以使用以下Python代码:

colour_range:             Full 
colour_primaries:         BT.709 
transfer_characteristics: BT.709 
matrix_coefficients:      BT.709 
Run Code Online (Sandbox Code Playgroud)

使用 FFmpeg转换bt709_full_range.mp4为图像序列:

ffmpeg -i bt709_full_range.mp4 -pix_fmt rgb24 %03d.png
Run Code Online (Sandbox Code Playgroud)

第一个“提取”帧的文件名是001.png.


比较结果:

  • 左边是1.png(OpenCV的结果)
  • 右边是001.png(FFmpeg命令行工具的结果)

在此输入图像描述 在此输入图像描述

正如你所看到的,颜色是不同的。

  • OpenCV 的红色像素的值为 RGB = [232, 0, 3]
  • FFmpeg 的红色像素的值为 RGB = [254, 0, 0]
    原始RGB值可能是[255, 0, 0](由于颜色转换,值为254)。

正如你所看到的,OpenCV 颜色是错误的!


解决方案- 选择 GStreamer 后端而不是 FFmpeg 后端:

默认的 OpenCV 版本不包括 GStreamer 支持(至少在 Windows 中)。

您可以使用以下说明通过 GStreamer 构建 OpenCV。


以下是使用 GStreamer 后端抓取第一帧的 C++ 代码示例:

void main()
{   
    cv::VideoCapture cap("filesrc location=bt709_full_range.mp4 ! decodebin ! videoconvert ! appsink", cv::CAP_GSTREAMER);

    cv::Mat frame;    
    cap >> frame;

    cv::imwrite("1g.png", frame);

    cap.release();
}
Run Code Online (Sandbox Code Playgroud)

结果:

  • 左边是1g.png(OpenCV使用GStreamer的结果)
  • 右边是001.png(FFmpeg命令行工具的结果)

在此输入图像描述 在此输入图像描述

使用 GStreamer 的 OpenCV 的红色像素值为 RGB = [254, 0, 1]。(由于颜色转换,蓝色为 1,而不是 0)。


结论:

  • 使用 GStreamer 后端(而不是 FFmpeg)后端似乎可以解决“颜色偏移”问题。
  • OpenCV 用户需要注意颜色偏移问题。
  • 希望 OpenCV 开发人员(或 FFmpeg 插件开发人员)能够解决该问题。