以前看过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. 除了Ubuntu以外的Linux系统可以安装Tensorflow/Pytorch的GPU版本吗???

    废话: 平时没事有用到的Linux系统有Centos/Redhat/Ubuntu/UOS/Deepin,其中Ubuntu系统主要是用来工作生产的,UOS就是看看国家队的进展如何,Deepin就是看看民 ...

  2. java多线程之ReentrantLock详解

    1.背景 2.基本语法 public class Test01 { // 定义锁 static ReentrantLock reentrantLock = new ReentrantLock(); p ...

  3. Java项目生产启动、关闭脚本

    1.直接启动 #!/bin/bash #这里可替换为你自己的执行程序,其他代码无需更改 APP_NAME=XXXX-api-1.0.jar #使用说明,用来提示输入参数 usage() { echo ...

  4. net8实现MediatR小示例C#

    MediatR是.net下的一个实现消息传递的库,简洁高效,它采用中介者设计模式,通过进程内消息传递机制,进行请求/响应.命令.查询.通知和事件的消息传递,可通过泛型来支持消息的智能调度,用于领域事件 ...

  5. FlashAttention简介

    前置知识 在GPU进行矩阵运算的时候,内部的运算单元具有和CPU类似的存储金字塔. 如果采用经典的Attention的计算方式,需要保存中间变量S和注意力矩阵O,这样子会产生很大的现存占用,并且这些数 ...

  6. 关于Arrays.asList返回List无法新增和删除?

    关于Arrays.asList返回的List无法新增和删除? 这个是在写项目的时候发现的,然后就分析了一下源码,得其内部原理 复现代码示例: public class ArraysAsList { p ...

  7. Adobe Photoshop cc2022 Mac中文破解版下载 支持intel/M1/M2/M3

    Adobe Photoshop cc2022 Mac版,支持intel.M1.M2.M3芯片,全系都能使用,最低系统需求:11.0以上.建议安装此版本或者ps2024版本. 下载地址放到最后,先来看安 ...

  8. jenkins动态切换环境

    一.代码层实现动态切换 1.首先在conftest.py下声明pytest_addoption钩子函数,写法如下 def pytest_addoption(parser): # 设置要接收的命令行参数 ...

  9. ERR_INVALID_REDIRECT

    原因为: response.sendRedirect(url) url地址写错了

  10. 性能、成本与 POSIX 兼容性比较: JuiceFS vs EFS vs FSx for Lustre

    JuiceFS 是一款为云环境设计的分布式高性能文件系统.Amazon EFS 易于使用且可伸缩,适用于多种应用.Amazon FSx for Lustre 则是面向处理快速和大规模数据工作负载的高性 ...