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

Я пытаюсь написать функцию, чтобы определить, идентичны ли два растровых изображения одинакового размера или нет. Функция, которую я имею прямо сейчас, просто сравнивает пиксель за раз в каждом растровом изображении, возвращая false на первом неравном пикселе.

Пока это работает и хорошо работает для небольших растровых изображений, в производстве я собираюсь использовать это в узком цикле и на больших изображениях, поэтому мне нужен лучший способ. Есть ли у кого-нибудь рекомендации?

Язык, который я использую, - это, кстати, - и да, я уже использую метод .LockBits. =)

Изменить. Я закодировал реализацию некоторых предложенных предложений, и вот эталонные тесты. Настройка: две идентичные (худшие) растровые изображения, размером 100x100, с 10 000 итераций каждый. Вот результаты:

CompareByInts (Marc Gravell) :   1107ms
CompareByMD5  (Skilldrick)   :   4222ms
CompareByMask (GrayWizardX)  :    949ms

В CompareByInts и CompareByMask Я использую указатели для прямого доступа к памяти; в методе MD5 я использую Marshal.Copy для извлечения байтового массива и передаю это как аргумент MD5.ComputeHash. CompareByMask только немного быстрее, но, учитывая контекст, я думаю, что любое улучшение полезно.

Спасибо всем. =)

Изменить 2: забыл включить оптимизацию - это делает GreyWizardX более равносильным:

CompareByInts   (Marc Gravell) :    944ms
CompareByMD5    (Skilldrick)   :   4275ms
CompareByMask   (GrayWizardX)  :    630ms
CompareByMemCmp (Erik)         :    105ms

Интересно, что метод MD5 вообще не улучшался.

Редактировать 3. Написал мой ответ (MemCmp), который вывел другие методы из воды. o.o

Ответ 1

Измените 8-31-12: за комментарий Joey ниже, обратите внимание на формат сопоставлений, которые вы сравниваете. Они могут содержать отступы на шагах, которые делают растровые изображения неравными, несмотря на то, что они эквивалентны по пикселям. Подробнее см. этот вопрос.


Чтение этого ответа на вопрос о сравнении байтовых массивов дал метод MUCH FASTER: использование P/Invoke и вызова API memcmp в msvcrt. Здесь код:

[DllImport("msvcrt.dll")]
private static extern int memcmp(IntPtr b1, IntPtr b2, long count);

