2012-07-26 32 views
3

只需對兩個非常相似的圖像(一個是另一個的編輯版本)進行逐像素比較,並將差異寫入新文件。C#更快速地比較兩個圖像之間的像素並只寫出差異

for (int y = 0; y < height; y++) 
{ 
    for (int x = 0; x < width; x++) 
    { 
     pix1 = src.GetPixel(x, y); 
     pix2 = comp.GetPixel(x, y); 
     if (pix1 != pix2) 
     { 
      dest.SetPixel(x, y, pix1); 
     } 
    } 
} 

srccomp是兩個圖像進行比較,並dest只是一個新的形象。 需要相當長的時間。

什麼是更快的方法來做到這一點?
也許沒必要實際獲取像素以便比較它?

+0

謝謝,你的問題幫了我很多。 – smoothumut 2017-11-29 07:22:55

回答

3

比較你需要閱讀的像素。但是,GetPixel()是一個非常緩慢的方法,除非您只是檢查非常少量的數據,否則不推薦。

爲了獲得更好的性能,最好的方法是使用unsafe-code並使用指針來代替。在互聯網上有很多這樣的樣本,下面是我發現的一點,解釋了這些問題,並提供了兩種不同的解決方案。

http://davidthomasbernal.com/blog/2008/03/13/c-image-processing-performance-unsafe-vs-safe-code-part-i

一定要檢查兩部分,以及在那裏,他有一定的基準,並鏈接到完整的源代碼。

+0

我發現這個指針方法足夠快,可以跟上30fps的視頻,這是很多年前在舊硬件上。 – MikeKulls 2012-07-26 04:50:46

+0

謝謝我會研究它。至少基準看起來很有希望,而且我的使用非常輕量。 – MxyL 2012-07-26 04:52:02

2

你可以在這個類來看一看:它是一個開放的源代碼提供了基於指針比較快的像素的方法,我could'nt找到鏈接,所以我發佈你的代碼:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Drawing; 
using System.Drawing.Imaging; 

namespace SampleGrabberNET 
{ 
    public unsafe class UnsafeBitmap 
    { 
     Bitmap bitmap; 

    // three elements used for MakeGreyUnsafe 
    int width; 
    BitmapData bitmapData = null; 
    Byte* pBase = null; 

    public UnsafeBitmap(Bitmap bitmap) 
    { 
    this.bitmap = new Bitmap(bitmap); 
    } 

     public UnsafeBitmap(int width, int height) 
    { 
     this.bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb); 
     } 

    public void Dispose() 
    { 
    bitmap.Dispose(); 
    } 

    public Bitmap Bitmap 
    { 
get 
    { 
    return(bitmap); 
    } 
    } 

    private Point PixelSize 
    { 
    get 
    { 
    GraphicsUnit unit = GraphicsUnit.Pixel; 
    RectangleF bounds = bitmap.GetBounds(ref unit); 

    return new Point((int) bounds.Width, (int) bounds.Height); 
    } 
    } 

    public void LockBitmap() 
    { 
    GraphicsUnit unit = GraphicsUnit.Pixel; 
    RectangleF boundsF = bitmap.GetBounds(ref unit); 
    Rectangle bounds = new Rectangle((int) boundsF.X, 
    (int) boundsF.Y, 
    (int) boundsF.Width, 
    (int) boundsF.Height); 

    // Figure out the number of bytes in a row 
    // This is rounded up to be a multiple of 4 
    // bytes, since a scan line in an image must always be a multiple of 4 bytes 
    // in length. 
    width = (int) boundsF.Width * sizeof(PixelData); 
if (width % 4 != 0) 
    { 
    width = 4 * (width/4 + 1); 
} 
    bitmapData = 
    bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 

    pBase = (Byte*) bitmapData.Scan0.ToPointer(); 
    } 

    public PixelData GetPixel(int x, int y) 
    { 
      PixelData returnValue = *PixelAt(x, y); 
    return returnValue; 
    } 

