Phần 9. Lọc ảnh sử dụng tích chập trong OpenCV

0
7835
Filter

Bài viết này sẽ giới thiệu cách sử dụng tích chập trong OpenCV và một số bộ lọc cơ bản để thực hiện lọc hình ảnh ví dụ như làm mờ hoặc tăng độ sắc nét cho ảnh gốc.

Bài viết gồm các nội dung chính sau:

1. Giới thiệu về mặt nạ tích chập (convolution kernel) trong xử lý ảnh

2. Sử dụng mặt nạ để làm mờ hoặc tăng độ nét của ảnh

3. Sử dụng mặt nạ đồng nhất (Identity Kernel) trong OpenCV

4. Làm mờ ảnh (blurring) sử dụng mặt nạ tích chập 2D tùy biến

5. Làm mờ ảnh sử dụng hàm có sẵn trong OpenCV

6. Áp dụng bộ lọc Gauss để làm mờ ảnh

7. Áp dụng bộ lọc trung vị (Median) để làm mờ ảnh

8. Tăng độ nét (Sharpening) của ảnh bằng mặt nạ tích chập 2D tùy biến

9. Áp dụng lọc song phương (Bilateral Filter) trong OpenCV

Trước khi bắt đầu vào từng phần cụ thể, hãy quan sát đoạn code mẫu dùng để lọc ảnh. Chi tiết chức năng từng đoạn code sẽ được giới thiệu ở các phần tương ứng.

Python

import cv2
import numpy as np

image = cv2.imread('test.jpg')

# Print error message if image is null
if image is None:
    print('Could not read image')

# Apply identity kernel
kernel1 = np.array([[0, 0, 0],
                    [0, 1, 0],
                    [0, 0, 0]])

identity = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

cv2.imshow('Original', image)
cv2.imshow('Identity', identity)
    
cv2.waitKey()
cv2.imwrite('identity.jpg', identity)
cv2.destroyAllWindows()

# Apply blurring kernel
kernel2 = np.ones((5, 5), np.float32) / 25
img = cv2.filter2D(src=image, ddepth=-1, kernel=kernel2)

cv2.imshow('Original', image)
cv2.imshow('Kernel Blur', img)
    
cv2.waitKey()
cv2.imwrite('blur_kernel.jpg', img)
cv2.destroyAllWindows()

C++

// Import dependencies
#include <opencv2/opencv.hpp>
#include <iostream>

// Using namespaces to nullify use of c::function(); syntax and std::function(); syntax
using namespace std;
using namespace cv;

