Computer Vision

Gradients and Edge detection

INTRODUCTION

Edge detection áp dụng các phương pháp toán học để tìm ra các pixel trong bức ảnh có cường độ sáng thay đổi rõ rệt.

Gradient là một trường vector, có chiều hướng về phía mức độ tăng lớn nhất của trường vô hướng và có độ lớn là mức độ thay đổi lớn nhất. Gradient của một grayscale image cho phép chúng ta tìm ra “edge-like regions” theo hướng x và y.


LAPLACIAN AND SOBEL GRADIENT FILTERS

Chúng ta dùng function cv2.Laplacian() để tính toán gradient cho grayscale image:

destImage= cv2.Laplacian(grayscaleImage, dtype)

Trong đó:

  • destImage – hình ảnh nhận được sau khi tính toán gradient.
  • dtype – cv2.CV_64F

Example 1: Laplacian high-pass filter

import numpy as np
import cv2

image = cv2.imread("image_path")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("Original", image)

lap = cv2.Laplacian(image, cv2.CV_64F)
lap = np.uint8(np.absolute(lap))
cv2.imshow("Laplacian", lap)
cv2.waitKey(0)

Output: 

laplacian

Figure 1 – Laplacian high-pass filter

Sau khi áp dụng function cv2.Laplacian(), ta tìm giá trị absolute của gradient và chuyển về dạng 8-bit unsigned integer.

Bây giờ, chúng ta sẽ tìm hiểu về function cv2.Sobel(). Sử dụng Sobel chúng ta có thể tính toán gradient của image theo 2 hướng X và Y, cho phép tìm ra cả horizontal và vertical edge-like regions.

Function:

destImage = cv2.Sobel(grayscaleImage, dtype, x, y)

Trong đó:

  • destImage – image nhận được sau khi tính toán gradient.
  • dtype – cv2.CV_64F
  • x, y – directions;

– x = 1, y = 0 – find vertical edge-like regions;
– x = 0, y = 1 – find horizontal edge-like regions.

Example 2: Sobel high-pass filter

import numpy as np
import cv2

image = cv2.imread("image_path")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("Original", image)
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)

cv2.imshow("Sobel X", sobelX)
cv2.imshow("Sobel Y", sobelY)
cv2.imshow("Sobel Combined", sobelCombined)
cv2.waitKey(0)

Output: 

sobel

Figure 2 – Sobel high-pass filter

Sau khi áp dụng function cv2.Sobel(), chúng ta cũng tìm giá trị absolute của gradient theo chiều X và Y, sau đó chuyển về dạng 8-bit unsigned integer. Kết quả cuối cùng chính là phép bitwise_or giữa sobelX và sobelY.

Có thể thấy rằng kết quả nhận được khá nhiễu và chưa sắc nét. Vấn đề này sẽ được giải quyết bởi Canny Edge Detector.


GRADIENT ORIENTATION AND MAGNITUDE IN OPENCV

Ngoài khái niệm Image Gradient đã tìm hiểu ở phần trước, chúng ta sẽ làm quen với 2 khái niệm mới, gồm: Gradient Magnitude và Gradient Orientation.

The gradient magnitude is used to measure how strong the change in image intensity is. The gradient magnitude is a real-valued number that quantifies the “strength” of the change in intensity.

While the gradient orientation is used to determine in which direction the change in intensity is pointing. As the name suggests, the gradient orientation will give us an angle or θ that we can use to quantify the direction of the change.

Giả sử sau khi áp dụng function Sobel, ta nhận được 2 ma trận Gx và Gy (tương ứng với Image Gradient theo trục X và trục Y), được biểu diễn ở Figure 3.

gradient_triangle

Figure 3 – Gradient Triangle

Cần chú ý rằng, Gx và Gy chỉ mới là Image Gradients, để tính Gradient Magnitude ta cần áp dụng định lý Pytago để tính cạnh G của tam giác vuông ở Figure 3: G = sqrt(Gx^2 + Gy^2); để tính Gradient Orientation ta cần tính giá trị góc θ: θ = math.atan2(Gy, Gx).

Example 3: gradientOrientation.py

import numpy as np
import argparse
import cv2

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="Path to the image")
ap.add_argument("-l", "--lower-angle", type=float, default=175.0,
	help="Lower orientation angle")
ap.add_argument("-u", "--upper-angle", type=float, default=180.0,
	help="Upper orientation angle")
args = vars(ap.parse_args())

image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("Original", image)

# compute gradients along the X and Y axis, respectively
gX = cv2.Sobel(gray, cv2.CV_64F, 1, 0)
gY = cv2.Sobel(gray, cv2.CV_64F, 0, 1)

# compute the gradient magnitude and orientation respectively
mag = np.sqrt((gX ** 2) + (gY ** 2))
orientation = np.arctan2(gY, gX) * (180 / np.pi) % 180

# find all pixels that are within the upper and low angle boundaries
index = np.where(orientation >= args["lower_angle"], orientation, -1)
index = np.where(orientation <= args["upper_angle"], index, -1)
mask = np.zeros(gray.shape, dtype="uint8")
mask[index > -1] = 255

