Nội dung bài viết
Đăng ký học lập trình C++
Tại STDIO bạn được dạy nền tảng lập trình tốt nhất.
Đăng ký học
Nẳm trong loạt bài viết trong chương trình Tự Học OpenCV Qua Các Ví Dụ Thực Tế. Bài viết sẽ giới thiệu khái niệm lọc số ảnh là gì?, khái niệm và công thức nhân chập ma trận, một số kỹ thuật lọc nhiễu và một số bộ lọc có sẵn trong OpenCV.

Giới thiệu

OpenCV (Open Computer Vision) là một thư viện mã nguồn mở chuyên dùng để xử lý các vấn đề liên quan đến thị giác máy tính. Nhờ một hệ thống các giải thuật chuyên biệt, tối ưu cho việc xử lý thị giác máy tính, vì vậy tính ứng dụng của OpenCV là rất lớn.

Xử lý ảnh là quá trình xử lý, thao tác hình ảnh để có một hình ảnh khác phù hợp với nhu cầu của người dùng,...

Tiền đề bài viết

Bài viết nằm trong loạt bài viết chương trình Tự Học OpenCV Qua Các Ví Dụ Thực Tế được tài trợ bởi STDIO.

Trong bài biết này sẽ giới thiệu một số bộ lọc ảnh số trong OpenCV.

Đối tượng hướng đến

Bài viết hướng tới các bạn đọc mới tìm hiểu về xử lý ảnh, đặc biệt là với OpenCV và chưa có kiến thức tương đương.

Lọc số ảnh là gì?

  • Một hệ thống dùng để làm biến dạng sự phân bố tần số của các thành phần tín hiệu theo các chỉ tiêu gọi đã cho được gọi là bộ lọc số.
  • Lọc số ảnh (Filter) có ý nghĩa quan trọng trong việc tạo ra các hiệu ứng trong ảnh, một số hiệu ứng nhờ sử dụng các bộ lọc làm mờ (Blur), làm trơn (Smooth),...

Nguyên tắc chung của các phương pháp lọc là cho ma trận ảnh nhân chập với một ma trận lọc (Kernel) hay còn được gọi là các phép tính nhân chập trên ảnh.  Ma trận lọc lọc (Kernel) còn có thể được gọi là cửa số chập, cửa sổ lọc, mặt nạ,... trong bài viết này tôi sử dụng thuật ngữ ma trận lọc (Kernel).

Idst = M*Isrc

Phân tích

  • Isrc: Là ảnh gốc được sử dụng để lọc số ảnh.
  • Idst: Là ảnh ra sau khi thực hiện xong phép lọc số ảnh.
  • M: Là ma trận lọc (Mask, kernel).

Với mỗi phép lọc ta có những ma trận lọc (Kernel) khác nhau, không có một quy định cụ thể nào cho việc xác định M. Kích thước ma trận M là một số lẻ. Ví dụ: 3x3, 5x5. 

Tổng Tpt các phẩn tử trong ma trận M thường là 1.

  • Tpt > 1: Ảnh sau khi thực hiện xong phép lọc số ảnh (Idst) có độ sáng lớn hơn so với ảnh ban đầu (Isrc). 
  • Tpt < 1: Ảnh sau khi thực hiện xong phép lọc số ảnh (Idst) có độ sáng nhỏ hơn so với ảnh ban đầu (Isrc).

Ví dụ

ss_1

Nhân chập ảnh với phép lọc số ảnh

Để thực hiện một phép lọc số ảnh, ta cần phải tiến ảnh nhân chập ảnh đầu vào với một ma trận lọc hay cửa sổ nhân chập (Kernel). Toàn bộ các điểm ảnh (Pixel) trên ảnh sẽ được tiến hành nhân chập với ma trận lọc, tâm của ma trận lọc sẽ được đặt trùng vào vị trí của điểm ảnh (Pixel) đang được tính nhân chập làm thay đổi các giá trị của pixel ban đầu.

Công thức tính nhân chập:

​​ ss_2

  • n = (kích thước ma trận lọc - 1)/2.
  • Tôi lấy tâm của ma trận lọc (Kernel) làm điểm gốc.

Ví dụ

ss_3

