【问题标题】:C++, OpenCV: Fastest way to read a file containing non-ASCII characters on windowsC++、OpenCV:在 Windows 上读取包含非 ASCII 字符的文件的最快方法
【发布时间】:2016-09-06 19:19:55
【问题描述】:

我正在使用 OpenCV 编写一个可以在 Windows 和 Linux 上运行的程序。现在 OpenCV 的问题是,它的 cv::imread 函数无法处理 Windows 上包含非 ASCII 字符的文件路径。一种解决方法是首先使用其他库(例如 std-libraries 或 Qt)将文件读入缓冲区,然后使用 cv::imdecode 函数从该缓冲区中读取文件。这就是我目前所做的。然而,它并不比仅仅使用cv::imread 快得多,也慢得多。我有一个大小约为 1GB 的 TIF 图像。用cv::imread 阅读它大约需要。 1s,用缓冲方法读取大约需要14s。我假设imread 只是读取显示图像所必需的 TIF 部分(无图层等)。要么这个,要么我将文件读入缓冲区的代码不好。

现在我的问题是是否有更好的方法来做到这一点。关于 OpenCV 的更好方法或将文件读入缓冲区的更好方法。

我尝试了两种不同的缓冲方法,一种使用 std 库,另一种使用 Qt(实际上它们在某些事情上都使用 QT)。它们都同样慢。:

方法一

std::shared_ptr<std::vector<char>> readFileIntoBuffer(QString const& path) {

#ifdef Q_OS_WIN
    std::ifstream file(path.toStdWString(), std::iostream::binary);
#else
    std::ifstream file(path.toStdString(), std::iostream::binary);
#endif
    if (!file.good()) {
        return std::shared_ptr<std::vector<char>>(new std::vector<char>());
    }
    file.exceptions(std::ifstream::badbit | std::ifstream::failbit | std::ifstream::eofbit);
    file.seekg(0, std::ios::end);
    std::streampos length(file.tellg());
    std::shared_ptr<std::vector<char>> buffer(new std::vector<char>(static_cast<std::size_t>(length)));
    if (static_cast<std::size_t>(length) == 0) {
        return std::shared_ptr<std::vector<char>>(new std::vector<char>());
    }
    file.seekg(0, std::ios::beg);
    try {
        file.read(buffer->data(), static_cast<std::size_t>(length));
    } catch (...) {
        return std::shared_ptr<std::vector<char>>(new std::vector<char>());
    }
    file.close();
    return buffer;
}

然后从缓冲区中读取图像:

std::shared_ptr<std::vector<char>> buffer = utility::readFileIntoBuffer(path);
cv::Mat image = cv::imdecode(*buffer, cv::IMREAD_UNCHANGED);

方法二

QByteArray readFileIntoBuffer(QString const & path) {
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        return QByteArray();
    }
    return file.readAll();
}

对于图像的解码:

QByteArray buffer = utility::readFileIntoBuffer(path);
cv::Mat matBuffer(1, buffer.size(), CV_8U, buffer.data());
cv::Mat image = cv::imdecode(matBuffer, cv::IMREAD_UNCHANGED);

更新

方法3

此方法使用QFileDevice::map 将文件映射到内存中,然后使用cv::imdecode

            QFile file(path);
            file.open(QIODevice::ReadOnly);
            unsigned char * fileContent = file.map(0, file.size(), QFileDevice::MapPrivateOption);
            cv::Mat matBuffer(1, file.size(), CV_8U, fileContent);
            cv::Mat image = cv::imdecode(matBuffer, cv::IMREAD_UNCHANGED);

但是,这种方法也没有比其他两种方法缩短时间。我也做了一些时间测量,发现读取内存中的文件或者映射到内存其实并不是瓶颈。花费大部分时间的操作是cv::imdecode。我不知道为什么会这样,因为在同一张图片上使用cv::imread 只需要一小部分时间。

可能的解决方法

我尝试使用以下代码在 Windows 上为包含非 ascii 字符的文件获取 8.3 路径名:

QString getShortPathname(QString const & path) {
#ifndef Q_OS_WIN
    return QString();
#else
    long length = 0;
    WCHAR* buffer = nullptr;
    length = GetShortPathNameW(path.toStdWString().c_str(), nullptr, 0);
    if (length == 0) return QString();
    buffer = new WCHAR[length];
    length = GetShortPathNameW(path.toStdWString().c_str(), buffer, length);
    if (length == 0) {
        delete[] buffer;
        return QString();
    }
    QString result = QString::fromWCharArray(buffer);
    delete[] buffer;
    return result;
#endif
}

但是,我必须发现 8.3 路径名生成在我的机器上被禁用,所以它可能也在其他机器上。所以我还不能对此进行测试,它似乎也没有提供可靠的解决方法。我也有这个函数没有告诉我 8.3 路径名生成被禁用的问题。

【问题讨论】:

  • 一种有点侵入性的方法是创建一个指向文件的符号链接,然后通过它来代替。如果目标机器上没有禁用 8.3 路径名生成,您也可以调用 GetShortPathName,并使用它。我相信短路径名不包含非 ASCII 字符。
  • 你能测试其他格式,比如 png 吗?
  • @IInspectable Thaks 对于这个想法,这确实是一个选择。 “如果目标机器上没有禁用 8.3 路径名生成”是什么意思? @ Miki 我可以,尽管很难将 png 膨胀到如此大的尺寸。我明天试试。
  • 您可以disable 8.3 file name creation on NTFS partitions。在这种情况下,不能使用GetShortPathName。有关更多详细信息,请参阅Short vs. Long Names
  • 好的,我刚刚检查了我的机器;显然 8.3 路径名生成被禁用。但是,我仍然尝试实现一个获取短路径名的函数。现在,该函数不再返回长度 0(如发生错误时所做的那样),而是返回原始文件路径。现在,我如何确定在我的程序中启用或禁用天气 8.3 路径名生成?无论如何,这个解决方案显然并没有真正帮助。假设在许多系统上禁用了 8.3 路径名生成。我添加了用于问题的代码。

标签: c++ file opencv buffer


【解决方案1】:

OpenCV GitHub 上有一张公开的票:https://github.com/opencv/opencv/issues/4292

其中一个 cmets 提出了一种解决方法,无需使用内存映射文件将整个文件读入内存(在 Boost 的帮助下):

mapped_file map(path(L"filename"), ios::in);
Mat file(1, numeric_cast<int>(map.size()), CV_8S, const_cast<char*>(map.const_data()), CV_AUTOSTEP);
Mat image(imdecode(file, 1));

【讨论】:

  • 不错的主意;但是,这意味着我必须为此添加对我的项目的提升。不过谢谢你的建议,我会记住的。
  • @user1488118:你可以使用QFileDevice::map(或QFile::map)来创建一个文件映射对象,因为你已经在使用Qt了。
  • 我使用 QFileDevice::map 做了一个快速的实现,但不幸的是它似乎并没有更快。只要有时间,我就必须进行一些详细的时间测量。
  • 我做了一些测量,发现瓶颈是cv::imdecode,而不是图像的缓冲/映射。我相应地更新了问题。
猜你喜欢
  • 2011-04-19
  • 2012-10-17
  • 1970-01-01
  • 2015-12-08
  • 2018-07-13
  • 1970-01-01
  • 1970-01-01
  • 2019-11-26
  • 2021-02-17
相关资源
最近更新 更多