int main()
{
    // Read Image
    Mat image = imread("test.jpg");

    // Print Error message if image is null
    if (image.empty()) 
        {
            cout << "Could not read image" << endl;
        }
    
    // Apply identity filter using kernel
    Mat kernel1 = (Mat_<double>(3,3) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
    Mat identity; 
    filter2D(image, identity, -1 , kernel1, Point(-1, -1), 0, 4);
    imshow("Original", image);
    imshow("Identity", identity);
    waitKey();
    imwrite("identity.jpg", identity);
    destroyAllWindows();

    // Blurred using kernel
    // Initialize matrix with all ones
    Mat kernel2 = Mat::ones(5,5, CV_64F);
    // Normalize the elements
    kernel2 = kernel2 / 25;
    Mat img;
    filter2D(image, img, -1 , kernel2, Point(-1, -1), 0, 4);
    imshow("Original", image);
    imshow("Kernel blur", img);
    imwrite("blur_kernel.jpg", img);
    waitKey();
    destroyAllWindows();
}

1. Giới thiệu về mặt nạ tích chập (convolution kernel) trong xử lý ảnh

Trong xử lý ảnh, mặt nạ tích chập là một ma trận 2D được sử dụng để lọc ảnh. Còn được gọi là ma trận tích chập, mặt nạ tích chập thường là ma trận vuông MxN, trong đó cả M và N đều là số nguyên lẻ (ví dụ: 3 × 3, 5 × 5, 7 × 7, v.v.). Xem ma trận ví dụ 3 × 3 ở dưới.

Các mặt nạ như vậy có thể được sử dụng để thực hiện các phép toán trên mỗi pixel của hình ảnh để đạt được hiệu ứng mong muốn (như làm mờ hoặc làm sắc nét hình ảnh). Lý do cần làm mờ ảnh:

– Làm giảm một số loại nhiễu nhất định trong hình ảnh. Vì lý do này, làm mờ thường được gọi là làm mịn.

– Để xóa phông nền như thực hiện chế độ chụp chân dung (Portrait) ở trên máy ảnh của thiết bị di động.

2. Sử dụng mặt nạ để làm mờ hoặc tăng độ nét của ảnh

Việc lọc ảnh gốc được thực hiện bằng cách tích chập mặt với ảnh.

– Giả sử rằng tâm của mặt nạ được đặt trên một pixel cụ thể (p), trong một hình ảnh.

– Sau đó, nhân giá trị của mỗi phần tử trong mặt nạ (1 trong trường hợp ma trận mặt nạ ví dụ ở phần 1), với phần tử pixel tương ứng (tức là cường độ pixel của nó) trong ảnh gốc.

– Tính tổng kết quả của các phép nhân đó và tính giá trị trung bình.

– Cuối cùng, thay thế giá trị của pixel (p) bằng giá trị trung bình mà vừa tính toán.
Khi thực hiện thao tác này cho mọi pixel trong ảnh gốc, sử dụng mặt nạ 3 × 3 ở trên, hình ảnh được lọc thu được sẽ bị mờ. Điều này là do phép toán tích chập với mặt nạ này có hiệu ứng trung bình, có xu hướng làm mịn hoặc mờ hình ảnh. Ngược lại bằng cách thay đổi giá trị của các phần tử trong mặt nạ, thì sẽ đạt được hiệu ứng làm sắc nét.

Bây giờ chúng ta tìm hiểu cách triển khai áp dụng mặt nạ tích chập để làm mờ hoặc làm sắc nét ảnh trong OpenCV. (Bạn cũng có thể truy cập tại đây để xem hướng dẫn và chạy tất cả các ví dụ mà không cần phải cài đặt bất cứ gì trên máy tính của mình). Hình ảnh dưới đây sẽ được sử dụng cho tất cả các ví dụ tiếp theo.

3. Sử dụng mặt nạ đồng nhất (Identity Kernel) trong OpenCV

Trước khi giới thiệu cách làm mờ và làm sắc nét ảnh bằng mặt nạ, trước tiên hãy tìm hiểu về mặt nạ đồng nhất. Mặt nạ đồng nhất là một ma trận vuông, trong đó phần tử ở giữa là 1 và tất cả các phần tử khác bằng 0:

Ma trận đồng nhất đặc biệt vì khi nhân nó với bất kỳ ma trận nào khác sẽ trả về ma trận ban đầu. Trong đoạn code bên dưới, chúng ta sẽ sử dụng Mặt nạ đồng nhất ở trên để thể hiện việc lọc sẽ không thay đổi hình ảnh ban đầu. Bắt đầu bằng cách khai báo OpenCV và Numpy:

Python

import cv2
import numpy as np

C++

// Import dependencies
#include <opencv2/opencv.hpp>
#include <iostream>

// Using namespaces to nullify use of c::function(); syntax and std::function(); syntax
using namespace std;
using namespace cv;

Tiếp theo ta có đoạn code thực hiện các việc sau:

– Đọc hình ảnh gốc

– Xác định mặt nạ đồng nhất, sử dụng mảng NumPy 3 × 3

– Sử dụng hàm filter2D() trong OpenCV để thực hiện thao tác lọc tuyến tính

– Hiển thị ảnh gốc và ảnh đã lọc, sử dụng imshow()

– Lưu hình ảnh đã lọc, sử dụng imwrite()

Cú pháp của hàm filter2D():

filter2D(src, ddepth, kernel)

Hàm yêu cầu ba đối số đầu vào:

– Đối số đầu tiên là hình ảnh gốc

– Đối số thứ hai là ddepth, cho biết độ sâu của hình ảnh thu được. Giá trị -1 cho biết rằng hình ảnh kết quả sẽ có cùng độ sâu với ảnh gốc

– Đối số cuối cùng là mặt nạ áp dụng cho việc lọc

Python

image = cv2.imread('test.jpg')
"""
Apply identity kernel
"""
kernel1 = np.array([[0, 0, 0],
                    [0, 1, 0],
                    [0, 0, 0]])
# filter2D() function can be used to apply kernel to an image.
# Where ddepth is the desired depth of final image. ddepth is -1 if...
# ... depth is same as original or source image.
identity = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

# We should get the same image
cv2.imshow('Original', image)
cv2.imshow('Identity', identity)

cv2.waitKey()
cv2.imwrite('identity.jpg', identity)
cv2.destroyAllWindows()

C++

// Apply identity filter using kernel
Mat kernel1 = (Mat_<double>(3,3) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
Mat identity;
filter2D(image, identity, -1 , kernel1, Point(-1, -1), 0, 4);
imshow("Original", image);
imshow("Identity", identity);
waitKey();
imwrite("identity.jpg", identity);
destroyAllWindows();

4. Làm mờ ảnh (blurring) sử dụng mặt nạ tích chập 2D tùy biến

Tiếp theo, bài viết sẽ trình bày cách làm mờ hình ảnh. Chúng ta sẽ cần xác định một mặt nạ tùy chỉnh và sử dụng hàm filter2D() trong OpenCV để áp dụng thao tác lọc trên ảnh nguồn.

Bắt đầu bằng cách xây dựng một mặt nạ 5 × 5, chỉ bao gồm số 1, rồi chia mặt nạ cho 25. Tại sao vậy? Trước khi áp dụng bất kỳ phép tích chập nào cho một hình ảnh, sử dụng ma trận tích chập 2D, thì cần đảm bảo rằng tất cả các giá trị đều được chuẩn hóa. Điều này được thực hiện bằng cách chia mỗi phần tử của mặt nạ cho số phần tử trong mặt nạ, trong trường hợp này là 25. Điều này đảm bảo tất cả các giá trị nằm trong phạm vi [0,1]. Bây giờ sử dụng hàm filter2D() để lọc hình ảnh. Hàm này có thể được sử dụng để lọc ảnh, với bất kỳ mặt nạ nào do người dùng xác định.

Python

"""
Apply blurring kernel
"""
kernel2 = np.ones((5, 5), np.float32) / 25
img = cv2.filter2D(src=image, ddepth=-1, kernel=kernel2)

cv2.imshow('Original', image)
cv2.imshow('Kernel Blur', img)

cv2.waitKey()
cv2.imwrite('blur_kernel.jpg', img)
cv2.destroyAllWindows()

C++

// Blurred using kernel
// Initialize matrix with all ones
Mat kernel2 = Mat::ones(5,5, CV_64F);

// Normalize the elements
kernel2 = kernel2 / 25;
Mat img;
filter2D(image, img, -1 , kernel2, Point(-1, -1), 0, 4);
imshow("Original", image);
imshow("Kernel blur", img);
imwrite("blur_kernel.jpg", img);
waitKey();
destroyAllWindows();

5. Làm mờ ảnh sử dụng hàm có sẵn trong OpenCV

Chúng ta cũng có thể làm mờ hình ảnh bằng cách sử dụng hàm blur() có sẵn trong OpenCV. Hàm này được sử dụng để làm mờ hình ảnh mà không cần xác định cụ thể mặt nạ. Chỉ cần chỉ định kích thước mặt nạ ksize, sau đó, hàm sẽ tạo ra một mặt nạ có kích thước như khai báo và áp dụng nó vào hình ảnh gốc. Đoạn code bên dưới sử dụng hàm blur() sẽ tạo ra đầu ra giống như ví dụ trên (sử dụng hàm filter2d()).

Python

"""
Apply blur using `blur()` function
"""
img_blur = cv2.blur(src=image, ksize=(5,5)) # Using the blur function to blur an image where ksize is the kernel size

# Display using cv2.imshow()
cv2.imshow('Original', image)
cv2.imshow('Blurred', img_blur)

cv2.waitKey()
cv2.imwrite('blur.jpg', img_blur)
cv2.destroyAllWindows()

C++

// Blurred using OpenCV C++ blur() function
Mat img_blur;
blur(image, img_blur, Size(5,5));
imshow("Original", image);
imshow("Blurred", img_blur);
imwrite("blur.jpg", img_blur);
waitKey();
destroyAllWindows();

6. Áp dụng bộ lọc Gauss để làm mờ ảnh

Trong phần này, chúng ta sẽ tìm hiểu cách sử dụng bộ lọc Gaussian trong OpenCV để làm mờ hình ảnh. Bản chất của bộ lọc Gaussian là thực hiện phép toán trung bình có trọng số, trái ngược với trung bình đồng nhất được mô tả trong mục 3. Cụ thể bộ lọc Gaussian làm mờ trọng số các giá trị pixel, dựa trên khoảng cách của chúng từ tâm mặt nạ. Các điểm ảnh xa trung tâm hơn có ít ảnh hưởng hơn đến mức trung bình có trọng số. Để thực hiện bộ lọc Gaussian thì sử dụng hàm GaussianBlur() với cú pháp sau:

GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])

