You are on page 1of 18

Chương 2: Ứng dụng và chương trình cụ thể

2.1. Các chương trình, hàm phân vùng bằng biên


2.1.1. Phân vùng bằng biên sử dụng toán tử Prewitt, Sobel
 Source code hàm tính ngưỡng tự động (tên file: “auto_threshold”).
 import numpy as np

def auto_threshold(img):
m, n = img.shape
img_arr = np.array(img).flatten()

# Sử dụng hàm unique() để lấy ra các giá trị điểm ảnh


duy nhất trong mảng
img_count = np.unique(img_arr)

# Sử dụng hàm sort() của NumPy để sắp xếp các giá trị
điểm ảnh trong mảng theo thứ tự tăng dần
g = np.sort(img_count).astype(float)

# Số lần xuất hiện của mỗi giá trị trong mảng


hg = np.zeros(len(g)).astype(float)
for i in range(len(hg)):
hg[i] = np.count_nonzero(img_arr == g[i])

# Tong xich ma so lan xuat hien ( hg )


tg = np.cumsum(hg).astype(float)

# g * hg
g_hg = np.zeros(len(g)).astype(float)
for i in range(len(g_hg)):
g_hg[i] = g[i] * hg[i]

# Tổng xích ma của g_hg


xichma_g_hg = np.cumsum(g_hg).astype(float)

# mg = (1 / tg) * xichma_g_hg
mg = np.zeros(len(g)).astype(float)
for i in range(len(mg)):
mg[i] = (1 / tg[i]) * xichma_g_hg[i]

# fg = (tg / (m * n - tg)) * ((mg - max(mg)) ** 2)


fg = np.zeros(len(g) - 1).astype(float)
for i in range(len(fg)):
fg[i] = (tg[i] / (m * n - tg[i])) * (((mg[i] -
mg[len(mg) - 1]) ** 2))

return g[np.where(fg == max(fg))]


 Source code hàm sử dụng toán tử Prewitt, Sobel (tên file:
“Sobel_Prewitt”)
 import cv2
import numpy as np
from auto_threshold import auto_threshold

def convolution(img, mask):


img_fix = np.pad(img, ((1, 1), (1, 1)),
mode='constant')
img_new = np.zeros([img.shape[0], img.shape[1]])
for i in range(img_fix.shape[0] - 2):
for j in range(img_fix.shape[1] - 2):
img_new[i, j] = np.sum(img_fix[i: i + 3, j: j
+ 3] * mask)
return img_new

def threshold(img, threshold):


m, n = img.shape
img_new = np.zeros([m, n], np.uint8)
for i in range(img.shape[0]):
for j in range(img.shape[1]):
if img[i, j] > threshold:
img_new[i, j] = 255
else:
img_new[i, j] = 0

return img_new

# Prewitt
def Prewitt(threshold_img):
# Bộ lọc Prewitt theo hướng X
Prewitt_X = np.array(([-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]))
# Bộ lọc Prewitt theo hướng Y
Prewitt_Y = np.array(([-1, -1, -1],
[0, 0, 0],
[1, 1, 1]))

# Nhân tích chập Prewitt theo hướng X


imgPrewitt_X = convolution(threshold_img, Prewitt_X)

# Nhân tích chập Prewitt theo hướng Y


imgPrewitt_Y = convolution(threshold_img, Prewitt_Y)

# Ảnh tổng Prewitt theo hướng X và Sobel theo hướng Y


prewitt_img = np.sqrt(imgPrewitt_X ** 2) +
np.sqrt(imgPrewitt_Y ** 2)
prewitt_img = prewitt_img / np.max(prewitt_img) * 255
return prewitt_img

# Sobel
def Sobel(threshold_img):
# Lọc Sobel
# Bộ lọc Sobel theo hướng X
Sobel_X = np.array(([-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]))
# Bộ lọc Sobel theo hướng Y
Sobel_Y = np.array(([-1, -2, -1],
[0, 0, 0],
[1, 2, 1]))

# Nhân tích chập Sobel theo hướng X


