Ngưỡng (thresholding) trong opencv

- Phạm Duy Tùng

Giá trị ngưỡng:

Nói theo kiểu lúa hóa, trong opencv, ngưỡng là một số nằm trong đoạn từ 0 đến 255. Giá trị ngưỡng sẽ chia tách giá trị độ xám của ảnh thành 2 miền riêng biệt. Miền thứ nhất là tập hợp các điểm ảnh có giá trị nhỏ hơn giá trị ngưỡng. Miền thứ hai là tập hợp các các điểm ảnh có giá trị lớn hơn hoặc bằng giá trị ngưỡng.

Đầu vào của một thuật toán phân ngưỡng trong opencv thường có input là ảnh nguồn (source image) và giá trị ngưỡng. Đầu ra là ảnh đích đã được phân ngưỡng (destination image). Một số thuật toán phân ngưỡng sẽ kèm thêm vài giá trị râu ria khác nữa, chúng ta sẽ không quan tâm đến chúng

Mã giải của thuật toán phân ngưỡng:

if src[i] >= T:
    dest[i] = MAXVAL
else:
    dest [i] = 0

Có rất nhiều thuật toán phân ngưỡng dựa trên cách chúng ta xác định ngưỡng. Chúng ta sẽ tìm hiểu lần lượt các thuật toán trên.

Thuật toán Simple Thresholding

Simple Thresholding thực hiện phân ngưỡng bằng cách thay thế giá trị lớn hơn hoặc bằng và giá trị bé hơn giá trị ngưỡng bằng một giá trị mới. Cụ thể chúng ta có thể xem mã nguồn bên dưới


import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('gradient.png',0)
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)

titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in xrange(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

plt.show()

Hình ảnh Hình ảnh và thuật toán của mô hình được lấy từ trang opencv-python-tutroals.readthedocs.io

Ở đoạn code trên, chúng ta thiết lập giá trị ngưỡng là 127, với các điểm ảnh có giá trị lớn hơn hoặc bằng 127, chúng ta sẽ gán lại giá trị của nó thành 255. Và các điểm ảnh có giá trị bé hơn 127 sẽ được gán bằng 0 (mặc định).



double cv::threshold    (   InputArray  src,
OutputArray     dst,
double  thresh,
double  maxval,
int     type 
)   

Thuật toán sample thresholding của opencv còn có 1 tham số nữa khá quan trọng nữa là loại ngưỡng (type). Hiện tại lúc mình viết bài viết này thì opencv hỗ trợ 8 loại là: THRESH_BINARY, THRESH_BINARY_INV, THRESH_TRUNC, THRESH_TOZERO, THRESH_TOZERO_INV, THRESH_MASK, THRESH_OTSU, THRESH_TRIANGLE. Ý nghĩa của từng loại như sau:

  • THRESH_BINARY: Có thể dịch là ngưỡng nhị phân. Ý nghĩa y hệt những gì mình đề cập ở trên.

  • THRESH_BINARY_INV: Ngưỡng nhị phân đảo ngược. Có thể hiểu là nó sẽ đảo ngược lại kết quả của THRESH_BINARY.

  • THRESH_TRUNC: Những giá trị điểm ảnh bé hơn ngưỡng sẽ giữ nguyên giá trị, những điểm ảnh lớn hơn hoặc ngưỡng sẽ được gán lại là maxvalue.

  • THRESH_TOZERO: Những điểm ảnh bé hơn ngưỡng sẽ bị gán thành 0, những điểm còn lại giữ nguyên.

  • THRESH_TOZERO_INV: Những điểm ảnh nhỏ hơn giá trị ngưỡng sẽ được giữ nguyên, những điểm ảnh còn lại sẽ bị gán thành 0.

  • THRESH_MASK: Ở bạn opencv4, hầu như không được xài.

  • THRESH_OTSU: Sử dụng thuật toán Otsu để xác định giá trị ngưỡng.

  • THRESH_TRIANGLE: Sử dụng thuật toán Triangle để xác định giá trị ngưỡng.

Giá trị 127 là giá trị trung bình cộng của 0 và 255 làm tròn xuống. Giá trị ngưỡng của thuật toán này đòi hỏi người sử dụng phải có mức độ hiểu biết nhất định về các loại ảnh mình đang xử lý để chọn ngưỡng cho phù hợp.

Adaptive Thresholding

Thuật toán simple thresholding hoạt động khá tốt. Tuy nhiên, nó có 1 nhược điểm là giá trị ngưỡng bị/được gán toàn cục. Thực tế khi chụp, hình ảnh chúng ta nhận được thường bị ảnh hưởng của nhiễu, ví dụ như là bị phơi sáng, bị đèn flask, …

Một trong những cách được sử dụng để giải quyết vấn đề trên là chia nhỏ bức ảnh thành những vùng nhỏ (region), và đặt giá trị ngưỡng trên những vùng nhỏ đó -> adaptive thresholding ra đời. Opencv cung cấp cho chúng ta hai cách xác định những vùng nhỏ

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('sudoku.png',0)
img = cv.medianBlur(img,5)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
            cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
            'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]
for i in xrange(4):
    plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

Hình ảnh Hình ảnh và thuật toán của mô hình được lấy từ trang docs.opencv.org



void cv::adaptiveThreshold  (   InputArray  src,
OutputArray     dst,
double  maxValue,
int     adaptiveMethod,
int     thresholdType,
int     blockSize,
double  C 
)   

Ở đây:

blockSize: Kích thước của vùng, bắt buộc phải là một số lẻ lớn hơn 0.

C: hằng số, giá trị từ -255 đến 255. Có thể gán C bằng 0 để đỡ rối.

adaptiveMethod nhận vào một trong hai giá trị là cv.ADAPTIVE_THRESH_MEAN_C và cv.ADAPTIVE_THRESH_GAUSSIAN_C, đó là các phương pháp tính ngưỡng.

  • ADAPTIVE_THRESH_MEAN_C: Tính trung bình các láng giềng xung quanh điểm cần xét trong vùng blockSize * blockSize trừ đi giá trị hằng số C.

  • ADAPTIVE_THRESH_GAUSSIAN_C: Nhân giá trị xung quanh điểm cần xét với trọng số gauss rồi tính trung bình của nó, sau đó trừ đi giá trị hằng số C.

thresholdType: Tương tự như Simple Thresholding đã trình bày ở trên.

Cảm ơn các bạn đã quan tâm và theo dõi bài viết, hẹn gặp bạn ở các bài viết tiếp theo.

Tham khảo