以前看过matlab的bwlookup函数,但是总感觉有点神秘,一直没有去仔细分析,最近在分析计算二值图像的欧拉数时,发现自己写的代码和matlab的总是对不少,于是又去翻了下matlab的源代码,看到了matlab里实现欧拉数的代码非常简单,如下所示:

if n==4
lut = 4*[0 0.25 0.25 0 0.25 0 .5 -0.25 0.25 0.5 0 -0.25 0 ...
-0.25 -0.25 0] + 2;
else
lut = 4*[0 0.25 0.25 0 0.25 0 -.5 -0.25 0.25 -0.5 0 -0.25 0 ...
-0.25 -0.25 0] + 2;
end
% Need to zero-pad the input
b = padarray(a,[1 1],'both'); weights = bwlookup(b,lut);
if coder.isColumnMajor
e = (sum(weights(:),'double') - 2*numel(b)) / 4;
else
e = (sum(sum(weights,2,'double'),1,'double') - 2*numel(b)) / 4;
end

  在内部就是调用了bwlookup函数,那没办法了,就仔细看了M的帮助文档,发现原来这个函数真的非常简单,我们贴下M的帮助文档:

  A = bwlookup(BW,lut)

  The bwlookup function performs these steps to determine the value of each pixel in the processed image A:

  • Locate the pixel neighborhood in input image BW based on the coordinates of the target pixel in A. The function zero-pads border pixels of image BW when the neighborhood extends past the edge of BW.

  • Calculate an index, idx, based on the binary pixel pattern of the neighborhood.

  • Set the target pixel in A as the value of the lookup table at the index idx, in other words, the value of lut(idx).

2-by-2 Neighborhood Lookup

For 2-by-2 neighborhoods, there are four pixels in each neighborhood. Each binary pixel has two possible states, therefore the total number of permutations is 24 and the length of the lookup table lut is 16.

To find the value of the target output pixel at (row, column) coordinate (r,c), bwlookup uses the 2-by-2 pixel neighborhood in the input binary image BW whose top left pixel is at coordinate (r,c). The index idx into the lookup table is the weighted sum of the four pixels in the neighborhood, plus 1.

3-by-3 Neighborhood Lookup

For 3-by-3 neighborhoods, there are nine pixels in each neighborhood. Each binary pixel has two possible states, therefore the total number of permutations is 29 and the length of the lookup table lut is 256.

To find the value of the target output pixel at (row, column) coordinate (r,c), bwlookup uses the 3-by-3 pixel neighborhood in the input binary image BW centered on coordinate (r,c). The index idx into the lookup table is the weighted sum of the nine pixels in the neighborhood, plus 1.

  简单的说,这个查找表只有2种可能,一个是表的元素是16个,一种是由512个元素,其中16个元素时,查表的依据是以当前点为起点,向右向下各扩展一个像素范围,得到一个2*2领域4个像素,4个像素,每个像素也就只有2种可能的取值,这样就有16种可能的组合,对应16个元素。

  当表的元素是512个时,查表的依据是以当前点为中心点,向左向右,向上向下各扩展一个像素,得到一个3*3的领域9个像素,同样每个像素也只有2种可能取值,所以共有1<<9即512种取值。

  因为涉及到领域,所有在原图边缘的地方会有越界的图像,这个时候的原则是填充0,而不是复制最近的像素。

  这样,当表中有不同的内容时,就可以实现不同的结果。

  我们以16个元素的表为例,假定 lut[16] ={ 6 3 16 11 7 14 8 5 15 1 2 4 13 9 10 12},

  对于下面的一个4*4的二值数据:

   0   0   1   1
0 0 1 1
1 1 0 0
1 1 0 0

  如果当前的点位于第二行第一列,则其2*2的领域范围的像素信息为:

   0   0
1 1
  按照位置信息即对应的值计算索引为: 1 + 0 * 1 + 1 * 2 + 0 * 4 + 1 * 8 = 11, 对用的查表结果为 lut[11] = 2。
  使用类似的计算方法可以得到其他位置经过查表后的结果为:
    6    13    12    11
2 8 14 3
12 11 6 6
14 3 6 6

 注意,以上所有的下标起点都是1(matlab内部的约定),而且行列顺序和我们常用的C语言的二维数组也是掉了边的。
