Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV C/C++

Hàm này sẽ mở ra một dialog để người dung đặt các giá trị cho chương trì fps, output_folder (bạn đọc có thể để thêm vào một số thông số như điều c sang cho ảnh trong quá trình xử lý chẳng hạn) Kết quả sau phép cài được ghi vào file config và được sử dụng cho các lần chạy chương trình ti ếp

pdf103 trang | Chia sẻ: tuanhd28 | Lượt xem: 3862 | Lượt tải: 1download
Bạn đang xem trước 20 trang tài liệu Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV C/C++, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
e); } else { m_file->EnableMenuItem(ID_FILE_SAVE1, MF_ENABLED); m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_ENABLED); } if(is_gray) Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 73 { m_image->EnableMenuItem(ID_MODE_HSV, MF_DISABLED|MF_GRAYED); m_image->EnableMenuItem(ID_MODE_YCrCb, MF_DISABLED|MF_GRAYED); } } Hàm EnableMenuItem() là hàm cho phép một menu ẩn hoặc hiển thị thông qua tham số MF_DISABLED hoặc MF_ENABLED Hình sau là kết quả của quá trình xử lý khi ta điều chỉnh độ sáng của một ảnh. 2. Nhận dạng biển số xe Bài toán nhận dạng biển số xe có nhiều ý nghĩa trong thực tế, nó giúp việc giám sát, quản lý, thống kê các phương tiện một cách dễ dàng, tiện lợi và nhanh chóng. Một số ứng dụng điển hình đã đư ợc triển khai trong thực tế như ứng dụng trong quản lý bãi đỗ xe thông minh, ứng dụng thu phí ở các trạm thu phí, ứng dụng phát hiện lỗi vi phạm giao thông một cách tự động Trong phần này ta nghiên cứu về mặt kĩ thuật của một bài toán nhận dạng biển số xe, viết chương trình demo và các bư ớc cần thiết để đưa bài toán vào ứng dụng thực tế. Giả sử ta đang xây dựng bài toán nhận dạng biển số xe ô tô, với đầu vào là một ảnh chứa biển số xe và đầu ra là một chuỗi kí tự của biển số đã được nhận dạng. Nếu quan sát bằng mắt người ta có thể dễ dàng nhận biết được một biển số, tuy nhiên với máy tính, đó là một điều không dễ dàng gì, nó rất dễ bị nhiễu, bị nhầm bởi các hình khối xung quanh tương Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 74 tự, bởi các điều kiện thời tiết, góc độ Để hạn chế bớt được những khó khăn này, nhiều hệ thống nhận dạng trong thực tế thường giới hạn các điều kiện, chẳng hặn như camera thu ảnh được cố định ở một vị trí, xe được đi vào một khe hẹp nhất định Có nhiều các khác nhau để thực hiện bài toán nhận dạng này, ở các phần mềm thương mại hoàn chỉnh, nó là sự kết hợp và tối ưu của khá nhiều thuật toán phức tạp, trong bài này, ta đi theo hướng tiếp cận chia bài toán thành hai bài toán nhỏ: phát hiện biển số xe, cách ly kí tự và nhận dạng các kí tự. Phát hiện vùng chứa biển số xe và cách ly kí tự. Vì biển số xe có những đặc trưng cơ bản được quy định bởi các cơ quan chức năng nên ta có thể dựa vào đặc trưng này để phân biệt với các đối tượng khác. Theo quy định của bộ công an, biển số xe đằng trước của các loại xe dân dụng là một hình chữ nhật, có kích thước 470x110 (mm), phông nền màu trắng và các kí tự chữ cái in hoa màu đen. Các kí tự chữ số bao gồm từ 0 tới 9 và các kí tự chữ số bao gồm A, B, C, D, E, F, G, H, K, L ,M , N, P, S, T, U, V, X, Y, Z (20 kí tự). Ta sẽ dựa vào các đặc trưng hình học này để trích chọn ra vùng chứa biển số xe, các bước thực hiện như sau: Bước 1: Load ảnh, tiền xử lý ảnh (khử nhiễu, làm mịm ) Bước 2: Chuyển ảnh ban đầu thành ảnh xám rồi nhị phân hóa ảnh đó. Để ảnh nhị phân thu được kết quả tốt và không bị phụ thuộc vào các điều kiện ánh sáng khác nhau, ta sử dụng phương pháp nhị phân hóa với ngưỡng động (adaptive threshold) như đã nói ở trên. Bước 3: Tìm các đường bao quanh đối tượng. Sau khi nhị phân ảnh, các đối tượng sẽ là các khối hình đen trên nền trắng, với mỗi đối tượng ta luôn vẽ được một đường biên Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 75 bao quanh đối tượng đó. Để tránh trình trạng các đối tượng không tạo ra được các đường biên khép kín do các vết dạn nứt hoặc nhiễu gây ra, trước khi tìm biên ta nên làm mịm đường biên bằng các phép giãn nở (hoặc co) như đã nói trong bài trước. Bước 4: Xác định hình chữ nhật bao quanh các đường bao quanh đã tìm được từ bước 3. Đường bao quanh đã tìm được ở bước 3 là một chuỗi các điểm biên nối liền của đối tượng, dựa vào tọa độ của các điểm biên này ta sẽ xác định được hình chữ nhật ngoại tiếp bao quanh đối tượng. Bước 5: Tìm ra các hình chữ nhật có khả năng là vùng chứa biển số, nếu hình chữ nhật thu được ở bước 4 là vùng chứa biển số thì nó phải thỏa mãn ít nhất được một số điều kiện sau: o Tỉ lệ chiều dài/ rộng phải xấp xĩ 4.3. Trong cài đặt thực tế ta có thể cho tỉ lệ này dao động trong khoảng [4.0, 4.5]. o Số đối tượng thỏa mãn là một kí tự biển số trong vùng hình chữ nhật phải là một số lớn hơn một ngưỡng nào đó. Với biển số xe 4 chữ số, số kí tự này là 7, với biển số xe mới với 5 kí tự số, số kí tự này là 8, cũng có một số biển số xe có nhiều hơn 8 kí tự do có 2 kí tự chữ cái Để xác định một đối tượng là kí tự hay không, ta cũng sẽ dựa vào đặc điểm hình học của kí tự đó như tỉ lệ chiều dài/rộng đối tượng, tỉ lệ chiều cao, dài của đối tượng so với tỉ lệ chiều cao, dài của hình chữ nhật đang được xem xét là biển số, tỉ lệ pixel đen/trắng của dối tượng Nếu xác định đó là một kí tự của biển số xe ta cũng sẽ đồng thời lưu nó lại, đó chính là cách ta cách ly đối tượng trong biển số. o Ngoài ra, tùy thuộc vào điều kiện của bài toán ta có thể cố định thêm một số đặc tính để chắc chắn hơn vùng chứa biển số, chẳng hạn như kích thước của hình chữ nhật không được vượt quá một nửa kích thước của ảnh đầu vào, tỉ lệ pixel đen/trắng trong hình chữ nhật phải nằm trong một ngưỡng nào đó Sau khi đã xác định được vùng có khả năng là biển số, ta sẽ cắt vùng hình đó, đồng thời lấy ra các kí tự trong vùng đó lưu vào một mảng để thực hiện việc nhận dạng. Nhận dạng kí tự Các kí tự sau khi được cách ly là những kí tự đơn lẽ trong một khung hình chữ nhật có kích thước nhất định. Để nhận dạng được kí tự này ta có thể sử dụng rất nhiều phương pháp khác nhau, từ đơn giản như phương pháp sử dụng độ tương quan chéo (cross correlation) cho đến những phương pháp sử dụng các mô hình máy học (machine learning) như mạng Neuron nhân tạo, SVM Đối với các phương pháp sử dụng máy học, ta cần sưu tầm một lượng mẫu các kí tự nhất định, từ vài trăm cho tới hàng nghìn mẫu rồi đưa vào các bộ huấn luyện, kết quả huấn Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 76 luyện này sẽ được dùng để nhận dạng các mẫu mới. Độ chính xác của kết quả nhận dạng nói chung của phương pháp này tùy thuộc vào độ phức tạp của mô hình, khối lượng mẫu huấn luyện, thời gian tính toán. Trong phần này ta sử dụng phương pháp SVM để nhận dạng kí tự. Phương pháp SVM. (Chi tiết về phương pháp này bạn đọc có thể tham khảo trong các tài liệu chuyên ngành khác). SVM (Surport Vector Machine) là một mô hình máy học giám sát được dùng trong việc phân tích, phân lớp dữ liệu dựa vào các siêu phẳng. Giả sử ta có một tập dữ liệu hai chiều như hình bên, khi đó ta có thể phân lớp dữ liệu này thành hai phần nhờ một siêu. Siêu phẳng trong mặt phẳng là một đường thằng, trong không gian 3 chiều là một mặt phẳng và tổng quát trong không gian n chiều là một không gian n-1 chiều. Trong trường hợp dữ dữ liệu là không tuyến tính, ta cần ánh xạ tập dữ liệu đó lên một không gian có số chiều lớn hơn để thuận tiện cho việc phân loại dữ liệu, nhiệm vụ là cần phải tìm siêu phẳng sao cho khoảng cách tới các biên của dữ liệu là lớn nhất. Hiểu một cách đơn giản về phương pháp này như sau: cho một tập các mẫu huấn luyện, với mỗi mẫu được gắn vào một nhãn, quá trình huấn luyện SVM sẽ xây dựng một mô hình cho phép dự đoán một tập dữ diệu khác thuộc về nhãn nào, tức phân loại tập dữ liệu đó thuộc vào lớp nào. Quay lại bài toán nhận dạng kí tự biển số xe ta đang xét, làm sao để nhận dạng được các kí tự này dựa trên mô hình SVM? Trước hết cần phải nhận thấy rằng SVM là một bộ máy phân loại dữ liệu, muốn sử dụng được nó ta cần phải có dữ liệu, dữ liệu đối với các kí tự mà ta cần nhận dạng ở đây chính là các đặc trưng trong ảnh của kí tự đó. Giả sử ta cần phân loại 30 lớp dữ liệu (tương ứng với 30 kí tự trong biển số xe), với mỗi lớp dữ liệu, ta tính toán được 10 vector đặc trưng (10 mẫu), và mỗi vector đặc trưng tưng ứng với các đặc trưng trong một ảnh. Khi đó ta sẽ đưa vào bộ huấn luyện SVM toàn bộ dữ liệu này, sau đó với một ảnh bất kì, ta sẽ tính toán một vector đặc trưng của ảnh đó, mô hình SVM sẽ xem xét xem dữ liệu này (tức vector đặc trưng này) thuộc vào lớp nào trong số những lớp mà nó đã được huấn luyện. Tính toán đặc trưng trong ảnh. Đặc trưng trong ảnh là những đặc điểm riêng biệt giúp phân biệt ảnh này với ảnh khác. Việc xem xét như thế nào là các đặc trưng trong ảnh là một việc không có quy ước trước, nó phụ thuộc vào cách nghiên cứu, cài đặt của từng trường hợp cụ thể và vẫn đang được nghiên cứu để đưa ra những phương pháp tốt nhất. Trong phần này ta sẽ tính toán các vector đặc trưng dựa trên ý tưởng của phương pháp Haar, chú ý rằng bạn đọc có áp dụng phương pháp tính toán đặc trưng trong ảnh hay hơn, hiệu quả hơn. Giả sử ta có hai kí tự như hình bên, nhìn bằng mắt thường ta có thể dễ dàng phân biệt được hai kí tự 0 và 4, tuy nhiên làm sao để máy tính phân biệt được hai kí tự này? Bây giờ Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 77 ta sẽ đưa hai kí tự này về cùng một kích thước, chia nhỏ các kí tự thành 16 ô nhỏ khác nhau như sau Ta nhận thấy rằng nếu tính tổng các pixel đen trong các ô của hai bức ảnh thì số 0 và số 4 có thể phân biệt dựa vào các ô (1,1), (1, 4), (2, 2), (3,3) tại những ô đó, tổng số các điểm ảnh đen là khác nhau hoàn toàn. Tính toán số điểm ảnh đen của 16 ô vuông này ta có thể thu được 16 đặc trưng của một ảnh, 16 đặc trưng này đủ để phân biệt kí tự 0 và 4. Tuy nhiên, với 30 kí tự ta cần phải tính toán nhiều hơn các đặc trưng, các đặc trưng không nhất thiết phải là 0 (tức không có điểm ảnh đen nào) hặc 1(tức là toàn số điểm ảnh đen trong ô) mà có thể là một tỉ lệ tương đối nào đó. Từ 16 đặc trưng cơ bản trên, ta kết hợp chúng lại để tạo ra những đặc trưng khác, chẳng hạn như lấy tổng các đặc trưng trên đường chéo (1,1) + (2,2) + (3,3) + (4,4) hoặc tổng các đặc trưng xung quanh đường biên của ảnh số đặc trưng càng lớn thì việc phân loại các lớp càng ít bị sai, có nghĩa là xác suất nhận dạng càng lớn. Cài đặt chương trình Nhận dạng biển số xe ô tô Ta tạo một chương trình dựa trên Dialog của MFC, đặt tên project là LPR (License Plate Recognition). Về cơ bản có thể chia chương trình thành 3 phần chính như sau: phần chứa giao diện chính của chương trình (được định nghĩa trong file LPRDlg.h và LPRDlg.cpp), phần chứa các hàm chính cho việc phát hiện, nhận dạng biển số xe và không liên quan tới giao diện (được định nghĩa trong file LprCore.h và LprCore.cpp) và phần chứa một công cụ giúp cho ta có thể huấn luyện được mô hình SVM một cách tùy ý (được định nghĩa trong file TrainSVM.h và TrainSVM.cpp). Ngoài ra còn có một số phần nhỏ giúp cho việc lập trình được dễ dàng hơn như phần chuyển đổi các biến dữ liệu trong MFC chằng hạn (unity_conversion.h). Huấn luyện mô hình SVM Để tạo được mô hình SVM phục vụ cho việc nhận dạng kí tự sau này, ta cần huấn luyện và lưu mô hình sau khi huấn luyện. Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 78 Chuẩn bị cơ sở dữ liệu. Ta cần chuẩn bị cơ sở dữ liệu là tập hợp của các kí tự trong biển số xe. Có 30 kí tự thường gặp trong biển số xe, do đó ta cần phân loại 30 lớp này, trong trường hợp ở đây giả sử với mỗi lớp, tức là mỗi tự tự ta có 10 ảnh, ta sẽ lưu các ảnh này vào các folder, tên các folder được đặt tên theo các kí tự, chẳng hạn như folder 0 chứa 10 ảnh của kí tự 0, folder 1 chứa 10 ảnh của kí tự 1, folder 30 chứa 10 ảnh của kí tự Z. ta cần đánh tên folder theo số tứ tự, vì số thứ tự cũng chính là nhãn tương ứng đưa vào việc nhận dạng. Ta sẽ tính toán đặc trưng của từng kí tự và lưu tất cả các đặc trưng này vào một ma trận để phục vụ cho việc huấn luyện. Hàm tính toán đặc trưng trong một ảnh sẽ dựa trên ý tưởng tổng các điểm đen trong một khung hình, tuy nhiên nó được chuẩn hóa bằng cách chia cho tổng tất cả các điểm ảnh đen của kí tự. vector calculate_feature(Mat src) { Mat img; if(src.channels() == 3) cvtColor(src, img, CV_BGR2GRAY); threshold(img, img, 100, 255, CV_THRESH_BINARY); vector r; resize(img, img, Size(40, 40)); int h = img.rows/4; int w = img.cols/4; int S = count_pixel(img); int T = img.cols * img.rows; for(int i = 0; i < img.rows; i += h) { for(int j = 0; j < img.cols; j += w) { Mat cell = img(Rect(i,j, h , w)); int s = count_pixel(cell); float f = (float)s/S; r.push_back(f); } } for(int i = 0; i < 16; i+= 4) { float f = r[i] + r[i+1] + r[i+2] + r[i+3]; r.push_back(f); } for(int i = 0; i < 4; ++i) { float f = r[i] + r[i+4] + r[i+8] + r[i+ 12]; r.push_back(f); } r.push_back(r[0] + r[5] + r[10] + r[15]); Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 79 r.push_back(r[3] + r[6] + r[9] + r[12]); ... return r; //32 dac trung } Trong đó hàm count_pixel là hàm tính toán số pixel đen trong một ảnh int count_pixel(Mat img, bool black_pixel = true) { int black = 0; int white = 0; for(int i = 0; i < img.rows; ++i) for(int j = 0; j < img.cols; ++j) { if(img.at(i,j) == 0) black++; else white++; } if(black_pixel) return black; else return white; } Đoạn code sau sẽ huấn luyên một mô hình svm, với đầu vào là một folder chứa dữ liệu huấn luyện như đã nói ở trên (folder này chứa 30 folder con, mỗi folder con chứa 10 kí kí tự mẫu) const int number_of_class = 30; const int number_of_sample = 10; const int number_of_feature = 32; CvSVMParams params; params.svm_type = CvSVM::C_SVC; params.kernel_type = CvSVM::RBF; params.gamma = 0.5; params.C = 16; params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6); SVM svm; Mat data = Mat(number_of_sample * number_of_class, number_of_feature, CV_32FC1); Mat label = Mat(number_of_sample * number_of_class, 1, CV_32FC1); int index = 0; vector folders = list_folder("D:/Data"); for(size_t i = 0; i < folders.size(); ++i) { cout<<i<<"..."<<endl; vector files = list_file(folders.at(i)); Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 80 string folder_path = folders.at(i); string label_folder = folder_path.substr(folder_path.length() - 1); for(size_t j = 0; j < files.size(); ++j) { src = imread(files.at(j)); if(src.empty()) continue; vector feature = calculate_feature(src); if(feature.size() < number_of_feature) { cout<<"error " <<endl; continue; } for(size_t t = 0; t < feature.size(); ++t) data.at(index, t) = feature.at(t); label.at(index, 0) = i; index++; } } svm.train_auto(data, label, Mat(), Mat(), params); svm.save("E:/svm.txt"); Trong đoan chương trình trên, ta lần lượt đọc qua tất cả các folder con trong thư mục D:/Data bằng hàm list_folder (hàm này ta viết dựa trên header dirent.h, với mục đích liệt kê danh sách các folder trong một thư mục), sau đó ta tính toán tất cả các đặc trưng của các ảnh trong các folder này và lưu vào ma trận data. Ma trận label chứa nhãn của các lớp cần nhận dạng, nó cũng chính là tên của các folder tương ứng đã được đánh số thứ tự tăng dần. Hàm train_auto sẽ thực hiện việc huấn luyện một cách tự động và tối ưu các thông số của mô hình. Ở đây các thông số params ta sử dụng chỉ là một trong số nhiều mô cách mà ta có thể đặt thông số. Công cụ cài đặt trong chương trình là một lớp TrainSVM kế thừa từ lớp CDialog tạo ra nhiều tùy chỉnh giúp cho việc đưa các thông số đầu vào, đầu ra một cách dễ dàng hơn. Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 81 Phát hiện và nhận dạng biển số xe Ta cài đặt một lớp nhận phát hiện và nhận dạng biển số xe theo như các bước đã nói ở trên, và đặt tên cho lớp này LprCore. Ta sẽ cài đặt lớp này với giao diện sao cho việc sử dụng nó là dễ dàng nhất, giao diện (interface) của lớp này được định nghĩa như sau: // lprcore.h header #pragma once #include #include using namespace std; using namespace cv; class LprCore { public: LprCore(void); ~LprCore(void); void set_image(Mat); void set_svm_model(string); void do_process(); vector get_text_recognition(); vector get_plate_detection(); vector get_character_isolation(); vector > get_character(); vector get_process_time(); Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 82 void clear(); private: bool done; bool ready; SVM svm; Mat image; vector plates; vector draw_character; vector > characters; vector text_recognition; vector process_time; char character_regconition(Mat); bool plate_detection(); bool plate_recognition(); }; ở các lớp khác khi sử dụng giao diện này, chỉ việc đưa ảnh đầu vào, đưa mô hình hình svm đã được huấn luyện, gọi hàm do_process và sau đó có thể lấy ra các kết quả như biển số nhận dạng được, kết quả nhận dạng được, thời gian của các quá trình Bản chất của hàm do_process sẽ gọi hàm plate_detection và hàm plate_recognition. Nếu gọi hai hàm này thành công, có nghĩa là quá trình thực hiện đã thành công và ta gán cho biến trạng thái là true void LprCore::do_process() { if(this->plate_detection() && this->plate_recognition()) done = true; } Các hàm lấy kết quả sẽ trả về kết quả tương ứng như biển số, chuỗi kí tự nhận dạng được vector LprCore::get_text_recognition() { if(done) return this->text_recognition; } vector LprCore::get_plate_detection() { if(done) return this->plates; } Ta sẽ xét hai hàm cài đặt hàm chính của lớp đó là hàm bool plate_detection() và hàm char character_recognition(Mat). Hàm bool plate_recognition() thực chất là việc là việc gọi hàm char character_recognition(Mat) cho một chuỗi các ảnh kí tự trong biển số. Hàm bool plate_detection(),trả về kết quả true nếu phát hiện được biển số đồng thời lưu các biển số phát hiện được vào vector vector plates: Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 83 Trước hết,ta thực hiện các phép biến đổi thông thường để thu được một ảnh nhị phân có kết quả tốt, sau đó thực hiện phép tìm đường bao quanh (contour), với mỗi đường bao, ta vẽ hình chữ nhật bao quanh và xem xét các điều kiện của hình chữ nhật đó: cvtColor(image, gray, CV_BGR2GRAY); adaptiveThreshold(gray, binary, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 55, 5); findContours(binary, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); if(contours.size() <= 0) return false; for(size_t i = 0; i < contours.size(); ++i) { Rect r = boundingRect(contours.at(i)); if(r.width > image.cols/2 || r.height > image.cols/2 || r.width < 120 || r.height 4.5 || (double)r.width/r.height < 3.5) continue; ... } Nếu các hình chữ nhật bao quanh không thỏa mãn được điều kiện là biển số ta sẽ bỏ qua không xét tiếp nữa mà chuyển tới các đường bao khac. Trong trường hợp thỏa mãn, ta tiếp tục vòng lặp bằng cách tìm các hình bao quanh đối tượng trong hình chữ nhật đó thỏa mãn điều kiện của một kí tự trong biển số xe Mat sub_image = image(r); vector r_characters; for(size_t j = 0; j < sub_contours.size(); ++j) { Rect sub_r = boundingRect(sub_contours.at(j)); if(sub_r.height > r.height/2 && sub_r.width < r.width/8 && sub_r.width > 5 && r.width > 15 && sub_r.x > 5) { Mat cj = _plate(sub_r); double ratio = (double)count_pixel(cj)/(cj.cols*cj.rows); if(ratio > 0.2 && ratio < 0.7) { r_characters.push_back(sub_r); rectangle(sub_image, sub_r, Scalar(0,0,255), 2, 8, 0); } } } Trong đoạn code trên, để thỏa mãn các đường bao quanh đối tượng là một kí tự biển số, ngoài so sách tỉ lệ tương quan dài, rộng của đối tượng so với biển số ta còn so sánh tỉ lệ pixel đen trên tổng số pixel của đối tượng và ta giả sử rằng nếu là một kí tự của biển số thì Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 84 nó nằm trong khoảng 0,2 đến 0,7. Sau bước này, nếu số lượng r_characters lớn một ngưỡng nào đó (thường là > 7) thì ta sẽ công nhận nó r là một vùng chứa biển số, các kí tự cắt ra được sau đó cần phải sắp xếp lại theo thứ tự từ trái sang phải để cho nó tương ứng với thứ tự các chữ cái trong biển số trước khi áp dụng cho việc nhận dạng. Hàm char character_regconition(Mat img_character) trả về kết quả là một kí tự kiểu char với một ảnh đầu vào tương ứng. Để thực hiện việc nhận dạng, đầu tiên ta cần tính toán các đặc trưng của ảnh đầu vào img_character. Việc tính toán các đặc trưng sẽ cho ta một vector chứa các đặc trưng của ảnh đó, ta sẽ dùng hàm predict của dối tượng svm để xem xem vector đó thuộc về lớp nào. Kết quả trả về của hàm predict là một số thực tương ứng với nhãn mà ta đã huấn luyện, có 30 nhãn tương ứng với các số từ 0 – 9 và từ A – Z. Chú ý là không phải tất cả các chữ cái đều được sử dụng, do vậy sau kết quả thu được từ hàm predict cần phải được chuyển đổi sang các kí tự tương ứng. Chú ý là trước khi sử dụng hàm predict để dự đoán ta cần phải load mô hình SVM được huấn luyện từ bước trên. void LprCore::set_svm_model(string file_name) { this->svm.load(file_name.c_str()); ready = true; } Hàm cài đặt cho nhận dạng kí tự như sau: char LprCore::character_recognition(Mat img_character) { char c = '*'; // truong hop khong cho ket qua tra ve * if(img_character.empty()) return c; // neu anh rong if(!ready) return c; // neu chua load mo hinh svm vector feature = calculate_feature(img_character); Mat m = Mat(number_of_feature, 1, CV_32FC1); for(size_t i = 0; i < feature.size(); ++i) m.at(i, 0) = feature[i]; float r = this->svm.predict(m); // du doan mau moi int ri = (int)r; if(ri >= 0 && ri <= 9) c = (char)(ri + 48); //ma ascii 0 = 48 if(ri >= 10 && ri < 18) c = (char)(ri + 55); //ma accii A = 65, --> tu A-H if(ri >= 18 && ri < 22) c = (char)(ri + 55 + 2); //K-N, bo I,J if(ri == 22) c = 'P'; if(ri == 23) c = 'S'; if(ri >= 24 && ri < 27) c = (char)(ri + 60); //T-V, if(ri >= 27 && ri < 30) c = (char)(ri + 61); //X-Z Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 85 return c; } Và cuối cùng, toàn bộ kí tự trong biển số được nhận dạng qua hàm bool plate_recognition() bool LprCore::plate_recognition() { if(plates.size() <= 0 ) return false; double t = (double)cvGetTickCount(); for(size_t i = 0; i < characters.size(); ++i) { string result; for(size_t j = 0; j < characters.at(i).size(); ++j) { char cs = character_recognition(characters.at(i).at(j)); result.push_back(cs); } this->text_recognition.push_back(result); } t = (double)cvGetTickCount() - t; t = (double)t/(cvGetTickFrequency()*1000.); //convert to second process_time.push_back(t); return true; } Giao diện chương trình chính Ta thiết kế giao diện chương trình chính bao gồm các picture control để hiển thị ảnh, các button, menu cho việc điểu khiển và các label để hiển thị kết quả như sau: Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 86 Các button, label được khai báo trong trong header LPRDlg.h và các hàm xử lý sự kiện được cài đặt trong file LPRDlg.cpp // LPRDlg.h : header file #pragma once #include #include "LprCore.h" #include "afxwin.h" // CLPRDlg dialog class CLPRDlg : public CDialogEx { public: CLPRDlg(CWnd* pParent = NULL); enum { IDD = IDD_LPR_DIALOG }; ... private: cv::Mat src; cv::Mat plate; cv::Mat character; LprCore lpr; string file_name; string text_recognition; // Implementation protected: ... DECLARE_MESSAGE_MAP() public: Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 87 afx_msg void OnUpdateFileOpenimage(CCmdUI *pCmdUI); afx_msg void OnBnClickedButton1(); afx_msg void OnBnClickedButton2(); CStatic text_result; ... }; Menu Open image hoặc button Load Image có chức năng tạo ra các dilog để mở file, file được mở sẽ được đọc, lư vào biến src và sẽ là ảnh đầu vào cho chương trình. // CLPRDlg.cpp void CLPRDlg::OnUpdateFileOpenimage(CCmdUI *pCmdUI) { // Open Image ... CFileDialog dlg(TRUE, _T("*.bitmap"), NULL, ...) if(dlg.DoModal() == IDOK) { file_name = to_string(dlg.GetPathName()); src = imread(file_name); if(src.empty()) return; ... } } Button Show Result sẽ thực hiện các hàm xử lý và hiển thị kết quả khi nó được nhấn vào. // CLPRDlg.cpp void CLPRDlg::OnBnClickedButton2() { // Show results if(src.empty()) return; Mat disp_plate, disp_character; lpr.set_image(src); lpr.do_process(); vector plates = lpr.get_plate_detection(); vector characters = lpr.get_character_isolation(); vector t = lpr.get_process_time(); vector text = lpr.get_text_recognition(); if(plates.size() > 0) { plate = plates[0]; resize(plate, disp_plate, Size(280,50)); imshow("plate", disp_plate); character = characters[0]; resize(character, disp_character, Size(280,50)); imshow("character", disp_character); text_recognition = text[0]; Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 88 text_result.SetWindowTextW(to_wstring(text_recognition)); num_plate.SetWindowTextW(to_wstring((int)plates.size())); time_detection.SetWindowTextW(to_wstring(t[0]) + " ms"); ... } else { ... } } Trong đoạn code trên, ta sẽ kiểm kiểm tra xem ảnh đầu vào có rỗng không trước khi đặt nó vào đối tượng lpr để thực hiện các phép xử lý. Sau khi thực hiện các phép xử lý, ta sẽ kiểm tra xem nếu như kết quả đầu ra mà lớn hơn một biển số (plates.size() > 0) thì ta sẽ hiển thị các kết quả lên. Lưu ý là hiện tại ta mới chỉ hiển thị kết quả đầu tiên, trong trong ảnh có nhiều hơn một biển số ta có thể lần lượt hiển thị các kết quả đó một cách dễ dàng. Ngoài ra, chương trình còn có một số menu khác nhằm giúp ta lưu kết quả ảnh biển số hoặc kết quả nhận dạng được dưới dạng một chuỗi text, menu hiển thị dialog cho việc huấn luyện mô hình SVM Hình sau mô tả kết quả chạy chương trình: Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 89 Bài toán nhận dạng biển số xe này xây dựng một mô hình chung tổng quát, để ứng dụng trong thưc tế ta cần giới hạn bớt lại một số điều kiện giúp cho việc tìm kiếm biển số được chính xác hơn, thêm vào đó các mẫu huấn luyện kí tự cần phải được sưu tập nhiều hơn, các vector đặc trưng cũng phải được tính toán tỉ mỉ hơn để giúp cho kết quả nhận dạng có độ chính xác cao hơn nữa. Ngoài ra, còn nhiều khía cạnh khác liên quanh đến bài toán ứng dụng này trong thực tế như cần phải xây dựng hệ cơ sở dữ liệu để lưu trữ kết quả, so sánh kết quả, xây dựng hệ thống phần cứng, điều khiển phần cứng để điều khiển hệ thống như các hệ thống thẻ từ RFID, hệ camera, hệ động cơ cho các điều khiển cơ học Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 90 3. MyCam, một số hiệu ứng ảnh với video. Trong phần này, ta sẽ tạo một chương trình có thể thu nhận ảnh từ webcam và bằng các hàm xử lý ảnh đơn giản như trong các ví dụ trên để tạo ra những hiệu ứng video đẹp mắt, chương trình có một số chức năng chính như: thu nhận video từ webcam, các chức năng xử lý video dựa trên các hàm xử lý ảnh (làm mờ, làm méo, chuyển đổi không gian màu ) chức năng chụp hình, ghi hình, điều chỉnh một số thông số Nhưng trước hết chúng ta hãy tìm hiểu cấu trúc của một video, cách xử lý trên video và sự hỗ trợ của thư viện OpenCV để làm việc với video như thế nào. Cấu trúc của một file video. Ta thường xem một bài clip ca nhạc, một bộ phim trên máy tính, trên Youtube Chúng được gọi chung là một video. Một file video có thể có nhiều định dạng khác nhau như avi, mp4, mov , xét về cầu trúc, một file video được tạo nên từ nhiều thành phần khác nhau gọi là các track, chẳng hạn như track về video, track về audio, track về phụ đề Ta cần phải phân biệt về định dạng một file video (file extention) và bộ giải mã video (video codec). Định dạng file, chẳng hạn như avi, mp4, mov, flv là cách cấu trúc của một file trong máy tính, nó chỉ ra cách lưu trữ các dữ liệu trên ổ đĩa như thế nào, các phần tử được xắp xếp ra sao trong khi bộ giải mã (video codec) chỉ ra video được mã hóa như thế nào, có được nén hay không và cách để đọc được dữ liệu video đó . Các bộ giải mã mà ta thường thấy như H.264, XVID, MPEG, UYVY Cùng là một định dạng file nhưng có thể có nhiều codec khác nhau, chẳng hạn như file *.avi có thể được mã hóa và giải mã bởi bộ codec XVID, UYVY , điều đó lý giải một số máy tính đọc được file avi này nhưng một số máy tính khác lại không đọc được file avi đó, nguyên nhân là do máy chưa được cài bộ codec mà file avi đó chứa bộ mã mã hóa/giải mã. Tương tự, audio cũng có bộ codec riêng của nó như ACC, MP3 nếu máy thiếu video codec mà có audio codec thì trong một số trường hợp, file video chỉ chạy được phần âm thanh mà không hiển thị được hình ảnh. Xử lý video. Xử lý video là một khái niệm chung, nếu như theo đúng như cấu trúc trên của một video, khi nói đến xử lý video có nghĩa là ta đang xử lý tất cả các track của một file video bao gồm track video, track audio tuy nhiên trong khuôn khổ của việc áp dụng xử lý ảnh trong thực tế, ta chỉ làm việc với track video mà không làm việc với các track còn lại của file video. Xét một cách tổng quát, ta có thể xem những track video (hay video stream) là một chuỗi các ảnh lien tiếp nhau gọi là các frame, cách hiển thị các frame này được quy định bởi một thông số về tốc độ frame (frame rate), chính là số frame xuất hiện trong một giây và có đơn vị tính là frame/giây (frame per second hay fps). Việc xư lý video thành ra lại là việc xử lý các frame ảnh này. Tuy nhiên cần phải chú ý là các phép xử lý ảnh tĩnh không có nhu cầu khắc khe về thời gian, tuy nhiên với video khi mà mỗi giây ta sẽ phải xử lý tới hang chục ảnh (các video thông thường có tốc độ 15-30 hình/giây Track Video Track Audio Track Phụ đề Track thời gian Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 91 hoặc hơn nữa) thì ta cần phải tối ưu hóa phép xử lý tới mức có thể để video có thể chạy được trong thời gian thực (không bị giật cục, tắc hình). Thư viện OpenCV đối với xử lý video. Thư viện OpenCV chỉ hỗ trợ ta các hàm để bắt ảnh từ một thiết bị như webcam, camera , ghi các frame ảnh thành file video, còn việc làm xử lý video ta có thể sử dụng các hàm xử lý ảnh mà thư viện cung cấp để xử lý các frame video. Tuy nhiên, thư viện OpenCV là một thư viện chuyên về xử lý ảnh, nên nó chỉ hỗ trợ track video trong cấu trúc của file video nói trên, nghĩa là khi thu video, ghi video ta chỉ có phẩn hình (video stream) mà không thể nghe được phần tiếng. Một hạn chế nữa là ban đầu OpenCV chỉ hỗ trợ để làm việc trên các video avi không nén, điều đó sẽ làm cho việc kích thước của việc lưu trữ file tăng lên rất nhiều, giả sử ta cần ghi một đoạn video avi không nén trong thời gian là một phút với tốc độ là 25 hình/giây và ảnh thu từ webcam có kích thức 640x480 (ảnh màu, mỗi kênh màu của một pixel là 8 bit) khi đó kích thước của một file cần phải có là 1*60*25*640*480*3*8 = 11059200000 bit tức khoảng 1Gb. Tuy nhiên, hiện tại ta có thể làm việc được trên nhiều dạng file avi nén, file mpeg, flv miễn máy có cài các bộ codec tương ứng. Để đọc video từ một file video hoặc một thiết bị webcam, camera thư viện OpenCV cung cấp cho ta lớp cv::VideoCapture. Ví dụ ta đọc từ một file video, ta có thể khởi tạo đối tượng VideoCaputre như sau: cv::VideoCapture capture = cv::VideoCapture(“D:/test.avi”); Để đọc video từ một thiết bị: cv::VideoCapture capture = cv::VideoCapture(int device) Trong đó, device là một số nguyên chỉ ra thiết bị video trên máy, thông thường ta để device bằng 0 để chỉ ra rằng thiết bị đang sử dụng là webcam mặc định trên máy tính laptop, nếu không biết cụ thể thiết bị đó là gì, ta có thể viết một chương trình nhỏ để kiểm tra thiết bị phù hợp bằng cách tằn biến device từ 0 tới một số nào đó (100 chẳng hạn), với mỗi giá trị của device, kiểm tra xem capture có được tạo ra hay không, nếu thiết bị phù hợp, capture sẽ trả về một giá trị khác null, ngược lại nó sẽ là null: for(int device = 0; device < 100; ++device) { cv::VideoCapture capture = cv::VideoCapture(int device) if(capture) cout<<”thiet bi phu hop” <<device <<endl } Để lấy ra được các frame ảnh trong video, ta có thể sử dụng hàm read(cv::Mat& img), kết quả là frame của video sẽ được lưu vào trong biến img. Ta cũng có thể sử dụng toán tử >> để lấy ra các frame từ đối tượng capture: cv::Mat img; capture << img; Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 92 Để lưu các frame ảnh thành video, OpenCV xây dựng một lớp cv::VideoWriter. Constructor của đối tượng này như sau: VideoWriter(string& filename, int fourcc, double fps, cv::Size frameSize, bool isColor) Trong đó, filename là file đường dẫn đầy đủ của file cần lưu, chẳng hạn D:/Movie/test.avi. fourcc là một số nguyên đại diện cho bốn kí tự của bộ codec dùng để mã hóa/giải mã video. Nếu ta biết tên của bộ mã hóa (tên của bộ mã hóa là bốn kí tự) thì ta có thể tìm được số nguyên này dựa vào macro CV_FOURCC(char c1, char c2, char c3, char c4), chẳng hạn như bộ mã hóa XVID sẽ được tìm bằng CV_FOURCC('X', 'V', 'I', 'D'). frameSize là kích thước của các frame ảnh, chẳng hạn 640x480 isColor là một biến kiểm tra xem video lưa vào có phải là dựa trên các ảnh màu hay không, nó có giá trị mặc định là true. Để lưa trữ video, ta có thể gọi hàm write, hoặc toán tử <<. Ví dụ nhỏ sau mô tả cách đọc video từ webcam và lưa ảnh thu được vào một file video. cv::VideoCapture capture = cv::VideoCapture(0); if(!capture.isOpened()) exit(0); cv::Mat img; capture >> img; cv::VideoWriter writer = cv::VideoWriter("D:/test.avi", CV_FOURCC('X', 'V', 'I', 'D'), 25, img.size(), true); while(1) { capture >> img; writer << img; } Tạo chương trình MyCam. Chương trình được tạo ra dựa trên nền Dialog của MFC, cùng các hàm xử lý ảnh trong OpenCV đã biết như trên. Ta chia chương trình làm hai phần, một phần chứa các hàm xử lý ảnh và một phần thuộc về giao diện MFC. Phần chứa các hàm xử lý ảnh được đặt tên là lớp MyCamCore, nó bao gồm một file header định nghĩa giao diện hàm mycamcore.h và một file cài đặt mycamcore.cpp. Cấu trúc của hai file này có dạng như sau: Mycamcore.h: #pragma once #include class MyCamCore { public: MyCamCore(void); ~MyCamCore(void); void Gray(cv::Mat&, cv::Mat&); Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 93 void Invert(cv::Mat&, cv::Mat&); void ToHSV(cv::Mat&, cv::Mat&); void Blur(cv::Mat&, cv::Mat&); }; Và mycamcore.cpp: #include "MyCamCore.h" #include #include #include using namespace cv; MyCamCore::MyCamCore(void) { } MyCamCore::~MyCamCore(void) { } void MyCamCore::Gray(Mat &src, Mat &dst) { if(src.channels() != 1) cvtColor(src, dst, CV_BGR2GRAY); else dst = src; } void MyCamCore::Invert(Mat &src, Mat &dst) { } void MyCamCore::ToHSV(Mat &src, Mat &dst) { } void MyCamCore::Blur(Mat &src, Mat &dst) { } } Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 94 Cách cài đặt này là khá giống với cách cài đặt của chương trình xử lý ảnh đơn giản như trên, nghĩa là cứ một ảnh đầu vào src thì qua phép xử lý sẽ cho ra một ảnh dst, tuy nhiên có một số điểm khác đó là các hàm được cài đặt tối ưu hơn để giúp cho việc hiển thị video có thể liên tục, không bị giật cục, chẳng hạn như với hàm invert. Hàm invert có tác dụng làm đảo ngược các giái trị pixel ảnh, nghĩa là dst(x,y) = 255 – src(x,y). Để cài đặt hàm này theo cách thông thường, ta phải duyệt qua tất cả các phần tử của các kênh màu và thực hiện phép toán trên. Tuy nhiên cách tiếp cận tới các điểm ảnh trong một ảnh là tốn khá nhiều thời gian và kết quả là tốn khá nhiều thời gian xử lý, làm cho video hiển thị lên bị giật cục. Để khắc phục tình trạng này ta quy ảnh về ma trận và thực hiện phép toán trên ma trận dst = matran(255) – src, với matran(255) là một ma trận có cùng kích thước và giá tri tất cả các pixel toàn là 255. Vì OpenCV đã tối ưu hóa các phép tính trên ma trận (như sử dụng cả block các ô nhơ của biến ma trận thay vì tiếp cận từng điểm, chia phép toán thành nhiều tiều trình nhỏ song song ) nên phép thực hiện trên sẽ nhanh hơn rất nhiều. Hàm invert bây giờ được cài đặt như sau: void MyCamCore::Invert(Mat &src, Mat &dst) { static Mat mask = 255*Mat::ones(src.rows, src.cols, CV_8UC1); std::vector src_bgr; std::vector dst_bgr; split(src, src_bgr); for(size_t i = 0; i < src_bgr.size(); ++i) { Mat temp = mask - src_bgr[i]; dst_bgr.push_back(temp); } cv::merge(dst_bgr, dst); } Trong hàm trên, phép cộng trừ ma trận chỉ được thực hiên trên một kênh màu, nên đầu tiên ta tách ảnh thành 3 kênh màu riêng biệt, sau khi thực hiện phép toán xong ta lại trôn lẫn 3 kênh mày này vào. Đối với phần giao diện của chương trình, ta thiết kế một giao diện trên Dialog như sau: Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 95 Các button được đặt tên biến có dạng btn_rgb, btn_capture Video được hiển thị nhờ hiển thị liên tiếp các frame ảnh lên một cửa sổ, cửu sổ này được lấy handle sau đó được gán vào picture box giống như cách hiển thị ảnh trên giao diện MFC trong các ví dụ trước đã nói. Lớp cài đặt giao diện và cũng là chương trình chính khi ch ạy là lớp CMyCam, lớp này được khởi tạo mặc định khi ta tiến hành tạo chương trình và chọn Dialog base. Ta sẽ thêm vào header MyCamDlg.h và file cài đặt MyCamDlg.cpp một số biến, một số hàm để điều khiển chương trình. Header CMyCam.h sẽ có dạng như sau: // MyCamDlg.h : header file #pragma once #include #include #include "MyCamCore.h" #include "afxwin.h" class CMyCamDlg : public CDialogEx { public: CMyCamDlg(CWnd* pParent = NULL); enum { IDD = IDD_MYCAM_DIALOG }; Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 96 enum {RGB = 0, GRAY = 1, INVERT = 2, HSV = 3, YCRCB = 4, CANNY = 5, SOBEL = 6, BLUR = 7, NOISE = 8, ROT = 9, DIV = 10, DISTORT = 11}; protected: virtual void DoDataExchange(CDataExchange* pDX); private: // Các biến, các hàm chức năng trong chương trình bool is_stoped; bool is_captured; int type; cv::Mat src; cv::Mat dst; double fps; std::string output_folder; cv::Size size; std::string video_file_name; std::string img_file_name; cv::VideoWriter writer; cv::VideoCapture capture; MyCamCore mycam; void Start(); void InitAppearance(); bool ReadConfig(std::string); std::string CreateFileName(); protected: HICON m_hIcon; // cac ham override virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnClose(); DECLARE_MESSAGE_MAP() public: // các button CButton btn_snapshot; CButton btn_capture; CButton btn_setting; CButton btn_rgb; CButton btn_gray; ... // Các hàm khi click vào button afx_msg void OnBnClickedSnapshot(); afx_msg void OnBnClickedCapture(); afx_msg void OnBnClickedSetting(); afx_msg void OnBnClickedRgb(); afx_msg void OnBnClickedGray(); ... Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 97 }; Các biến điều khiển của chương trình bao gồm is_stoped, is_captured, type để nhằm kiểm tra xem vòng lặp có tiếp tục được thực hiện hay không, button capture có được bấm chưa hoặc thể hàm xử lý ảnh nào mà ta đang gọi Một số hàm được cài đặt trong file MyCamDlg.cpp như: Hàm void InitAppearance(): Hàm này có tác dụng cài đặt giao diện khởi tạo cho chương trình, mục đích là làm cho giao diện trở nên đẹp hơn bằng cách thêm các ảnh trang trí vào các button. Chẳng hạn như bốn button Snapshot, Capture, Open Folder, Setting được trang trí bằng các ảnh như sau: Trong MFC, để đặt một ảnh vào một button hay một Control nào đó ta có thể dùng hàm SetBitmap với đối số của hàm là một HBITMAP, như vậy ta có thể cài đặt hàm tạo ra giao diện trang trí InitAppearance như sau: void CMyCamDlg::InitAppearance() { HBITMAP snapshot_bmp = (HBITMAP)LoadImageA(0, "../MyCam/res/snapshot.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); btn_snapshot.SetBitmap(snapshot_bmp); HBITMAP capture_bmp = (HBITMAP)LoadImageA(0, "../MyCam/res/capture.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); btn_capture.SetBitmap(capture_bmp); HBITMAP setting_bmp = (HBITMAP)LoadImageA(0, "../MyCam/res/setting.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); btn_setting.SetBitmap(setting_bmp); HBITMAP folder_bmp = (HBITMAP)LoadImageA(0, "../MyCam/res/folder.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); btn_folder.SetBitmap(folder_bmp); ... } Hàm bool ReadConfig(std::string) có tác dụng đọc file config của chương trình, file này chứa một số tham số như tốc độ hình trên giây (fps), folder chứa ảnh chụp hoặc video ghi vào (output_folder) Ban đầu các tham số này chứa giá trị Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 98 mặc định, tuy nhiên khi người dùng điều chỉnh bằng cách nhấn vào button Setting thì các tham số này sẽ được thay đổi và lưu lại trong file config, những lần chạy sau, các thông số đó sẽ được đọc và có giá trị là giá trị mà người dùng đã tủy chỉnh cho chúng, hàm này sẽ được gọi ngay khi chương trình được chạy, hàm cài đặt chủ yếu dựa vào các hàm chuẩn trong C++ có dạng như sau: bool CMyCamDlg::ReadConfig(std::string file_name) { std::ifstream ifs(file_name); if(!ifs) return false; std::vector lines; std::string line; while(ifs >> line) { lines.push_back(line); } if(lines.size() < 2) return false; fps = atof(lines.at(0).c_str()); output_folder = lines.at(1); ifs.close(); return true; } Hàm std::string CreateFileName() có tác dụng tạo ra một chuỗi tên khác nhau ở các thời điểm khác nhau nhằm mục đích đặt tên cho các file ảnh chụp hoặc file video quay. Để tạo ra được các chuỗi tên không bị trùng nhau, tốt nhất là ta lấy theo thời gian, như vậy ở mỗi thời điểm chỉ có duy nhất một tên được tạo ra. Nếu như ta nhấn vào nut Snapshot (chụp ảnh) ở thời điểm là 11 giờ 30 phút 10 giây thứ 6 ngày 28 tháng 12 năm 2012 thì tên của file ảnh lưu lại sẽ là Fri Dec 28 11_30_10 2012.jpg. Ý tưởng cho việc cài đặt hàm này là lấy thời gian của hệ thống, sau đó thay thế các kí tự hai chấm “:” của thời gian bằng kí tự gạch nối dưới “_” (vì kí tự hai chấm là không hợp lệ để đặt tên trong window): std::string CMyCamDlg::CreateFileName() { CreateDirectoryA(output_folder.c_str(), NULL); time_t rawtime; time(&rawtime); std::string t = (std::string)ctime(&rawtime); int pos = t.find(":"); if(pos != t.npos) { t.replace(pos, 1, "_"); t.replace(pos + 3, 1, "_"); t.pop_back(); } std::string path = output_folder + "/"; return path + t; } Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 99 14. Hàm void Start() là hàm chứa vòng lặp của có chức năng xử lý ảnh thu được từ webcam và hiển thị ảnh đã qua xử lý. Ảnh được xử lý như thế nào thì tùy vào biến số type. Hàm này sẽ được gọi ngay từ đầu chương trình sau khi các giao diện và các thông số đã được khởi tạo hết, biến is_stoped sẽ dung để kết thúc vòng lặp của hàm này, biến này được đặt giá trị là true khi chương trình kết thúc trong hàm OnClose(). Hàm Start được cài đặt như sau: void CMyCamDlg::Start() { if(!capture.isOpened()) return; size = cv::Size((int) capture.get(CV_CAP_PROP_FRAME_WIDTH), (int) capture.get(CV_CAP_PROP_FRAME_HEIGHT)); while(!is_stoped) { capture >> src; switch(type) { case RGB : dst = src; break; case GRAY : mycam.Gray(src, dst); break; case INVERT: mycam.Invert(src, dst); break; case HSV : mycam.ToHSV(src, dst); break; case ... : ... break; default: dst = src; break; } cv::imshow("Video", dst); if(is_captured && writer.isOpened()) writer << dst; cv::waitKey(1); } } Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 100 15. Các hàm dùng để điều khiển quá trình xử lý video khi click vào các button đều có chung một dòng lệnh và có dạng như sau (giả sử hàm khi click vào button btn_invert): void CMyCamDlg::OnBnClickedInvert() { type = INVERT; } 16. Hàm khi click vào button Snapshot (chụp ảnh): void CMyCamDlg::OnBnClickedSnapshot() { img_file_name = CreateFileName() + ".jpg"; std::vector p(2); p.at(0) = CV_IMWRITE_JPEG_QUALITY; p.at(1) = 90; if(!dst.empty()) { cv::imwrite(img_file_name, dst, p); } } Hàm này có tác dụng lưu ảnh đã qua xử lý với tên file ảnh là tên chuỗi được tạo ra từ hàm CreateFileName như đã nói ở trên và định dạng là jpg. 17. Hàm khi click vào button Capture (ghi hình) void CMyCamDlg::OnBnClickedCapture() { is_captured = !is_captured; if(is_captured) { video_file_name = CreateFileName() + ".avi"; if(size.height > 0 && size.width > 0) writer = cv::VideoWriter(video_file_name, CV_FOURCC('X','V', 'I', 'D'),8, size, true); btn_capture.SetBitmap((HBITMAP)(LoadImageA(0,"../MyCam/res/ stop.bmp",0, 0, 0, LR_LOADFROMFILE))); btn_snapshot.EnableWindow(false); btn_setting.EnableWindow(false); btn_folder.EnableWindow(false); } else { btn_capture.SetBitmap((HBITMAP)(LoadImageA(0,"../MyCam/res/ capture.bmp",0, 0, 0, LR_LOADFROMFILE))); btn_snapshot.EnableWindow(true); btn_setting.EnableWindow(true); btn_folder.EnableWindow(true); Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 101 if(writer.isOpened()) writer.release(); } } Khi button Capture được nhấn, đối tượng writer sẽ được tạo mới với tên của file video được tạo ra từ hàm CreateFileName(), định dạng ta sử dụng ở đây là avi với codec là XVID, đồng thời trạng thái của chương trình cũng chuyển sang trạng thái ghi hình, nghĩa là button Capture bây giờ được thay bởi ảnh có tên lá Stop, các button khác như Snapshot, Open Folder, Setting sẽ bị vô hiệu hóa. Nếu button này được click lần thứ hai (khi quá trình ghi hình đang diễn ra) thì ta sẽ ngừng việc ghi video lại, hủy đối tượng writer và trạng thái chương trình trở về như ban đầu. 18. Hàm khi click vào button Open Folder (mở folder chứa file ảnh file video): void CMyCamDlg::OnBnClickedFolder() { std::string str_command = "explorer.exe " + output_folder; const char *command = str_command.c_str(); system(command); } Hàm này sẽ mở folder chứa chứa ảnh chụp và video được ghi lại bằng hàm sytem(command), trong đó command là một chuỗi kiểu char để gọi hàm explorer.exe với đối số là đường dẫn tới folder cần mở. 19. Hàm khi click button Setting: void CMyCamDlg::OnBnClickedSetting() { Setting setting; is_stoped = true; setting.DoModal(); fps = setting.Fps; output_folder = setting.OutputPath; if(fps <= 0) fps = 8; if(output_folder == "") output_folder = "..\\Output"; std::ofstream ofs("config.txt"); if(!ofs) return; ofs << fps <<std::endl; ofs << output_folder; ofs.flush(); ofs.close(); is_stoped = false; Start(); } Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 102 Hàm này sẽ mở ra một dialog để người dung đặt các giá trị cho chương trình như fps, output_folder (bạn đọc có thể để thêm vào một số thông số như điều chỉnh độ sang cho ảnh trong quá trình xử lý chẳng hạn) Kết quả sau phép cài đặt này được ghi vào file config và được sử dụng cho các lần chạy chương trình tiếp theo. Sau đây là kết quả chạy chương trình với trường hợp tạo ra các hình đối xứng và ảnh âm bản: Ng uy ễn V ăn L on g Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 103 Chương trình trên đư ợc cài đặt ở dạng đơn giản để tạo ra một chương trình có thể làm việc với các video stream mà cụ thể ở đây là thu từ webcam. Trên một khía cạnh nào đó nó giống với ý tưởng của chương trình Cyberlink YouCam khá nổi tiếng và đã được thương mại hóa, tuy nhiên nó đơn giản hơn khá nhiều cả về chức năng lẫn giao diện. Bạn đọc có thể tự phát triển thêm nhiều hàm, nhiều modul để tự tạo ra cho mình một chương trình thực sự thú vị dựa trên những điều cơ bản nhất. ------------------------------Hết------------------------------ Một số vấn đề dự kiến sẽ được thảo luận trong phần tới: Phát hiện - nhận dạng mặt người, nhận dạng chữ viết tay, theo dõi đối tượng (object tracking), camera calibration - sterio vision và tái tạo không gian 3D, robot vision – một sô vấn đề về việc áp dụng xử lý ảnh trong các kì thi robocon

Các file đính kèm theo tài liệu này:

  • pdfung_dung_xu_ly_anh_trong_thuc_te_voi_thu_vien_opencv_1707.pdf
Tài liệu liên quan