我正在使用 Qt 4.5 开发一个图形应用程序并将图像放入 QPixmapCache,我想对此进行优化,以便如果用户插入已经在缓存中的图像,它将使用它。
现在每个图像都有一个唯一的 id,这有助于优化自己的绘画事件。但是我意识到,如果我可以计算图像的哈希值,我可以查找缓存以查看它是否已经存在并使用它(当然这对重复对象更有帮助)。
我的问题是,如果它的 QPixmap 很大,它的哈希计算会减慢速度还是有更快的方法?
对此有几点评论:
如果您要生成像素图的哈希/缓存键,那么您可能希望跳过 QPixmapCache 并直接使用 QCache。这将消除使用 QStrings 作为键的一些开销(除非您还想使用文件路径来定位项目)
从 Qt4.4 开始,QPixmap 有一个与之关联的“散列”值(参见QPixmap::cacheKey())。文档声称“如果不同的 QPixmap 对象引用相同的内容,它们只能具有相同的缓存键。” 然而,由于 Qt 使用共享数据复制,这可能只适用于复制的像素图,而不适用于从同一图像加载的两个不同的像素图。一些测试会告诉你它是否有效,如果有效,它会让你轻松获得哈希值。
如果你真的想通过删除重复来做一个很好的、相当快速的缓存,你可能想看看你自己的数据结构,它根据大小、颜色深度、图像类型和诸如此类的东西进行排序。那么你只需要在找到具有相同尺寸,位深度等的相同类型的图像后,对实际图像数据进行哈希处理。当然,如果你的用户通常打开很多具有相同内容的图像,它就不会一点帮助都没有。
性能:不要忘记 Qt 在 4.5 中添加的基准测试内容,它可以让您比较各种散列想法并查看哪个运行得最快。我还没有检查它,但它看起来很整洁。
以防万一有人遇到这个问题(并且在散列事物方面不是非常有经验,尤其是像图像这样的东西),这是一个非常简单的解决方案,我用于散列 QPixmap 并将它们输入查找表以供以后比较:
qint32 HashClass::hashPixmap(QPixmap pix)
{
QImage image = pix.toImage();
qint32 hash = 0;
for(int y = 0; y < image.height(); y++)
{
for(int x = 0; x < image.width(); x++)
{
QRgb pixel = image.pixel(x,y);
hash += pixel;
hash += (hash << 10);
hash ^= (hash >> 6);
}
}
return hash;
}
这是散列函数本身(如果您希望减少冲突,可以将其散列到 qint64 中)。如您所见,我将像素图转换为 QImage,并简单地遍历其尺寸并对每个像素执行非常简单的一次一个哈希并返回最终结果。有很多方法可以改进此实现(请参阅此问题的其他答案),但这是需要完成的基本要点。
OP 提到了他将如何使用这个散列函数来构造一个查找表,以便以后比较图像。这需要一个非常简单的查找初始化函数——像这样:
void HashClass::initializeImageLookupTable()
{
imageTable.insert(hashPixmap(QPixmap(":/Image_Path1.png")), "ImageKey1");
imageTable.insert(hashPixmap(QPixmap(":/Image_Path2.png")), "ImageKey2");
imageTable.insert(hashPixmap(QPixmap(":/Image_Path3.png")), "ImageKey2");
// Etc...
}
我在这里使用了一个名为 imageTable 的 QMap,它需要在类中声明如下:
QMap<qint32, QString> imageTable;
然后,最后,当您想将图像与查找表中的图像进行比较时(即:“在我知道的图像中,这是什么图像,是这个特定的图像吗?”),您只需调用散列函数图像(我假设它也将是一个 QPixmap)和返回的 QString 值将允许您弄清楚这一点。像这样的东西会起作用:
void HashClass::compareImage(const QPixmap& pixmap)
{
QString value = imageTable[hashPixmap(pixmap)];
// Do whatever needs to be done with the QString value and pixmap after this point.
}
就是这样。我希望这对某人有所帮助——它会为我节省一些时间,尽管我很高兴有解决这个问题的经验。
哈希计算应该很快(如果不涉及磁盘 I/O,则在 100 MB/s 以上),具体取决于您使用的算法。在散列之前,您还可以进行一些快速测试以筛选出潜在的候选者 - fe 图像必须具有相同的宽度和高度,否则比较它们的散列值是没有用的。
当然,您还应该保留插入图像的哈希值,这样您只需为新图像计算哈希值,而不必为缓存的图像再次计算它。
如果图像足够不同,那么不散列整个图像而是散列较小的缩略图或图像的一部分(fe 前 10 行和后 10 行)可能就足够了,这会更快,但会导致更多的冲突。
我假设您正在谈论实际计算图像数据的哈希值,而不是获取 QT 生成的唯一 ID。
根据您的图像,您可能不需要遍历整个图像来计算哈希值。也许只读取前 10 个像素?第一条扫描线?
也许从整个图像中伪随机选择像素?(使用已知种子,以便您可以重复序列)不要忘记将图像的大小也添加到哈希中。