夕暮ログ

C#やJavascript、最近はAndroidなんかも好きなtinqのブログ。「夕暮れログ」

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

C#の画像高速比較について

私のサイトの「画像を高速で比較する」についてです。
C#やVB.NETなどで画像を扱う際、比較をしたいと思うことがあります。
しかし、普通に比較してもオブジェクトを判定するだけなので、falseになってしまいます。
GetHashCodeでハッシュをとっても、それもやっぱり画像ではなくてオブジェクトのハッシュなため、比較できません。
ということで、画像を比較するには特殊な方法を用いる必要があります。
ここら辺の説明については、上記のサイトのほうをご覧ください。

それで、ここにいたるまで、どれが早いのか、いろいろと研究をしてみました。
そのコード一覧です。
サイトに乗せたものは、一番早かった、最後のものです。
画像からbyte配列を取得して、それのハッシュを比較しています。


あまり早いパソコンではないですし、キャッシュなどのおかげで正確な数値ではないかもしれませんが、参考までにどうぞ。
利用する前に必自分で確認することをオススメします(^^ゞ
bool Compare1(Bitmap img1, Bitmap img2)
{
	//高さや幅が違えばfalse
    if (img1.Width != img2.Width || img1.Height != img2.Height) return false;
    for (int i = 0; i < img1.Width; i++)
    {
        for (int j = 0; j < img1.Height; j++)
        {
        	//総当りで比較。違う部分あればfalseを返す
            if(img1.GetPixel(i, j) != img2.GetPixel(i,j))return false;
        }
    }
    return true;
}

bool Compare2(Image img1, Image img2)
{
	//高さや幅が違えばfalse
    if (img1.Width != img2.Width || img1.Height != img2.Height) return false;
    //ImageConverterで配列に変換
    ImageConverter ic = new ImageConverter();
    byte[] byte1 = (byte[])ic.ConvertTo(img1, typeof(byte[]));
    byte[] byte2 = (byte[])ic.ConvertTo(img2, typeof(byte[]));
    //配列を比較
    return byte1.SequenceEqual(byte2);
}

bool Compare3(Bitmap img1, Bitmap img2)
{
	//高さや幅が違えばfalse
    if (img1.Width != img2.Width || img1.Height != img2.Height) return false;
    
    //BitmapDataを取得
    BitmapData bd1 = img1.LockBits(new Rectangle(0, 0, img1.Width, img1.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, img1.PixelFormat);
    BitmapData bd2 = img2.LockBits(new Rectangle(0, 0, img2.Width, img2.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, img2.PixelFormat);
    if (bd1.Stride * img1.Height != bd2.Stride * img2.Height) return false;
    int bsize = bd1.Stride * img1.Height;
    byte[] byte1 = new byte[bsize];
    byte[] byte2 = new byte[bsize];
    //配列にコピー
    Marshal.Copy(bd1.Scan0, byte1, 0, bsize);
    Marshal.Copy(bd2.Scan0, byte2, 0, bsize);
    //ロックを解除
    img1.UnlockBits(bd1);
    img2.UnlockBits(bd2);
    
    //比較
    return byte1.SequenceEqual(byte2);
}

bool Compare4(Image img1, Image img2)
{
	//高さや幅が違えばfalse
    if (img1.Width != img2.Width || img1.Height != img2.Height) return false;
    //ImageConverterで比較
    ImageConverter ic = new ImageConverter();
    byte[] byte1 = (byte[])ic.ConvertTo(img1, typeof(byte[]));
    byte[] byte2 = (byte[])ic.ConvertTo(img2, typeof(byte[]));

	//MD5ハッシュを取る
    System.Security.Cryptography.MD5CryptoServiceProvider md5 =
        new System.Security.Cryptography.MD5CryptoServiceProvider();
    byte[] hash1 = md5.ComputeHash(byte1);
    byte[] hash2 = md5.ComputeHash(byte2);
    //ハッシュを比較
    return hash1.SequenceEqual(hash2);
}

bool Compare5(Image image1, Image image2)
{
    Bitmap img1 = (Bitmap)image1;
    Bitmap img2 = (Bitmap)image2;

	//高さが違えばfalse
    if (img1.Width != img2.Width || img1.Height != img2.Height) return false;
    //BitmapData取得
    BitmapData bd1 = img1.LockBits(new Rectangle(0, 0, img1.Width, img1.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, img1.PixelFormat);
    BitmapData bd2 = img2.LockBits(new Rectangle(0, 0, img2.Width, img2.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, img2.PixelFormat);
    //スキャン幅が違う場合はfalse
    if (bd1.Stride != bd2.Stride)
    {
        //ロックを解除
        img1.UnlockBits(bd1);
        img2.UnlockBits(bd2);
        return false;
    } 
    int bsize = bd1.Stride * img1.Height;
    byte[] byte1 = new byte[bsize];
    byte[] byte2 = new byte[bsize];
    //バイト配列にコピー
    Marshal.Copy(bd1.Scan0, byte1, 0, bsize);
    Marshal.Copy(bd2.Scan0, byte2, 0, bsize);
    //ロックを解除
    img1.UnlockBits(bd1);
    img2.UnlockBits(bd2);
	
	//MD5ハッシュを取る
    System.Security.Cryptography.MD5CryptoServiceProvider md5 =
        new System.Security.Cryptography.MD5CryptoServiceProvider();
    byte[] hash1 = md5.ComputeHash(byte1);
    byte[] hash2 = md5.ComputeHash(byte2);
    
    //ハッシュを比較
    return hash1.SequenceEqual(hash2);
}
私の環境では以下のようになりました。
関数名 説明 1024x768の画像の比較時間(秒) 2048x1536の画像の比較時間(秒)
Compare1 GetPixelを利用 3 13
Compare2 ImageConverterを利用 0.3 0.9
Compare3 LockBitsを利用 0.15 0.4
Compare4 GetPixelと、MD5ハッシュを利用 0.25 1
Compare5 LockBitsとMD5ハッシュを利用 0.1 0.25
もちろん、メモリや環境によって誤差が出てきます。参考程度にお願いします。
ImageConverterは、結構遅いです。
LockBitsを使って直にポインタを取得して、バイト配列にコピーしたほうが早くなります。
また、ハッシュを使えば比較する時間が少なくなるためか、わずかながら短くなります。それでも、2倍ほどの開きがあります。
1枚や2枚ならいいんですが、10000枚くらい比較するとなると、速度に大きな差が出てくるでしょう。
ちなみに、全部完全に一致しなければ、同じとはみなされませんので、注意。
--------
修正 2011/9/28 Compare3とCompare5が正しく比較することが出来ていませんでした。ご指摘ありがとうございました。
2012/7/31 Compare5でスキャン幅が違う場合にUnlockBitsを実行せずにfalseを返していた問題を修正しました。ご指摘ありがとうございました。
同時にCompare5の第一引数にthisがついていたり、同じはずの高さを掛け算していたところなども見つけたのでついでに修正。
関連記事

コメント

No title

79行のreturn,
UnlockBits抜けてますよ。

修正しました

ありがとうございます。修正しました。
try-finallyを使ったほうがわかりやすいなという気がしてきました・・・

ここをクリックしてコメントを投稿

非公開コメント

トラックバック

http://tinqwill.blog59.fc2.com/tb.php/46-85c65602

« next  ホーム  prev »

プロフィール

tinq tinq(もしくはTinqWill)

Sky  For   Every 改装予定

プログラミングお勉強中の高校生。月一くらいは更新したい

最新記事

カテゴリ

月別アーカイブ

検索フォーム

最新コメント

リンク

最新トラックバック

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。