imgSobel_X = convolution(threshold_img, Sobel_X)

# Nhân tích chập Sobel theo hướng Y


imgSobel_Y = convolution(threshold_img, Sobel_Y)

# Ảnh tổng Sobel theo hướng X và Sobel theo hướng Y


sobel_img = np.sqrt(imgSobel_X ** 2) +
np.sqrt(imgSobel_Y ** 2)
sobel_img = sobel_img / np.max(sobel_img) * 255

return sobel_img

2.1.2. Phân vùng bằng biên sử dụng phương pháp Canny


 Source code
 import numpy as np
import cv2

def convolution(img, mask):


pad_img = np.pad(img, ((1, 1), (1, 1)),
mode='constant')
new_img = np.zeros([pad_img.shape[0],
pad_img.shape[1]])
for i in range(pad_img.shape[0] - 2):
for j in range(pad_img.shape[1] - 2):
new_img[i + 1, j + 1] =
np.float32(np.sum(pad_img[i: i + 3, j: j + 3] * mask))

return new_img

def sobel(img):
x = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])
y = np.array([[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]])

sobel_x = convolution(img, x)
sobel_y = convolution(img, y)
sobel_img = np.sqrt(sobel_x * sobel_x) +
np.sqrt(sobel_y * sobel_y)
gradient_direction = np.arctan2(sobel_x, sobel_y)

return sobel_img, gradient_direction

def non_max_suppression(gradient_magnitude,
gradient_direction):
img_row, img_col = gradient_magnitude.shape
output = np.zeros(gradient_magnitude.shape)
PI = 180

for i in range(1, img_row - 1):


for j in range(1, img_col - 1):
direction = gradient_direction[i, j]
# Góc 0, 180 độ
if (0 <= direction < PI / 8) or (15 * PI / 8
<= direction <= 2 * PI):
before_pixel = gradient_magnitude[i, j -
1]
after_pixel = gradient_magnitude[i, j +
1]
# Góc 45 độ
elif (PI / 8 <= direction < 3 * PI / 8) or (9
* PI / 8 <= direction < 11 * PI / 8):
before_pixel = gradient_magnitude[i + 1,
j - 1]
after_pixel = gradient_magnitude[i - 1, j
+ 1]
# Góc 90 độ
elif (3 * PI / 8 <= direction < 5 * PI / 8)
or (11 * PI / 8 <= direction < 13 * PI / 8):
before_pixel = gradient_magnitude[i - 1,
j]
after_pixel = gradient_magnitude[i + 1,
j]
# Góc 135 độ
else:
before_pixel = gradient_magnitude[i - 1,
j - 1]
after_pixel = gradient_magnitude[i + 1, j
+ 1]
if gradient_magnitude[i, j] >= before_pixel
and gradient_magnitude[i, j] >= after_pixel:
output[i, j] = gradient_magnitude[i, j]

return output

def threshold(img, low_ratio, high_ratio):


output = np.zeros(img.shape)
high_threshold = np.max(img) * high_ratio
low_threshold = high_threshold * low_ratio

strong_row, strong_col = np.where(img >=


high_threshold)
weak_row, weak_col = np.where((img <= high_threshold)
& (img >= low_threshold))

output[strong_row, strong_col] = 255


output[weak_row, weak_col] = 50

return output

def hysteresis(img):
img_row, img_col = img.shape
weak = 50
final_img = img.copy()

for i in range(1, img_row):


for j in range(1, img_col):
if final_img[i, j] == weak:
if final_img[i, j + 1] == 255 or\
final_img[i, j - 1] == 255 or\
final_img[i - 1, j] == 255 or \
final_img[i + 1, j] == 255 or \
final_img[i - 1, j - 1] == 255 or\
final_img[i + 1, j - 1] == 255 or\
final_img[i - 1, j + 1] == 255 or\
final_img[i + 1, j + 1] == 255:
final_img[i, j] = 255
else:
final_img[i, j] = 0

return final_img

