2010-05-03 180 views
7

我正在嘗試編寫一些代碼,可以方便地處理視頻幀。我正在接收幀爲System.Windows.Media.Imaging.WriteableBitmap。出於測試目的,我只是應用一個簡單的閾值過濾器來處理BGRA格式圖像,並根據BGR像素的平均值將每個像素分配爲黑色或白色。爲什麼我的不安全代碼塊比我的安全代碼慢?

這裏是我的「安全」的版本:

public static void ApplyFilter(WriteableBitmap Bitmap, byte Threshold) 
{ 
    // Let's just make this work for this format 
    if (Bitmap.Format != PixelFormats.Bgr24 
     && Bitmap.Format != PixelFormats.Bgr32) 
    { 
     return; 
    } 

    // Calculate the number of bytes per pixel (should be 4 for this format). 
    var bytesPerPixel = (Bitmap.Format.BitsPerPixel + 7)/8; 

    // Stride is bytes per pixel times the number of pixels. 
    // Stride is the byte width of a single rectangle row. 
    var stride = Bitmap.PixelWidth * bytesPerPixel; 

    // Create a byte array for a the entire size of bitmap. 
    var arraySize = stride * Bitmap.PixelHeight; 
    var pixelArray = new byte[arraySize]; 

    // Copy all pixels into the array 
    Bitmap.CopyPixels(pixelArray, stride, 0); 

    // Loop through array and change pixels to black/white based on threshold 
    for (int i = 0; i < pixelArray.Length; i += bytesPerPixel) 
    { 
     // i=B, i+1=G, i+2=R, i+3=A 
     var brightness = 
       (byte)((pixelArray[i] + pixelArray[i+1] + pixelArray[i+2])/3); 

     var toColor = byte.MinValue; // Black 

     if (brightness >= Threshold) 
     { 
      toColor = byte.MaxValue; // White 
     } 

     pixelArray[i] = toColor; 
     pixelArray[i + 1] = toColor; 
     pixelArray[i + 2] = toColor; 
    } 
    Bitmap.WritePixels(
     new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight), 
     pixelArray, stride, 0 
    ); 
} 

這是我認爲是使用不安全的代碼塊和WriteableBitmap的後臺緩衝區,而不是forebuffer直接翻譯:

public static void ApplyFilterUnsafe(WriteableBitmap Bitmap, byte Threshold) 
{ 
    // Let's just make this work for this format 
    if (Bitmap.Format != PixelFormats.Bgr24 
     && Bitmap.Format != PixelFormats.Bgr32) 
    { 
     return; 
    } 

    var bytesPerPixel = (Bitmap.Format.BitsPerPixel + 7)/8; 

    Bitmap.Lock(); 

    unsafe 
    { 
     // Get a pointer to the back buffer. 
     byte* pBackBuffer = (byte*)Bitmap.BackBuffer; 

     for (int i = 0; 
      i < Bitmap.BackBufferStride*Bitmap.PixelHeight; 
      i+= bytesPerPixel) 
     { 
      var pCopy = pBackBuffer; 
      var brightness = (byte)((*pBackBuffer 
            + *++pBackBuffer 
            + *++pBackBuffer)/3); 
      pBackBuffer++; 

      var toColor = 
        brightness >= Threshold ? byte.MaxValue : byte.MinValue; 

      *pCopy = toColor; 
      *++pCopy = toColor; 
      *++pCopy = toColor;      
     } 
    } 

    // Bitmap.AddDirtyRect(
    //   new Int32Rect(0,0, Bitmap.PixelWidth, Bitmap.PixelHeight)); 
    Bitmap.Unlock(); 

} 

這是我第一次涉及不安全的代碼塊和指針,所以也許邏輯不是最優的。

我已經在使用相同的測試WriteableBitmaps代碼兩個塊:

var threshold = Convert.ToByte(op.Result); 
var copy2 = copyFrame.Clone(); 
Stopwatch stopWatch = new Stopwatch(); 
stopWatch.Start(); 
BinaryFilter.ApplyFilterUnsafe(copyFrame, threshold); 
stopWatch.Stop(); 

var unsafesecs = stopWatch.ElapsedMilliseconds; 
stopWatch.Reset(); 
stopWatch.Start(); 
BinaryFilter.ApplyFilter(copy2, threshold); 
stopWatch.Stop(); 
Debug.WriteLine(string.Format("Unsafe: {1}, Safe: {0}", 
       stopWatch.ElapsedMilliseconds, unsafesecs)); 