Hàm GaussianBlur() yêu cầu bốn đối số đầu vào:

– Đối số đầu tiên chỉ định hình ảnh gốc muốn lọc.

– Đối số thứ hai là ksize, xác định kích thước của mặt nạ Gaussian. Trong đoạn code ví dụ đang sử dụng kích thước 5 × 5.

– Hai đối số cuối cùng là sigmaX và sigmaY, cả hai đều được đặt là 0. Đây là độ lệch chuẩn của mặt nạ Gaussian, theo hướng X (ngang) và Y (dọc). Mặc định giá trí của sigmaY là 0. Nếu đặt sigmaX bằng 0, thì độ lệch chuẩn được tính từ kích thước mặt nạ (chiều rộng và chiều cao tương ứng). Các đối số này cũng là các giá trị dương khác lớn hơn 0.

Python

"""
Apply Gaussian blur
"""
# sigmaX is Gaussian Kernel standard deviation 
# ksize is kernel size
gaussian_blur = cv2.GaussianBlur(src=image, ksize=(5,5), \\
sigmaX=0, sigmaY=0)

cv2.imshow('Original', image)
cv2.imshow('Gaussian Blurred', gaussian_blur)
    
cv2.waitKey()
cv2.imwrite('gaussian_blur.jpg', gaussian_blur)
cv2.destroyAllWindows()

