【发布时间】: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 路径名生成。我添加了用于问题的代码。