Giải thuật phân vùng Region Growing trên ảnh màu

  Jun 1, 2019      2m
   

OpenCV - tut 14: Region Growing

Giải thuật phân vùng Region Growing trên ảnh màu

Môi trường "hành sự"

  • Linux (bài viết sử dụng Ubuntu 16.04)
  • OpenCV (bài viết sử dụng OpenCV 3.4.1)
  • Python (bài viết sử dụng Python 3.5.5)
  • Ảnh mẫu để xử lý: jung_yun.jpg

Bạn có thể download ảnh mẫu về:

jung_yun.jpg (Nguồn: Hình ảnh nghệ sĩ Jung Yun)

Jung Yun

Giải thuật Region Growing

Giải thuật Region Growing (tạm dịch: lan vùng) là giải thuật phân vùng dựa trên mức sáng. Từ các điểm xác định trước trên ảnh (seed), vùng phân đoạn ảnh sẽ được lan dần rộng ra các pixel liền kề xung quanh, nếu các pixel liền kề có mức sáng gần tương tự mức sáng của điểm xác định trước (seed) (dựa theo ngưỡng) thì chúng là các điểm ta cần phân đoạn. Tiếp tục lặp lại quá trình lan rộng ra xung để thu thập các pixel hợp lệ theo mức sáng thì ta được kết quả phân đoạn ảnh.

Diễn giải trên có thể hơi khó hình dùng, các bạn có thể tham khảo thêm trên Wikipedia: https://en.wikipedia.org/wiki/Region_growing

Hiện thực giải thuật Region Growing trên ảnh màu

Prototype về ý tưởng hiện thực giải thuật region growing trên ảnh màu:

  1. Vì ý tưởng là ta sẽ bắt đầu lan màu từ các điểm, do đó ta sẽ dùng 1 stack để chứa các điểm tọa độ 2D sẽ duyệt. Biến đó chính là loop_points trong code hiện thực bên dưới.
  2. Ta tiến hành lặp trên stack để xem xét từng tọa độ, nếu mức sáng tại đó thỏa yêu cầu (hiệu mức sáng trên 3 kênh đều nhỏ hơn ngưỡng delta), ta sẽ đánh dấu chúng lên mặt nạ mask để cho biết rằng pixel ở tọa độ này đã duyệt rồi (nhằm tránh duyệt trùng í mà :P). Đường nhiên mask có cùng kích thước so với ảnh.
  3. Thêm 8 điểm hàng xóm xung quanh pixel hợp lệ đang xét vào stack! Lưu ý rằng phải kiểm tra rằng các "bà hàng xóm" này đã được duyệt chưa, cũng như tọa độ có hợp lệ không rồi mới đưa vào stack cho lần lặp tới.

Vậy là xong giải thuật Region Growing rồi đó các bạn. Một số nhận xét về giải thuật region growing này:

  • Lan trên vùng cùng màu với điểm khởi tạo.
  • Nhạy với ánh sáng, màu sắc.
  • Nhạy với nhiễu (do đó nên áp dụng làm mờ để giảm nhiễu), nếu để ý, bạn sẽ thấy code bên dưới mình lọc Gauss trước khi chạy giải thuật region growing.
  • Chi phí tính toán lớn với ảnh có độ phân giải cao. Ý tưởng optimize cho trường hợp này: bạn có thể resize về ảnh bé để chạy region growing, sau đó resize mask về kích thước gốc. Dĩ nhiên sẽ hy sinh một chút độ chính xác.
  • Do lan dần ra các pixel xung quanh nên ta phải lặp tuần tự, khó tối ưu hóa bằng xử lý song song.
  • Ngưỡng delta cho mức sáng / màu sắc là quan trọng! Nó là siêu tham số cần điều chỉnh phù hợp.
  • Kết quả phân đoạn phụ thuộc vào điểm gốc ban đầu (seed) và ngưỡng delta thiết lập.
import os
import cv2
import numpy as np

def region_growing(img, x, y, delta=15):
    """
        x: relative coord (0-1)
        y: relative coord (0-1)
    """
    def is_valid_px(x, y, img):
        if x>=0 and x<img.shape[1] and y>=0 and y<img.shape[0]:
            return True
        return False
    mask = np.zeros(img.shape[:2])
    y_abs = int(y * img.shape[0])
    x_abs = int(x * img.shape[1])
    
    loop_points = []
    loop_points.append((x_abs, y_abs))
    px_intensity = img[y_abs, x_abs, :]
    blue = px_intensity[0]
    green = px_intensity[1]
    red = px_intensity[2]
    
    while len(loop_points) != 0:
        (cx, cy) = loop_points.pop()
        current_intensity = img[cy, cx, :]
        b = int(current_intensity[0])
        g = int(current_intensity[1])
        r = int(current_intensity[2])
        
        if abs(r-red)<delta and abs(g-green)<delta and abs(b-blue)<delta:
            mask[cy, cx] = 255
            neighbors = [
                (cx-1, cy),
                (cx+1, cy),
                (cx-1, cy-1),
                (cx, cy-1),
                (cx+1, cy-1),
                (cx-1, cy+1),
                (cx, cy+1),
                (cx+1, cy+1)
            ]
            for (nx, ny) in neighbors:
                if is_valid_px(nx, ny, img) and mask[ny, nx] == 0:
                    loop_points.append((nx, ny))
        else:
            pass
    return mask

def main(img_path):
    img = cv2.imread(img_path, cv2.IMREAD_COLOR)
    KERNEL_WIDTH = KERNEL_HEIGHT = 5
    SIGMA_X = SIGMA_Y = 2
    img[:,:,0] = cv2.GaussianBlur(img[:,:,0], ksize=(KERNEL_WIDTH, KERNEL_HEIGHT), sigmaX=SIGMA_X, sigmaY=SIGMA_Y)
    img[:,:,1] = cv2.GaussianBlur(img[:,:,1], ksize=(KERNEL_WIDTH, KERNEL_HEIGHT), sigmaX=SIGMA_X, sigmaY=SIGMA_Y)
    img[:,:,2] = cv2.GaussianBlur(img[:,:,2], ksize=(KERNEL_WIDTH, KERNEL_HEIGHT), sigmaX=SIGMA_X, sigmaY=SIGMA_Y)
    cv2.imwrite('out_blur_'+os.path.basename(img_path), img)
    
    x = 0.5
    y = 0.6
    delta = 20
    mask = region_growing(img=img, x=x, y=y, delta=delta)
    cv2.imwrite('out_mask.jpg', mask)
    
    img_color = cv2.imread(img_path, cv2.IMREAD_COLOR)
    img_color[mask>0,:] = (0, 255, 255)
    
    cv2.circle(img_color, (int(x*img.shape[1]), int(y*img.shape[0])), radius=5, color=(0, 0, 255), thickness=2)
    cv2.imwrite('out_seg_'+os.path.basename(img_path), img_color)

if __name__ == "__main__":
    print('Running...')
    main('jung_yun.jpg')
    print('* Follow me @ ' + "\x1b[1;%dm" % (34) + ' https://www.facebook.com/minhng.info/' + "\x1b[0m")
    print('* Join GVGroup for discussion @ ' + "\x1b[1;%dm" % (34) + 'https://www.facebook.com/groups/ip.gvgroup/' + "\x1b[0m")
    print('* Thank you ^^~')

Ảnh kết quả sau khi chạy phân vùng nè. Chấm đó chính là điểm seed, vùng màu vàng là kết quả phân vùng:

Jung Yun Region Growing


Cảm ơn bạn đã theo dõi bài viết. Hãy kết nối với tớ nhé!


Danh sách bài viết series OpenCV: