Đây là bài dịch từ tutorial opencv . Để cho dễ hiểu mình không viết lại giống 100% mà theo hướng dễ hiểu nhất.
Bài này hướng dẫn cách dùng hàm erode() và dilate() để tách đường thẳng khỏi ảnh. Các bạn có thể tìm hiểu thêm cách sử dụng tại trang tutorial opencv
Lý thuyết
Hình thái học là các phép biến đổi ảnh dựa trên “phần tử có cấu trúc” do người dùng định nghĩa. (Hay còn gọi là kernel). Giá trị của pixel được tính dựa vào giá trị của nó và các giá trị lân cận. Dựa vào cách tạo kernel, bạn có thể tự xây dựng phép biến đổi sao cho phù hợp với ảnh đầu vào.
2 phép biến đổi phổ biến nhất là dilate và erode, dilate thêm pixel vào biên của đối tượng. Còn erode thì ngược lại: xóa pixel ở biên đối tượng. Số lượng pixel bị xóa hay thêm vào phụ thuộc vào kích cỡ và hình dạng của phần tử có cấu trúc mà bạn đã chọn.
Giả sử ta dùng kernel là ma trận kích thước 1×3 như sau: [1 1 1]. Cho kernel này duyệt tuần tự qua tất cả các pixel để tính toán.
Dilate
Giá trị của pixel hiện tại là giá trị lớn nhất của những pixel mà kernel bao lấy. Như hình bên dưới khi áp dụng kernel [1 1 1] thì giá trị pixel (0;1) là 1 (lấy 3 pixel theo hình dạng kernel).
Đối với ảnh xám cũng tương tự, giá trị được lấy là giá trị lớn nhất
Erode
Phương pháp này ngược lại với erode, giá trị pixel là giá trị nhỏ nhất của những pixel mà kernel bao lấy. Do đó nó sẽ xóa đi biên của đối tượng theo hình dạng kernel.
Ta có thể thấy được cùng kernel nhưng ảnh output khác nhau hoàn toàn
Kernel
Kernel là ma trận chỉ có giá trị 0 hoặc 1 do người dùng định nghĩa. Do nó phải duyệt qua toàn bộ ảnh nên sẽ nhỏ hơn ảnh. Trung tâm của kernel là điểm xác định pixel sẽ xử lý, có nghĩa là với pixel đầu tiên kernel sẽ nằm lệch ra ngoài ảnh.
Hình bên dưới là kernel hình thoi
Kernel do người dùng định nghĩa sao cho phù hợp với nhu cầu. Và để phù hợp với việc tách đường thẳng thì tạo kernel là đường thẳng.
Demo
Đây là hình ảnh khuôn nhạc sẽ dùng để minh họa
Chuyển sang ảnh xám rồi phân ngưỡng để được ảnh nhị phân
Để giữ lại các đường ngang ta dùng kernel sau
Kết quả sau khi áp dụng là tách được các đường ngang
Để loại bỏ các đường ngang ta dùng kernel sau
Đây là kết quả sau khi loại bỏ đường ngang
Source code
Copy nguyên gốc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(int, char** argv) { // Load the image Mat src = imread(argv[1]); // Check if image is loaded fine if(!src.data) cerr << "Problem loading image!!!" << endl; // Show source image imshow("src", src); // Transform source image to gray if it is not Mat gray; if (src.channels() == 3) { cvtColor(src, gray, CV_BGR2GRAY); } else { gray = src; } // Show gray image imshow("gray", gray); // Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol Mat bw; adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2); // Show binary image imshow("binary", bw); // Create the images that will use to extract the horizontal and vertical lines Mat horizontal = bw.clone(); Mat vertical = bw.clone(); // Specify size on horizontal axis int horizontalsize = horizontal.cols / 30; // Create structure element for extracting horizontal lines through morphology operations Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1)); // Apply morphology operations erode(horizontal, horizontal, horizontalStructure, Point(-1, -1)); dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1)); // Show extracted horizontal lines imshow("horizontal", horizontal); // Specify size on vertical axis int verticalsize = vertical.rows / 30; // Create structure element for extracting vertical lines through morphology operations Mat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize)); // Apply morphology operations erode(vertical, vertical, verticalStructure, Point(-1, -1)); dilate(vertical, vertical, verticalStructure, Point(-1, -1)); // Show extracted vertical lines imshow("vertical", vertical); // Inverse vertical image bitwise_not(vertical, vertical); imshow("vertical_bit", vertical); // Extract edges and smooth image according to the logic // 1. extract edges // 2. dilate(edges) // 3. src.copyTo(smooth) // 4. blur smooth img // 5. smooth.copyTo(src, edges) // Step 1 Mat edges; adaptiveThreshold(vertical, edges, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2); imshow("edges", edges); // Step 2 Mat kernel = Mat::ones(2, 2, CV_8UC1); dilate(edges, edges, kernel); imshow("dilate", edges); // Step 3 Mat smooth; vertical.copyTo(smooth); // Step 4 blur(smooth, smooth, Size(2, 2)); // Step 5 smooth.copyTo(vertical, edges); // Show final result imshow("smooth", vertical); waitKey(0); return 0; } |