Cython implementation of MB-LBP. Updated MB-LBP visualization without matplotlib.Examples to gallery were added. Tests are made more easily readable.

This commit is contained in:
dan
2015-06-08 09:16:22 +02:00
parent 086d8c667b
commit 83c3bd10c8
5 changed files with 315 additions and 78 deletions
@@ -0,0 +1,87 @@
"""
===========================================================
Multi-Block Local Binary Pattern for texture classification
===========================================================
In this example, we will see how to compute the multi-block
local binary pattern at a specified image and how to visualize it.
The features are calculated in a way similar to local binary
patterns, except that block summed up pixel values
rather than pixel values are used.
`MB-LBP` is an extension of LBP that can be computed on any
scales in a constant time using integral image. It consists of
`9` equal-sized rectangles. They are used to compute a feature.
Sum of pixels' intensity values in each of them are compared
to the central rectangle and depending on comparison result,
the feature descriptor is computed.
We will start with a simple image that we will generate by our
own to show how the `MB-LBP` works. We will create a `(9, 9)`
rectangle with and divide it into `9` blocks. After this
we will apply `MB-LBP` on it.
"""
from __future__ import print_function
from skimage.feature import multiblock_local_binary_pattern
import numpy as np
from skimage.util import img_as_float
from skimage.transform import integral_image
# 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] = 50
test_img[6:, 6:] = 50
# MB-LBP is filled in reverse order.
# So the first and fifth bits from the end should
# be filled.
correct_answer = 0b10001000
# The function accepts the float images.
# Also it has to be C-contiguous.
test_img = img_as_float(test_img)
int_img = integral_image(test_img)
lbp_code = multiblock_local_binary_pattern(int_img, 0, 0, 3, 3)
print(lbp_code == correct_answer)
"""
Now let's apply the operator to a real image and see how the visualization works.
"""
from __future__ import print_function
from skimage.feature import (multiblock_local_binary_pattern,
visualize_multiblock_lbp)
from skimage.util import img_as_float
from skimage.transform import integral_image
from skimage import data
from matplotlib import pyplot as plt
test_img = data.coins()
test_img = img_as_float(test_img)
int_img = integral_image(test_img)
lbp_code = multiblock_local_binary_pattern(int_img, 0, 0, 90, 90)
img = visualize_multiblock_lbp(test_img, 0, 0, 90, 90,
lbp_code=lbp_code)
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 intensity than the central
rectangle are marked with cyan color. The ones that have bigger intensity values
are marked with white color. The central rectangle is left untouched.
"""
+3 -1
View File
@@ -2,8 +2,10 @@ from ._canny import canny
from ._daisy import daisy
from ._hog import hog
from .texture import (greycomatrix, greycoprops,
local_binary_pattern, multiblock_local_binary_pattern,
local_binary_pattern,
visualize_multiblock_lbp)
from ._texture import multiblock_local_binary_pattern
from .peak import peak_local_max
from .corner import (corner_kitchen_rosenfeld, corner_harris,
corner_shi_tomasi, corner_foerstner, corner_subpix,
+171
View File
@@ -264,3 +264,174 @@ def _local_binary_pattern(double[:, ::1] image,
output[r, c] = lbp
return np.asarray(output)
cdef inline Py_ssize_t _clip(Py_ssize_t x, Py_ssize_t low,
Py_ssize_t high) nogil:
"""Clips coordinate between high and low.
Parameters
----------
x : int
Coordinate to be clipped.
low : int
The lower bound.
high : int
The higher bound.
Returns
-------
x : int
`x` clipped between `high` and `low`.
"""
if(x > high):
return high
if(x < low):
return low
return x
cdef inline cnp.double_t _integ(
cnp.double_t[:, ::1] img, Py_ssize_t r0, Py_ssize_t c0,
Py_ssize_t r1, Py_ssize_t c1) nogil:
"""Integrate over the integral image in the given window
This method was created so that `multiblock_local_binary_pattern`
does not have to make a Python call.
Parameters
----------
img : array
The integral image over which to integrate.
r0 : int
The row number of the top left corner.
c0 : int
The column number of the top left corner.
r1 : int
The row number of the bottom right corner.
c1 : int
The column number of the bottom right corner.
Returns
-------
ans : double
The integral over the given window.
"""
r = _clip(r0, 0, img.shape[0] - 1)
c = _clip(c0, 0, img.shape[1] - 1)
r2 = _clip(r1, 0, img.shape[0] - 1)
c2 = _clip(c1, 0, img.shape[1] - 1)
cdef cnp.double_t ans = img[r1, c1]
if (r0 >= 1) and (c0 >= 1):
ans += img[r0 - 1, c0 - 1]
if (r0 >= 1):
ans -= img[r0 - 1, c1]
if (c0 >= 1):
ans -= img[r1, c0 - 1]
return ans
def multiblock_local_binary_pattern(cnp.double_t[:, ::1] int_image,
Py_ssize_t x,
Py_ssize_t y,
Py_ssize_t width,
Py_ssize_t height):
"""Multi-block local binary pattern.
The features are calculated in a way similar to local binary
patterns, except that block summed up pixel values
rather than pixel values are used.
MB-LBP is an extension of LBP that can be computed on any
scales in a constant time using integral image. It consists of
9 equal-sized rectangles. They are used to compute a feature.
Sum of pixels' intensity values in each of them are compared
to the central rectangle and depending on comparison result,
the feature descriptor is computed.
Parameters
----------
int_image : (N, M) double array
Integral image.
x : int
X-coordinate of top left corner of a rectangle containing feature.
y : int
Y-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
8bit 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
"""
# Top-left coordinates of central rectangle
cdef:
Py_ssize_t central_rect_x = x + width
Py_ssize_t central_rect_y = y + height
# Sum of intensity values of central rectangle
cdef double central_rect_val = _integ(int_image,
central_rect_y,
central_rect_x,
central_rect_y + height - 1,
central_rect_x + width - 1)
#print central_rect_x, central_rect_y
# Offsets of neighbour rectangles relative to central one.
# It has order starting from top left and going clockwise
cdef:
Py_ssize_t *x_offsets = [-1, 0, 1, 1, 1, 0, -1, -1]
Py_ssize_t *y_offsets = [-1, -1, -1, 0, 1, 1, 1, 0]
Py_ssize_t element_num, offset_x, offset_y
Py_ssize_t current_rect_x, current_rect_y
double current_rect_val
int has_greater_value
int lbp_code = 0
for element_num in range(8):
offset_x = x_offsets[element_num]
offset_y = y_offsets[element_num]
current_rect_x = central_rect_x + offset_x * width
current_rect_y = central_rect_y + offset_y * height
current_rect_val = _integ(int_image,
current_rect_y,
current_rect_x,
current_rect_y + height - 1,
current_rect_x + width - 1)
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
+14 -3
View File
@@ -6,9 +6,9 @@ from skimage.feature import (
multiblock_local_binary_pattern
)
from skimage._shared.testing import test_parallel
from skimage.transform import integral_image
from skimage.util import img_as_float
class TestGLCM():
@@ -235,7 +235,10 @@ class TestLBP():
[ 9, 58, 0, 57, 7, 14]])
np.testing.assert_array_almost_equal(lbp, ref)
def test_multiblock_lbp(self):
class TestMBLBP():
def test_single_mblbp(self):
# Create dummy matrix where first and fifth
# rectangles have greater value than the central one
@@ -245,11 +248,19 @@ class TestLBP():
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
# The function accepts the float images.
# Also it has to be C-contiguous.
test_img = img_as_float(test_img)
int_img = integral_image(test_img)
lbp_code = multiblock_local_binary_pattern(int_img, 0, 0, 3, 3)
np.testing.assert_equal(lbp_code, 17)
np.testing.assert_equal(lbp_code, correct_answer)
if __name__ == '__main__':
+40 -74
View File
@@ -4,10 +4,9 @@ Methods to characterize image textures.
import numpy as np
from .._shared.utils import assert_nD
from ..util import img_as_float
from ._texture import _glcm_loop, _local_binary_pattern
from ..transform import integrate
def greycomatrix(image, distances, angles, levels=256, symmetric=False,
normed=False):
@@ -294,8 +293,9 @@ def local_binary_pattern(image, P, R, method='default'):
output = _local_binary_pattern(image, P, R, methods[method.lower()])
return output
def multiblock_local_binary_pattern(int_image, x, y, width, height):
"""Multi-block local binary pattern.
def visualize_multiblock_lbp(img, x, y, width, height, lbp_code=0):
"""Multi-block local binary pattern visualization.
MB-LBP is an extension of LBP that can be computed on many
scales in a constant time using integral image. It consists of
@@ -304,10 +304,15 @@ def multiblock_local_binary_pattern(int_image, x, y, width, height):
depending on comparison result, the feature descriptor is
computed.
The blocks visualized in the following manner: the center block
is left untouched. The blocks that have higher are covered with
transparent white rectangles. The blocks that have less intensity
are covered with cyan rectangles.
Parameters
----------
int_image : (N, M) array
Integral image.
img :
Image on which to visualize the pattern.
x : int
X-coordinate of top left corner of a rectangle containing feature.
y : int
@@ -318,11 +323,14 @@ def multiblock_local_binary_pattern(int_image, x, y, width, height):
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.
Returns
-------
output : int
8bit MB-LBP feature descriptor.
output :
Float image with visualization.
References
----------
@@ -332,55 +340,21 @@ def multiblock_local_binary_pattern(int_image, x, y, width, height):
http://www.cbsr.ia.ac.cn/users/scliao/papers/Zhang-ICB07-MBLBP.pdf
"""
# Top-left coordinates of central rectangle
central_rect_x = x + width
central_rect_y = y + height
# 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([1, 1, 1], dtype='float64')
color_less_block = np.asarray([0, 0.69, 0.96], dtype='float64')
# Sum of intensity values of central rectangle
central_rect_val = integrate(int_image,
central_rect_y,
central_rect_x,
central_rect_y + height - 1,
central_rect_x + width - 1)
# Copy array to avoid the changes to the original one
output = np.copy(img)
# Offsets of neighbour rectangles relative to central one.
# It has order starting from top left and going clockwise
neighbour_rect_offsets = ((-1, -1), (0, -1), (1, -1),
(1, 0), (1, 1), (0, 1),
(-1, 1), (-1, 0))
# As the visualization uses RGB color we need 3 bands.
if len(img.shape) < 3:
output = np.dstack((img,) * 3)
lbp_code = 0
for element_num, offset in enumerate(neighbour_rect_offsets):
offset_x, offset_y = offset
current_rect_x = central_rect_x + offset_x * width
current_rect_y = central_rect_y + offset_y * height
current_rect_val = integrate(int_image,
current_rect_y,
current_rect_x,
current_rect_y + height - 1,
current_rect_x + width - 1)
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 << element_num
print lbp_code
return lbp_code
def visualize_multiblock_lbp(img, x, y, width, height, lbp_code=0):
import matplotlib.patches as patches
import matplotlib.pyplot as plt
plt.imshow(img)
img_desc = plt.gca()
plt.set_cmap('gray')
# 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
@@ -396,27 +370,19 @@ def visualize_multiblock_lbp(img, x, y, width, height, lbp_code=0):
offset_x, offset_y = offset
current_rect_x = central_rect_x + offset_x * width
current_rect_y = central_rect_y + offset_y * height
curr_x = central_rect_x + offset_x * width
curr_y = central_rect_y + offset_y * height
has_greater_value = lbp_code & (1 << element_num)
# Hatch the rectangles that has less
# intensity than the central rectangle.
hatch = '\\'
has_greater_value = lbp_code & (1 << (7-element_num))
# Mix-in the visualization colors
if has_greater_value:
hatch = ''
output[curr_y:curr_y+height, curr_x:curr_x+width] = \
0.5 * output[curr_y:curr_y+height, curr_x:curr_x+width] \
+ 0.5 * color_greater_block
else:
output[curr_y:curr_y+height, curr_x:curr_x+width] = \
0.5 * output[curr_y:curr_y+height, curr_x:curr_x+width] \
+ 0.5 * color_less_block
img_desc.add_patch(
patches.Rectangle(
(current_rect_x, current_rect_y),
width,
height,
fill=False,
hatch=hatch,
color='w'
)
)
plt.show()
return output