加速OpenCV中将图像写入硬盘的过程

7

我正在使用一个 50 帧每秒的相机(在 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格式写入它们。每个图像在硬盘上的大小约为500K字节。
由于我大约在20毫秒内捕获一帧,因此我无法实时将它们全部写入硬盘。我使用Qthread编写了我的代码并创建了一个队列来查看是否有改进,但结果仍然相同,只是内存开销而已。
是否可能改变这种情况,或者使用其他库更快地将这些图像写入硬盘?如果有Qt解决方案,我也会更喜欢使用它...
此外,我需要将每个单独的帧写入硬盘,因此请不要建议使用Motion压缩算法,因为它们不适用于我的情况....
代码: Mainwindow.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”标签。 - László Papp
1
然后,请将您想要的内容写入问题中。我无法读取您的思维。 :) - László Papp
PANAHI,为什么不使用记录帧之间像素差异的MPEG流,而不是始终发送图像?你甚至可以只记录差异,而不是整个图像。 - László Papp
这更像是一个算法问题而不是编程问题,因此也许数学子站可能更适合这个问题。 - László Papp
2
你没有说明:你需要JPEG输出吗?因为大部分时间都会被压缩所消耗(或者你有一个16兆字节/秒的驱动器)。图像流是2048x1080x1x50 = 110兆字节/秒。对于2013年,大多数旋转磁盘驱动器都可以支持该写入吞吐量。保存为BMP格式。 - jdr5ca
显示剩余6条评论
5个回答

5
让我们来看一下:如果您在以原始格式写入图像,则 2048*1080*3(通道数)*50 fps 约为 316MB/s。如果您使用JPEG格式,取决于压缩参数,您可能会得到大量的数据减少,但如果是1/5,您仍然要向硬盘驱动器写入大量数据,特别是在笔记本电脑上使用5400 rpm的情况下。
以下是您可以采取的措施:
  1. 如David Schwartz所建议的那样,您应该使用队列和多个线程。
  2. 如果您要有效地写入图像序列,请保存视频。数据压缩更多,写入磁盘更快。
  3. 检查当前设备的规格,并估算可以写入的最大图像大小。选择符合该大小约束的压缩参数。

队列真的有什么帮助吗?原帖作者的观点是他无法在获取间隔内完成此操作。 - László Papp
1
根据他使用的图像格式,调用cv::imwrite函数包含压缩和写入硬盘两个组件。压缩过程可以通过使用辅助线程来提高效率。 - user1906
1
@DavidSchwartz:您正在解释一般的线程。如果您认为他有编码问题,请在评论中要求详细信息。在您能够指出确切问题之前,最好避免回答。 - László Papp
1
小细节:楼主指定了50帧每秒,而不是30。 - user2802841
我已经使用Qthread编写了我的代码,并创建了一个队列来查看是否有任何改进,但结果是相同的,只是多了一点内存开销。请考虑暂时删除您的答案。如果您有更具体的问题指出,稍后可以恢复它。 - László Papp
显示剩余6条评论

3

这里的关键在于压缩。

Imwrite文档

对于JPEG格式,可以使用从0到100的质量值(CV_IMWRITE_JPEG_QUALITY),数字越高表示越好。默认为95。

对于PNG格式,可以使用从0到9的压缩级别(CV_IMWRITE_PNG_COMPRESSION)。数字越高表示文件大小越小压缩时间越长。默认值为3。

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

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

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

如果您不关心磁盘大小,我建议使用.bmp格式,因为它的速度几乎比写入.png快10倍,比写入.jpg快6倍。


编写BMP图像的时间大于两个!我认为你在这里犯了一个错误! - PsP
1
我认为你的代码中有一些错误。我使用kernprof进行检查,发现写入bmp格式比写入jpg或png格式更快。 - Ankur Jain