2.2. Các ứng dụng của phương pháp phân vùng bằng biên
2.2.1. Nhận diện vùng chứa biển số xe.
2.2.1.1. Ý tượng bài toán
1. Tiền xử lý ảnh: Áp dụng các bước tiền xử lý để làm sạch ảnh và cải thiện
chất lượng ảnh. Các bước tiền xử lý có thể bao gồm làm mờ, cân bằng
histogram, điều chỉnh độ tương phản, chuyển đổi không gian màu, loại bỏ
nhiễu, v.v.
2. Phân vùng bằng biên: Sử dụng phương pháp phân vùng bằng biên, tìm
các biên cạnh trong ảnh. Các biên cạnh sẽ giúp phát hiện vùng chứa biển
số xe, vì biên cạnh của biển số thường có độ tương phản cao và sắc nét.
Ta sẽ áp dụng toán tử Prewitt, Sobel đã cài đặt ở trên để tìm biên cho
bước này.
3. Phát hiện vùng chứa biển số: Dựa trên các biên cạnh đã tìm được, áp
dụng các thuật toán phân đoạn hoặc phương pháp phát hiện vùng để xác
định vùng chứa biển số xe trong ảnh.
4. Xử lý và nhận dạng ký tự: Sau khi xác định được vùng chứa biển số xe,
áp dụng các bước xử lý và nhận dạng ký tự để trích xuất thông tin từ biển
số.
5. Post-processing: xử lý nhiễu, kiểm tra kiểu ký tự, sử dụng thông tin ngữ
cảnh,.. ảnh kết quả cuối cùng.
2.2.1.2. Áp dụng phương pháp phân vùng bằng biên để xác định vùng chứa biển
số xe.
Ta sẽ phân vùng bằng biên ảnh sử dụng Sobel, Prewitt.
• Source code
import cv2
from Sobel_Prewitt import Sobel, Prewitt, threshold
from auto_threshold import auto_threshold

img = cv2.imread("D:\Image_Processing\\biensoxe2.jpg", 0)
img = cv2.GaussianBlur(img, (5, 5), 0)
threshold_img = threshold(img, auto_threshold(img))

sobel_img = Sobel(img)
sobel_img = threshold(sobel_img, auto_threshold(sobel_img))

prewitt_img = Prewitt(threshold_img)
prewitt_img = threshold(prewitt_img,
auto_threshold(prewitt_img))

cv2.imshow("Sobel", sobel_img)
cv2.imshow("Prewitt", prewitt_img)
cv2.waitKey()
cv2.destroyAllWindows()
• Ảnh ban đầu

Hình:
• Kết quả tìm biên ảnh bằng toán tử Sobel

Hình:
• Kết quả tìm biên ảnh bằng toán tử Prewitt
Hình:
Sau khi có biên ảnh bắt đầu xác định vùng chứa biển số xe, sử hàm
cv2.findContours.
• Source code
import cv2
from Sobel_Prewitt import Sobel, threshold
from auto_threshold import auto_threshold

img = cv2.imread("D:/Image_Processing/biensoxe2.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Làm mờ ảnh để làm giảm nhiễu
blurred_img = cv2.GaussianBlur(gray, (5, 5), 0)
threshold_img = threshold(blurred_img,
auto_threshold(blurred_img))

# Áp dụng Sobel
edges = Sobel(threshold_img)
edges = threshold(edges, auto_threshold(edges))

# Tìm các đường viền trong ảnh


contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)

# Tìm vùng chứa biển số xe


license_plate_contour = None
for contour in contours:
# Xác định diện tích đường viền
area = cv2.contourArea(contour)
if area > 500: # Đặt ngưỡng diện tích tối thiểu để loại
bỏ nhiễu
# Xác định hình dạng đường viền
peri = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.08 * peri, True)
if len(approx) == 4: # Kiểm tra xem đường viền có
phải là hình chữ nhật hay không
license_plate_contour = approx
break

# Vẽ vùng chứa biển số lên ảnh gốc (đường viền màu xanh lá
cây)
if license_plate_contour is not None:
cv2.drawContours(img, [license_plate_contour], -1, (0,
255, 0), 2)

