【问题标题】:Speeding up writing images into hard disk in OpenCV在 OpenCV 中加快将图像写入硬盘的速度
【发布时间】:2014-01-22 18:24:54
【问题描述】:

我正在使用 50 fps 的相机(在 Ubuntu 环境和 Qt 框架中),每 20 毫秒我会处理一个帧。

我编写了一个代码来从相机中读取图像,然后将它们存储在硬盘中。

while(3.14)
{
 cv::Mat Camera_Image = Capture_Image();
 double T1 = (double)cv::getTickCount();
 cv::imwrite (STORE_ADDRESS,Camera_Image);
 T1 = (((double)cv::getTickCount() -T1)*1000)/cv::getTickFrequency();
 print(T1);
}

当我看到输出时,对于 2048*1080 的图像大小,将单个图像写入硬盘的时间约为 30 毫秒。每个图像都是单通道(灰度),但我将它们以 .jpg 格式写入硬盘。硬盘中每张图片的大小约为 500Kbytes。

由于我在大约 20 毫秒内捕获一帧,因此我无法将它们全部实时写入硬盘。我已经使用 Qthread 编写了我的代码并创建了一个队列以查看是否有任何改进,但结果是相同的,这只是内存开销。

是否有可能改变这种情况,或者使用其他库更快地将这些图像写入硬盘?如果可用,我也更喜欢 Qt 解决方案...

另外我需要将每一帧都写入硬盘,所以请不要建议使用运动压缩算法,因为它们不适用于我的情况......

代码: 主窗口.cpp

 Qlist<cv::Mat> FINAL_IM_VEC;
MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow)
{
  ui->setupUi(this);

  IMREAD *IMR = new IMREAD(this);   // an instance of IMREAD Class which reads camera frames
  IMWRITE *IMW = new IMWRITE(this);  // an instance of IMWRITE Class which Writes camera frames into hard disk
  QThread *IMAGE_READ_Thread = new QThread(this);
  QThread *Image_Store_Thread = new QThread(this);
  connect(IMAGE_READ_Thread,SIGNAL(started()),IMR,SLOT(IMREAD_Process()));
  connect(Image_Store_Thread,SIGNAL(started()),IMW,SLOT(IMWrite_Process()));
  IMR.moveToThread(IMAGE_READ_Thread);
  IMW.moveToThread(Image_Store_Thread);
  IMAGE_READ_Thread->start();
  Image_Store_Thread->start();
}

imread.hpp

class IMREAD : public QObject
{
    Q_OBJECT
public:
    explicit IMREAD(QObject *parent = 0);

signals:

public slots:
    void IMREAD_Process();
private:
    bool Stop;
};

imread.cpp

IMREAD::IMREAD(QObject *parent) :
    QObject(parent)
{
  this->Stop = false;
}

void IMREAD::IMREAD_Process()
{

  while(!Stop)
    {
          cv::Mat Image = CAM::Campture_F(25);//wait a maximum of 25 milisecond to grab a new frame
          if(Image.data())
            {
          FINAL_IM_VEC.push_back(Image);
            }
      }
    }

}

imwrite.hpp

#ifndef IMWRITE_H
#define IMWRITE_H
#pragma once
#include <QObject>
class IMWRITE : public QObject
{
    Q_OBJECT
public:
    explicit IMWRITE(QObject *parent = 0);
signals:

public slots:
    void IMWrite_Process();
private:
    bool Stop;
};

imwrite.cpp

IMWRITE::IMWRITE(QObject *parent) :
    QObject(parent)
{
  this->Stop =false;
}
void IMWRITE::IMWrite_Process()
{
    static int counter = 0;
    while(!Stop)
      {
        for(int i = 0 ; i < FINAL_IM_VEC.size() ; i++)
            {
                QString address = "/home/Provisioner/ThreadT/Results/" + QString::number(counter++) + ".jpg";
                cv::imwrite(address.toUtf8().constData(),FINAL_IM_VEC[i]);
                FINAL_IM_VEC.erase(FINAL_IM_VEC.begin() + i);
                i--;
            }
      }

}

由于这只是整个项目的一部分,我已经删除了一些不相关的部分......但它展示了我如何在大图中编写多线程代码......所以如果有任何问题请告知我。

提前致谢。

【问题讨论】:

  • "qt" 标签被移除,因为问题中没有 Qt 代码。
  • 亲爱的 Laszlo 我使用了 qt 标签,因为我希望其他人提出 qt 函数而不是 opencv imwrite 函数 ....
  • 然后,请在问题中写下您希望的内容。我无法读懂你的心思。 :)
  • @EmilioGaravaglia:您也应该扩展问题内容。不看cmets还是不清楚...
  • 您没有声明:您需要 JPEG 输出吗?因为你的大部分时间都被压缩消耗了(或者你有一个 16 Mbyte/s 的驱动器)。图像流为 2048x1080x1x50 = 110 Mbyte/s。对于 2013 年,大多数旋转磁盘驱动器都可以支持该写入吞吐量。另存为 BMP。

