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
103 trang |
Chia sẻ: tuanhd28 | Lượt xem: 4069 | Lượt tải: 1
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:
- ung_dung_xu_ly_anh_trong_thuc_te_voi_thu_vien_opencv_1707.pdf