c++ - 仅计算“核心”QImage 数据(不包括元数据)的 QCryptographicHash
问题描述
我有一堆“JPG”文件,它们只对 EXIF 数据不同。
我想做一个快速检查(使用 Qt 框架),尝试计算“核心”图像数据的哈希值(而不是文件本身,它将包含元数据)。到现在为止还挺好。
这就是我加载图像并计算哈希的方式:
QImage img(R"(D:\Picture.jpg)");
auto data = QByteArray::fromRawData(reinterpret_cast<const char *>(img.constBits()), int(img.sizeInBytes()));
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(data);
qDebug() << hash.result().toHex();
我想将相同的概念扩展到“JPG”以外的文件,因此我将原始 JPG 文件保存为不同的 LOSSLESS 格式(BMP、PNG、TIF),而不改变分辨率。
我这里有问题。BMP、PNG、TIF 图像的Hash给了我相同的结果,但与 JPG 中相同图像的结果不同。
如果我要从 LOSSLES 格式创建 JPG 文件,我可以理解结果。
但反过来呢???
有人可以帮我理解我错在哪里吗?
给出以下代码,我看到:
- 所有 QImage 具有相同的字节大小
- 两张JPG的QByteArray是一样的
- BMP、PNG、TIF 的 QByteArray 相同
- JPG 和 BMP(PNG 和 TIF 也是)的 QByteArray 不相同
// JPG w/o EXIF data
QImage img1(R"(D:\Picture.jpg)");
auto data1 = QByteArray::fromRawData(reinterpret_cast<const char *>(img1.constBits()), int(img1.sizeInBytes()));
// JPG w/ EXIF data
QImage img2(R"(D:\Picture_EXIF.jpg)");
auto data2 = QByteArray::fromRawData(reinterpret_cast<const char *>(img2.constBits()), int(img2.sizeInBytes()));
// BMP
QImage img3(R"(D:\Picture.bmp)");
auto data3 = QByteArray::fromRawData(reinterpret_cast<const char *>(img3.constBits()), int(img3.sizeInBytes()));
// PNG w/o transparency
QImage img4(R"(D:\Picture.png)");
auto data4 = QByteArray::fromRawData(reinterpret_cast<const char *>(img4.constBits()), int(img4.sizeInBytes()));
// TIF (lossles)
QImage img5(R"(D:\Picture.tif)");
auto data5 = QByteArray::fromRawData(reinterpret_cast<const char *>(img5.constBits()), int(img5.sizeInBytes()));
qDebug() << img1.sizeInBytes(); // 23918592
qDebug() << img2.sizeInBytes(); // 23918592
qDebug() << img3.sizeInBytes(); // 23918592
qDebug() << img4.sizeInBytes(); // 23918592
qDebug() << img5.sizeInBytes(); // 23918592
qDebug() << (data1 == data2); // True
qDebug() << (data1 == data3); // False
qDebug() << (data3 == data4); // True
qDebug() << (data3 == data5); // True
qDebug() << img1.format(); // 4 = QImage::Format_RGB32
qDebug() << img2.format(); // 4 = QImage::Format_RGB32
qDebug() << img3.format(); // 4 = QImage::Format_RGB32
qDebug() << img4.format(); // 5 = QImage::Format_ARGB32
qDebug() << img5.format(); // 6 = QImage::Format_ARGB32_Premultiplied
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.reset(); hash.addData(data1); qDebug() << hash.result().toHex(); // c37143639914056add1f90be4bfe780e14500d24f1d3484a087fc1943508157f
hash.reset(); hash.addData(data2); qDebug() << hash.result().toHex(); // c37143639914056add1f90be4bfe780e14500d24f1d3484a087fc1943508157f
hash.reset(); hash.addData(data3); qDebug() << hash.result().toHex(); // 0149c60b883df67ba002d791a1362dbd02ccab09241864341483a16ec0af635d
hash.reset(); hash.addData(data4); qDebug() << hash.result().toHex(); // 0149c60b883df67ba002d791a1362dbd02ccab09241864341483a16ec0af635d
hash.reset(); hash.addData(data5); qDebug() << hash.result().toHex(); // 0149c60b883df67ba002d791a1362dbd02ccab09241864341483a16ec0af635d
最终结论
在随后的测试中,我意识到问题既不是 Qt 也不是我的(最后)代码实现(感谢@Scheff)。BMP、PNG 和 TIF 实际上与原始 JPG 文件不同!
BMP、PNG 和 TIF 文件是通过在Windows Paint中打开原始 JPG 文件并将其保存为这些无损格式而创建的。因此,Windows Paint在读取(或)保存步骤中以某种方式失败。商业软件Duplicate Cleaner也失败了,因为它报告 JPG 文件与 BMP、PNG、TIF 版本 100% 相同。
解决方案
前言:
我认为
data1.append(reinterpret_cast<const char *>(img1.constBits()));
QByteArray
作为用不存储 C 字符串的数据填充 a 的错误方法。
QByteArray::append(const char*)
在 a 中复制 C 字符串很好QByteArray
。它复制数据直到找到一个 0 字节(一个 0 终止符)。0 字节可能在图像的原始数据中的任何位置,也可能不在任何位置。在第一种情况下,复制的数据太少,在后一种情况下,会考虑超出范围的数据。两者都是无意的。
顺便提一句。甚至不需要复制图像数据(可能很大)。
我做了一个样本直接比较两个QImage
s的原始数据,包括预先检查大小和格式是否匹配。
我的样品testQImageRawCmp.cc
#include <QtWidgets>
bool equalImgData(const QImage &qImg1, const QImage &qImg2, int eps = 0)
{
// test properties
#define TEST_PROP(PROP) \
do if (qImg1.PROP != qImg2.PROP) { \
qDebug() << "qImg1."#PROP" != qImg2."#PROP; \
return false; \
} while(false)
TEST_PROP(width());
TEST_PROP(height());
TEST_PROP(format());
#undef TEST_PROP
// test raw data
const uchar *const data1 = qImg1.bits();
const uchar *const data2 = qImg2.bits();
const int bytesPerLine1 = qImg1.bytesPerLine();
const int bytesPerLine2 = qImg2.bytesPerLine();
const int nBits = qImg1.depth() * qImg1.width();
const int nBytes = (nBits + 7) / 8;
assert(nBytes <= bytesPerLine1);
assert(nBytes <= bytesPerLine2);
for (int y = 0; y < qImg1.height(); ++y) {
const uchar *row1 = data1 + y * bytesPerLine1;
const uchar *row2 = data2 + y * bytesPerLine2;
for (int x = 0; x < nBytes; ++x) {
if (abs(row2[x] - row1[x]) > eps) {
qDebug() << "Row" << y << "byte" << x << "difference:" << (row2[x] - row1[x]);
return false;
}
}
}
return true;
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
// load sample data
QImage img1("Picture.jpg");
QImage img2("Picture.bmp");
#if 0 // U.B.
// Juttas test:
QByteArray data1; data1.append(reinterpret_cast<const char *>(img1.constBits()));
QByteArray data2; data2.append(reinterpret_cast<const char *>(img2.constBits()));
#endif // 0
// My test:
if (!equalImgData(img1, img2, 3)) {
qDebug() << "Images not equal!";
} else {
qDebug() << "Images equal.";
}
}
我用 OP 提供的样本数据测试了这个程序:
并得到以下输出:
Qt Version: 5.13.0
Row 0 byte 60 difference: 1
Images not equal!
我必须承认,这段代码的第一个版本刚刚报告了不等式。
然后,我尝试制作自己的反样本并将其转换Picture.bmp
为Picture.bmp.jpg
GIMP(我认为质量设置为 100% 的无损)。这导致了Row 0 byte 0
. 哎呀!
然后,我变得好奇并修改了代码,看看图像有多大不同。
一个像素的红色、绿色或蓝色值相差 1 并不大。我怀疑这对普通人来说甚至是可见的。
因此,我修改了代码(到公开版本)以容忍某种差异。
有eps
3 个:
if (equalImgData(img1, img2, 3)) {
这些图像被认为是平等的。
推荐阅读
- c# - 试图为我的游戏提高速度,但我不断收到此错误:类型“CarController”已经定义了一个名为“OnControllerColliderHit”的成员
- javascript - 如何多次解决响应
- arrays - Mongo:以数据树的形式获取数据
- tcl - 如何在 tcl 中精确复制?
- reactjs - api put 调用中 react/redux 表单中的 Datepicker 字段
- java - 当数据库中没有条目时如何处理 javax.persistence.NoResult 异常?
- python - Python数据框第一列中的值较小但第二列中的值较高?
- azure - Azure Python SDK:创建预算?
- html - Css网格,如何在不弄乱整个页面的情况下调整元素的大小
- firebase - Flutter Web/Firebase - 在浏览器中按回绕过验证过程