Tôi có một ma trận điểm ảnh I và ma trận lọc (Kernel) M:

  • n = (3-1)/2 = 1.
  • Idst(2,2) = Isrc(2,2)*M(u,v) = Isrc(1,1)*M(-1,-1) + Isrc(1,2)*M(-1,0) + Isrc(1,3)*M(-1,1) + Isrc(2,1)*M(0,-1) +  Isrc(2,2)*M(0,0) + Isrc(2,3)*M(0,1) + Isrc(3,1)*M(1,-1) + Isrc(3,2)*M(1,0) +  Isrc(3,3)*M(1,1) = 2*1 + 4*2 + 3*3 + 5*4 + 7*5 + 2*6 + 7*7 + 6*8 + 2*9 = 201.

Một số kỹ thuật lọc nhiễu

Lọc trung bình

Ý tưởng

Với lọc trung bình, mỗi điểm ảnh (Pixel) được thay thế bằng trung bình trọng số của các điểm trong vùng lân cận. Tôi giả sử rằng tôi có một ma trận lọc (Kernel) (3x3) quét qua từng điểm ảnh của ảnh đầu vào Isrc. Tại vị trí mỗi điểm ảnh lấy giá trị của các điểm ảnh tương ứng trong vùng (3x3) của ảnh gốc đặt vào ma trận lọc (Kernel). Giá trị điểm ảnh của ảnh đầu ra Idst là giá trị trung bình của tất cả các điểm trong ảnh trong ma trận lọc (Kernel).

Thuật toán

Tôi có một một ảnh đầu vào với I(x,y) là giá trị điểm ảnh tại một điểm (x,y) và một ngưỡng θ.

  • Bước 1: Tính tổng các thành phần trong ma trận lọc (Kernel).
  • Bước 2: Chia lấy trung bình của tổng các thành phần trong ma trận được tính ở trên với số lượng các phần tử của cửa sổ lọc ra một giá trị Itb(x, y).
  • Bước 3: Hiệu chỉnh:
    -Nếu I(x,y) - Itb(x,y) > θ thì I(x,y) = Itb(x,y).
    -Nếu I(x,y) - Itb(x,y) <= θ thì I(x,y)=I(x,y).

Chú ý

  • θ là một giá trị cho trước và có thể có hoặc không tùy thuộc vào mục đích của bạn.

Tác dụng

Trong lọc trung bình, thường người ta ưu tiên cho các hướng để bảo vệ biên của ảnh khỏi bị mờ khi làm trơn ảnh. Các kiểu ma trận lọc (Kernel) được sử dụng tùy theo các trường hợp khác nhau. Các bộ lọc trên là bộ lọc tuyến tính theo nghĩa là điểm ảnh ở tâm cửa sổ sẽ được thay bởi tổ hợp các điểm lân cận chập với ma trận lọc (Kernel). 

Ví dụ

Tôi có ma trận lọc (Kernel) M ở nội dung phần Ví dụ: Lọc số ảnh là gì? như ở trên và phía dưới lần lượt là ma trận điểm ảnh đầu vào (Isrc) và ma trận điểm ảnh đầu ra (Idst).

ss_4

Idst(3,3) = (7+2+1+6+2+8+6+7+7) / 9 = 46 / 9 = 5. 

Chú ý

  • Các hệ số trong ma trận lọc (Kernel) này có tổng bằng 1, nên độ sáng ảnh giữ nguyên, và các hệ số đều dương nên nó có khuynh hướng làm nhoè ảnh.

Lọc trung vị

Ý tưởng

Lọc trung vị là lọc phi tuyến. Một phép lọc phi tuyến là một kết quả không thể thu được từ một tổng trọng số của các điểm ảnh (Pixel) lân cận.  Sau khi đã định nghĩa kích thước vùng lân cận, giá trị điểm ảnh (Pixel) trung tâm được thay bằng trung vị tức là giá trị chính giữa của tất cả các giá trị của các điểm trong vùng lân cận.

Cho một dãy số X1, X2, X3, ..., Xn được sắp xếp theo thứ tự tăng dần hoặc giảm dần. Khi đó Xtv được tính bởi công thức:

Nếu n lẻ:

ss_5

Nếu n chẵn:

ss_6

Kích thước của ma trận lọc (Kernel) thường dùng có kích thước 3x3, 5x5, 7x7.

Thuật toán

  • Bước 1: Xác định điểm ảnh (Pixel) I(x,y).
  • Bước 2: Sắp xếp các điểm ảnh (Pixel) lân cận của theo một thứ tự nhất định (Tăng dần hoặc giảm dần).
  • Bước 3: Xác định Itv (Chính là Xtv trong nội dung phía trên).
  • Bước 4: Hiệu chỉnh:
    -Nếu I(x,y) - Itb(x,y) >  θ thì I(x,y) = Itb(x,y).
    -Nếu I(x,y) - Itb(x,y) <=  θ thì I(x,y) = I(x,y).

