Ashwin Kumar
Ashwin Kumar

Reputation: 63

Sobel Filter giving poor results

I was trying to implement the sobel filter in python, but the output was poor and full of noise. The Output image i got was: enter image description here

The Input I gave was(after blurring):

enter image description here

The code for sobel filter is below:

def sobel_filters(img):
  Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
  Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])

  Ix = ndimage.filters.convolve(img, Kx)
  Iy = ndimage.filters.convolve(img, Ky)

  G = np.sqrt(np.square(Ix) + np.square(Iy))
  G *= 255.0 / G.max()
  

  return G

I blurred the results using sigma=1.3. The input image size is 512 by 512. I expected the output to be similar to what is shown in here:https://www.adeveloperdiary.com/data-science/computer-vision/how-to-implement-sobel-edge-detection-using-python-from-scratch/

Upvotes: 1

Views: 4070

Answers (2)

fmw42
fmw42

Reputation: 53081

Here is your way to do that with float images and proper normalization in Python/OpenCV. Without float data, you get only one sided derivatives (not both positive and negative results).

As Cris Luengo has pointed out, the derivatives in your answer are in error relative to standard ones in OpenCV. The derivatives in the reference are for a proper convolution. But Scipy has both convolution and correlation. Most "convolutions" are in fact actually correlations. (OpenCV cv2.filter2D is such. The function actually computes correlation, not the convolution). So I have corrected the kernels for a correlation to be consistent with what is in OpenCV Sobel or used by cv2.filter2D(). Convolutions and correlations are related by a transpose. See https://medium.com/@aybukeyalcinerr/correlation-vs-convolution-filtering-2711d8bb3666

Input:

enter image description here

import cv2
import numpy as np
import scipy.ndimage as ndimage
import skimage.exposure as exposure

# read the image and convert to float
img = cv2.imread('gray_lena.png').astype(np.float32)

# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# blur
blur = cv2.GaussianBlur(gray, (0,0), 1.3, 1.3)

# define Sobel X and Y (correlation) kernels
Kx = np.array([[-1, 0, 1], 
               [-2, 0, 2], 
               [-1, 0, 1]])

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

# apply correlations and normalize by sum of absolute values of elements
sobelx = ndimage.filters.correlate(blur, Kx)
sobely = ndimage.filters.correlate(blur, Ky)

#OpenCV alternate:
#sobelx = cv2.filter2D(blur, cv2.CV_32F, Kx)
#sobely = cv2.filter2D(blur, cv2.CV_32F, Ky)

# optionally normalize to range 0 to 255 for proper display and saving as 8-bit data.
sobelx_norm= exposure.rescale_intensity(sobelx, in_range='image', out_range=(0,255)).clip(0,255).astype(np.uint8)
sobely_norm= exposure.rescale_intensity(sobelx, in_range='image', out_range=(0,255)).clip(0,255).astype(np.uint8)

# add and take square root
sobel_magnitude = np.sqrt(np.square(sobelx) + np.square(sobely))

# normalize to range 0 to 255 and clip negatives
sobel_magnitude = exposure.rescale_intensity(sobel_magnitude, in_range='image', out_range=(0,255)).clip(0,255).astype(np.uint8)

# save results
cv2.imwrite('gray_lena_sobelx_norm2.jpg', sobelx_norm)
cv2.imwrite('gray_lena_sobely_norm2.jpg', sobely_norm)
cv2.imwrite('gray_lena_sobel_magnitude2.jpg', sobel_magnitude)

# show results
cv2.imshow('sobelx_norm', sobelx_norm)  
cv2.imshow('sobely_norm', sobely_norm)  
cv2.imshow('sobel_magnitude', sobel_magnitude)  
cv2.waitKey(0)
cv2.destroyAllWindows()

Sobel X (normalized):

enter image description here

Sobel Y (normalized):

enter image description here

Sobel Magnitude:

enter image description here

Upvotes: 2

fmw42
fmw42

Reputation: 53081

Here is one way to do that in Python/OpenCV. Your issue is that your derivatives are not properly normalized and should be processed as floats. Your normalization also does not take into account negative values. In this answer, I make use of OpenCV built-in Sobel and other methods. So there is no need to introduce scipy.ndimage

Input:

enter image description here

import cv2
import numpy as np
import skimage.exposure as exposure

# read the image
img = cv2.imread('gray_lena.png')

# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# blur
blur = cv2.GaussianBlur(gray, (0,0), 1.3, 1.3)

# apply sobel derivatives
sobelx = cv2.Sobel(blur,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(blur,cv2.CV_64F,0,1,ksize=3)

# optionally normalize to range 0 to 255 for proper display
sobelx_norm= exposure.rescale_intensity(sobelx, in_range='image', out_range=(0,255)).clip(0,255).astype(np.uint8)
sobely_norm= exposure.rescale_intensity(sobelx, in_range='image', out_range=(0,255)).clip(0,255).astype(np.uint8)

# square 
sobelx2 = cv2.multiply(sobelx,sobelx)
sobely2 = cv2.multiply(sobely,sobely)

# add together and take square root
sobel_magnitude = cv2.sqrt(sobelx2 + sobely2)

# normalize to range 0 to 255 and clip negatives
sobel_magnitude = exposure.rescale_intensity(sobel_magnitude, in_range='image', out_range=(0,255)).clip(0,255).astype(np.uint8)

# save results
cv2.imwrite('gray_lena_sobelx_norm.jpg', sobelx_norm)
cv2.imwrite('gray_lena_sobely_norm.jpg', sobely_norm)
cv2.imwrite('gray_lena_sobel_magnitude.jpg', sobel_magnitude)

# show results
cv2.imshow('sobelx_norm', sobelx_norm)  
cv2.imshow('sobely_norm', sobely_norm)  
cv2.imshow('sobel_magnitude', sobel_magnitude)  
cv2.waitKey(0)
cv2.destroyAllWindows()

Sobel X (normalized):

enter image description here

Sobel Y (normalized):

enter image description here

Sobel Magnitude:

enter image description here

Upvotes: 3

Related Questions