首页 > 解决方案 > 仅计算“核心”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 文件,我可以理解结果。
但反过来呢???

有人可以帮我理解我错在哪里吗?


给出以下代码,我看到:

// 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% 相同。

标签: c++qtqt5qimage

解决方案


前言:

我认为

data1.append(reinterpret_cast<const char *>(img1.constBits()));

QByteArray作为用不存储 C 字符串的数据填充 a 的错误方法。

QByteArray::append(const char*)在 a 中复制 C 字符串很好QByteArray。它复制数据直到找到一个 0 字节(一个 0 终止符)。0 字节可能在图像的原始数据中的任何位置,也可能不在任何位置。在第一种情况下,复制的数据太少,在后一种情况下,会考虑超出范围的数据。两者都是无意的。

顺便提一句。甚至不需要复制图像数据(可能很大)。

我做了一个样本直接比较两个QImages的原始数据,包括预先检查大小和格式是否匹配。

我的样品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.bmpPicture.bmp.jpgGIMP(我认为质量设置为 100% 的无损)。这导致了Row 0 byte 0. 哎呀!

然后,我变得好奇并修改了代码,看看图像有多大不同。

一个像素的红色、绿色或蓝色值相差 1 并不大。我怀疑这对普通人来说甚至是可见的。

因此,我修改了代码(到公开版本)以容忍某种差异。

eps3 个:

if (equalImgData(img1, img2, 3)) {

这些图像被认为是平等的。


推荐阅读