C++

// Performing Gaussian Blur
Mat gaussian_blur;
GaussianBlur(image, gaussian_blur, Size(5,5), SigmaX=0, SigmaY=0);
imshow("Original", image);
imshow("Gaussian Blurred", gaussian_blur);
imwrite("gaussian_blur.jpg", gaussian_blur);
waitKey();
destroyAllWindows();

7. Áp dụng bộ lọc trung vị (Median) để làm mờ ảnh

Trong OpenCV có hỗ trợ hàm medianBlur() có chức năng áp dụng bộ lọc trung vị để làm mờ ảnh. Đối với bộ lọc trung vị, mỗi pixel trong ảnh gốc được thay thế bằng giá trị trung bình của các pixel ảnh trong vùng mặt nạ. Cú pháp của hàm medianBlur():

medianBlur(src, ksize)

Hàm có 2 đối số:

– Đối số đầu tiên là hình ảnh gốc          

– Đối số thứ 2 là kích thước mặt nạ, phải là số nguyên dương và lẻ

Python

"""
Apply Median blur
"""
# medianBlur() is used to apply Median blur to image
# ksize is the kernel size
median = cv2.medianBlur(src=image, ksize=5)

cv2.imshow('Original', image)
cv2.imshow('Median Blurred', median)
    
cv2.waitKey()
cv2.imwrite('median_blur.jpg', median)
cv2.destroyAllWindows()

C++

// Apply Median Blur
Mat median_blurred;
medianBlur(image, median_blurred, (5,5));
imshow("Original", image);
imshow("Median Blurred", median_blurred);
imwrite("median_blur.jpg", median_blurred);
waitKey();
destroyAllWindows();

