From 22873b803f3b1c805a2d11fc23656c9f1f6f6137 Mon Sep 17 00:00:00 2001 From: Alexis Mignon Date: Thu, 22 Aug 2013 15:05:22 +0200 Subject: [PATCH 1/5] Added non rotation invariant local binary patterns --- skimage/feature/_texture.pyx | 72 +++++++++++++++++++++++++++++------- skimage/feature/texture.py | 3 ++ 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index 9daa215d..a36e2a12 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -90,12 +90,13 @@ def _local_binary_pattern(double[:, ::1] image, the angular space). R : float Radius of circle (spatial resolution of the operator). - method : {'D', 'R', 'U', 'V'} + method : {'D', 'R', 'U', 'N', 'V'} Method to determine the pattern. * 'D': 'default' * 'R': 'ror' * 'U': 'uniform' + * 'N': 'nri_uniform' * 'V': 'var' Returns @@ -125,6 +126,9 @@ def _local_binary_pattern(double[:, ::1] image, cdef double lbp cdef Py_ssize_t r, c, changes, i + cdef Py_ssize_t r, c, changes, i, rot_index, n_ones + cdef cnp.int8_t first_zero, first_one + for r in range(image.shape[0]): for c in range(image.shape[1]): for i in range(P): @@ -146,19 +150,61 @@ def _local_binary_pattern(double[:, ::1] image, changes = 0 for i in range(P - 1): changes += abs(signed_texture[i] - signed_texture[i + 1]) - - if changes <= 2: - for i in range(P): - lbp += signed_texture[i] - else: - lbp = P + 1 - - if method == 'V': - var = np.var(texture) - if var != 0: - lbp /= var + if method == 'N': + # Non rotation invariant LBP. + # for P there are P + 1 ror and uniform patterns + # ex: P = 4 + # 1: 0 0 0 0 + # 2: 0 0 0 1 -> 0 0 1 0, 0 1 0 0, 1 0 0 0 + # 3: 0 0 1 1 -> 0 1 1 0, 1 1 0 0, 1 0 0 1 + # 4: 0 1 1 1 -> 1 1 1 0, 1 1 0 1, 1 0 1 1 + # 5: 1 1 1 1 + # The first and last patterns are always invariant under + # rotation. The (P - 1) remaining patterns have P rotated + # variants hence we have P * (P - 1) + 2 uniform patterns + # to which we add the non uniform pattern. + if changes <= 2: + # We have a uniform pattern + n_ones = 0 # determies the number of ones + first_one = -1 # position was the first one + first_zero = -1 # position of the first zero + for i in range(P): + if signed_texture[i]: + n_ones += 1 + if first_one == -1: + first_one = i + else: + if first_zero == -1: + first_zero = i + if n_ones == 0: + lbp = 0 + elif n_ones == P: + lbp = P * (P - 1) + 1 + else: + # There are (P - n_ones) patterns starting with 0. + # This patterns are indexed starting from the + # position where all zeros are on the right and + # applying circular right shifts. + if first_zero == 0: + var_index = P - n_ones - first_one + else: + var_index = P - first_zero + lbp = 1 + (n_ones - 1) * P + var_index + else: # changes > 2 + lbp = P * (P - 1) + 2 + else: # method != 'N' + if changes <= 2: + for i in range(P): + lbp += signed_texture[i] else: - lbp = np.nan + lbp = P + 1 + + if method == 'V': + var = np.var(texture) + if var != 0: + lbp /= var + else: + lbp = np.nan else: # method == 'default' for i in range(P): diff --git a/skimage/feature/texture.py b/skimage/feature/texture.py index 7549cfdd..5a162333 100644 --- a/skimage/feature/texture.py +++ b/skimage/feature/texture.py @@ -248,6 +248,8 @@ def local_binary_pattern(image, P, R, method='default'): * 'uniform': improved rotation invariance with uniform patterns and finer quantization of the angular space which is gray scale and rotation invariant. + * 'nri_uniform': non rotation-invariant uniform patterns variant + which is only gray scale invariant. * 'var': rotation invariant variance measures of the contrast of local image texture which is rotation but not gray scale invariant. @@ -269,6 +271,7 @@ def local_binary_pattern(image, P, R, method='default'): 'default': ord('D'), 'ror': ord('R'), 'uniform': ord('U'), + 'nri_uniform': ord('N'), 'var': ord('V') } image = np.ascontiguousarray(image, dtype=np.double) From eae691cf2177132527fd399a9c1ee83329fdcacd Mon Sep 17 00:00:00 2001 From: Alexis Mignon Date: Thu, 22 Aug 2013 15:24:49 +0200 Subject: [PATCH 2/5] Corrected duplicate variable declarations, variable name change from 'var_index' to 'rot_index' and corrected minor bugs --- skimage/feature/_texture.pyx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index a36e2a12..7b052d08 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -126,7 +126,7 @@ def _local_binary_pattern(double[:, ::1] image, cdef double lbp cdef Py_ssize_t r, c, changes, i - cdef Py_ssize_t r, c, changes, i, rot_index, n_ones + cdef Py_ssize_t rot_index, n_ones cdef cnp.int8_t first_zero, first_one for r in range(image.shape[0]): @@ -145,7 +145,7 @@ def _local_binary_pattern(double[:, ::1] image, lbp = 0 # if method == 'uniform' or method == 'var': - if method == 'U' or method == 'V': + if method == 'U' or method == 'N' or method == 'V': # determine number of 0 - 1 changes changes = 0 for i in range(P - 1): @@ -181,15 +181,16 @@ def _local_binary_pattern(double[:, ::1] image, elif n_ones == P: lbp = P * (P - 1) + 1 else: - # There are (P - n_ones) patterns starting with 0. + # There are (P - n_ones) patterns starting with 0 + # followed by n_ones patterns starting with 1. # This patterns are indexed starting from the # position where all zeros are on the right and # applying circular right shifts. if first_zero == 0: - var_index = P - n_ones - first_one + rot_index = P - n_ones - first_one else: - var_index = P - first_zero - lbp = 1 + (n_ones - 1) * P + var_index + rot_index = P - first_zero + lbp = 1 + (n_ones - 1) * P + rot_index else: # changes > 2 lbp = P * (P - 1) + 2 else: # method != 'N' From 7b022c8b5fc5b6346fb6bdf3caabec263b7fd579 Mon Sep 17 00:00:00 2001 From: Alexis Mignon Date: Sun, 25 Aug 2013 14:56:13 +0200 Subject: [PATCH 3/5] Finalized changes. Added reference for non rotation-invariant uniform patterns. Added test case. Modified comments to clarify the process, and changed the code to make it more consistent with explanations and actuall encoding --- skimage/feature/_texture.pyx | 56 +++++++++++++++++---------- skimage/feature/tests/test_texture.py | 11 ++++++ skimage/feature/texture.py | 6 ++- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index 7b052d08..6caa7ea3 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -151,18 +151,39 @@ def _local_binary_pattern(double[:, ::1] image, for i in range(P - 1): changes += abs(signed_texture[i] - signed_texture[i + 1]) if method == 'N': - # Non rotation invariant LBP. - # for P there are P + 1 ror and uniform patterns - # ex: P = 4 - # 1: 0 0 0 0 - # 2: 0 0 0 1 -> 0 0 1 0, 0 1 0 0, 1 0 0 0 - # 3: 0 0 1 1 -> 0 1 1 0, 1 1 0 0, 1 0 0 1 - # 4: 0 1 1 1 -> 1 1 1 0, 1 1 0 1, 1 0 1 1 - # 5: 1 1 1 1 - # The first and last patterns are always invariant under - # rotation. The (P - 1) remaining patterns have P rotated - # variants hence we have P * (P - 1) + 2 uniform patterns - # to which we add the non uniform pattern. + # Uniform local binary patterns are defined as patterns + # with at most 2 value changes (from 0 to 1 or from 1 to + # 0). Uniform patterns can be caraterized by their number + # `n_ones` of 1. The possible values for `n_ones` range + # from 0 to P. + # Here is an example for P = 4: + # n_ones=0: 0000 + # n_ones=1: 0001, 1000, 0100, 0010 + # n_ones=2: 0011, 1001, 1100, 0110 + # n_ones=3: 0111, 1011, 1101, 1110 + # n_ones=4: 1111 + # + # For a pattern of size P there are 2 constant patterns + # corresponding to n_ones=0 and n_ones=P. For each other + # value of `n_ones` , i.e n_ones=[1..P-1], there are P + # possible patterns which are related to each other through + # circular permutations. The total number of uniform + # patterns is thus (2 + P * (P - 1)). + # Given any pattern (uniform or not) we must be able to + # associate a unique code: + # 1. Constant patterns patterns (with n_ones=0 and + # n_ones=P) and non uniform patterns are given fixed + # code values. + # 2. Other uniform patterns are indexed considering the + # value of n_ones, and an index called 'rot_index' + # reprenting the number of circular right shifts + # required to obtain the pattern starting from a + # reference position (corresponding to all zeros stacked + # on the right). This number of rotations (or circular + # right shifts) 'rot_index' is efficiently computed by + # considering the positions of the first 1 and the first + # 0 found in the pattern. + if changes <= 2: # We have a uniform pattern n_ones = 0 # determies the number of ones @@ -181,15 +202,10 @@ def _local_binary_pattern(double[:, ::1] image, elif n_ones == P: lbp = P * (P - 1) + 1 else: - # There are (P - n_ones) patterns starting with 0 - # followed by n_ones patterns starting with 1. - # This patterns are indexed starting from the - # position where all zeros are on the right and - # applying circular right shifts. - if first_zero == 0: - rot_index = P - n_ones - first_one + if first_one == 0: + rot_index = n_ones - first_zero else: - rot_index = P - first_zero + rot_index = P - first_one lbp = 1 + (n_ones - 1) * P + rot_index else: # changes > 2 lbp = P * (P - 1) + 2 diff --git a/skimage/feature/tests/test_texture.py b/skimage/feature/tests/test_texture.py index d48a14f7..e4fb6acb 100644 --- a/skimage/feature/tests/test_texture.py +++ b/skimage/feature/tests/test_texture.py @@ -199,5 +199,16 @@ class TestLBP(): np.testing.assert_array_almost_equal(lbp, ref) + def test_nri_uniform(self): + lbp = local_binary_pattern(self.image, 8, 1, 'nri_uniform') + ref = np.array([[ 0, 54, 0, 57, 12, 57], + [34, 0, 58, 58, 3, 22], + [58, 57, 15, 50, 0, 47], + [10, 3, 40, 42, 35, 0], + [57, 7, 57, 58, 0, 56], + [ 9, 58, 0, 57, 7, 14]]) + np.testing.assert_array_almost_equal(lbp, ref) + + if __name__ == '__main__': np.testing.run_module_suite() diff --git a/skimage/feature/texture.py b/skimage/feature/texture.py index 5a162333..1ead9f7d 100644 --- a/skimage/feature/texture.py +++ b/skimage/feature/texture.py @@ -249,7 +249,7 @@ def local_binary_pattern(image, P, R, method='default'): finer quantization of the angular space which is gray scale and rotation invariant. * 'nri_uniform': non rotation-invariant uniform patterns variant - which is only gray scale invariant. + which is only gray scale invariant [2]. * 'var': rotation invariant variance measures of the contrast of local image texture which is rotation but not gray scale invariant. @@ -265,6 +265,10 @@ def local_binary_pattern(image, P, R, method='default'): Timo Ojala, Matti Pietikainen, Topi Maenpaa. http://www.rafbis.it/biplab15/images/stories/docenti/Danielriccio/\ Articoliriferimento/LBP.pdf, 2002. + .. [2] Face recognition with local binary patterns. + Timo Ahonen, Abdenour Hadid, Matti PietikainenAhonen, + http://masters.donntu.edu.ua/2011/frt/dyrul/library/article8.pdf, + 2004. """ methods = { From 515335e9175d8ee82972504313ca8bbc2b626052 Mon Sep 17 00:00:00 2001 From: Alexis Mignon Date: Sun, 25 Aug 2013 15:05:35 +0200 Subject: [PATCH 4/5] Corrected name in reference [2] --- skimage/feature/texture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/texture.py b/skimage/feature/texture.py index 1ead9f7d..94e5343c 100644 --- a/skimage/feature/texture.py +++ b/skimage/feature/texture.py @@ -266,7 +266,7 @@ def local_binary_pattern(image, P, R, method='default'): http://www.rafbis.it/biplab15/images/stories/docenti/Danielriccio/\ Articoliriferimento/LBP.pdf, 2002. .. [2] Face recognition with local binary patterns. - Timo Ahonen, Abdenour Hadid, Matti PietikainenAhonen, + Timo Ahonen, Abdenour Hadid, Matti Pietikainen, http://masters.donntu.edu.ua/2011/frt/dyrul/library/article8.pdf, 2004. """ From f4a097713cb3b617c11e1de8efaceacdae507242 Mon Sep 17 00:00:00 2001 From: Alexis Mignon Date: Sun, 25 Aug 2013 17:13:25 +0200 Subject: [PATCH 5/5] Changed link to the reference [2] --- skimage/feature/texture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/texture.py b/skimage/feature/texture.py index 94e5343c..f9bf6c9f 100644 --- a/skimage/feature/texture.py +++ b/skimage/feature/texture.py @@ -267,7 +267,7 @@ def local_binary_pattern(image, P, R, method='default'): Articoliriferimento/LBP.pdf, 2002. .. [2] Face recognition with local binary patterns. Timo Ahonen, Abdenour Hadid, Matti Pietikainen, - http://masters.donntu.edu.ua/2011/frt/dyrul/library/article8.pdf, + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.214.6851, 2004. """