【查蟲日誌】快速判斷一副灰度圖像中是否只有黑色和白色值(便是否爲二值圖像)過程當中bool變量的是是非非。

  二值圖像咱們在圖像處理過程當中是常常遇到的,有的時候咱們在進行一個算法處理前,須要判斷下一副圖像的數據是否符合二值圖的需求,這個時候咱們能夠寫個簡單的函數來作個判斷,好比我寫了一個很簡單的的代碼以下:算法

bool IM_IsBinaryImage_C(unsigned char *Src, int Width, int Height, int Stride) { int Channel = Stride / Width; if (Src == NULL)                            return false; if ((Width <= 0) || (Height <= 0))            return false; if (Channel != 1)                            return false; for (int Y = 0; Y < Height; Y++) { unsigned char *LinePS = Src + Y * Stride; for (int X = 0; X < Width * Channel; X++) { if ((LinePS[X] != 255) && (LinePS[X] != 0))    return false; //if (((LinePS[X] == 255) || (LinePS[X] == 0)) == false) return false;
 } } return true; }

  即若是存在一個像素若是不爲255,也不爲0,則這副圖就不是二值圖,當即能夠返回了,而無需進行後續的判斷了。  網絡

  當一副圖不是二值圖時,一般,咱們很快就能返回結果了,那麼最壞的狀況就是他剛好是二值圖,這樣,咱們就要遍歷完全部的像素。咱們測試過對於16MB的二值圖(4000*4000),測試須要15ms的時間,爲了能儘可能減小耗時,可使用以下的SIMD指令來優化這個判斷:ide

bool IM_IsBinaryImage_SSE_Bug(unsigned char *Src, int Width, int Height, int Stride) { int Channel = Stride / Width; if (Src == NULL)                            return false; if ((Width <= 0) || (Height <= 0))            return false; if (Channel != 1)                            return false; int BlockSize = 16, Block = (Width * Channel)/ BlockSize; for (int Y = 0; Y < Height; Y++)                                        // 速度提高約16倍
 { unsigned char *LinePS = Src + Y * Stride; for (int X = 0; X < Block * BlockSize; X += BlockSize) { __m128i SrcV = _mm_loadu_si128((__m128i *)(LinePS + X)); __m128i MaskW = _mm_cmpeq_epi8(SrcV, _mm_set1_epi8(255)); __m128i MaskB = _mm_cmpeq_epi8(SrcV, _mm_setzero_si128()); __m128i Mask = _mm_or_si128(MaskW, MaskB); if (_mm_movemask_epi8(Mask) != 65535)    return false;            // if (((LinePS[X] == 255) || (LinePS[X] == 0)) == false) return false;
 } for (int X = Block * BlockSize; X < Width * Channel; X++) { if ((LinePS[X] != 255) && (LinePS[X] != 0))    return false; } } return true; }

  因爲SIMD指令裏沒有_mm_cmpneq_epi8函數,咱們該用代碼1片斷裏被註釋掉的那種邏輯來判斷一個像素是不是黑色和白色,這裏固然也有一些技巧,好比_mm_movemask_epi8指令的運用。咱們判斷這個像素是否等於255和0,固然,一個像素不可能同時知足這兩個條件,不知足的Mask返回0,知足則Mask返回255,因此若是他是黑色和白色,大家這兩個Mask進行或操做確定就爲255,不然或操做後就爲0,SIMD中這樣的比較能夠一次性進行16個像素,若是這16個像素都符合條件,那麼或操做後的mask都爲255,這樣經過使用_mm_movemask_epi8來判斷這個mask就完成了16個像素的判斷。函數

  很顯然,這個過程的效率要高不少,測試16MB的真二值圖,也就1ms就完成了判斷。測試

  好,我用上面的那個代碼寫成DLL,供C#調用,相關的函數聲明以下:優化

[DllImport("IsBinaryImage.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true)]  private static extern bool IM_IsBinaryImage_C(byte* Src, int Width, int Height, int Stride); [DllImport("IsBinaryImage.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true)] private static extern bool IM_IsBinaryImage_SSE_Bug(byte* Src, int Width, int Height, int Stride);

  可出來的結果令我很是詫異,我測試了下面這2幅圖:spa

           

            測試圖1                               測視圖2 (頁面壓縮了)code

  這兩幅圖都不是二值圖,他們在某些邊緣位置都有抗鋸齒操做。可是那個IM_IsBinaryImage_C檢測圖1不是二值圖像,檢測圖2 是二值圖像,而IM_IsBinaryImage_SSE_Bug則檢測圖1是二值圖像,圖2不是二值圖像。開始我覺得是個人SSE代碼寫錯了,我就又換了一種寫法,以下所示:blog

bool IM_IsBinaryImage_SSE(unsigned char *Src, int Width, int Height, int Stride) { int Channel = Stride / Width; if (Src == NULL)                            return false; if ((Width <= 0) || (Height <= 0))            return false; if (Channel != 1)                            return false; int BlockSize = 16, Block = (Width * Channel) / BlockSize; bool Flag = true; for (int Y = 0; Y < Height; Y++)                                        // 速度提高約16倍
 { unsigned char *LinePS = Src + Y * Stride; if (Flag == false)    break; for (int X = 0; X < Block * BlockSize; X += BlockSize) { __m128i SrcV = _mm_loadu_si128((__m128i *)(LinePS + X)); __m128i MaskW = _mm_cmpeq_epi8(SrcV, _mm_set1_epi8(255));        // _mm_cmpeq_epi8是自帶的,若是使用_mm_cmpneq_epu8則慢了一些。
            __m128i MaskB = _mm_cmpeq_epi8(SrcV, _mm_setzero_si128()); __m128i Mask = _mm_or_si128(MaskW, MaskB); if (_mm_movemask_epi8(Mask) != 65535) { Flag = false;                            // if ((LinePS[X] == 255) || (LinePS[X] == 0)) = false, return false
                break; } } for (int X = Block * BlockSize; X < Width * Channel; X++) { if ((LinePS[X] != 255) && (LinePS[X] != 0)) { Flag = false; break; } } } return Flag; }

  這個時候測繪對全部的圖像結果都正確了。get

  可是,我以爲代碼片斷2應該是不會有任何錯誤的啊。爲何會出現這種現象呢。

  後面從網上查了下,C++的bool變量就只有true和false, 是字節變量,這個能夠用printf("%d", sizeof(false));來驗證,會打印1。而在其餘語言中,彷佛是int類型。可是我在C#中用 MessageBox.Show(sizeof(bool).ToString());  彷佛也是彈出1。

  可是,當咱們把這些函數的返回值都改成int後,在C#中調用就正常了,好比:

int IM_IsBinaryImage_C(unsigned char *Src, int Width, int Height, int Stride)

  也就是說上述的IM_IsBinaryImage_SSE_Bug函數體並沒有Bug。這究竟是怎麼回事,還請萬能的網絡高手有空予以解疑。

  附上測試工程和代碼:https://files.cnblogs.com/files/Imageshop/ISBinaryImage.rar

相關文章
相關標籤/搜索