OpenCV: Prepare angle-based kernel for erosion

138 Views Asked by At

I have an image that contains several straight lines at various angles (e.g. horizontal, vertical, diagonal, cross-diagonal). I have a code that can filter the lines in a desired direction since I know how to set a kernel matrix for horizontal, vertical, diagonal, and cross-diagonal directions. However, for lines at an arbitrary angle, I have no clue how to do it. See my code below.

import cv2
import numpy as np
%matplotlib inline 
from matplotlib import pyplot as plt
 

# Define a image display function to work on Jupyter notebook 
def showimage(title,myimage):
    # Reverse OpenCV's BGR order to RGB for colored images (ndim>2)
    if (myimage.ndim>2):  
        myimage = myimage[:,:,::-1]  
         
    fig, ax = plt.subplots(figsize=(6,6))
    ax.imshow(myimage, cmap = 'gray', interpolation = 'bicubic')
    plt.xticks([]), plt.yticks([])  # hide tick values on X and Y axes
    plt.title(title)
    plt.show()
    

# Load the image
imfile = 'multilines.jpg' # working image file
image = cv2.imread(imfile)  
showimage('Original',image)

# Convert the image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Apply Canny edge detection
edges = cv2.Canny(gray, 50, 150, apertureSize=3)


### Erosion: 
# Create a horizontal kernel for erosion
kernel_h = np.array([[0, 0, 0],
                   [1, 1, 1],
                   [0, 0, 0]], dtype=np.uint8)


# Create a vertical kernel for erosion
kernel_v = np.array([[0, 1, 0],
                   [0, 1, 0],
                   [0, 1, 0]], dtype=np.uint8)



# Create a diagonal kernel for erosion (45 degrees)
kernel_d = np.array([[1, 0, 0],
                     [0, 1, 0],
                     [0, 0, 1]], dtype=np.uint8)


# Create a cross-diagonal kernel for erosion (45 degrees)
kernel_crossd = np.array([[0, 0, 1],
                     [0, 1, 0],
                     [1, 0, 0]], dtype=np.uint8)


kernel = kernel_h; tag = 'Horizontal'
kernel = kernel_v; tag = 'Vertical'
kernel = kernel_d; tag = 'Diagonal'
kernel = kernel_crossd; tag = 'Cross-diagonal'


# TO DO begins ----------------------
# Create a generic angle dependent kernel 
theta = 90
theta = np.deg2rad(theta)

## kernel_angle =  ? <- Here to uncomment and modify
## kernel = kernel_angle ; tag = 'Generic angle-based' # Uncomment once modify above

# TO DO ends ------------------------


print(f'{tag} erosion applied.')

# Apply erosion to isolate desired lines
erosion_lines = cv2.erode(edges, kernel, iterations=1)
showimage('After erosion',erosion_lines)


# Invert the colors (convert to negative to get white background)
result = cv2.bitwise_not(erosion_lines)

# Show final image
showimage('Final',result)

How can I create a generic kernel that creates filters for any arbitrarily angled line? This will also reduce the tedious task of assigning the kernel of each type of line (e.g. horizontal, vertical, diagonal, etc).

The "TO DO" part needs to get modified. Any solution?

I using this image (filename: 'multilines.jpg') :

enter image description here

Output image: enter image description here

1

There are 1 best solutions below

2
Flow On

You can apply the sobel filter manually in order get access to the (local) orientation of an edge as described on Wikipedia about Sobel filter.

image = cv2.imread(imfile)

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# applying sobel filter manually to get access to the angle

sobel_x = np.array([[1, 0, -1], [2, 0, -2], [1, 0, -1]])
sobel_y = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])

x = sig.convolve2d(gray, sobel_x)
y = sig.convolve2d(gray, sobel_y)

angle = np.arctan2(y,x)

# bringing range from [-np.pi, np.pi] to [0, 2*np.pi]
angle = angle + np.pi

The resulting array of angles can then be filtered with respect to the angle(s) of interest.


# filtering; 30 +/- 5 ° chosen as an example
target = 30
upper = np.pi / 180 * (target + 5)
lower = np.pi / 180 * (target - 5)

target_img = np.where(angle < upper, angle, 0)
target_img = np.where(target_img > lower, angle, 0)


cv2.imshow('angle', (target_img) / np.max(target_img))