Chú ý

θ là một giá trị cho trước và có thể có hoặc không tùy thuộc vào mục đích của bạn.

Tác dụng

  • Loại bỏ các điểm ảnh mà vẫn đảm bảo độ phân giải.
  • Hiệu quả trong việc giảm đi điểm nhiễu trong ma trận lọc (Kernel) lớn hay bằng một nửa số điểm trong ma trận lọc (Kernel).

Ví dụ

ss_7

Idst(1,1)= (2, 2, 2, 2, 2, 2, 4, 7, 5) = 2.

Idst(3,3) = (1, 2, 2, 6, 6, 7, 7, 7, 8) = 6.

Chú ý

  • Idst(1,1), Idst(1,5), Idst(5,1), Idst(5,5) : Bạn hoàn toàn có thể làm như cách trên hoặc không cần kiểm tra điểm biên.

Lọc thông thấp

Là một bộ lọc tuyến tín, thường được sử dụng để làm trơn nhiễu. Trong kỹ thuật lọc nhiễu ảnh này, bạn cần sử dụng một số ma trận lọc (Kernel) như sau:

ss_8

Hoặc

ss_9

Lọc thông cao

Là một bộ lọc phi tuyến tính. Thường dùng trong việc làm trơn ảnh và tìm biên đối tượng có ở trong ảnh. Một số ma trận lọc (Kernel) dùng trong lọc thông cao:

ss_10

Chú ý

  • Trong lọc thông cao, các ma trận lọc (Kernel) có tổng hệ số các giá trị của bộ lọc bằng 1.

Một số bộ lọc trong OpenCV

Blur

Là một phép lọc làm cho trơn ảnh và khử nhiễu hạt và là một bộ lọc trung bình. Ma trận lọc (Kernel) của bộ lọc Blur có dạng:

ss_11

Trong OpenCV để sử dụng Blur cho một hình ảnh, bạn sử dụng hàm sau:

cv::blur(cv::InputArray src, cv::InputArray dst, cv::Size ksize,
          cv::Point anchor = cv::Point(-1,-1), int borderType = 4)

Phân tích

  • src: Là ảnh gốc.
  • dst: Là ảnh sau khi thực hiện phép lọc số ảnh.
  • ksize: Là kích thước của ma trận lọc.
  • anchor: Là Anchor Point của ma trận lọc. Giá trị mặc định là (-1,-1).
  • borderType: Là phương pháp để ước lượng và căn chỉnh các điểm ảnh nếu phép lọc chúng vượt ra khỏi giới hạn của ảnh. Giá trị mặc định là 4.

Ví dụ

cv::blur(imageSrc, imageDst, 3);

ss_12

Sobel

Kỹ thuật sử dụng 2 ma trận lọc (Kernel) xấp xỉ đạo hàm theo hướng x và y:

ss_13

Là một phép lọc giúp tìm đường biên cho ảnh. Trong OpenCV để sử dụng Sobel cho một hình ảnh, bạn sử dụng hàm sau:

cv::Sobel(cv::InputArray src, cv::OutputArray dst, int ddepth, int dx,
          int dy, int ksize = 3, double scale = (1,0) , double delta = (0,0),
          int borderType = 4);

Phân tích

  • src: Là ảnh gốc.
  • dst: Là ảnh sau khi thực hiện phép lọc số ảnh.
  • ksize: Là kích thước của ma trận lọc. Giá trị mặc định là 3.
  • ddepth: Làđộ sâu của ảnh sau phép lọc: VD: CV_32F, CV_64F,...
  • dx: Là đạo hàm theo hướng x.
  • dy: Là đạo hàm theo hướng y.
    Để đạo hàm theo hướng nào thì ta đặt giá trị đó lên 1.
  • scale delta: Là 2 thông số tùy chọn cho việc tính giá trị đạo hàm lưu giá trị vi sai vào ảnh sau phép lọc. Mặc định là 1 và 0.
  • borderType: Là phương pháp để ước lượng và căn chỉnh các điểm ảnh nếu phép lọc chúng vượt ra khỏi giới hạn của ảnh. giá trị mặc định là 4.

Ví dụ

cv::Sobel(imageSrc, imageDst, imageSrc.depth(), 1, 0, 3);

ss_14

cv::Sobel(imageSrc, imageDst, imageSrc.depth(), 0, 1, 3);

ss_15

cv::Sobel(imageSrc, imageDst, imageSrc.depth(), 1, 1, 3);

