Thị giác máy tính với OpenCV-Python Bài 4, Phần 3: Tạo ngưỡng hình ảnh

Thị giác máy tính với OpenCV-Python Bài 4, Phần 3: Tạo ngưỡng hình ảnh

Thị giác máy tính với OpenCV-Python Bài 4, Phần 3: Tạo ngưỡng hình ảnh

11:13 - 29/12/2021

Trong hướng dẫn này chúng ra sẽ tìm hiểu về ngưỡng đơn giản, ngưỡng thích ứng và ngưỡng Otsu thông qua các hàm cv2.threshold và cv2.adaptiveThreshold.

Thị giác máy tính với OpenCV-Python Bài 7 Phần 3: Nhận diện khuôn mặt
Thị giác máy tính với OpenCV-Python Bài 7 Phần 2: Phát hiện người đi bộ trong video
Thị giác máy tính với OpenCV-Python Bài 7 Phần 1: Phát hiện người đi bộ trong hình ảnh
Thị giác máy tính với OpenCV-Python Bài 6 Phần 2: Phép trừ nền
Thị giác máy tính với OpenCV-Python Bài 6 Phần 1: Bắt bám đối tượng với Meanshift và Camshift

Tạo ngưỡng trong xử lý ảnh (thresholding) là chuyển đổi hình ảnh từ màu sắc hoặc thang độ xám thành một hình ảnh nhị phân chỉ gồm hai màu đen và trắng. Câu hỏi đặt ra là tạo ngưỡng để làm gì? để chọn ra các khu vực quan tâm bên trong hình ảnh và bỏ qua các phần không quan tâm (ví dụ như lọc bỏ nhiễu).

Ngưỡng đơn giản

Vấn đề tạo ngưỡng khá đơn giản: nếu giá trị pixel lớn hơn giá trị ngưỡng, nó được gán một giá trị (có thể là màu trắng), nếu không nó sẽ được gán một giá trị khác (có thể là màu đen). Hàm được sử dụng là cv2.threshold. Đối số đầu tiên là hình ảnh nguồn, phải là hình ảnh thang độ xám. Đối số thứ hai là giá trị ngưỡng được sử dụng để phân loại các giá trị pixel. Đối số thứ ba là maxVal đại diện cho giá trị được gán nếu giá trị pixel lớn hơn (đôi khi nhỏ hơn) giá trị ngưỡng. OpenCV cung cấp các kiểu định ngưỡng khác nhau và nó được quyết định bởi tham số thứ tư của hàm bao gồm:

cv2.THRESH_BINARY

cv2.THRESH_BINARY_INV

cv2.THRESH_TRUNC

cv2.THRESH_TOZERO

cv2.THRESH_TOZERO_INV

Tham khảo thêm các nguồn khác để biết rõ mỗi loại dùng để làm gì.

Ở đầu ra sẽ nhận được hai tham số: retval (sẽ được giải thích sau) và hình ảnh ngưỡng. Xem đoạn code minh họa dưới đây:

  1. import cv2
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. img = cv2.imread('grayscale.jpg', 0)
  5. ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
  6. ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
  7. ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
  8. ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
  9. ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
  10. titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
  11. images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
  12. for i in range(6):
  13.      plt.subplot(2, 3, i+1), plt.imshow(images[i], 'gray')
  14.      plt.title(titles[i])
  15.      plt.xticks([]), plt.yticks([])
  16. plt.show()

Ghi chú: hàm plt.subplot() được sử dụng để vẽ nhiều hình ảnh cùng lúc.

Kết quả được đưa ra dưới đây:


 

Ngưỡng thích ứng

Trong phần trước, chúng ta đã sử dụng giá trị toàn cục làm giá trị ngưỡng. Nhưng nó có thể không phù hợp trong các trường hợp mà hình ảnh có các điều kiện ánh sáng khác nhau ở những khu vực khác nhau. Trong trường hợp này, cần sử dụng ngưỡng thích ứng, tức là sử dụng thuật toán tính toán ngưỡng cho một vùng nhỏ của hình ảnh. Từ đó nhận được các ngưỡng khác nhau cho các vùng khác nhau của cùng một hình ảnh và nó mang lại kết quả tốt hơn đối với hình ảnh có độ sáng khác nhau.

          Cú pháp của hàm ngưỡng thích ứng: cv2.adaptiveThreshold(source, maxVal, adaptiveMethod, thresholdType, blocksize, constant). Trong đó adaptiveMethod quyết định xem giá trị ngưỡng được tính toán như thế nào:

  • ADAPTIVE_THRESH_MEAN_C: giá trị ngưỡng là giá trị trung bình của khu vực lân cận.
  • ADAPTIVE_THRESH_GAUSSIAN_C: giá trị ngưỡng là tổng trọng số của các giá trị lân cận trong đó trọng số là một cửa sổ gaussian.

