Edge Detection with OpenCV
Here we will cover the following topics related to edge detection with OpenCV
Edge detection is needed to detect objects in a picture. An edge is mathematically defined as a distinct change in pixel value.
Note the edge can be identified by derivatives
The first derivative or slope will have the highest value at the point of the edge. Sobel Filters use this concept to identify edges.
The second derivative will be 0 at the edge. Laplacian uses the second derivative to identify the edge.
import numpy as np
import cv2 as cv2
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
#Routine to fix
def fixColor(image):
return(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
image = cv2.imread("images/devices_small.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.imshow(fixColor(image))
<matplotlib.image.AxesImage at 0x7f62681b5b38>
The Laplacian involves calculating the double derivative of pixel values. In an ideal scenario the edge the first derivative is highest and the second derivative is 0. This is the fundamental idea behind Laplacian edge detection.
The laplacian use a 64 bit float representation as opposed to an 8-bit representation used for images before. This is because edges definations can be +ve (black-white) and -ve (white-black). 8-bit representation is no longer sufficient.
lap = cv2.Laplacian(image, cv2.CV_64F)
lap = np.uint8(np.absolute(lap))
plt.imshow(fixColor(lap))
<matplotlib.image.AxesImage at 0x7f626819abe0>
Sobel filters involve a matrix convolution that gives the approximate derivative and therefore gradient. These filters are applied both horizontally and vertically.
sobelX = cv2.Sobel(image, cv2.CV_64F, 1, 0)
sobelY = cv2.Sobel(image, cv2.CV_64F, 0, 1)
sobelX = np.uint8(np.absolute(sobelX))
sobelY = np.uint8(np.absolute(sobelY))
sobelCombined = cv2.bitwise_or(sobelX, sobelY)
titles = ['Original Image', 'Combined',
'Sobel X', 'Sobel Y']
images = [image, sobelCombined, sobelX, sobelY]
for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
plt.imshow(fixColor(sobelCombined))
<matplotlib.image.AxesImage at 0x7f62682a0e10>
The Canny edge detector is a multi-step process. It involves blurring the image to remove noise, computing Sobel gradient images in the x and y direction, suppressing edges, and finally a hysteresis thresholding stage that determines if a pixel is “edge-like” or not.
3. After getting gradient magnitude and direction, a full scan of image is done to remove any unwanted pixels which may not constitute the edge. For this, at every pixel, pixel is checked if it is a local maximum in its neighborhood in the direction of gradient. 4. This stage decides which are all edges are really edges and which are not. For this, we need two threshold values, minVal and maxVal. Any edges with intensity gradient more than maxVal are sure to be edges and those below minVal are sure to be non-edges, so discarded. Those who lie between these two thresholds are classified edges or non-edges based on their connectivity. If they are connected to "sure-edge" pixels, they are considered to be part of edges. Otherwise, they are also discarded. See the image below:
The edge A is above the maxVal, so considered as "sure-edge". Although edge C is below maxVal, it is connected to edge A, so that also considered as valid edge and we get that full curve. But edge B, although it is above minVal and is in same region as that of edge C, it is not connected to any "sure-edge", so that is discarded. So it is very important that we have to select minVal and maxVal accordingly to get the correct result.
This stage also removes small pixels noises on the assumption that edges are long lines.
image = cv2.imread("images/devices_small.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
plt.imshow(fixColor(blurred))
<matplotlib.image.AxesImage at 0x7f626828d828>
canny = cv2.Canny(blurred, 30, 300)
plt.imshow(fixColor(canny))
<matplotlib.image.AxesImage at 0x7f626832e2e8>
(cnts, _) = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
coins = image.copy()
cv2.drawContours(coins, cnts, -1, (255, 0, 0), 2)
array([[[152, 159, 162], [153, 160, 163], [154, 161, 164], ..., [200, 205, 208], [201, 208, 211], [205, 212, 215]], [[154, 161, 164], [156, 163, 166], [156, 163, 166], ..., [205, 210, 213], [204, 211, 214], [206, 213, 216]], [[154, 161, 164], [156, 163, 166], [157, 164, 167], ..., [205, 210, 213], [209, 214, 217], [212, 217, 220]], ..., [[185, 192, 195], [187, 194, 197], [185, 192, 195], ..., [213, 216, 220], [214, 219, 222], [212, 217, 220]], [[177, 184, 187], [182, 189, 192], [186, 193, 196], ..., [211, 216, 219], [213, 218, 221], [213, 218, 221]], [[180, 187, 190], [185, 192, 195], [187, 194, 197], ..., [209, 214, 217], [210, 215, 218], [212, 217, 220]]], dtype=uint8)
plt.imshow(fixColor(coins))
<matplotlib.image.AxesImage at 0x7f6268339978>
You can also add a bounding box around each contour by using the bounding rect function.
for cnt in cnts:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(coins,(x,y),(x+w,y+h),(0,255,0),2)
plt.imshow(fixColor(coins))
<matplotlib.image.AxesImage at 0x7f62683ca278>