Xem kết quả làm mờ sử dụng bộ lọc trung vị thể hiện ở hình bên dưới. Có thể thấy rằng đối với cùng một kích thước mặt nạ, hiệu quả làm mờ của bộ lọc trung vị nổi bật hơn bộ lọc Gaussian. Bộ lọc trung vị thường được sử dụng để giảm nhiễu dạng ‘salt and pepper’ trong ảnh.

8. Tăng độ nét (sharpening) của ảnh bằng mặt nạ tích chập 2D tùy biến

Mặt nạ tích chập 2D cũng có thể được dùng làm sắc nét hình ảnh. Đầu tiên xác định một mặt nạ 2D tùy chỉnh, sau đó sử dụng hàm filter2D() để áp dụng phép toán tích chập cho hình ảnh. Đoạn code mẫu ở dưới xây dựng một mặt nạ 3 × 3 để làm sắc nét ảnh.

Python

"""
Apply sharpening using kernel
"""
kernel3 = np.array([[0, -1,  0],
                   [-1,  5, -1],
                    [0, -1,  0]])
sharp_img = cv2.filter2D(src=image, ddepth=-1, kernel=kernel3)

cv2.imshow('Original', image)
cv2.imshow('Sharpened', sharp_img)
    
cv2.waitKey()
cv2.imwrite('sharp_image.jpg', sharp_img)
cv2.destroyAllWindows()

C++

// Apply sharpening using kernel
Mat sharp_img;
Mat kernel3 = (Mat_<double>(3,3) << 0, -1,  0, 
                                   -1,  5, -1, 
                                    0, -1, 0);
filter2D(image, sharp_img, -1 , kernel3, Point(-1, -1), 0, BORDER_DEFAULT);
imshow("Original", image);
imshow("Sharpenned", sharp_img);
imwrite("sharp_image.jpg", sharp_img);
waitKey();
destroyAllWindows();

Kết quả được thể hiện ở hình bên dưới. Hình ảnh được làm sắc nét bên phải cho thấy những vết nứt trên gỗ mà trước đây không thể nhìn thấy được.

9. Áp dụng lọc song phương (Bilateral Filtering) trong OpenCV

Mặc dù làm mờ cũng được xem là một cách hiệu quả để giảm nhiễu trong hình ảnh, nhưng thường không nên làm mờ toàn bộ hình ảnh, vì các chi tiết quan trọng và các cạnh sắc nét có thể bị mất. Trong những trường hợp như vậy, bộ lọc song phương có thể hiệu quả hơn.

– Phương pháp này lọc một cách có lựa chọn để làm mờ các pixel có cường độ tương tự trong một vùng lân cận. Các cạnh được giữ nguyên.

– Phương pháp này cho phép kiểm soát không chỉ kích thước không gian của bộ lọc mà còn cả mức độ bao gồm các pixel lân cận trong đầu ra được lọc. Điều này được thực hiện, dựa trên sự thay đổi về cường độ màu của chúng và cũng như khoảng cách từ pixel được lọc.

Lọc song phương về cơ bản áp dụng phương pháp làm mờ Gaussian 2D (có trọng số) cho hình ảnh, đồng thời xem xét sự thay đổi về cường độ của các pixel lân cận để giảm thiểu việc làm mờ gần các cạnh (mà muốn giữ nguyên). Điều này có nghĩa là dạng của mặt nạ phụ thuộc vào nội dung hình ảnh cục bộ, tại mọi vị trí pixel.

Ví dụ chúng ta đang cần lọc một vùng trong hình ảnh, gần một cạnh. Bộ lọc làm mờ Gaussian đơn giản sẽ làm mờ cạnh vì nó nằm gần vùng được lọc (gần tâm của bộ lọc Gaussian). Nhưng bộ lọc song phương có thể phân biệt được cạnh, bởi vì nó cũng xem xét sự khác biệt về cường độ pixel. Vì vậy, nó sẽ tính toán trọng lượng thấp hơn nhiều cho các pixel nằm dọc theo cạnh, do đó giảm ảnh hưởng của chúng lên vùng được lọc. Các vùng có cường độ đồng đều hơn sẽ bị mờ nặng hơn, vì chúng không liên kết mạnh với các cạnh.