     public void SetPixel(int x, int y, PixelData colour) 
     { 
      PixelData* pixel = PixelAt(x, y); 
      *pixel = colour; 
     } 

    public void UnlockBitmap() 
    { 
    bitmap.UnlockBits(bitmapData); 
    bitmapData = null; 
    pBase = null; 
    } 
     public PixelData* PixelAt(int x, int y) 
     { 
      return (PixelData*)(pBase + y * width + x * sizeof(PixelData)); 
     } 
    } 
    public struct PixelData 
    { 
     public byte blue; 
     public byte green; 
     public byte red; 
    } 

}

+0

發佈某人elses代碼(這不僅是一個片段,但這次是一個完整的類),並說它是開源的,但沒有關於它是哪個許可證是有點危險的。如果按照GPL或其他可能與他的項目不兼容的方式獲得許可,該怎麼辦? – 2012-07-26 17:58:40

+0

它是免費的,現在我不知道他的項目是什麼性質,猜測他必須做更多的研究 – 2012-07-26 20:30:04

+0

GPL下發布的代碼也可以免費使用,只要你釋放GPL下的其他項目。只要您告訴全世界您正在使用別人的代碼,其他許可也可以使代碼免費使用,因此瞭解更大代碼片段的許可證可能非常重要。 – 2012-07-27 07:03:33

2

此代碼與我使用的代碼類似。 當然,在這種情況下,不安全的代碼是唯一的出路。 將內存中的像素編組在內存周圍並不是必需的,而且您必須爲此進行兩次比較!

但是,比較像素應視爲比較兩個數字。 對於顏色的紅色,綠色和藍色成分中的每一個,以及當然經常被忽略的Alpha,像素都是3個字節,您可能想要將值作爲UInt32進行比較。這可以比較一個數字(一個像素)與另一個數字的好處,如果它們相同,那麼你繼續前進。

爲了這個工作,你不需要一個字節*,而是一個UInt32 *。 其次,上面的代碼沒有考慮我能看到的Stride,這是圖像的每條水平線可能實際使用的字節數不同於像素本身總和的地方。正如你可能處理24位(rgb)和alpha(a)這意味着像素應該已經排隊,因此,上面的代碼應該工作。然而,不能保證100%的時間。

這可能聽起來像我很挑剔,但我猜測性能對你來說意義重大 - 而且來自遊戲背景,對我來說也是如此。

(從上面的鏈接採取第1部分,由於卡爾 - 約翰·舍格倫)

BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat); 

當你點擊這條線,你真的要小心 - 你與鎖定的PixelFormat應該匹配一個你的源圖像的PixelFormat。否則,事情可能不會像你期望的那樣行事。

來自LockBits,您將獲得需要深入挖掘的BitmapData。

byte* scan0 = (byte*)bData.Scan0.ToPointer(); 

這是我指的是行 - 我會想辦法讓這個UInt32的*所以你不讀一氣呵成每指令一個字節,但4,作爲一個UInt32的。

我會以一個數組的形式訪問這個內存 - 我計算一維數組的偏移量,通過識別步幅是相當於一個Y像素,然後乘以Y.然而,你會在這一點上我陷入了同樣的陷阱!

int stride = bData.Stride/4; // the width is expressed in bytes, yet we need it as UInt32's. As there's 4 bytes per UInt32, this makes sense of the divide by 4. 

所以讀出象素可以這樣做:

int x = 123; 
int y = 321; 
UInt32 pixelColour = scan0[(y * stride) + x]; 

最後的話,不要忘了,當你完成解鎖你的位圖。一個容易忘記的人。 此外,如果您希望將更改後的像素保存到另一個位圖,則需要以相同的方式寫入像素。我認爲我使用指針作爲數組讀取像素的示例應該很明顯,那麼如何執行相同的操作來寫入像素。

另一個想法 - 我希望你沒有試圖比較Jpeg或其他有損壓縮圖像 - 因爲這些變化可能是非常不可預測的,並且不是你想象的那樣。相反,您需要使用無損圖像格式,如BMP和PNG。

希望這可以幫助別人。