除了刚刚说的欧拉数的计算(其实没有搞懂欧拉数里的那个查找表是如何设计的,且8领域的欧拉数也是用的2*2的表,而不是3*3的),我们以matlab的bwarea函数为例,说明这个函数的价值。
  matlab中关于bwarea函数的定义为:

  bwarea 通过对图像中每个像素的面积求和来估计图像中所有 on 像素的面积。单个像素的面积是通过观察其 2×2 邻域来确定的。有六种不同的情形,每种情形表示一个不同面积:

  • 具有零个 on 像素的情形(面积 = 0)

  • 具有一个 on 像素的情形(面积 = 1/4)

  • 具有两个相邻 on 像素的情形(面积 = 1/2)

  • 具有两个对角 on 像素的情形(面积 = 3/4)

  • 具有三个 on 像素的情形(面积 = 7/8)

  • 具有所有四个 on 像素的情形(面积 = 1)

  用图形化的表达方式上述六种情况对应如下(为了显示,使用绿色表示黑色值,红色表示白色值):

                                       

索引值              0     1      2    3      4    5    6       7     8       9       10     11      12     13      14      15  

面积值      0    1/4     1/4   1/2    1/4   1/2   3/4     7/8   1/4     3/4     1/2    7/8     1/2      7/8      7/8      1

把面积值放大八倍得到面积值依次为:  0 2 2 4 2 4 6 7 2 6 4 7 4 7 7 8,我们翻看matlab的bwarea函数,可以得到其代码如下:

lut = [0     2     2     4     2     4     6     7     2     6     4     7 ...
4 7 7 8]; % Need to zero-pad the input.
bb = false(size(b)+2);
bb(2:end-1,2:end-1) = b; weights = bwlookup(bb,lut);
a = sum(weights(:),[],'double')/8;

  查找表和这里的一摸一样。

  使用查找表的优点是把领域所有的组合提前计算出结果来,而不用到程序里进行一些列复杂的判断,这样在很多情况下是可以提高速度的,比如刚刚这个面积计算的东西,如果在循环内部做,则要做N种判断,那如果是涉及到3*3的领域,那就更为复杂了。

  朴素的来说,有了bwlookup的原理,要把这个算法移植到C++中,就是一个比较简单的过程了,假如是处理16元素的表,也就是涉及到了4个像素,假定他们的值分别为P0\P1\P2\P3,则最基本的C代码如下所示:

int Index = 0;
if (P0 == 255) Index += 1;
if (P1 == 255) Index += 2;
if (P2 == 255) Index += 4;
if (P3 == 255) Index += 8;
LinePD[X] = Lut[Index];

  处理512个元素的过程也是类似的,只不过索引值多加了几个(注意,这里用255表示白色,而不是1)。

  如果考虑指令集优化,一个自然的翻译过程如下所示:

    __m128i Index = _mm_setzero_si128();
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(1)), _mm_cmpeq_epi8(P0, _mm_set1_epi8(255)));
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(2)), _mm_cmpeq_epi8(P1, _mm_set1_epi8(255)));
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(4)), _mm_cmpeq_epi8(P2, _mm_set1_epi8(255)));
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(8)), _mm_cmpeq_epi8(P3, _mm_set1_epi8(255)));
_mm_storeu_si128((__m128i*)(LinePD + X), _mm_shuffle_epi8(Lut128, Index));

  一次性处理16个字节,最为关键的一点是,这个查表可以方便的直接使用_mm_shuffle_epi8非常高效的实现,这个在我前期的文章里多次提到(16个字节表的查找,我们假定lut的类型为字节类型,这个在实际的应用中也足够了),

   这个代码的性能提升相对于C++来说是非常可观的,大概又4倍多的速度提升。
  仔细上述代码其实还有提高的地方,我们使用_mm_blendv_epi8来进行结果的混合, 而混合的标志是值是否等于255,这里用_mm_cmpeq_epi8做判断,而_mm_cmpeq_epi8的结果就是如果P等于255,则返回255,否则返回0,那这个不就是P本省吗,所以根本就不用做这个判断,这样代码就变为:

    __m128i Index = _mm_setzero_si128();
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(1)), P0, _mm_set1_epi8(255));
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(2)), P1, _mm_set1_epi8(255));
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(4)), P2, _mm_set1_epi8(255));
Index = _mm_blendv_epi8(Index, _mm_add_epi8(Index, _mm_set1_epi8(8)), P3, _mm_set1_epi8(255));
_mm_storeu_si128((__m128i*)(LinePD + X), _mm_shuffle_epi8(Lut128, Index));

  另外,再观察这个C语言的特殊性,我们可以把上述C代码修改为:

    int Index = 0;
Index += (1 & P0);
Index += (2 & P1);
Index += (4 & P2);
Index += (8 & P3);
LinePD[X] = Lut[Index];

  即直接使用位运算替换掉那个判断,这样对应的SSE指令也可以进行修改为如下代码:

    __m128i Index = _mm_setzero_si128();
