【查虫日志】快速判断一副灰度图像中是否只有黑色和白色值(便是否为二值图像)过程当中bool变量的是是非非。...

  二值图像咱们在图像处理过程当中是常常遇到的,有的时候咱们在进行一个算法处理前,须要判断下一副图像的数据是否符合二值图的需求,这个时候咱们能够写个简单的函数来作个判断,好比我写了一个很简单的的代码以下:html

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指令来优化这个判断:网络

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个像素的判断。ide

  很显然,这个过程的效率要高不少,测试16MB的真二值图,也就1ms就完成了判断。函数

  好,我用上面的那个代码写成DLL,供C#调用,相关的函数声明以下:post

[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幅图:测试

           

            测试图1                               测视图2 (页面压缩了)优化

  这两幅图都不是二值图,他们在某些边缘位置都有抗锯齿操做。可是那个IM_IsBinaryImage_C检测图1不是二值图像,检测图2 是二值图像,而IM_IsBinaryImage_SSE_Bug则检测图1是二值图像,图2不是二值图像。开始我觉得是个人SSE代码写错了,我就又换了一种写法,以下所示:spa

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; }

  这个时候测绘对全部的图像结果都正确了。code

  可是,我以为代码片断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

转载于:https://www.cnblogs.com/Imageshop/p/11110198.html