Trong OpenCV, hàm bilateralFilter() được sử dụng để thực thi bộ lọc song phương. Hàm có cú pháp như sau:

bilateralFilter(src, d, sigmaColor, sigmaSpace)

Hàm có 4 đối số:

– Đối số đầu tiên của hàm là ảnh gốc.

– Đối số tiếp theo d, xác định đường kính của vùng lân cận pixel được sử dụng để lọc.

– Hai đối số tiếp theo, sigmaColor và sigmaSpace xác định độ lệch chuẩn của phân bố cường độ màu (1D) và phân bố không gian (2D) tương ứng.

+ Tham số sigmaSpace xác định phạm vi không gian của mặt nạ, theo cả hướng x và y (giống như bộ lọc mờ Gaussian).

+ Tham số sigmaColor xác định phân phối Gaussian một chiều, chỉ định mức độ mà sự khác biệt về cường độ pixel có thể được chấp nhận.

Giá trị cuối cùng (có trọng số) cho một pixel trong hình ảnh kết quả phụ thuộc trọng số không gian và cường độ của nó. Vì vậy,

– Các pixel tương tự và gần pixel được lọc sẽ có ảnh hưởng

– Pixel ở xa pixel được lọc sẽ có ít ảnh hưởng (do không gian Gaussian)

– Các pixel có cường độ khác nhau sẽ có ít ảnh hưởng (do cường độ màu Gaussian), ngay cả khi chúng ở gần tâm của mặt nạ

Python

"""
Apply Bilateral Filtering
"""
# Using the function bilateralFilter() where d is diameter of each...
# ...pixel neighborhood that is used during filtering.
# sigmaColor is used to filter sigma in the color space.
# sigmaSpace is used to filter sigma in the coordinate space.
bilateral_filter = cv2.bilateralFilter(src=image, d=9, sigmaColor=75, sigmaSpace=75)

cv2.imshow('Original', image)
cv2.imshow('Bilateral Filtering', bilateral_filter)

cv2.waitKey(0)
cv2.imwrite('bilateral_filtering.jpg', bilateral_filter)
cv2.destroyAllWindows()

C++

// Apply bilateral filtering
Mat bilateral_filter;
bilateralFilter(image, bilateral_filter, 9, 75, 75);
imshow("Original", image);
imshow("Bilateral filtering", bilateral_filter);
imwrite("bilateral_filtering.jpg", bilateral_filter);
waitKey(0);
destroyAllWindows();
return 0;

Kết quả bộ lọc song phương được thể hiện ở hình dưới đây. Các vùng có cường độ điểm ảnh đồng đều hơn đã được làm mịn (làm mờ), trong khi vẫn giữ được các vết nứt nhỏ (các cạnh) trên gỗ. Lọc song phương là một kỹ thuật rất hiệu quả, nhưng có nhược điểm là tốn kém về mặt tính toán (đặc biệt là đối với kích thước mặt nạ lớn). Vì vậy, tùy thuộc vào ứng dụng cụ thể mà lựa chọn bộ lọc cho phù hợp.

Kết luận

Bài viết đã giới thiệu về tích chập và cách sử dụng để lọc hình ảnh như làm mờ hoặc làm sắc nét hình ảnh. Ngoài ra trình bày cách sử dụng hàm filter2D() trong OpenCV để thực hiện lọc với các mặt nạ tích chập 2D tùy biến. Sau đó đã giới thiệu một số bộ lọc quan trọng được xây dựng sẵn trong OpenCV bộ lọc Gaussian, bộ lọc trung vị và bộ lọc song phương. Các đoạn code mẫu cùng với hình ảnh kết quả đã làm rõ hơn hoạt động của các bộ lọc này.

Biên dịch: Thảo Nguyễn

Để cập nhật tin tức công nghệ mới nhất và các sản phẩm của công ty AIoT JSC, vui lòng truy cập link: http://aiots.vn hoặc linhkienaiot.com

5 1 Bỏ phiếu
Article Rating
Subscribe
Notify of
guest
0 Comments
Phản hồi nội tuyến
Xem tất cả các bình luận