Index = _mm_add_epi8(Index, _mm_and_si128(_mm_set1_epi8(1), P0));
Index = _mm_add_epi8(Index, _mm_and_si128(_mm_set1_epi8(2), P1));
Index = _mm_add_epi8(Index, _mm_and_si128(_mm_set1_epi8(4), P2));
Index = _mm_add_epi8(Index, _mm_and_si128(_mm_set1_epi8(8), P3));
// 可以直接接用shuffle实现查找表
_mm_storeu_si128((__m128i*)(LinePD + X), _mm_shuffle_epi8(Lut128, Index));

  相比于原始的SSE代码,这个改动也约有30%的性能提升的。

  对于512个元素的表,情况会有所不同,因为索引值大于了255,所以已经无法用字节类型来保存累加后得到的索引了,至少的用ushort类型,但是呢,前8个位置的索引相加还是不会超出字节类型的,所以前8个位置还是可以一次性处理16个像素,到最后一个位置时,单独转换为16位,然后再接用2次16数据的处理指令,就可以一次性得到16个字节对应的查找表索引。

  但是这个时候,由于表的大小时512,所以已经无法使用SSE去优化这个查表功能了,只能把索引值单个的提取出来,然后用普通的C语言去执行代码。如果CPU能支持AVX2,那么AVX2倒是又有关指令能直接查表,不过也要做很多的改造工程。

  我们测试,对于512个元素的表,优化后的SSE指令处理一副 3000*2000的二值图,大概耗时在2ms不到,速度还是相当的快的。

  搜索matlab的代码,除了bweuler及bwarea内使用了bwlookup,另外就是bwmorph里也大量的使用了bwlookup,而且都是使用的512个元素的表,也就是说使用3*3的领域,我们看bwmorph的帮助文档,有很多相关内容:

  这些对应的查找表在 MATLAB\R2023b\toolbox\images\images\+images\+internal 目录下以lut开头的文件中可以找到,比如说,这个clean的表内容如下:

  

  这些表的内容都是提前设计好的,然后可以多次重复的调用,从而得到某种效果。

  一般来说,这种3*3的领域算子,在迭代了一定的次数后效果就不会有变化了。

   我们在morph中也能看到一些常用的算子,比如这个remove就可以得到二值图像的边界,这个majority可以平滑噪音(和我博客里专门讲的那个majority效果还是有差异的)。

          

        原图                  remove              majority

          

        fatten                       skeleton                 thin

   个人感觉这个方法在处理一些比较复杂的领域信息时还是很有帮助的,特别是不方便用判断条件一个一个的写的,如果能提前把这个表弄出来,那就效率能得到很大的提升的。

   在个人的SSE Demo里,也集成了这个算法,其内容在Binary(二值处理)--》Processing(后处理)-->> Morph(形态学)中,有兴趣的朋友可以看看。

  

翻译

搜索

复制