# Hiển thị ảnh gốc và ảnh đã vẽ đường viền


cv2.imshow('Original Image', img)
cv2.imshow('Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()

• Ảnh kết quả


Hình:
Ảnh có thể xác định sai vùng chứa biển số xe do chỉ dựa trên Contours của ảnh
biên. Do giới hạn đề tài nên chỉ áp dụng cách đơn giản để minh họa. Để tăng độ
chính xác nên sử dụng các phương pháp học máy cho bước này.
2.2.2. Đếm số lượng vi khuẩn lạc
2.2.2.1. Ý tưởng bài toán
1. Tiền xử lý ảnh: áp dụng các phép biến đổi hình thái học và bộ lọc nhiễu
nếu cần thiết để làm sạch và cải thiện chất lượng ảnh.
2. Phân vùng bằng biên: sử dụng phương pháp phân vùng bằng biên, tìm
các biên cạnh trong ảnh. Phép phát hiện biên này sẽ thể hiện rõ hình biên
vi khuẩn.
3. Đếm số lượng vi khuẩn dựa trên các đường biên đã tìm được: một đường
biên được coi là một vi khuẩn nếu nó là một đường biên hoàn chỉnh
không đứt đoạn. Chúng ta có thể sử dụng các thuật toán đơn giản như
đếm số lượng đường biên hoàn chỉnh để đếm số lượng vi khuẩn.
Ta sẽ phân vùng bằng biên sử dụng toán tử Sobel.
• Source code
import cv2
from Sobel_Prewitt import Sobel, threshold
from auto_threshold import auto_threshold

img = cv2.imread("D:\Image_Processing\\vikhuan.png", 0)
threshold_img = threshold(img, auto_threshold(img))

sobel_img = Sobel(threshold_img)
sobel_img = threshold(sobel_img, auto_threshold(sobel_img))

cv2.imshow("Sobel", sobel_img)
cv2.waitKey()
cv2.destroyAllWindows()

• Ảnh ban đầu

Hình:
• Ảnh sau khi phân vùng bằng biên
Hình:
Sau khi có được biên ảnh ta hàm cv2.findContours để đếm số lượng contours từ
đó suy ra số lượng vi khuẩn.
• Source code
import cv2
import numpy as np
from Sobel_Prewitt import Sobel, threshold
from auto_threshold import auto_threshold

# Đọc ảnh và chuyển sang ảnh đen trắng


image = cv2.imread("D:\Image_Processing\\vikhuan.png")
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Áp dụng Sobel
edges = Sobel(gray_img)
edges = threshold(edges, auto_threshold(edges))

# Tìm các đường biên


contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)

# Lọc các đường biên theo diện tích và hình dạng (gần hình
tròn)
min_area = 10 # Diện tích tối thiểu để loại bỏ các contour
nhỏ
circularity_threshold = 0.1 # Ngưỡng hình dạng gần tròn
count = 0

for contour in contours:


area = cv2.contourArea(contour)
perimeter = cv2.arcLength(contour, True)
if perimeter == 0:
continue # Bỏ qua contour có chiều dài đường biên
bằng 0

circularity = 4 * np.pi * (area / (perimeter ** 2))

if area > min_area and circularity >


circularity_threshold:
if area > min_area * 100: # Kiểm tra xem diện tích có
lớn hơn 100 lần min_area hay không
continue # Bỏ qua các contour có diện tích lớn
hơn 100 lần min_area để bỏ qua biên của khuôn, và nhiễu
count += 1
cv2.drawContours(image, [contour], -1, (0, 255, 0), 2)

# Hiển thị kết quả


cv2.imshow('Origin Image', image)
cv2.imshow("Edges", edges)
cv2.waitKey(0)
cv2.destroyAllWindows()

# In số lượng vi khuẩn
print("Số lượng vi khuẩn: ", count)

• Ảnh kết quả