ss_16

Laplace

Là một phép lọc giúp tìm đường biên cục bộ cho ảnh. Tư tưởng là lấy đạo hàm bậc hai của các điểm. Ma trận lọc (Kernel) của bộ lọc Laplace có dạng:

ss_17
Trong thực tế, người ta dùng nhiều kiểu ma trận lọc (Kernel) khác nhau để xấp xỉ rời rạc đạo hàm bậc hai Laplace. 3 kiểu ma trận lọc (Kernel) thường dùng:

ss_18

Trong OpenCV để sử dụng Laplace cho một hình ảnh, bạn sử dụng hàm sau:

cv::Laplace(cv::InputArray src, cv::OutputArray dst, int ddepth, int ksize = 1,
             double scale = (1,0), double delta = (0,0), int borderType = 4);

Chú ý

  • Có một phươn pháp lọc dùng để tìm đường biên cục bộ khác là Gradient, phương pháp này làm việc khá tốt khi độ sáng thay đổi rõ nét, khi mức xám thay đổi chậm hoặc miền chuyển tiếp trải rộng thì phương pháp này tỏ ra kém hiệu quả. Vậy nên người ta sử dụng phương pháp laplace để khắc phục nhược điểm này.
  • Trong kỹ thuật lọc laplace, điểm biên được xác định bởi điểm cắt điểm không. Và điểm không là duy nhất do vậy kỹ thuật này cho đường biên rất mảnh (Rộng 1 pixel). Rất nhạy cảm với nhiễu do đạo hàm bậc 2 không ổn định.

Ví dụ

cv::Laplacian(imageSrc, imageDst, CV_64F);

ss_19

Cài đặt bộ lọc trong OpenCV

Trong nội dung ở phía trên bài viết, tôi đã giới thiệu cho các bạn một số bộ lọc có sẵn trong OpenCV. Trong OpenCV có một hàm tính chập, nhờ đó bạn có thể tự cài các bộ lọc cho riêng bạn để phù hợp với mục đích xử lý ảnh của bạn. Hàm tính chập trong OpenCV như sau:

cv::Filter2D(cv::InputArray src, cv::OutputArray dst, int ddepth
              cv::InputArray kernel, cv::Point anchor=cv::Point(-1, -1)
              double delta = (0,0), int borderType = 4);

Phân tích

  • src: Là ảnh ban đầu.
  • dst: Là ảnh sau khi nhân chập
  • kernel: Là mặt nạ nhân chập
  • anchor: Là điểm neo để đặt mặt nạ nhân chập.

Điều quan trọng nhất với các tự cài đặt các bộ lọc cho riêng bạn đó chính là ma trận lọc (Kernel). Bạn có thể dễ dàng tìm được các định ma trận lọc (Kernel). Trong các nội dung ở phần trên, các bộ lọc được cài đặt sẵn trong OpenCV, tôi có giới thiệu các ma trận lọc đi kèm cho từng phép lọc.

Ví dụ

Tôi có một đoạn code ví dụ cho việc tôi tự cài đặt một một lọc để xử lý hình ảnh.

#include "stadafx.h"
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highui\highui.hpp>

int main() {
    cv::Mat imageSrc = imread("stdio.vn", CV_LOAD_IMAGE_COLOR);
    cv::Mat imageDst;
   
    cv::Mat kernel = cv::Mat::ones(Size(3, 3), CV_32F) / (float)(9);

    cv::filter2D(imageSrc, imageDst, imageSrc.depth(), kernel,
                 Point(-1, -1), 0, BORDER_DEFAULT);

    imshow("Image Srd", imageSrc);
    imshow("Image Dst", imageDst);

    cv::waiKey(0);
    return(0);
}

Build/Run

Kết quả ví dụ ở trên chính tương tự như khi bạn sử dụng bộ lọc Blur.

ss_20

Tổng kết

Trong bài viết này, tôi đã hoàn thành việc giới thiệu về khái niệm Lọc Số Trong Ảnh.Trong OpenCV mặc dù đã xây dựng rất nhiều các xử lý ảnh có sẵn để cho lập trình viên có thể xử lý nhanh chóng. Nhưng bạn hoàn toàn có thể can thiệp vào cũng như xây dựng những phương thức xử lý ảnh riêng nhờ vào các ý tưởng chung trên.

Mọi thắc mắc có thể bình luận tại bài viết hoặc liên hệ với tác giả.

Tham khảo

https://www.wikipedia.org

http://docs.opencv.org/doc/tutorials/tutorials.html

THẢO LUẬN