1 关于对Mediapipe C++ SDK使用不当造成的内存泄漏内存溢出问题的记录

最近在对我的开源项目:https://github.com/HW140701/GoogleMediapipePackageDll进行性能测试的时候发现,检测一帧视频帧的检测延时很长,一帧大概有20-40ms,所以开始了故障代码排查。

定位到的故障代码如下:

    // 1 视频输出结果帧
    mediapipe::Packet packet;
    if (!m_pVideoPoller->Next(&packet))
    {
        return absl::InvalidArgumentError("no next packet");
    }
    if (show_result_image)
    {
        // 从视频输出获取mediapipe::ImageFrame结果
        auto& output_frame = packet.Get<mediapipe::ImageFrame>();

        // 转换mediapipe::ImageFrame为cv::Mat
        cv::Mat output_frame_mat = mediapipe::formats::MatView(&output_frame);

        // 显示cv::Mat结果
        cv::cvtColor(output_frame_mat, output_frame_mat, cv::COLOR_RGB2BGR);
        cv::Mat dst;
        cv::resize(output_frame_mat, dst, cv::Size(output_frame_mat.cols, output_frame_mat.rows));
        cv::imshow("MediapipeHolistic", dst);
        cv::waitKey(1);
    }

上述代码为什么会如此耗时呢?因为获取视频结果帧的耗时很长,性能很差,这一句m_pVideoPoller->Next(&packet)代码获取结果视频帧耗时很长。在正常情况下我们是不需要结果视频帧输出的,只是需要在显示结果视频的时候再输出,所以为了提升性能,将上述代码修改如下:

    // 1 视频输出结果帧
    if (show_result_image)
    {
        mediapipe::Packet packet;

        if (!m_pVideoPoller->Next(&packet))
        {
            return absl::InvalidArgumentError("no next packet");
        }

        // 从视频输出获取mediapipe::ImageFrame结果
        auto& output_frame = packet.Get<mediapipe::ImageFrame>();

        // 转换mediapipe::ImageFrame为cv::Mat
        cv::Mat output_frame_mat = mediapipe::formats::MatView(&output_frame);

        // 显示cv::Mat结果
        cv::cvtColor(output_frame_mat, output_frame_mat, cv::COLOR_RGB2BGR);
        cv::imshow("MediapipeHolistic", output_frame_mat);
    }

修改之后,确实性能有了很大的提升,处理一帧从20-40ms提升到了0ms,但是经过测试,随着处理的视频帧越多,内存一直暴涨到100%,然后程序退出,上述代码出现了严重的内存泄漏和内存溢出问题。

1.2 内存溢出问题的定位与修复

其实最开始出现内存泄漏的问题我也是百思不得骑解,经过两天白用功的试探修改之后还是没有找到问题所在,最后不得不静下心来好好看修改的代码。代码改动的地方就是我们只想要在显示视频帧的时候才去获取结果视频帧,但是我们在初始化模型的时候已经使用m_Graph.AddOutputStreamPoller将video添加到Mediapipe的图中去了,所以,不管这一帧我们是否设置显示输出结果视频帧,结果都会压入到m_pVideoPoller的结果队列中去,而我们又设置不显示结果视频,所以没有办法通过m_pVideoPoller->Next(&packet)拿到packet并释放,导致video的结果视频帧队列越来越大,最后造成内存溢出。

Bingo!

问题解决,修复的代码已经在https://github.com/HW140701/GoogleMediapipePackageDll进行了更新。

1.3 Google Mediapipe对内存溢出问题的解决方案

其实Google在Mediapipe官方文档的Troubleshooting页面也有对内存溢出和内存不足问题进行了解答:https://google.github.io/mediapipe/getting_started/troubleshooting.html#out-of-memory-error

Mediapipe – 关于对Mediapipe C++ SDK使用不当造成的内存泄漏和内存溢出问题的记录-StubbornHuang Blog

官方文档说内存不足的问题可能是正在运行的MediaPipe图表中累积太多数据包导致的(PS.我也是看到这句话才灵光一现)。而发生累积太多数据包的原因有很多,例如

  • (1)图中的一些计算器根本无法跟上来自实时输入流(如摄像机)的数据包的输入速度
  • (2)一些计算器正在等待永远不会到达的数据包

对于问题 (1),可能需要丢弃一些较旧的旧数据包以处理较新的数据包。
对于问题 (2),可能是一个输入流由于某种原因缺少数据包。设备或计算器可能配置错误或仅偶尔产生数据包。这可能会导致下游计算器等待许多永远不会到达的数据包,这反过来又会导致数据包在它们的一些输入流上累积。

MediaPipe设置CalculatorGraphConfig::max_queue_size通过限制图表的输入来限制在任何输入流上排队的数据包数量。对于实时输入流,在输入流中排队的数据包数量几乎总是为零或一。如果不是这种情况,您可能会看到以下警告消息:

Resolved a deadlock by increasing max_queue_size of input stream

此外,该设置CalculatorGraphConfig::report_deadlock可以设置图运行失败时显示将死锁错误,以便意识到设置max_queue_size进行内存限制。