标签: c++ qt opencv qthread qtcore


【解决方案1】:

让我们看看:2048*1080*3(通道数)*50 fps ~= 316MB/s,如果您以原始格式写入图像。如果您使用 JPEG,根据压缩参数的不同,您可能会大幅减少,但如果是 1/5,您仍然会向硬盘驱动器写入大量数据,特别是如果您在笔记本电脑上使用 5400rpm .

你可以做的事情:

  1. 正如 David Schwartz 建议的那样,您应该使用队列和多线程。
  2. 如果您正在有效地编写图像序列,请改为保存视频。数据压缩得更多,写入磁盘的速度更快。
  3. 检查您当前设备的斑点并估计您可以写入的图像的最大大小。选择压缩参数以适应该尺寸限制。

【讨论】:

  • 队列真的有什么帮助吗? OP的观点他无法在获取间隔内完成此操作。
  • 根据他使用的图像格式,对cv::imwrite 的调用具有压缩组件和写入硬盘组件。压缩确实受益于使用辅助线程。
  • @LaszloPapp 队列会增加允许的间隔。例如,如果他的写入需要 30 毫秒,并且他需要每 20 毫秒处理一次图像,那么有两个工作人员为其服务的队列会将允许的间隔增加到 40 毫秒,即超过 30 毫秒。
  • @DavidSchwartz:您正在解释一般线程。如果您认为,他有编码问题,请在 cmets 中询问详细信息。在您指出确切的问题之前,最好避免回答。
  • 次要挑剔:OP 指定 50 fps,而不是 30。
【解决方案2】:

通常有多种解决方案,但您需要指定图像的格式 - 灰度什么? 8位? 12 位? 16 位?

大多数其他答案完全忽略了您正在尝试做的事情的物理现实:带宽,无论是在 I/O 和处理方面,都是最重要的。

您是否在实际条件下验证了系统上可用的存储带宽? 将此流存储在操作系统所在的同一驱动器上通常是个坏主意,因为搜索到期其他应用程序的要求会占用您的带宽。请记住,在具有 5ms 寻道的现代 50+Mbyte/s 硬盘驱动器上,一次寻道花费您 0.25MBytes 的带宽,这是相当乐观的,因为现代“普通”硬盘驱动器平均读取速度更快,寻道速度更慢。我会说每次搜索丢失 1MByte 是对过去的消费者驱动器的保守估计。

  1. 如果您需要写入原始帧并且不想以无损方式压缩它们,那么您需要一个能够支持所需带宽的存储系统。假设 8 位灰度,您将转储 2Mbytes/帧,在 50Hz 时为 100Mbytes/s。由两个现代现成驱动器组成的条带化 RAID 0 阵列应该能够毫无问题地应对它。

  2. 如果您可以刻录一些严重的 CPU 或 GPU 进行压缩,但仍需要无损存储,那么 JPEG2000 是默认选择。如果您使用GPU implementation,它将让您的 CPU 独自处理其他事情。我认为预期的带宽减少是 2 倍,因此您的 RAID 0 将有足够的带宽可供使用。这将是使用它的首选方式 - 它非常健壮,无论系统在做什么(当然是在合理范围内),您都不会丢失任何帧。

  3. 如果您对有损压缩没问题,那么现成的 jpeg 库就可以解决问题。您可能希望大小减少 4 倍,由此产生的 12.5Mbytes/s 数据流可以由操作系统所在的硬盘驱动器处理。

关于实现:如果没有压缩,两个线程就足够了。一个线程捕获图像,另一个线程将它们转储到驱动器。如果与单线程相比,您没有看到任何改进,那么这完全是由于您的驱动器的带宽限制。如果使用 GPU 进行压缩,那么一个处理压缩的线程就足够了。如果您使用 CPU 进行压缩,那么您需要与内核一样多的线程。

存储图像差异完全没有问题,事实上 JPEG2k 很喜欢这一点,如果幸运的话,我会获得 2 倍的整体压缩改进(总共 4 倍)。您所做的是为完整存储的每个参考帧存储大量不同的帧。该比率仅基于之后完成的处理需求 - 您正在权衡数据丢失的弹性和交互式处理延迟以减少存储时间带宽。

我会说 1:5 到 1:50 之间的任何比例都是合理的。对于后者,参考帧的丢失会破坏 1s 的数据,并且随机寻找数据中的任何位置平均需要读取参考帧和 24 个增量帧,再加上解压缩 25 个帧的成本。

