Merge pull request #1536 from warmspringwinds/mb-lbp

FEAT: multi-block local binary patterns (MB-LBP)
This commit is contained in:
Josh Warner
2015-06-16 12:43:32 -05:00
5 changed files with 360 additions and 4 deletions
@@ -0,0 +1,73 @@
"""
===========================================================
Multi-Block Local Binary Pattern for texture classification
===========================================================
This example shows how to compute multi-block local binary pattern (MB-LBP)
features as well as how to visualize them.
The features are calculated similarly to local binary patterns (LBPs), except
that summed blocks are used instead of individual pixel values.
MB-LBP is an extension of LBP that can be computed on multiple scales in
constant time using the integral image. 9 equally-sized rectangles are used to
compute a feature. For each rectangle, the sum of the pixel intensities is
computed. Comparisons of these sums to that of the central rectangle determine
the feature, similarly to LBP (See `LBP <plot_local_binary_pattern.html>`_).
First, we generate an image to illustrate the functioning of MB-LBP: consider
a (9, 9) rectangle and divide it into (3, 3) block, upon which we then apply
MB-LBP.
"""
from __future__ import print_function
from skimage.feature import multiblock_lbp
import numpy as np
from numpy.testing import assert_equal
from skimage.transform import integral_image
# Create test matrix where first and fifth rectangles starting
# from top left clockwise have greater value than the central one.
test_img = np.zeros((9, 9), dtype='uint8')
test_img[3:6, 3:6] = 1
test_img[:3, :3] = 50
test_img[6:, 6:] = 50
# First and fifth bits should be filled. This correct value will
# be compared to the computed one.
correct_answer = 0b10001000
int_img = integral_image(test_img)
lbp_code = multiblock_lbp(int_img, 0, 0, 3, 3)
assert_equal(correct_answer, lbp_code)
"""
Now let's apply the operator to a real image and see how the
visualization works.
"""
from skimage import data
from matplotlib import pyplot as plt
from skimage.feature import draw_multiblock_lbp
test_img = data.coins()
int_img = integral_image(test_img)
lbp_code = multiblock_lbp(int_img, 0, 0, 90, 90)
img = draw_multiblock_lbp(test_img, 0, 0, 90, 90,
lbp_code=lbp_code, alpha=0.5)
plt.imshow(img, interpolation='nearest')
"""
.. image:: PLOT2RST.current_figure
On the above plot we see the result of computing a MB-LBP and visualization of
the computed feature. The rectangles that have less intensities' sum than the
central rectangle are marked in cyan. The ones that have higher intensity
values are marked in white. The central rectangle is left untouched.
"""
+7 -1
View File
@@ -1,7 +1,11 @@
from ._canny import canny
from ._daisy import daisy
from ._hog import hog
from .texture import greycomatrix, greycoprops, local_binary_pattern
from .texture import (greycomatrix, greycoprops,
local_binary_pattern,
multiblock_lbp,
draw_multiblock_lbp)
from .peak import peak_local_max
from .corner import (corner_kitchen_rosenfeld, corner_harris,
corner_shi_tomasi, corner_foerstner, corner_subpix,
@@ -25,6 +29,8 @@ __all__ = ['canny',
'greycomatrix',
'greycoprops',
'local_binary_pattern',
'multiblock_lbp',
'draw_multiblock_lbp',
'peak_local_max',
'structure_tensor',
'structure_tensor_eigvals',
+92
View File
@@ -6,6 +6,7 @@ import numpy as np
cimport numpy as cnp
from libc.math cimport sin, cos, abs
from .._shared.interpolation cimport bilinear_interpolation, round
from .._shared.transform cimport integrate
cdef extern from "numpy/npy_math.h":
@@ -264,3 +265,94 @@ def _local_binary_pattern(double[:, ::1] image,
output[r, c] = lbp
return np.asarray(output)
# Constant values that are used by `_multiblock_lbp` function.
# Values represent offsets of neighbour rectangles relative to central one.
# It has order starting from top left and going clockwise.
cdef:
Py_ssize_t[::1] mlbp_r_offsets = np.asarray([-1, -1, -1, 0, 1, 1, 1, 0])
Py_ssize_t[::1] mlbp_c_offsets = np.asarray([-1, 0, 1, 1, 1, 0, -1, -1])
def _multiblock_lbp(float[:, ::1] int_image,
Py_ssize_t r,
Py_ssize_t c,
Py_ssize_t width,
Py_ssize_t height):
"""Multi-block local binary pattern (MB-LBP) [1]_.
Parameters
----------
int_image : (N, M) float array
Integral image.
r : int
Row-coordinate of top left corner of a rectangle containing feature.
c : int
Column-coordinate of top left corner of a rectangle containing feature.
width : int
Width of one of 9 equal rectangles that will be used to compute
a feature.
height : int
Height of one of 9 equal rectangles that will be used to compute
a feature.
Returns
-------
output : int
8-bit MB-LBP feature descriptor.
References
----------
.. [1] Face Detection Based on Multi-Block LBP
Representation. Lun Zhang, Rufeng Chu, Shiming Xiang, Shengcai Liao,
Stan Z. Li
http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf
"""
cdef:
# Top-left coordinates of central rectangle.
Py_ssize_t central_rect_r = r + height
Py_ssize_t central_rect_c = c + width
Py_ssize_t r_shift = height - 1
Py_ssize_t c_shift = width - 1
# Copy offset array to multiply it by width and height later.
Py_ssize_t[::1] r_offsets = mlbp_r_offsets.copy()
Py_ssize_t[::1] c_offsets = mlbp_c_offsets.copy()
Py_ssize_t current_rect_r, current_rect_c
Py_ssize_t element_num, i
double current_rect_val
int has_greater_value
int lbp_code = 0
# Pre-multiply offsets with width and height.
for i in range(8):
r_offsets[i] = r_offsets[i]*height
c_offsets[i] = c_offsets[i]*width
# Sum of intensity values of central rectangle.
cdef float central_rect_val = integrate(int_image, central_rect_r, central_rect_c,
central_rect_r + r_shift,
central_rect_c + c_shift)
for element_num in range(8):
current_rect_r = central_rect_r + r_offsets[element_num]
current_rect_c = central_rect_c + c_offsets[element_num]
current_rect_val = integrate(int_image, current_rect_r, current_rect_c,
current_rect_r + r_shift,
current_rect_c + c_shift)
has_greater_value = current_rect_val >= central_rect_val
# If current rectangle's intensity value is bigger
# make corresponding bit to 1.
lbp_code |= has_greater_value << (7 - element_num)
return lbp_code
+29 -2
View File
@@ -1,7 +1,11 @@
import numpy as np
from skimage.feature import greycomatrix, greycoprops, local_binary_pattern
from skimage._shared.testing import test_parallel
from skimage.feature import (greycomatrix,
greycoprops,
local_binary_pattern,
multiblock_lbp)
from skimage._shared.testing import test_parallel
from skimage.transform import integral_image
class TestGLCM():
@@ -229,5 +233,28 @@ class TestLBP():
np.testing.assert_array_almost_equal(lbp, ref)
class TestMBLBP():
def test_single_mblbp(self):
# Create dummy matrix where first and fifth rectangles have greater
# value than the central one. Therefore, the following bits
# should be 1.
test_img = np.zeros((9, 9), dtype='uint8')
test_img[3:6, 3:6] = 1
test_img[:3, :3] = 255
test_img[6:, 6:] = 255
# MB-LBP is filled in reverse order. So the first and fifth bits from
# the end should be filled.
correct_answer = 0b10001000
int_img = integral_image(test_img)
lbp_code = multiblock_lbp(int_img, 0, 0, 3, 3)
np.testing.assert_equal(lbp_code, correct_answer)
if __name__ == '__main__':
np.testing.run_module_suite()
+159 -1
View File
@@ -4,7 +4,11 @@ Methods to characterize image textures.
import numpy as np
from .._shared.utils import assert_nD
from ._texture import _glcm_loop, _local_binary_pattern
from ..util import img_as_float
from ..color import gray2rgb
from ._texture import (_glcm_loop,
_local_binary_pattern,
_multiblock_lbp)
def greycomatrix(image, distances, angles, levels=256, symmetric=False,
@@ -291,3 +295,157 @@ def local_binary_pattern(image, P, R, method='default'):
image = np.ascontiguousarray(image, dtype=np.double)
output = _local_binary_pattern(image, P, R, methods[method.lower()])
return output
def multiblock_lbp(int_image, r, c, width, height):
"""Multi-block local binary pattern (MB-LBP) [1]_.
The features are calculated similarly to local binary patterns (LBPs),
(See :py:meth:`local_binary_pattern`) except that summed blocks are
used instead of individual pixel values.
MB-LBP is an extension of LBP that can be computed on multiple scales
in constant time using the integral image. Nine equally-sized rectangles
are used to compute a feature. For each rectangle, the sum of the pixel
intensities is computed. Comparisons of these sums to that of the central
rectangle determine the feature, similarly to LBP.
Parameters
----------
int_image : (N, M) array
Integral image.
r : int
Row-coordinate of top left corner of a rectangle containing feature.
c : int
Column-coordinate of top left corner of a rectangle containing feature.
width : int
Width of one of the 9 equal rectangles that will be used to compute
a feature.
height : int
Height of one of the 9 equal rectangles that will be used to compute
a feature.
Returns
-------
output : int
8-bit MB-LBP feature descriptor.
References
----------
.. [1] Face Detection Based on Multi-Block LBP
Representation. Lun Zhang, Rufeng Chu, Shiming Xiang, Shengcai Liao,
Stan Z. Li
http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf
"""
int_image = np.ascontiguousarray(int_image, dtype=np.float32)
lbp_code = _multiblock_lbp(int_image, r, c, width, height)
return lbp_code
def draw_multiblock_lbp(img, r, c, width, height,
lbp_code=0,
color_greater_block=[1, 1, 1],
color_less_block=[0, 0.69, 0.96],
alpha=0.5
):
"""Multi-block local binary pattern visualization.
Blocks with higher sums are colored with alpha-blended white rectangles,
whereas blocks with lower sums are colored alpha-blended cyan. Colors
and the `alpha` parameter can be changed.
Parameters
----------
img : ndarray of float or uint
Image on which to visualize the pattern.
r : int
Row-coordinate of top left corner of a rectangle containing feature.
c : int
Column-coordinate of top left corner of a rectangle containing feature.
width : int
Width of one of 9 equal rectangles that will be used to compute
a feature.
height : int
Height of one of 9 equal rectangles that will be used to compute
a feature.
lbp_code : int
The descriptor of feature to visualize. If not provided, the
descriptor with 0 value will be used.
color_greater_block : list of 3 floats
Floats specifying the color for the block that has greater
intensity value. They should be in the range [0, 1].
Corresponding values define (R, G, B) values. Default value
is white [1, 1, 1].
color_greater_block : list of 3 floats
Floats specifying the color for the block that has greater intensity
value. They should be in the range [0, 1]. Corresponding values define
(R, G, B) values. Default value is cyan [0, 0.69, 0.96].
alpha : float
Value in the range [0, 1] that specifies opacity of visualization.
1 - fully transparent, 0 - opaque.
Returns
-------
output : ndarray of float
Image with MB-LBP visualization.
References
----------
.. [1] Face Detection Based on Multi-Block LBP
Representation. Lun Zhang, Rufeng Chu, Shiming Xiang, Shengcai Liao,
Stan Z. Li
http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf
"""
# Default colors for regions.
# White is for the blocks that are brighter.
# Cyan is for the blocks that has less intensity.
color_greater_block = np.asarray(color_greater_block, dtype=np.float64)
color_less_block = np.asarray(color_less_block, dtype=np.float64)
# Copy array to avoid the changes to the original one.
output = np.copy(img)
# As the visualization uses RGB color we need 3 bands.
if len(img.shape) < 3:
output = gray2rgb(img)
# Colors are specified in floats.
output = img_as_float(output)
# Offsets of neighbour rectangles relative to central one.
# It has order starting from top left and going clockwise.
neighbour_rect_offsets = ((-1, -1), (-1, 0), (-1, 1),
(0, 1), (1, 1), (1, 0),
(1, -1), (0, -1))
# Pre-multiply the offsets with width and height.
neighbour_rect_offsets = np.array(neighbour_rect_offsets)
neighbour_rect_offsets[:, 0] *= height
neighbour_rect_offsets[:, 1] *= width
# Top-left coordinates of central rectangle.
central_rect_r = r + height
central_rect_c = c + width
for element_num, offset in enumerate(neighbour_rect_offsets):
offset_r, offset_c = offset
curr_r = central_rect_r + offset_r
curr_c = central_rect_c + offset_c
has_greater_value = lbp_code & (1 << (7-element_num))
# Mix-in the visualization colors.
if has_greater_value:
new_value = ((1-alpha) * output[curr_r:curr_r+height, curr_c:curr_c+width]
+ alpha * color_greater_block)
output[curr_r:curr_r+height, curr_c:curr_c+width] = new_value
else:
new_value = ((1-alpha) * output[curr_r:curr_r+height, curr_c:curr_c+width]
+ alpha * color_less_block)
output[curr_r:curr_r+height, curr_c:curr_c+width] = new_value
return output