所以我分析同一圖像。視頻幀的輸入流的試運行:

Unsafe: 110, Safe: 53 
Unsafe: 136, Safe: 42 
Unsafe: 106, Safe: 36 
Unsafe: 95, Safe: 43 
Unsafe: 98, Safe: 41 
Unsafe: 88, Safe: 36 
Unsafe: 129, Safe: 65 
Unsafe: 100, Safe: 47 
Unsafe: 112, Safe: 50 
Unsafe: 91, Safe: 33 
Unsafe: 118, Safe: 42 
Unsafe: 103, Safe: 80 
Unsafe: 104, Safe: 34 
Unsafe: 101, Safe: 36 
Unsafe: 154, Safe: 83 
Unsafe: 134, Safe: 46 
Unsafe: 113, Safe: 76 
Unsafe: 117, Safe: 57 
Unsafe: 90, Safe: 41 
Unsafe: 156, Safe: 35 

爲什麼我的不安全版本總是會慢?是否由於使用後臺緩衝區?或者我做錯了什麼?

感謝

+0

你是否在Release版本中運行這些測試? – 2010-05-03 19:21:43

+0

現在不在調試器中。 – 2010-05-03 19:23:35

+3

我建議在發佈版本中運行性能測試以獲得有意義的結果。如果你讓優化器與代碼一起使用,你可能會發現性能差距不足以證明使用'不安全'版本的理由。 – 2010-05-03 20:12:04

回答

9

也許是因爲你的不安全的版本是做乘法和屬性訪問:

Bitmap.BackBufferStride*Bitmap.PixelHeight 

在每一個循環迭代。將結果存儲在一個變量中。

+0

不知道這是問題所在,但肯定會對它有所貢獻。 – McAden 2010-05-03 19:27:40

+1

你是對的!我非常想弄清楚不安全塊如何工作和指針,我對我的迭代器條件邏輯操作一無所知。將它存儲在一個變量中確實讓我跌破了「安全」版本,儘管還不足以成爲改變遊戲規則的人。如果還有其他事情我做的是不是最優的,我全都是耳朵,如果不是的話,再次感謝您的快速解決! – 2010-05-03 19:30:25

+1

這不是繁複的主要問題,而是你正在訪問的屬性(BackBufferStride和PixelHeight,這是真正的殺手鐗。我知道你正在使用WPF,但是對於Silverlight,我創建了一個緩存的包裝器所有的位圖屬性值,以獲得所需的性能 – 2010-05-03 20:25:44

5

在安全或不安全代碼中進一步優化: 在循環中停止除以3。在循環之外將您的閾值乘以3。您需要使用byte以外的其他類型,但這不應該成爲問題。實際上,你已經使用了比byte更大的數據類型:)

+0

+1:這也有幫助!我得到約26-30毫秒的範圍之前和現在在循環之外執行單個乘法,正如你所建議的那樣,將它縮小到17-20毫秒的範圍內。不知道我能夠降低得多。 – 2010-05-03 20:15:43

+0

@jomtois我試過幾件事...但是如果我的測試證明什麼的話,微型優化通常是不值得的,我嘗試使用一些位掩碼並使用'while(pBackBuffer Thorarin 2010-05-03 20:43:50

0

很難說出代碼分析,尤其是代碼非常不同(儘管它看起來很相似),但一些關鍵點(它們是一切都只是猜測)

停止條件,如果如果不安全的版本是不計算在安全

  • 的指數pixelArray 可能eventhough他們使用了兩次只能使用一次 計算陣列。
  • 即使他們沒有「緩存」,並稱 的數字加在一起而不存儲 他們(而不是++ P)將仍然 更快(更少的指令和 更少的內存存取)
  • 你是不是鎖定位圖在 安全版本
  • pixelArray [I],pixelArray [I + 1],pixelArray [I + 2] 可能會存儲在當地人製作 訪問它們第二次 可能比一次迭代 指針快。
  • 你在 不安全的代碼(PCOPY = pBackBuffer)和 一個額外的增量額外分配(pBackBuffer ++;)

這就是所有的想法我可以拿出。希望它有幫助

+0

我懷疑停止條件是如上所述的大打者。我會檢查一下你的其他想法和@ Thorarin的想法。基本上我想要將指針移動到代表像素的4個字節的塊中。我需要詢問並更改塊中的前三個字節並跳過第四個字節。我不知道微型優化是最好的。 – 2010-05-03 19:59:27