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

bool IM_IsBinaryImage_C(unsigned char *Src, int Width, int Height, int Stride)
{
int Channel = Stride / Width;
if (Src == NULL) return false;
if ((Width <= ) || (Height <= )) return false;
if (Channel != ) return false;
for (int Y = ; Y < Height; Y++)
{
unsigned char *LinePS = Src + Y * Stride;
for (int X = ; X < Width * Channel; X++)
{
if ((LinePS[X] != ) && (LinePS[X] != )) 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 <= ) || (Height <= )) return false;
if (Channel != ) return false;
int BlockSize = , Block = (Width * Channel)/ BlockSize; for (int Y = ; Y < Height; Y++) // 速度提升约16倍
{
unsigned char *LinePS = Src + Y * Stride;
for (int X = ; X < Block * BlockSize; X += BlockSize)
{
__m128i SrcV = _mm_loadu_si128((__m128i *)(LinePS + X));
__m128i MaskW = _mm_cmpeq_epi8(SrcV, _mm_set1_epi8());
__m128i MaskB = _mm_cmpeq_epi8(SrcV, _mm_setzero_si128());
__m128i Mask = _mm_or_si128(MaskW, MaskB);
if (_mm_movemask_epi8(Mask) != ) return false; // if (((LinePS[X] == 255) || (LinePS[X] == 0)) == false) return false;
}
for (int X = Block * BlockSize; X < Width * Channel; X++)
{
if ((LinePS[X] != ) && (LinePS[X] != )) 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幅图:

           

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

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

bool IM_IsBinaryImage_SSE(unsigned char *Src, int Width, int Height, int Stride)
{
int Channel = Stride / Width;
if (Src == NULL) return false;
if ((Width <= ) || (Height <= )) return false;
if (Channel != ) return false;
int BlockSize = , Block = (Width * Channel) / BlockSize;
bool Flag = true;
for (int Y = ; Y < Height; Y++) // 速度提升约16倍
{
unsigned char *LinePS = Src + Y * Stride;
if (Flag == false) break;
for (int X = ; X < Block * BlockSize; X += BlockSize)
{
__m128i SrcV = _mm_loadu_si128((__m128i *)(LinePS + X));
__m128i MaskW = _mm_cmpeq_epi8(SrcV, _mm_set1_epi8()); // _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) != )
{
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] != ) && (LinePS[X] != ))
{
Flag = false;
break;
}
}
}
return Flag;
}

  这个时候测绘对所有的图像结果都正确了。

  但是,我觉得代码片段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

【查虫日志】快速判断一副灰度图像中是否只有黑色和白色值(即是否为二值图像)过程中bool变量的是是非非。的更多相关文章

  1. 我试了试用 SQL查 Linux日志,好用到飞起

    大家好,我是小富~ 最近发现点好玩的工具,迫不及待的想跟大家分享一下. 大家平时都怎么查Linux日志呢? 像我平时会用tail.head.cat.sed.more.less这些经典系统命令,或者aw ...

  2. 快速判断ie10及以上版本浏览器

    if (!(/msie [6|7|8|9]/i.test(navigator.userAgent))){ //ie10以上 }; 快速判断ie10及以上版本浏览器

  3. PHP取二进制文件头快速判断文件类型

    <?php /*文件扩展名说明 *7173 gif *255216 jpg *13780 png *6677 bmp *239187 txt,aspx,asp,sql *208207 xls.d ...

  4. JavaScript正则表达式快速判断技巧

    这里是JS的正则的一点心得,并不是最完整的规则汇总,更侧重实际运用中的快速判断,初学者接触正则之后往往会被一堆星号括号给弄晕,有了一些速判技巧就能从整体上把握从而不慌乱. JS正则快速判断技巧的核心就 ...

  5. 腾讯面试题 腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?

    腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?  这个题目已经有一段时间了,但是腾讯现在还在用来面试.腾讯第一次面 ...

  6. 利用linq快速判断给定数字是否包含在某个段范围内

    一.需求: 知道某段范围0x0020~0x007F0x00A0~0x017F0x01A0~0x01CF0x01F0~0x01FF0x0210~0x021F0x1EA0~0x1EFF给定一个值,快速判断 ...

  7. zoj 月赛B题(快速判断一个大数是否为素数)

    给出一个64位的大数,如何快速判断其是否为素数 #include<algorithm> #include<cstdio> #include<cstring> #in ...

  8. vuex中filter的使用 && 快速判断一个数是否在一个数组中

    vue中filter的使用 computed: mapState({ items: state => state.items.filter(function (value, index, arr ...

  9. 布隆过滤器 - 如何在100个亿URL中快速判断某URL是否存在?

    题目描述 一个网站有 100 亿 url 存在一个黑名单中,每条 url 平均 64 字节.这个黑名单要怎么存?若此时随便输入一个 url,你如何快速判断该 url 是否在这个黑名单中? 题目解析 这 ...

随机推荐

  1. 学术论文写作的 paper、code 资源

    (机器学习/计算机视觉/深度学习)代码 0. 核心期刊 Best paper awards at - CV NIPS: JMLR COLT & ICML(每年度的官网) 1. Computin ...

  2. intel edison with grove lcd

    由intel xdk,例如,下面的过程能够打印Hello world至grove lcd上 var mraa = require ('mraa'); var LCD = require ('jsupm ...

  3. Java和Flex积分误差(两)

    1.错误原因 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -help ...

  4. AngularJS $http和$.ajax

    $http请求 $http请求返回之后,给前台绑定数据赋值,会自动更新数据 ajax请求 $.ajax请求返回之后,给前台绑定数据赋值,不会自动更新数据,需要用$scope.$apply手动刷新 ap ...

  5. WPF 4 动态覆盖图标(Dynamic Overlay Icon)

    原文:WPF 4 动态覆盖图标(Dynamic Overlay Icon)      在<WPF 4 开发Windows 7 任务栏>一文中我们学习了任务栏的相关开发内容,同时也对覆盖图标 ...

  6. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻(转)

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  7. Plupload上传插件中文文档

    Plupload上传插件中文帮助文档 如有疑问,加群交流:646104701 下载地址:https://www.plupload.com/download/ 配置参数 实例化一个plupload对象时 ...

  8. Linux ssh密钥自动登录 专题

    在开发中,经常需要从一台主机ssh登陆到另一台主机去,每次都需要输一次login/Password,很繁琐.使用密钥登陆就可以不用输入用户名和密码了 实现从主机A免密码登陆到主机B(即把主机A的pub ...

  9. Gralde 同步失败

    Gralde 同步失败 尝试了各种方法,至少我觉得常见的一些方法我都尝试了.但一直下载依赖失败 > Could not resolve all files for configuration ' ...

  10. mysql索引创建&查看&删除

    1.索引作用 在索引列上,除了上面提到的有序查找之外,数据库利用各种各样的快速定位技术,能够大大提高查询效率.特别是当数据量非常大,查询涉及多个表时,使用索引往往能使查询速度加快成千上万倍. 例如,有 ...