在OpenCV中加速将图像写入硬盘

Speeding up writing images into hard disk in OpenCV

本文关键字:硬盘 图像 OpenCV 加速      更新时间:2023-10-16

我使用的是一台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的图像大小,将单个图像写入硬盘的时间约为30ms。每个图像都是单通道(灰度),但我在硬盘上用.jpg格式写。硬盘中每个图像的大小大约为500K字节。

由于我在大约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--;
}
}
}

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

提前谢谢。

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

你可以做的事情:

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

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

大多数其他答案完全没有切中要害,因为它们忽略了你想要做的事情的物理现实:带宽,无论是在I/O还是处理方面,都是至关重要的。

您是否在实际情况下验证了系统上可用的存储带宽将此流存储在操作系统所在的驱动器上通常是个坏主意,因为其他应用程序的要求会占用您的带宽。请记住,在具有5ms寻道的现代50兆字节/秒硬盘驱动器上,一次寻道会花费0.25兆字节的带宽,这是相当乐观的,因为现代"普通"硬盘驱动器平均读取速度更快,寻道速度较慢。我想说,每一次搜索损失1MByte是对去年消费者硬盘的保守估计。

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

  2. 如果你可以烧一些严肃的CPU或GPU进行压缩,但仍然想要无损存储,那么JPEG2000是默认的选择。如果你使用GPU实现,它会让你的CPU单独做其他事情。我认为预期的带宽减少是2倍,所以RAID 0将有足够的带宽可用。这将是使用它的首选方式——它将非常健壮,无论系统在做什么,都不会丢失任何帧(当然是合理的)。

  3. 如果你可以接受有损压缩,那么现成的jpeg库就可以了。你可能想要将大小缩小4倍,这样操作系统所在的硬盘就可以处理12.5Mbytes/s的数据流。

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

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

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

压缩是这里的关键。

Imwrite文档

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

对于PNG,它可以是压缩级别(CV_IMWRITE_PNG_compression)从0到9。A.越高的值意味着更小的尺寸和更长的压缩时间。违约值为3。

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

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

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

如果你不在乎磁盘大小,我会选择.bmp格式,它几乎比写png快10倍,比写jpg快6倍。

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

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

当然,你也可以看看更重的压缩算法,但这不在Qt的范围内,Qt交易可能只是对接算法。

您应该有一个要处理的图像队列。您应该有一个捕获线程来捕获图像并将其放置在队列中。您应该有一些压缩/写入线程,可以从队列中取出图像并对其进行压缩/写入。

现在CPU有多个核心是有原因的——所以你不必在开始下一件事之前完成一件事。

如果您认为这就是您正在做的事情,并且您仍然看到相同的问题,请向我们展示您的代码。你很可能做得不对。

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