# show the images
cv2.imshow("Mask", mask)
cv2.waitKey(0)

Execution:
$ python gradientOrientation.py -i yourImage -l 50 -u 150

Output:

Mask
Figure 4 – Gradient Orientation

Với kết quả trên ta thấy rằng, các pixel có Gradient Orientation nằm trong khoẳng [50, 150] tập trung chủ yếu ở cạnh trên và dưới của các đồng xu.

Vì không cần phải hiển thị Gradient Images (edged-like regions) ra màn hình nên chúng ta không cần phải chuyển kết quả của function cv2.Sobel() về dạng 8-bit unsigned integer như ở example 2.

Có thể các bạn vẫn chưa nhận ra ý nghĩa của việc tính toán Gradient Orientation, nhưng đây chính là những gì mà các “advanced image descriptors” như HOG và SIFT thực hiện. Các image descriptor này sẽ được giới thiệu ở các bài viết sau.


CANNY EGDE DECTECTOR

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.

The Canny edge detection algorithm can be broken down into 5 steps:

Step 1: Smooth the image using a Gaussian filter to remove high frequency noise.
Step 2: Compute the gradient intensity representations of the image.
Step 3: Apply non-maximum suppression to remove “false” responses to to edge detection.
Step 4: Apply thresholding using a lower and upper boundary on the gradient values.
Step 5: Track edges using hysteresis by suppressing weak edges that are not connected to strong edges.

OpenCV cung cấp cho chúng ta function cv2.Canny() để thực hiện Canny algorithm:

destImage = cv2.Canny(blurredgrayscaleImage, threshold1, threshold2)

Trong đó:

  • destImage – image nhận được sau khi áp dụng Canny edge detection algorithm.
  • blurredgrayscaleImage – ảnh grayscale đã được blurring.
  • threshold1 – bất cứ giá trị gradient nào nhỏ hơn threshold1 sẽ không được coi là edge.
  • threshold2 – bất cứ giá trị gradient nào lớn hơn threshold2 sẽ được coi là edge.

Example 4: Canny edge detector

import numpy as np
import cv2

image = cv2.imread("image_path")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = cv2.GaussianBlur(image, (5,5), 0)
cv2.imshow("Blurred", image)

canny = cv2.Canny(image, 30, 150)
cv2.imshow("Canny", canny)
cv2.waitKey(0)

Output:
canny

Figure 5 – Canny edge detector

Kết quả nhận được đã sắc nét hơn so với phương pháp Laplacian và Sobel. Trước khi áp dụng function cv2.Canny(), chúng ta cần thực hiện blurring cho grayscale image, ở đây chúng ta đã áp dụng GaussianBlur.


AUTOMATIC CANNY EDGE DETECTOR

In order to obtain an accurate edge map, you’ll need to tune the cv2.Canny edge detection parameters, specifically the lower and upper thresholds by hand. If the image you are working with comes from semi-controlled lighting conditions, then you’ll likely be able to find a set of edge detection parameters that work well across the dataset.

However, for “natural” images with no restrictions on lighting conditions, it can become very challenging to define a set of parameters that obtains decent accuracy.

Example 5: Auto Canny edge detector

import numpy as np
import argparse
import glob
import cv2

def auto_canny(image, sigma = 0.33):
    v = np.median(image)
    lower = int(max(0, (1.0-sigma)*v))
    upper = int(min(255, (1.0+sigma)*v))
    edged = cv2.Canny(image, lower, upper)
    return edged

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image")
args = vars(ap.parse_args())

for imagePath in glob.glob(args["image"] + "\*.png"):
    image = cv2.imread(imagePath)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (3, 3), 0)

    wide = cv2.Canny(blurred, 10, 200)
    tight = cv2.Canny(blurred, 225, 250)
    auto = auto_canny(blurred)
    cv2.imshow("Original", image)
    cv2.imshow("Edges", np.hstack([wide, tight, auto]))
    cv2.waitKey(0)

Output:
autocanny

Figure 6 – Auto Canny edge detector

Trong function auto_canny(), chúng ta tìm giá trị trung vị (median) của image, tham số sigma chính là độ lệch so với median, sigma = 0.33 được cho là sẽ mang lại kết quả tốt nhất trong nhiều trường hợp. Dựa vào giá trị median và sigma, ta tính toán được 2 giá trị lower threshold và upper threshold.

Từ kết quả nhận được ta thấy rằng, wide Canny detector cho kết quả với khá nhiều nhiễu tần số cao (quá nhiều cạnh), tight Canny detector lại cho kết quả với nhiều thiết sót, còn auto Canny detector cho kết quả tương đối tốt.

Với auto Canny edge detector, chúng ta nhận được kết quả khá tốt trong khi không cần tốn thời gian để tìm các giới hạn của threshold.

Cảm ơn các bạn đã theo dõi bài viết của mình.

Thân ái và quyết thắng.

Reference:
[1] Zero-parameter, automatic Canny edge detection with Python and OpenCV.
[2] Filename expansion with glob
[3] Command-line parsing with argparse

One thought on “Gradients and Edge detection

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s