5 Best Ways to Perform Distance Transformation on Images with OpenCV in Python

πŸ’‘ Problem Formulation: Distance transformations are powerful tools in image processing used to calculate the minimum distance from each pixel to a certain feature, typically edges in a binary image. For instance, given a binary image of a printed circuit board (PCB), one may need to find the distance from each pixel to the nearest solder point. This article will describe several methods to compute this using OpenCV and Python, with the expected output being a new image where each pixel’s intensity corresponds to the computed distance.

Method 1: Using cv2.distanceTransform with the L2 Distance Type

This method utilizes the function cv2.distanceTransform() provided by OpenCV to compute the distance from each pixel to the nearest zero pixel. The L2 distance type refers to the Euclidean distance, providing more accurate results for continuous images. This function takes the binary image, distance type, and mask size as arguments.

Here’s an example:

import cv2
import numpy as np

# Load binary image
binary_image = cv2.imread('pcb_binary.png', 0)

# Apply distance transform
dist_transform = cv2.distanceTransform(binary_image, cv2.DIST_L2, 5)

# Display image
cv2.imshow('Distance Transform Image', dist_transform)
cv2.waitKey(0)
cv2.destroyAllWindows()

The output will be an image where each non-zero pixel value indicates the distance from the nearest zero-value pixel.

This code snippet reads a binary image of a printed circuit board (PCB), uses distanceTransform() to compute the distance using the L2 norm, and then displays the resulting transformation. The cv2.DIST_L2 parameter specifies the Euclidean distance, and the mask size of 5 is an arbitrary choice that can be tuned based on the specific details of the input image or the desired precision of the output.

Method 2: Using cv2.distanceTransform with the L1 Distance Type

The L1 distance type computes the distance using the Manhattan (city block) distance metric provided by the cv2.distanceTransform() function. While it’s usually faster than L2, it can give less accurate results for diagonal moves since those are underestimated in the L1 metric.

Here’s an example:

import cv2
import numpy as np

# Load binary image
binary_image = cv2.imread('pcb_binary.png', 0)

# Apply distance transform with L1 Distance Type
dist_transform = cv2.distanceTransform(binary_image, cv2.DIST_L1, 3)

# Display image
cv2.imshow('L1 Distance Transform Image', dist_transform)
cv2.waitKey(0)
cv2.destroyAllWindows()

The output will show distances within the image based on the city block distance metric.

This snippet is similar to the Method 1, but instead, it uses the cv2.DIST_L1 parameter for the Manhattan distance metric. This could be more suitable for grid-like images where diagonal moves should be penalized or represented differently. The mask size is set to 3, which is appropriate for the L1 distance.

Method 3: Custom Distance Transformation with Brute Force Approach

For educational purposes, a custom brute force method can be implemented to understand the process behind distance transformations. This approach iteratively compares every non-zero pixel to every other non-zero pixel to compute the minimum distance, which is highly computationally intensive and not recommended for large images or real-time processing.

Here’s an example:

import cv2
import numpy as np

def brute_force_distance_transform(binary_image):
    dist_transform = np.zeros_like(binary_image, dtype=np.float32)
    non_zero_points = np.argwhere(binary_image > 0)
    for y in range(binary_image.shape[0]):
        for x in range(binary_image.shape[1]):
            if binary_image[y, x] > 0:
                dist_transform[y, x] = min([np.linalg.norm([y - ny, x - nx])
                                            for ny, nx in non_zero_points])
    return dist_transform

# Load binary image
binary_image = cv2.imread('pcb_binary.png', 0)

# Apply custom brute force distance transform
dist_transform = brute_force_distance_transform(binary_image)

# Display image
cv2.imshow('Brute Force Distance Transform Image', dist_transform)
cv2.waitKey(0)
cv2.destroyAllWindows()

This method will output an image showing the calculated distances, but processing time may be long.

This code defines a function to execute a brute-force approach to distance transformation. It illustrates the concept but is inefficient for actual use cases due to its O(n^2) complexity, where n is the number of non-zero pixels.

Bonus One-Liner Method 4: Fast Approximation with cv2.distanceTransform

The function cv2.distanceTransform() can also be used with cv2.DIST_LABEL_PIXEL to perform a fast approximation of the distance transformation, which can be beneficial in cases where a ballpark measure of distance suffices.

Here’s an example:

import cv2

# Load binary image
binary_image = cv2.imread('pcb_binary.png', 0)

# Fast approximation of distance transform
dist_transform = cv2.distanceTransform(binary_image, cv2.DIST_C, cv2.DIST_MASK_3)

# Display image
cv2.imshow('Approximated Distance Transform Image', dist_transform)
cv2.waitKey(0)
cv2.destroyAllWindows()

The output is a quickly generated approximation of the distance transformation.

This code exemplifies how to apply a fast, approximate distance transformation using the cv2.distanceTransform() function with a particular combination of distance type and mask size for speed over accuracy.

Summary/Discussion

  • Method 1: L2 Euclidean Distance. Provides accurate and true geometric distance measurements. Can be computationally intensive.
  • Method 2: L1 Manhattan Distance. Faster than L2 but can distort diagonal distances. Suitable for specific use cases that align with Manhattan metric assumptions.
  • Method 3: Custom Brute Force. Offers a deep understanding of the distance transformation process. Highly inefficient for practical applications.
  • Method 4: Fast Approximation. Ideal for real-time applications where an approximate distance calculation is sufficient. Not suitable when precise distance measurement is needed.