Sourve: hình ảnh đầu vào.

MaxVal: giá trị tối đa có thể gán cho 1 pixel.

ThresholdType: kiểu ngưỡng được áp dụng.

Blocksize quyết định kích thước của khu vực lân cận để tính toán.

constant là một hằng số bị trừ khỏi giá trị trung bình hoặc giá trị trung bình có trọng số được tính toán.

Đoạn code dưới đây so sánh ngưỡng toàn cầu và ngưỡng thích ứng cho một hình ảnh có độ chiếu sáng khác nhau:

  1. import cv2
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. img = cv2.imread('sudoku.jpg', 0)
  5. img = cv2.medianBlur(img, 5)
  6. ret,th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
  7. th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
  8. th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
  9. titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
  10. images = [img, th1, th2, th3]
  11. for i in range(4):
  12.     plt.subplot(2, 2, i+1), plt.imshow(images[i], 'gray')
  13.     plt.title(titles[i])
  14.     plt.xticks([]), plt.yticks([])
  15. plt.show()

Xem kết quả ở hình dưới đây:

Ngưỡng Otsu

Trong phần đầu chúng ta thấy có một tham số thứ hai là retVal. Việc sử dụng nó xuất hiện khi chúng ta tiến hành quá trình nhị phân của Otsu.

Trong ngưỡng toàn cầu, chúng ta đã sử dụng một giá trị tùy ý cho giá trị ngưỡng, nhưng làm thế nào để biết một giá trị đã chọn là tốt hay không? Câu trả lời nằm ở phương pháp thử và sai. Nhưng hãy xem xét một hình ảnh hai đỉnh (biểu đồ của nó có hai đỉnh). Đối với hình ảnh loại này, chúng ta có thể lấy một giá trị ở giữa các đỉnh làm giá trị ngưỡng và đó chính xác là những gì ngưỡng Otsu làm. Nói một cách đơn giản, nó tự động tính toán một giá trị ngưỡng từ biểu đồ hình ảnh cho một hình ảnh hai đỉnh. Đối với hình ảnh không phải là hai đỉnh, quá trình phân chia hai đỉnh sẽ không chính xác.

Trong trường hợp này, hàm cv2.threshold() được sử dụng, nhưng cờ bổ sung cv2.THRESH_OTSU được truyền vào. Đối với giá trị ngưỡng, chỉ cần truyền giá trị 0. Sau đó, thuật toán tìm giá trị ngưỡng tối ưu và trả về dưới dạng đầu ra thứ hai, retVal. Nếu ngưỡng Otsu không được sử dụng, retVal giống với giá trị ngưỡng chúng ta đã sử dụng.

Xem ví dụ dưới đây. Hình ảnh đầu vào là hình ảnh nhiễu. Trong trường hợp đầu tiên, chúng ta áp dụng ngưỡng toàn cầu cho giá trị 127. Trong trường hợp thứ hai, chúng ta áp dụng trực tiếp ngưỡng Otsu. Trong trường hợp thứ ba, thực hiện lọc hình ảnh với gaussian 5x5 để loại bỏ nhiễu, sau đó áp dụng ngưỡng Otsu.

  1. import cv2
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. img = cv2.imread('noisyimage.jpg', 0)
  5. # global thresholding
  6. ret1,th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
  7. # Otsu's thresholding
  8. ret2,th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  9. # Otsu's thresholding after Gaussian filtering
  10. blur = cv2.GaussianBlur(img, (5, 5), 0)
  11. ret3,th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
  12. # plot all the images and their histograms
  13. images = [img, 0, th1, img, 0, th2, blur, 0, th3]
  14. titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)', 'Original Noisy Image', 'Histogram', "Otsu's Thresholding", 'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]
  15. for i in range(3):
  16.      plt.subplot(3, 3, i*3+1), plt.imshow(images[i*3], 'gray')
  17.      plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
  18.      plt.subplot(3, 3, i*3+2), plt.hist(images[i*3].ravel(), 256)
  19.      plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
  20.      plt.subplot(3, 3, i*3+3), plt.imshow(images[i*3+2], 'gray')
  21.      plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
  22. plt.show()

 

 Ở phần tiếp theo chúng ta sẽ cùng tìm hiểu cách làm mịn ảnh và giảm bớt nhiễu hình ảnh.

(Sưu tầm)
VIỆN IMC
Tòa nhà IMC Tower, Số 176 Trường Chinh, Phường Khương
Thượng, Quận Đống Đa, Thành phố Hà Nội, Việt Nam
Tel/Fax : (+84) 24 3566 6232 / 24 3566 6234
Email: contact@imc.org.vn   Website: https://imc.org.vn