3
通常有多种解决方案可行,但是您需要指定图像的格式 - 灰度是什么?8位?12位?16位?大多数其他答案完全忽略了您试图做的事情的物理实际情况:带宽在I/O和处理方面都非常重要。您是否验证了系统中可用的存储带宽,在实际条件下?将此流存储在与操作系统相同的驱动器上通常是一个坏主意,因为由于其他应用程序的要求而导致的寻道会侵占您的带宽。请记住,在现代50+Mbyte/s硬盘上,每个5ms寻道成本为0.25MBytes带宽,这相当乐观,因为现代“普通”硬盘平均读取速度更快,寻道速度较慢。我认为,对于昨天的消费者驱动器,每次寻道损失1MB是一个保守的估计。
  1. 如果您需要编写原始帧并且不想以无损方式压缩它们,那么您需要一个支持所需带宽的存储系统。假设为8位灰度,每帧将会有2MB大小,在50Hz下,即100MB/s。两个当代现成硬盘的条带RAID 0阵列应该能够轻松处理。

  2. 如果您可以承受一些CPU或GPU的压缩负担,但仍需要无损存储,则JPEG2000是默认选择。如果使用GPU实现,它将为其他任务留出CPU资源。我认为预期的带宽降低率为2倍,因此您的RAID 0将有足够的带宽。这将是首选的方法-它非常稳健,并且无论系统正在执行什么其他操作(当然要合理),都不会丢失任何帧。

  3. 如果您可以接受有损压缩,则现成的jpeg库可以解决问题。您可能需要4倍的尺寸缩小,产生的12.5MB/s数据流可以由操作系统所在的硬盘处理。

关于实现:如果没有压缩,两个线程就足够了。一个线程捕获图像,另一个线程将它们转储到驱动器中。如果与单个线程相比没有看到任何改进,则仅是由于驱动器的带宽限制。如果使用GPU进行压缩,则处理压缩的一个线程就足够了。如果使用CPU进行压缩,则需要与核心数相同的线程。
存储图像差异完全没有问题,事实上,JPEG2k非常喜欢这样做,如果你很幸运,可以得到总体2倍的压缩改进(总因子为4倍)。你要做的就是为每个完整存储的参考帧存储一堆差异帧。比率仅基于之后处理的需求 - 你正在权衡数据丢失的弹性和交互式处理延迟与减少存储时间带宽之间的折衷。
我会说1:5到1:50之间的比率都是合理的。对于后者,参考帧的丢失会使1秒钟的数据失效,并且在数据的任何位置进行随机查找平均需要读取一个参考帧和24个增量帧,以及解压缩25帧的成本。

谢谢...我会尝试您提出的解决方案,看看是否有效...哦,我有一个关于在操作系统驱动器中写入的解释的问题(速度限制为25 MB/s)...我只有一个驱动器,我的操作系统是Ubuntu 12.04?您认为这可能是我的程序的罪魁祸首吗?我以为在基于Linux的操作系统中,不会有这样的问题。 - PsP
@PANAHI:操作系统与此无关。这是您使用的硬盘的物理限制。您需要检查驱动器的实际带宽!无论使用哪种基准测试,都要确保使用直接驱动器访问,因为缓存的存在会给您带来受缓存数据影响的结果。 - Kuba hasn't forgotten Monica
这基本上是压缩和写入/硬盘速度之间的权衡,但还有一件事要补充:操作系统可能会带来一些额外的问题。我曾经在Debian环境中想要写入30 fps,一开始很顺利,直到某个随机时间出现了许多图像丢失(可能是缓存/碎片整理问题或其他任何问题,仍然不清楚)。添加一个线程队列就可以让它重新工作了。 - Micka
@PANAHI:你还必须检查OpenCV实际写入磁盘的图像大小。我们只是假设它是单通道图像,但由于某些原因可能存储在三个(或四个!)通道中,从而使必要的带宽增加三倍或四倍。目前,你根本不知道发生了什么-你既不知道图像的真实大小,也不知道磁盘可以处理的写入速度。 - Kuba hasn't forgotten Monica

0