Hình:
Hình:
Vì sử dụng contours của ảnh biên nên số lượng vi khuẩn có thể bị tính nhầm,
hoặc không tìm được do đè, che lấp bởi môi trường. Trường hợp nhiều vi khuẩn
trùng nhau cũng khó xác định số lượng vi khuẩn. Do giới hạn đề tài nên chỉ áp
dụng cách đơn giản để minh họa. Để tăng độ chính xác nên sử dụng các phương
pháp học máy cho bước này.
2.2.3. Nhận diện khuôn mặt
2.2.3.1. Ý tượng bài toán
1. Tiền xử lý ảnh: Áp dụng các bước tiền xử lý để làm sạch ảnh và cải thiện
chất lượng ảnh. Các bước tiền xử lý có thể bao gồm làm mờ, cân bằng
histogram, điều chỉnh độ tương phản, loại bỏ nhiễu,...
2. Phân vùng bằng biên: Sử dụng phương pháp phân vùng bằng biên, tìm
các biên cạnh trong ảnh. Các biên cạnh sẽ giúp xác định các đường viền
của khuôn mặt.Trong bài toán này ta sẽ sử dụng phương pháp tìm biên
Canny.
3. Phân đoạn khuôn mặt: Dựa trên các biên cạnh đã tìm được, áp dụng các
thuật toán phân đoạn hoặc phương pháp phát hiện vùng để xác định và
phân đoạn vùng khuôn mặt trong ảnh.
4. Xử lý và nhận dạng khuôn mặt: Sau khi xác định được vùng khuôn mặt,
áp dụng các bước xử lý và nhận dạng khuôn mặt để trích xuất thông tin từ
khuôn mặt.
5. Post-processing: xử lý nhiễu, kiểm tra kiểu khuôn mặt, sử dụng thông tin
ngữ cảnh,.. ảnh kết quả cuối cùng.
Ta sẽ dùng hàm Canny ở trên.
• Source code

if __name__ == '__main__':

img = cv2.imread("D:\Image_Processing\\test1.png", 0)

blurred_image = cv2.GaussianBlur(img, ksize=(5, 5),


sigmaX=1, sigmaY=1)
# Tùy chọn hàm gradient, ở đây dùng sobel
gradient_magnitude, gradient_direction = sobel(img)

new_img = non_max_suppression(gradient_magnitude,
gradient_direction)

new_img = threshold(new_img, 0.65, 0.15)

new_img = hysteresis(new_img)

cv2.imshow("Final Result", new_img)


cv2.waitKey()
cv2.destroyAllWindows()

• Ảnh ban đầu

Hình:
• Ảnh kết quả sử dụng hàm Canny
Hình:
 Ảnh kết quả nếu áp dụng hàm Sobel, Prewitt

Hình:
Một ví dụ khác.
• Source code
if __name__ == '__main__':

img = cv2.imread("D:\Image_Processing\cogai.jpg", 0)

blurred_image = cv2.GaussianBlur(img, ksize=(5, 5),


sigmaX=1, sigmaY=1)
# Tùy chọn hàm gradient, ở đây dùng sobel
gradient_magnitude, gradient_direction = sobel(img)

new_img = non_max_suppression(gradient_magnitude,
gradient_direction)
new_img = threshold(new_img, 0.95, 0.1)

new_img = hysteresis(new_img)

cv2.imshow("Final Result", new_img)


cv2.waitKey()
cv2.destroyAllWindows()

• Ảnh ban đầu

Hình:
• Ảnh kết quả sử dụng hàm Canny.

Hình:
• Ảnh kết quả nếu áp dụng hàm Sobel, Prewitt
Hình:
Có thể thấy hàm Canny cho đường biên rõ nét, chi tiết hơn khi dùng hàm Sobel,
Prewitt. Đồng thời đường biên của hàm Canny cho ra mỏng hơn, và thể hiện
chính xác phạm vi đối tượng hơn nên rất phù hợp để áp dụng vào bài toán nhận
diện khuôn mặt- bài toán yêu cầu xác định chính xác các đối tượng.
Sau khi có chính xác ví trị đối tượng, ta sẽ dùng những phương pháp học máy
để nhận dạng khuôn mặt để trích xuất thông tin từ vùng đối tượng đã xác định.

You might also like