Of course! Here is a comprehensive guide to implementing Uniform Local Binary Patterns (Uniform LBP) in Python, including the theory, code from scratch, and using a popular library.

What is LBP and why "Uniform"?
Local Binary Pattern (LBP) is a powerful texture descriptor used in computer vision. The basic idea is to describe the local texture of an image by comparing each pixel with its neighbors.
The Standard LBP Process:
- For a given pixel (the center pixel), consider its
Pneighbors arranged in a circle of radiusR. - For each neighbor, compare its intensity value with the center pixel's value.
- If the neighbor's value is greater than or equal to the center, assign a
1. - Otherwise, assign a
0.
- If the neighbor's value is greater than or equal to the center, assign a
- This results in a binary number (a string of
Pbits). - Convert this binary number to a decimal value. This value is the LBP code for that pixel.
- Repeat for all pixels in the image to get an LBP image, where each pixel's value is its LBP code (an integer from
0to2^P - 1).
The Problem with Standard LBP:
The number of possible LBP codes is 2^P. For P=8 neighbors, this results in 256 possible codes. This high dimensionality can be problematic for tasks like classification, as many of these codes correspond to very similar or rare patterns.
The Solution: Uniform LBP
A binary pattern is called "Uniform" if it contains at most two bitwise transitions (from 0 to 1 or 1 to 0) when traversing the circular neighborhood.

- Example (P=8):
11111111(0 transitions) -> Uniform00000000(0 transitions) -> Uniform11100000(1 transition) -> Uniform10101010(4 transitions) -> Non-Uniform11001100(2 transitions) -> Uniform
How Uniform LBP Works:
- Calculate the standard LBP code for every pixel.
- Count the number of bitwise transitions in the binary pattern.
- If the number of transitions is ≤ 2, the pattern is uniform. It gets a unique label from
0toP+1(forP=8, labels0to9). - If the number of transitions is > 2, the pattern is non-uniform. All non-uniform patterns are grouped into a single "miscellaneous" bin, labeled
P+1(forP=8, this is10).
Result: Instead of 256 possible codes, Uniform LBP with P=8 has only 59 possible codes (0 to 58), making it much more robust and efficient.
Implementing Uniform LBP from Scratch
This is a great way to understand the underlying mechanics. We'll use NumPy for efficient array operations.
import numpy as np
import cv2
import matplotlib.pyplot as plt
def get_uniform_lbp_codes(P, R):
"""
Pre-calculates the LBP codes and their uniformity for a given P and R.
This is an optimization to avoid recalculating for every pixel.
"""
num_codes = 2 ** P
lbp_codes = np.arange(num_codes, dtype=np.uint8)
# Create a binary representation of each code
binary_repr = np.unpackbits(lbp_codes[:, np.newaxis], axis=1, count=P)
# Calculate bitwise transitions (circular)
# Shift the binary representation to the left and compare with the original
shifted = np.roll(binary_repr, -1, axis=1)
transitions = np.sum(np.abs(binary_repr - shifted), axis=1)
# Determine if a code is uniform
is_uniform = (transitions <= 2)
# Map uniform codes to their new labels (0 to P+1)
# Non-uniform codes are mapped to P+1
uniform_labels = np.where(is_uniform, lbp_codes, P + 1)
return uniform_labels
def calculate_uniform_lbp(image, P=8, R=1):
"""
Calculates the Uniform Local Binary Pattern for a given grayscale image.
Args:
image (np.ndarray): Input grayscale image.
P (int): Number of neighboring points.
R (int): Radius of the circle.
Returns:
np.ndarray: The Uniform LBP image.
"""
h, w = image.shape
lbp_image = np.zeros((h, w), dtype=np.uint8)
# Pre-calculate the mapping from standard LBP codes to uniform labels
uniform_labels = get_uniform_lbp_codes(P, R)
# Pad the image to handle borders
padded_image = cv2.copyMakeBorder(image, R, R, R, R, cv2.BORDER_CONSTANT, value=0)
# Iterate over the original image size
for i in range(R, h + R):
for j in range(R, w + R):
center = padded_image[i, j]
# Get the P neighbors in a circle
# We can sample angles for P points
angles = 2 * np.pi * np.arange(P) / P
neighbor_coords = []
for angle in angles:
x = int(i + R * np.cos(angle))
y = int(j + R * np.sin(angle))
neighbor_coords.append((x, y))
# Compare neighbors to center and form binary string
binary_string = 0
for k, (x, y) in enumerate(neighbor_coords):
if padded_image[x, y] >= center:
binary_string |= (1 << k) # Set the k-th bit
# Get the uniform label from the pre-calculated map
lbp_image[i - R, j - R] = uniform_labels[binary_string]
return lbp_image
# --- Example Usage ---
# Load an image and convert to grayscale
image_path = 'path/to/your/image.jpg' # Replace with your image path
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# Resize for faster processing (optional)
image = cv2.resize(image, (256, 256))
# Calculate Uniform LBP
P = 8
R = 1
uniform_lbp_image = calculate_uniform_lbp(image, P, R)
# Display results
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(image, cmap='gray')'Original Image')
plt.axis('off')
plt.subplot(1, 2, 2)
# Use a colormap that makes sense for integer labels
plt.imshow(uniform_lbp_image, cmap='viridis')f'Uniform LBP (P={P}, R={R})')
plt.axis('off')
plt.show()
Implementing Uniform LBP using scikit-image
The scikit-image library has a built-in, highly optimized function for this. This is the recommended approach for practical applications.