你应该有一个图像队列来进行处理。你应该有一个捕获线程,它捕获图像并将它们放入队列中。你应该有几个压缩/写入线程,它们将图像从队列中取出并进行压缩/写入。

现在CPU有多个核心是有原因的——这样你就不必在开始下一个任务之前完成一个任务。

如果你认为这就是你正在做的事情,但仍然遇到同样的问题,请向我们展示你的代码。你很可能使用了错误的方法。

更新:正如我所怀疑的那样,你正在以一种无法实现使用线程的目标的方式使用线程。我们使用线程的整个目的是同时压缩多个图像,因为我们知道压缩一个图像需要30毫秒,并且我们知道每30毫秒压缩一个图像是不够的。你使用线程的方式仍然只尝试压缩一个图像。因此,要压缩/写入一个图像需要30毫秒仍然太长了。队列没有任何作用,因为只有一个线程从中读取。


1
@PANAHI 可能是多线程代码出了问题。好处是巨大的——您可以同时使用多个核心压缩图像。当然,您必须限制队列的大小。如果队列不断增长,这意味着您无法跟上图像采样速率,需要丢弃图像或进行调整。 - David Schwartz
@LaszloPapp请解释一下为什么如果正确使用,多线程不能加快压缩速度。我不需要读心术,我只需要看到过相同的问题几十次。(请参见上文,了解它如何改善I/O的原因。) - David Schwartz
尽管OP写道:“我已经使用Qthread编写了我的代码并创建了一个队列来查看是否有改进,但结果仍然相同,只是内存开销增加了”,但您仍然固执于多线程。 - László Papp
@LaszloPapp 因此证明他做错了,因为正如我所解释的那样,如果正确地完成这项工作,将会产生巨大的差异。 - David Schwartz
@DavidSchwartz 谢谢您的评论...但我已经尝试了多线程的方式来运行我的代码。我知道如何适当地进行线程处理,但请相信我,这对我没有起到作用...我在线程中没有使用任何信号量或互斥锁来确保它们同时运行,但最终并没有出现任何显著的改进(速度变快了,但仍然无法弥补20毫秒的帧率)。 - PsP
显示剩余16条评论

0
我建议您查看QtMultimedia模块,如果您处理的是流而不是图像,请尝试将代码转换为MPEG。
这样可以避免一直处理每个像素,因为只有像素差异会被处理。这可能会提高处理性能。
当然,您也可以查看更重的压缩算法,但这超出了Qt的范围,Qt交易可能仅涉及算法的接口。

它们不是你想象中的普通图像...这些相机帧用于检测一些特定的异常生物,它们只存在于一个帧中,然后在下一个帧中消失...并且在存储这些时刻之后,我们将处理这些图像以计算这些生物的数量。 - PsP
抱歉,这并不像你想象的那么简单...差异输出的质量取决于许多因素...已经提出了许多减法算法,并且我们测试了40多种算法!!!此外,某些噪声的存在,如高斯噪声和胡椒盐噪声,可能会干扰我们获取所需对象。 - PsP
我不太明白。你不想压缩,但又想让它更快?这听起来有些矛盾。如果你想加速,可以使用压缩技术,而不是每次都存储每个像素等。否则,我建议重新设计你的项目,采用硬件加速,例如使用具有指令集和VLW架构的专用DSP。 - László Papp
亲爱的Laszlo,我的问题是关于更快地编写函数,而不是压缩(Mytopic说了这个 :) ...我想通过更快地写入硬盘来加速我的算法,而不是避免压缩...压缩不是这里的问题...我希望你现在明白了...运动压缩对我来说不好...我只想在从相机获取它们后尽快存储帧。 - PsP
我不明白你想如何加速一个需要固定时间(磁盘IO)的操作。根据我的知识和图像处理经验,唯一可以改进的地方就是通过压缩写入更少的数据。如果你说“压缩不是问题”,那么你必须接受慢速度。此外,你的问题在SO上是不相关的。SO是用于编程而非算法开发的。有数学SE和其他网站可供使用。 - László Papp
显示剩余10条评论

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