public static bool CompareMemCmp(Bitmap b1, Bitmap b2)
{
    if ((b1 == null) != (b2 == null)) return false;
    if (b1.Size != b2.Size) return false;

    var bd1 = b1.LockBits(new Rectangle(new Point(0, 0), b1.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    var bd2 = b2.LockBits(new Rectangle(new Point(0, 0), b2.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

    try
    {
        IntPtr bd1scan0 = bd1.Scan0;
        IntPtr bd2scan0 = bd2.Scan0;

        int stride = bd1.Stride;
        int len = stride * b1.Height;

        return memcmp(bd1scan0, bd2scan0, len) == 0;
    }
    finally
    {
        b1.UnlockBits(bd1);
        b2.UnlockBits(bd2);
    }
}

Ответ 2

Если вы пытаетесь определить, равны ли они на 100%, вы можете инвертировать один и добавить его к другому, если его ноль идентичен. Расширяя это с помощью небезопасного кода, принимайте 64 бита за раз, как долго, и делайте математику таким образом, любые различия могут привести к немедленному сбою.

Если изображения не идентичны на 100% (сравнение png с jpeg), или если вы не ищете 100% -ную совпадение, у вас впереди еще больше работы.

Удачи.

Ответ 3

Ну, вы используете .LockBits, поэтому предположительно вы используете небезопасный код. Вместо того, чтобы рассматривать начало каждой строки (Scan0 + y * Stride) как byte*, рассмотрим ее как int*; int Арифметика довольно быстро, и вам нужно делать только 1/4 работы. И для изображений в ARGB вы все еще можете говорить в пикселях, делая математику простой.

Ответ 4

Не могли бы вы взять хэш каждого и сравнить? Это было бы немного вероятностно, но практически не было.

Благодаря Ram, здесь примерная реализация этого метода.

Ответ 5

Если исходная проблема заключается только в том, чтобы найти точные дубликаты между двумя растровыми изображениями, вам нужно будет просто сравнить уровень бит. Я не знаю С#, но в C я бы использовал следующую функцию:

int areEqual (long size, long *a, long *b)
{
    long start = size / 2;
    long i;
    for (i = start; i != size; i++) { if (a[i] != b[i]) return 0 }
    for (i = 0; i != start; i++) { if (a[i] != b[i]) return 0 }
    return 1;
}

Я бы начал смотреть посередине, потому что я подозреваю, что есть гораздо лучший шанс найти неравные биты ближе к середине изображения, чем к началу; конечно, это будет действительно зависеть от изображений, которые вы дедуплируете, выбор случайного места для начала может быть лучшим.

Если вы пытаетесь найти точные дубликаты среди сотен изображений, то сравнение всех пар их не нужно. Сначала вычислите хэш MD5 каждого изображения и поместите его в список пар (md5Hash, imageId); затем отсортируйте список по m5Hash. Затем выполняйте только попарные сравнения на изображениях, которые имеют один и тот же md5Hash.

Ответ 6

Если эти растровые изображения уже находятся на вашей графической карте, вы можете распараллелить такую ​​проверку, выполнив ее на графической карте, используя язык, например CUDA или OpenCL.

Я объясню с точки зрения CUDA, так как тот, который я знаю. В основном CUDA позволяет писать код общего назначения для параллельной работы по каждой node вашей видеокарты. Вы можете получить доступ к растровым изображениям, находящимся в общей памяти. Каждому вызову функции также присваивается индекс в пределах множества параллельных прогонов. Таким образом, для такой проблемы вы просто запускаете одну из вышеперечисленных функций сравнения для некоторого подмножества растрового изображения - используя распараллеливание для покрытия всего растрового изображения. Затем просто напишите 1 в определенную ячейку памяти, если сравнение не удастся (и ничего не напишет, если оно получится).

Если у вас еще нет растровых изображений на вашей видеокарте, это, вероятно, не так, потому что затраты на загрузку двух растровых изображений на вашей карте легко затмевают экономию, которую такая распараллелировка вас принесет.

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

// kernel to run on GPU, once per thread
__global__ void compare_bitmaps(long const * const A, long const * const B, char * const retValue, size_t const len)
{
 // divide the work equally among the threads (each thread is in a block, each block is in a grid)
 size_t const threads_per_block = blockDim.x * blockDim.y * blockDim.z;
 size_t const len_to_compare = len / (gridDim.x * gridDim.y * gridDim.z * threads_per_block);
# define offset3(idx3,dim3)  (idx3.x + dim3.x * (idx3.y + dim3.y * idx3.z))
 size_t const start_offset = len_to_compare * (offset3(threadIdx,blockDim) + threads_per_block * offset3(blockIdx,gridDim));
 size_t const stop_offset = start_offset + len_to_compare;
# undef offset3

 size_t i;
 for (i = start_offset; i < stop_offset; i++)
 {
  if (A[i] != B[i]) 
  {
   *retValue = 1;
   break;
  }
 }
 return;
}

Ответ 7

Если вы можете реализовать на своем языке что-то вроде Duff Device, это может дать вам значительное ускорение скорости по простому циклу. Обычно он используется для копирования данных, но нет причин, по которым он не может использоваться для сравнения данных.

Или, если на то пошло, вы можете просто использовать какой-то эквивалент memcmp().

Ответ 8

Вы можете попытаться добавить их в базу данных "blob", а затем использовать механизм базы данных для сравнения своих двоичных файлов. Это дало бы вам ответ "да" или "нет" на то, совпадают ли двоичные данные. Было бы очень легко сделать 2 изображения, которые производят одну и ту же графику, но имеют разные бинарные файлы.

Вы также можете выбрать несколько случайных пикселей и сравнить их, а затем, если они будут такими же, продолжайте больше, пока не проверите все пиксели. Это приведет только к возврату более быстрого отрицательного совпадения, хотя для поиска 100% -ных положительных результатов все равно потребуется

Ответ 9

На основе подхода сравнения хэшей вместо сравнения каждого отдельного пикселя это то, что я использую:

public static class Utils
{
    public static byte[] ShaHash(this Image image)
    {
        var bytes = new byte[1];
        bytes = (byte[])(new ImageConverter()).ConvertTo(image, bytes.GetType());

        return (new SHA256Managed()).ComputeHash(bytes);
    }

    public static bool AreEqual(Image imageA, Image imageB)
    {
        if (imageA.Width != imageB.Width) return false;
        if (imageA.Height != imageB.Height) return false;

        var hashA = imageA.ShaHash();
        var hashB = imageB.ShaHash();

        return !hashA
            .Where((nextByte, index) => nextByte != hashB[index])
            .Any();
    }
]

Использование прямолинейно:

bool isMatch = Utils.AreEqual(bitmapOne, bitmapTwo);