The function is local_binary_pattern from skimage.feature. We need to set the method='uniform' parameter.
import numpy as np
import cv2
import matplotlib.pyplot as plt
from skimage.feature import local_binary_pattern
# Load an image and convert to grayscale
image_path = 'path/to/your/image.jpg' # Replace with your image path
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
image = cv2.resize(image, (256, 256))
# Parameters
P = 8 # Number of neighbors
R = 1 # Radius of the circle
# Calculate Uniform LBP using scikit-image
# The function returns an image where pixel values are the LBP codes.
# With method='uniform', the codes will be in the range [0, P+1].
uniform_lbp_image = local_binary_pattern(
image,
P,
R,
method='uniform'
)
# Display results
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(image, cmap='gray')'Original Image')
plt.axis('off')
plt.subplot(1, 2, 2)
# 'viridis' or 'nipy_spectral' are good for showing distinct integer labels
plt.imshow(uniform_lbp_image, cmap='viridis')f'Uniform LBP (P={P}, R={R}) - skimage')
plt.axis('off')
plt.show()
# You can also get a histogram of the LBP codes, which is often used as a feature vector
hist, _ = np.histogram(uniform_lbp_image.ravel(), bins=P+2, range=(0, P+2))
# Normalize the histogram
hist = hist.astype("float")
hist /= (hist.sum() + 1e-7)
print("LBP Histogram (Feature Vector):")
print(hist)
How to Use Uniform LBP (Typical Workflow)
The LBP image itself is often not the final goal. The goal is usually to use it as a feature descriptor for tasks like face recognition or object detection.
- Divide the Image: Split the input image into small, non-overlapping regions (e.g., 16x16 pixel blocks).
- Compute LBP for Each Block: For each block, calculate its Uniform LBP image.
- Create a Histogram for Each Block: From the LBP image of each block, compute a histogram of the LBP codes. Since there are
P+2bins (59 for P=8), this histogram is a 59-dimensional vector. - Concatenate Histograms: Concatenate all the block histograms to form a single, long feature vector for the entire image. This vector represents the texture distribution of the whole image.
- Use for Classification: This final feature vector can be fed into a machine learning classifier (like SVM, k-NN, etc.).
Summary of Methods
| Method | Pros | Cons | Best For |
|---|---|---|---|
| From Scratch | Excellent for learning the algorithm. No external dependencies. | Slow for large images. Prone to implementation errors. | Education, understanding the core concepts. |
scikit-image |
Fast, optimized, and well-tested. Easy to use. Part of a larger ecosystem. | Requires an external library. | Practical applications, research, and production code. |
For any serious project, always prefer the scikit-image implementation. Use the "from scratch" version only for learning purposes.