【讨论】:

  • 谢谢...我会尝试您提出的解决方案,看看它是否有效...哦,我对您在 OS 驱动器中写入的解释有疑问(速度限制为 25 MB/s) ...我只有一个驱动器,我的操作系统是 ubuntu 12.04?你认为这可能是我的程序的邪恶来源吗?我认为在基于 linux 的操作系统中,没有这样的问题
  • @PANAHI:操作系统与它没有太大关系。这是您正在使用的硬盘驱动器的物理限制。您需要检查驱动器的实际带宽!无论您使用什么基准,请确保使用直接驱动器访问,因为缓存的存在会给您带来受缓存数据影响的结果。
  • 这基本上是压缩和写入/高清速度之间的权衡,但还有一件事要补充:操作系统可能会带来一些额外的问题。我有一个 Debian 环境,我想写 30 fps,它运行良好,直到某个随机时间丢弃了许多图像(可能是缓存/碎片整理问题或其他任何问题,仍然不知道)。在那里添加一个线程队列使其再次工作。
  • @PANAHI:您还必须检查 OpenCV 实际写入磁盘的图像大小。我们只是假设它是单通道图像,它可能出于某种原因碰巧存储在三个(或四个!)通道中,从而使必要带宽增加三倍或四倍。目前,您根本不知道发生了什么——您既不知道图像的真实大小,也不知道磁盘可以应对的写入速度。
【解决方案3】:

压缩是这里的关键。

Imwrite docs

对于 JPEG,它可以是从 0 到的质量 (CV_IMWRITE_JPEG_QUALITY) 100(越高越好)。默认值为 95。

对于 PNG,它可以是压缩级别 (CV_IMWRITE_PNG_COMPRESSION) 从 0 到 9。 更高的值意味着更小的尺寸和更长的压缩时间。默认 值为 3。

对于 PPM、PGM 或 PBM,它可以是二进制格式标志 ( CV_IMWRITE_PXM_BINARY ), 0 或 1。默认值为 1。

.bmp 格式不需要压缩,因为它直接写入位图。

总结:图片写入时间 png > jpg > bmp

如果您不关心磁盘大小,我会说使用 .bmp 格式,它比编写 png 快 10 倍,比编写 jpg 快 6 倍。

【讨论】:

  • bmp 的写入时间图像不止这两个!我认为你在这里犯了一个错误!
  • 我认为你的代码有一些错误。我用kernprof检查,写bmp总是比写jpg或png快。
【解决方案4】:

我建议查看QtMultimedia 模块,如果您处理的是流而不是图像,请尝试将您的代码转换为 MPEG。

这将避免一直处理每个像素,因为只会处理像素差异。这可能会提高处理性能。

当然,您也可以看看更强大的压缩算法,但这超出了 Qt 的范围,Qt 交易可能只是与算法的接口。

【讨论】:

  • 谢谢,但我需要在未来访问每一帧...我已将此添加到我的问题中,并将代码放入分析...
  • 为什么需要流的每一帧?听起来很不寻常。能详细点吗?
  • 它们不是你想象的一些正常的图像......这些相机帧用于检测一些特定的异常生物,它们只存在一帧,然后在下一帧消失..存储这些之后时刻,我们将处理这些图像以计算这些生物
  • 你能详细说明为什么你不能用像素差存储做到这一点吗?
  • 很抱歉,这并不像您想象的那么容易......差异输出的质量取决于许多因素..为此提出了许多减法算法,我们测试了 40 多种算法!!!。 ...还存在一些噪声,例如高斯噪声和胡椒噪声以及盐噪声可能会干扰获得我们想要的对象...
【解决方案5】:

您应该有一个要处理的图像队列。您应该有一个捕获线程来捕获图像并将它们放在队列中。您应该有一些压缩/写入线程,将图像从队列中取出并压缩/写入。

如今 CPU 拥有多个内核是有原因的 - 因此您不必在开始下一件事之前完成一件事。

如果您认为这是您正在做的事情并且您仍然遇到同样的问题,请向我们展示您的代码。您很可能这样做不正确。

更新:正如我所怀疑的,您使用线程的方式并没有实现首先使用线程的目标。重点是一次压缩多张图像,因为我们知道压缩一张图像需要 30 毫秒,而且我们知道每 30 毫秒压缩一张图像是不够的。您使用线程的方式,您仍然只尝试一次压缩一个图像。所以压缩/写入图像的 30 毫秒仍然太长。队列没有任何作用,因为只有一个线程从中读取。

【讨论】:

  • 这在某种程度上是这里的伪代码......我已经以多线程方式编写了我的代码......但是没有任何速度提高的迹象......
  • 值得注意的是,创建这个队列是危险的,因为它的大小每秒都会增加......而且它没有任何好处......(内存填充和其他问题)
  • @David,您的回答如何加快磁盘写入速度?我认为 OP 无法做出重大改进。
  • @LaszloPapp “磁盘写入”包括图像压缩。需要时间的不是 I/O。
  • @PANAHI 多线程代码可能有问题。好处是巨大的——您可以使用多个内核同时压缩图像。当然,您确实必须绑定队列。如果它在增长,则意味着您无法跟上图像采样率,需要丢弃图像或对其进行调整。
猜你喜欢
  • 1970-01-01
  • 2021-02-03
  • 2020-02-06
  • 1970-01-01
  • 1970-01-01
  • 2011-06-25
  • 2012-07-09
  • 2015-03-22
  • 1970-01-01
相关资源
最近更新 更多