Вычислить QCryptographicHash только для «базовых» данных QImage (исключая метаданные) - PullRequest
1 голос
/ 08 ноября 2019

У меня есть куча файлов "JPG", которые отличаются только для данных EXIF.

Я хотел сделать быструю проверку (используя Qt Framework), пытаясь вычислить хэш "ядра"данные изображения (а не сам файл, который будет включать метаданные). Пока все хорошо.

Вот как я загружаю изображение и вычисляю хэш:

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 дает мне тот же результат, но отличается от того же изображения в JPG.

Если бы я создал файл JPG изформат LOSSLES я могу понять результат. Но наоборот ...

Может ли кто-нибудь помочь мне понять, в чем я не прав?


Дайте следующий код, который я вижу:

  • Все QImage имеют одинаковый размер байтов
  • QByteArray двух JPG идентичны
  • QByteArray BMP, PNG, TIF идентичны
  • QByteArray для JPG и BMP (также PNG и TIF) НЕ идентичны
// 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 ​​были созданы путем открытия оригинального файла JPG в Windows Paint и сохраняя его в тех форматах без потерь. Таким образом, Windows Paint завершается с ошибкой чтения (или) при сохранении. Коммерческое программное обеспечение Duplicate Cleaner также не работает, поскольку сообщается, что файл JPG на 100% идентичен версии BMP, PNG, TIF.

1 Ответ

0 голосов
/ 09 ноября 2019

Предисловие:

Я считаю

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

неправильным способом заполнения QByteArray данными, в которых не хранится строка C.

QByteArray::append(const char*) хорошо для копирования строк C в QByteArray. Он копирует данные до тех пор, пока не будет найден байт 0 (терминатор 0). 0 байт могут быть где угодно в необработанных данных изображения или нигде. В первом случае копируется слишком мало данных, во втором случае рассматриваются данные вне диапазона. Оба непреднамеренно.

Кстати. даже нет необходимости копировать данные изображения (которые могут иметь значительный размер).

Я сделал образец для сравнения необработанных данных двух QImage напрямую, включая предварительную проверку размера и формата

Мой образец 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)) {

изображения считались равными.

...