[快速阅读八] Matlab中bwlookup的实现及其在计算二值图像的欧拉数、面积及其他morph变形中的应用。的更多相关文章

  1. C# 中根据datetime的值来计算属于本年的第几周,类似delphi中的weekoftheyear功能

    /// <summary> /// 获得今天是今年的第几周 /// </summary> /// <param name="year">< ...

  2. 201904<<快速阅读术>>

    在看过了几本数之后,发现原来培养读书的习惯好像也不太难,“将读书融入生活,框定读书时间” 生活中,我确实也是这样执行了.利用每天上下班的时间听书,有些觉得可以读快的书籍用了1.5倍速度在听,难懂的部分 ...

  3. SpeedReader for Mac(快速阅读器)v1.6免费版

    SpeedReader for Mac是一款运行在Mac平台上的阅读软件,通过这款软件就可以自行调整阅读速度.通过SpeedReader Mac版用户可以将想要阅读的内容拖入到软件中,调整速度和字体, ...

  4. JAVA之旅(八)——多态的体现,前提,好处,应用,转型,instanceof,多态中成员变量的特点,多态的案例

    JAVA之旅(八)--多态的体现,前提,好处,应用,转型,instanceof,多态中成员变量的特点,多态的案例 学习是不能停止的 一.多态 我们今天又要学习一个新的概念了,就是多态,它是面向对象的第 ...

  5. MATLAB实例:新建文件夹,保存.mat文件并保存数据到.txt文件中

    MATLAB实例:新建文件夹,保存.mat文件并保存数据到.txt文件中 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 用MATLAB实现:指定路径下 ...

  6. 【COCOS2DX-LUA 脚本开发之一】在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)并介绍脚本在游戏中详细用途!

    [COCOS2DX-LUA 脚本开发之一]在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)并介绍脚本在游戏中详细用途! 分类: [Cocos2dx Lua 脚本开发 ] 2012-04-1 ...

  7. phpspreadsheet 中文文档(三) 计算引擎

    2019年10月11日13:59:52 使用PhpSpreadsheet计算引擎 执行公式计算 由于PhpSpreadsheet表示内存中的电子表格,因此它还提供公式计算功能.单元格可以是值类型(包含 ...

  8. 无法为数据库 XXX 中的对象XXX 分配空间,因为 'PRIMARY' 文件组已满。请删除不需要的文件、删除文件组中的对象、将其他文件添加到文件组或为文件组中的现有文件启用自动增长,以便增加可用磁盘空间。

    无法为数据库 XXX 中的对象XXX 分配空间,因为 'PRIMARY' 文件组已满.请删除不需要的文件.删除文件组中的对象.将其他文件添加到文件组或为文件组中的现有文件启用自动增长,以便增加可用磁盘 ...

  9. pojg2744找一个最长的字符串x,使得对于已经给出的字符串中的任意一个y,x或者是y的子串,或者x中的字符反序之后得到的新字符串是y的子串。

    http://poj.grids.cn/practice/2744 描述现在有一些由英文字符组成的大小写敏感的字符串,你的任务是找到一个最长的字符串x,使得对于已经给出的字符串中的任意一个y,x或者是 ...

  10. SpringMVC,MyBatis项目中兼容Oracle和MySql的解决方案及其项目环境搭建配置、web项目中的单元测试写法、HttpClient调用post请求等案例

     要搭建的项目的项目结构如下(使用的框架为:Spring.SpingMVC.MyBatis): 2.pom.xml中的配置如下(注意,本工程分为几个小的子工程,另外两个工程最终是jar包): 其中 ...

随机推荐

  1. PowerBI_一分钟学会计算门店开业前3天销售金额_计算列及度量值方法

    在某些特殊场景,我们往往需要去计算一些特定的组别的聚合数据 今天,就以计算门店开业前3天的销售情况,来学习一下,利用计算列和DAX度量值,两种快捷计算此类问题的方案. 一:XMIND 二:示例数据 2 ...

  2. css 样式 element.style 覆盖问题

    问题: 我们在写网页定制样式的时候发现展示效果跟我们预想的不一样? 打开F12一看原来是element.style 覆盖的我定义的效果. 解决: 只要在定义的内容后面加上 !important 就行啦 ...

  3. 部署MatterMost-开源团队协作平台

    前言 之前的文章有提到部署 MatterMost 的事. 本文来记录一下. 关于 MatterMost MatterMost 有点像 Slack 这种协作工具,而且和 GitLab 的集成还不错,正好 ...

  4. 电脑打不开CHM格式文件解决办法

    如图所示 比如说jdk1.8的api 双击打开后,这个样子 就ok了

  5. 【倍增】Rigged Games

    题意 两队打比赛,大比分 2b − 1 赢,小比分 2a − 1 赢. 给定的长度为 n 的串,两队比赛的每个小分结果是这个串的循环重复. 问从该串的每个位置开始,最终谁会赢得整个比赛. 思路 倍增. ...

  6. Deformable DETR:商汤提出可变型 DETR,提点又加速 | ICLR 2021 Oral

    DETR能够消除物体检测中许多手工设计组件的需求,同时展示良好的性能.但由于注意力模块在处理图像特征图方面的限制,DETR存在收敛速度慢和特征分辨率有限的问题.为了缓解这些问题,论文提出了Deform ...

  7. [APIO2019] 路灯 题解

    LG5445 把询问 \(x,y\) 看作平面上的点 记当前时刻 \(t\),\(l\) 是与 \(i\) 连通的最左端,\(r\) 是与 \(i+1\) 连通的最右端,可以通过 set 维护断边找到 ...

  8. Vue3.5新增的baseWatch让watch函数和Vue组件彻底分手

    前言 在Vue 3.5.0-beta.3版本中新增了一个base watch函数,这个函数用法和我们熟知的watch API一模一样.区别就是我们之前用的watch API是和Vue组件以及生命周期是 ...

  9. SAR靶机笔记

    SAR 靶机笔记 概述 SAR 是 Vulnhub 上的靶机,大家可以去 vulnhub 网站上去进行下载. 这里有链接: https://download.vulnhub.com/sar/sar.z ...

  10. Android 获取当前获取焦点的组件

    在Activity中,使用this.getCurrentFocus(),获取当前焦点所在的View, 再判断是否是EditText(可调整成其他组件),看个人需要再做特定的逻辑处理 String co ...