web-dev-qa-db-ja.com

2つの同じサイズのビットマップを比較して、それらが同一であるかどうかを判断する最も速い方法は何ですか?

2つの同じサイズのビットマップが同一かどうかを判断する関数を記述しようとしています。私が今持っている関数は、単純に各ビットマップのピクセルを一度に比較し、最初の等しくないピクセルでfalseを返します。

これは機能し、小さなビットマップでもうまく機能しますが、本番環境では、これをタイトループで使用し、より大きな画像で使用するため、より良い方法が必要です。誰か推奨事項はありますか?

ちなみに私が使用している言語はC#です。そして、はい、既に.LockBitsメソッドを使用しています。 =)

編集:与えられた提案のいくつかの実装をコード化しました。これがベンチマークです。設定:2つの同一の(最悪の場合)ビットマップ、サイズが100x100、それぞれ10,000回の反復。結果は次のとおりです。

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

CompareByIntsとCompareByMaskでは、ポインタを使用してメモリに直接アクセスしています。 MD5メソッドでは、Marshal.Copyを使用してバイト配列を取得し、それをMD5.ComputeHashの引数として渡します。 CompareByMaskの方がわずかに高速ですが、コンテキストを考えると、改善が役立つと思います。

みんな、ありがとう。 =)

編集2:最適化をオンにするのを忘れました-これを行うと、GrayWizardXの答えがさらに向上します。

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

MD5メソッドがまったく改善しなかったことは興味深い。

編集:他の方法を水から吹き飛ばした私の回答(MemCmp)を投稿しました。 o.O

40
Erik Forbes

編集8-31-12:以下の Joey's コメントごとに、比較するビットマップの形式に注意してください。ピクセル単位で同等であるにもかかわらず、ビットマップを不均等にレンダリングするストライドにパディングが含まれる場合があります。詳細は この質問 を参照してください。


バイト配列の比較に関する質問に対して この回答 を読むと、はるかに高速な方法が得られました。msvcrtでP/Invokeとmemcmp API呼び出しを使用します。これがコードです:

[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);
    }
}
37
Erik Forbes

それらが100%等しいかどうかを判断しようとしている場合は、1つを反転させて、それらがゼロである場合は、それらを同じに追加できます。安全でないコードを使用してこれを拡張し、一度に64ビットを長く取り、そのように計算を行うと、違いがあるとすぐに失敗する可能性があります。

画像が100%同一でない(pngとjpegを比較する)場合、または100%の一致を探していない場合は、さらに作業が進んでいます。

幸運を。

9
GrayWizardx

さて、あなたは.LockBitsを使用しているので、おそらく安全でないコードを使用しています。各行Origin(Scan0 + y * Stride)をbyte*として扱うのではなく、int*として扱うことを検討してください。 int算術演算はかなり高速で、必要な作業は1/4だけです。また、ARGBの画像の場合、ピクセル単位で話しているため、計算が簡単になります。

8
Marc Gravell

それぞれのハッシュを取り、比較できますか?少し確率的ですが、実際にはそうではありません。

Ramのおかげで、このテクニックの サンプル実装 を次に示します。

6
Skilldrick

元の問題が2つのビットマップ間の正確な重複を見つけることだけである場合は、ビットレベルの比較だけで十分です。 C#はわかりませんが、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を持つイメージでのみペアワイズ比較を行います。

3
Jeff Kubina

これらのビットマップがすでにグラフィックカードにある場合、グラフィックカードで [〜#〜] cuda [〜#〜] または OpenCL

私が知っているCUDAについて説明します。基本的にCUDAを使用すると、グラフィックスカードの各ノードで並列に実行する汎用コードを記述できます。共有メモリにあるビットマップにアクセスできます。関数の各呼び出しには、並列実行のセット内のインデックスも与えられます。したがって、このような問題の場合は、ビットマップ全体をカバーするために並列化を使用して、ビットマップの一部のサブセットに対して上記の比較関数の1つを実行するだけです。次に、比較が失敗した場合は、特定のメモリ位置に1を書き込みます(成功した場合は何も書き込みません)。

グラフィックカードにビットマップがまだない場合、カードに2つのビットマップをロードするためのコストは簡単にEclipseになり、並列化によって節約できるため、これはおそらく方法ではありません。

これは(かなり悪い)コードの例です(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;
}
3
rampion

言語で Duff's Device のようなものを実装できる場合、単純なループよりも大幅に速度が向上する可能性があります。通常、データのコピーに使用されますが、代わりにデータの比較に使用できない理由はありません。

または、その点については、memcmp()と同等の機能を使用することもできます。

0
rmeador

それらをデータベース「ブロブ」に追加してから、データベースエンジンを使用してそれらのバイナリを比較することができます。これは、バイナリデータが同じであるかどうかについて、yesまたはnoの答えを与えるだけです。同じグラフィックを生成するが、バイナリが異なる2つの画像を作成するのは非常に簡単です。

いくつかのランダムなピクセルを選択して比較することもできます。それらが同じである場合は、すべてのピクセルをチェックするまで続けます。これはより速い否定的な一致を返すだけですが、それでも100%の肯定的な一致を見つけるのに長い時間がかかります

0
Drew