You are on page 1of 103

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

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

Nguyễn Văn Long


long.06clc@gmail.com

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 1


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

Mở đầu

ng
Xử lý ảnh và thị giác máy là lĩnh v ực mà ngày nay được phát triển và ứng dụng rất rộng
rãi trong nhiều lĩnh vực khác nhau nhờ vào sự phát triển ngày càng mạnh mẽ của các hệ
thống máy tính, các thuật toán và công trình nghiên cứu khác nhau của nhiều nhà khoa
học trên thế giới.
Ở Việt Nam, các ứng dụng về xử ảnh đã bư ớc đầu được triển khai trên một số lĩnh vực

Lo
như lắp đặt hệ thống nhận dạng biển biển số xe ở các bãi đổ xe, hệ thống nhận dạng vân
tay chấm công ở các công sở … môn học xử lý ảnh ở các trường đại học được xem là
môn học bắt buộc ở một số ngành như công nghệ thông tin, điện tử viễn thông …Tuy
nhiên nhìn một cách khách quan thì số lượng các ứng dụng được triển khai trên thực tế là
quá ít ỏi, lĩnh vực này sẽ còn phát triển mạnh mẽ trong tương lai nếu như được quan tâm
một cách nghiêm túc.
Xuất phát từ thực tế rằng môn học xử lý ảnh ở các trường đại học là một môn học mang

ăn
nặng tính học thuật, khô khan, các vấn đề được mô tả dưới dạng toán học, sinh viên nắm
bắt môn học một cách chung chung mà không đi vào bản chất vấn đề, ứng dụng thực tiễn
của môn học, thêm vào đó số lượng tài liệu về chuyên ngành này bằng tiếng Việt là không
nhiều, bằng quá trình nghiên cứu nghiêm túc, kinh nghiệm thực tế tác giả đã cố gắng cho
nV
ra đời cuốn sách Ứng dụng xử lý ảnh trong thưc tế với thư viện OpenCV.
Cuốn đề cập tới một số phần của lĩnh vực xử lý ảnh và thị giác máy, thông qua sự diễn
giải trực quan, không xa vào những công thức toán học trừu tượng, phức tạp nhưng vẫn
làm nổi bật nên được vấn đề, giúp người đọc có được cái nhìn tổng quát, hiểu được khái
niệm và hơn nữa biết được những vấn đề đó ứng dụng vào thực tế như thế nào. Các chủ
đề trong cuôn sách này đều đi kèm với một chương trình mô ph ỏng được viết bằng ngôn
ngữ C++ với sự giúp đỡ của thư viện OpenCV, một thư viện mã nguông mở được đánh
giá là mạnh mẽ về tốc độ xử lý đáp ứng được các ứng dụng trong thời gian thực.

Cuốn sách được chia thành bốn phần, phần đầu giới thiệu về thư viện OpenCV, phần thứ
hai nói về một số vấn đề chọn lọc thường gặp trong xử lý ảnh như không gian màu, các
uy

bộ lọc, cách phát hiện đường thẳng đường tròn trong ảnh …, phần thứ ba nói về một số
thủ thuật để lập trình với thư viện MFC và phần cuối cùng nói về một số ứng dụng thực tế
như bài toán nhận dạng biển số xe …
Cuốn sách không chỉ là tài liệu tham khảo bổ ích trong quá trình học tập của các bạn sinh
viên, quá trình làm luận văn, đồ án … mà còn là công cụ tốt hỗ trợ cho việc triển khai các
Ng

ứng dụng thương mại của các kĩ sư, doanh nghi ệp và những người quan tâm tới lĩnh v ực.
Cuối cùng dù đã dành nhiều tâm huyết để hoàn thành cuốn sách nhưng chắc chắn cuốn
sách vẫn còn nhiều sai xót, tác giả mong được sự góp ý của bạn đọc. Xin gửi lời chúc tốt
tốt đẹp và lời cảm ơn sâu sắc tới độc giả

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 2


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

Hướng dẫn sử dụng sách

ng
Cuốn sách được viết dựa trên những nghiên cứu và quá trình làm việc thực tế của tác giả,
với mỗi vấn đề nêu trong sách bạn đọc có thể đọc qua để nắm bắt được ý tưởng chính, sau
đó có thể tìm thêm tài liệu để nâng cao hơn vấn đề và có thể thực hành dựa vào mẫu
chương trình, source code đi kèm.
Thư viện OpenCV được viết trong sách là bản OpenCV 2.4.3, đối với các bản OpenCV

Lo
khác thì bạn đọc có thể tùy chỉnh lại một chút tuy nhiên về bản chất của vấn đề là tương
đối giống nhau. Ngôn ngữ lập trình cho các ví dụ là C/C++, IDE sử dụng là Visual Studio
2010. Tuy nhiên đa số chương trình trong cuốn sách này đều được tách biệt phần xử lý
chính ra vào một file *.cpp nào đó nên ta có thể lấy nó để áp dụng vào các trình dịch
khác.
Có 10 chủ đề chính bao quát một số khía cạnh của lĩnh vực xử lý ảnh được viết khá chi
tiết và giải thích đầy đủ, 3 project được tác giả mô tả chung chung hơn. Do đó bạn đọc

ăn
nếu chưa thực sự quen với thư viện OpenCV nên đọc theo thứ tự từ đầu tới cuối
Trong cuốn sách có nhiều vấn đề liên quan tới kĩ thuật lập trình nhưng do phạm vi giới
hạn, tác giả chỉ có thể nói qua được một số khía cạnh, trên thực tế có nhiều cách khác
nhau để giải quyết cùng một công việc, với những vấn đề lập trình bạn đọc chưa rõ có thể
tham khảo thêm tài ở các nguồn khác nhau hoặc giải quyết theo hướng mà bạn đọc cảm
nV
thấy là thỏa đáng nhất

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 3


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

Mục Lục

ng
Chương I. Làm quen với thư viện OpenCV
1. Giới thiệu về thư viện OpenCV 5

Lo
2. Phiên bản OpenCV 1 hay OpenCV 2 5
3. Hướng dẫn sử dụng OpenCV trên Window 6
Chương II. Các phép xử lý đơn giản trong OpenCV
1. Chương trình đ ầu tiên 12
2. Không gian màu, chuyển đổi không gian màu 13
3. Điều chỉnh độ sang, độ tương phản 17
4. Ảnh nhị phân, nhị phân hóa với ngưỡng động 19

7. Lọc số trong ảnh


ăn
5. Histogram, cân bằng histogram
6. Phóng to, thu nhỏ, xoay ảnh

8. Các phép toán hình thái học trong ảnh


9. Tìm biên ảnh với bộ lọc Canny
23
27
30
37
43
nV
10. Chuyển đổi Hough, Phát hiện đường thẳng, đường tròn trong ảnh 46
Chương III. Lập trình xử lý ảnh với giao diện MFC
1. Giới thiệu về MFC 51
2. Khởi tạo project MFC 51
3. Làm việc với các điều khiển (Control) 54
4. Chuyển đổi các kiểu dữ liệu trong MFC 59
5. Chương trình tải ảnh và hiển thị ảnh lên giao diện MFC 61

Chương IV. Một số ứng dụng trong thực tế


uy

1. My Photo Editor, phần mềm chỉnh sửa ảnh đơn giản 64


2. Nhận dạng biển số xe 73
3. MyCam, một số hiệu ứng ảnh trong video 90
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 4


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

Chương I. Làm quen với thư viện OpenCV


1. Giới thiệu về thư viện OpenCV

ng
OpenCV (Open Source Computer Vision) là một thư viện mã nguồn mở về thị giác máy
với hơn 500 hàm và hơn 2500 các thuật toán đã đư ợc tối ưu về xử lý ảnh, và các vấn đề
liên quan tới thị giác máy. OpenCV được thiết kế một cách tối ưu, sử dụng tối đa sức
mạnh của các dòng chip đa lõi… đ ể thực hiện các phép tính toán trong thời gian thực,
nghĩa là tốc độ đáp ứng của nó có thể đủ nhanh cho các ứng dụng thông thường. OpenCV

Lo
là thư viện được thiết kế để chạy trên nhiều nền tảng khác nhau (cross-patform), nghĩa là
nó có thể chạy trên hệ điều hành Window, Linux, Mac, iOS … Việc sử dụng thư viện
OpenCV tuân theo các quy định về sử dụng phần mềm mã nguồn mở BSD do đó bạn có
thể sử dụng thư viện này một cách miễn phí cho cả mục đích phi thương mại lẫn thương
mại.
Dự án về OpenCV được khởi động từ những năm 1999, đến năm 2000 nó được giới thiệu
trong một hội nghị của IEEE về các vấn đề trong thị giác máy và nhận dạng, tuy nhiên

ăn
bản OpenCV 1.0 mãi tới tận năm 2006 mới chính thức được công bố và năm 2008 bản 1.1
(pre-release) mới được ra đời. Tháng 10 năm 2009, bản OpenCV thế hệ thứ hai ra đời
(thường gọi là phiên bản 2.x), phiên bản này có giao diện của C++ (khác với phiên bản
trước có giao diện của C) và có khá nhiều điểm khác biệt so với phiện bản thứ nhất.
nV
Thư viện OpenCV ban đầu được sự hỗ trợ từ Intel, sau đó được hỗ trợ bở Willow Garage,
một phòng thí nghiệm chuyên nghiên cứu về công nghệ robot. Cho đến nay, OpenCV vẫn
là thư viện mở, được phát triển bởi nguồn quỹ không lợi nhuận (none -profit foundation)
và được sự hưởng ứng rất lớn của cộng đồng.

2. Phiên bản OpenCV 1 hay OpenCV 2?


Cho tới nay, trải qua hơn 6 năm từ lúc phiên bản OpenCV đầu tiên được công bố, đã có
lần lượt nhiều phiên bản OpenCV ra đời, tuy nhiên có thể chia thư viện này thành hai bản

chính dựa trên những đặc điểm khác biệt lớn nhất của chúng: phiên bản OpenCV thế hệ
thứ nhất (hay còn gọi là phiên bản OpenCV 1.x) và phiên bản OpenCV thứ hai (hay còn
uy

gọi là phiên bản OpenCV 2.x). Sau đây ta sẽ chỉ ra một số điểm khác biệt cơ bản giữa hai
phiên bản này.
- OpenCV 1.x (bao gồm bản 1.0 và bản pre-release 1.1) dựa trên giao diện C, cấu
trúc của một ảnh số dựa trên cấu trúc của IplImage, trong khi thư OpenCV 2.x dựa
trên giao diện C++, cấu trúc của ảnh số, ma trận dựa trên cấu trúc của cv::Mat.
Ng

- Trong OpenCV 1.x, người sử dụng phải hoàn toàn quản lý bộ nhớ của các đối
tượng, nghĩa là khi m ột đối tượng mới được tạo ra, ta phải luôn chú ý để giải
phóng nó khi không còn sử dụng nữa (trong nhiều trường hợp có thể sẽ bị tràn bộ
nhớ nếu không chú ý đều này), trong khi thư viện OpenCV 2.x việc quản lý bộ nhớ
trở nên dễ dàng hơn nhờ các hàm hủy các các lớp đối tượng trong OpenCV 2.x đã
thực hiện điều này khi một đối tượng không còn được sử dụng nữa.
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 5
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

- Việc viết các dòng lệnh để thực hiện cùng một chức năng trong OpenCV 2.x là dễ
dàng hơn nhiều so với OpenCV 1.x, một phần là là giao diện C++ có phần dễ hiểu

ng
hơn so với C, một phần là các hàm trong OpenCV 2.x đã được tối ưu hóa nhiều
bước trung gian không cần thiết về mặt giao diện người sử dụng. Chẳng hạn ta hãy
xét ví dụ về việc phát hiện đường tròn trong ảnh mầu dựa vào thuật toán Hough,
các bước để thực hiện là load một ảnh mầu, chuyển sang ảnh nhị phân, tìm biên
dựa trên bộ lọc canny và phát hiện đường tròn dựa trên thuật toán Hough. OpenCV
1.x thực hiện như sau:

Lo
// Phát hiện đường tròn trong ảnh OpenCV 1.x
IplImage* src = cvLoadImage(“image.jpg”);
IplImage* gray = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvCvtColor(src, gray, CV_BGR2GRAY);
cvCanny(gray, gray, 10, 30, 3);
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* circles = cvHoughCircles(gray, storage, CV_HOUGH_GRADIENT, 1,
50, 100, 50);

ăn
Trong khi đó, OpenCV 2.x thực hiện như sau:

// Phát hiện đường tròn trong ảnh OpenCV 1.x


Mat src = imread(“image.jpg”);
Mat gray;
nV
CvtColor(src, gray, CV_BGR2GRAY);
Canny(gray, gray, 10, 30, 3);
Vector<Vec3f> circles;
HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, 50, 100, 50);

Ta thấy rằng đối tượng ảnh gray trong OpenCV 2.x không cần phải khởi tạo, đối
tượng storage (đối tượng trung gian, không có ý nghĩa v ề mặt sử dụng) cũng
không cần phải khởi tạo (và do đó không cần giải phóng).

- Thư viện OpenCV 1.x tuy chứa một lượng lớn hàm xử lý và thuật toán, tuy nhiên
nó vẫn ở dạng sơ khai. Thư viện OpenCV 2.x đã đư ợc bổ xung khá nhiều hàm,
uy

thuật toán và được tối ưu khá nhiều đặc biệt trong các khía cạnh về phát hiện đối
tượng (detection), nhận dạng đối tượng (partten regconition) và theo dỗi đối tượng
(tracking). Hơn thế nữa, tuy có giao diện là C++ nhưng OpenCV 2.x vẫn dữ một
phần giao diện C để tương thích với các phiên bản của OpenCV 1.x …
Từ một số đặc điểm trên ta có thể thấy rằng thư viện OpenCV phiên bản 2.x là có nhiều
Ng

điểm nổi trội hơn so với phiên bản 1.x, Tuy nhiên trong một số trường hợp như ở các hệ
thống nhúng khi mà trình dịch chỉ đơn thuần chấp nhận ngôn ngữ C thì phiển bản 1.x vẫn
còn giá trị. Trong cuốn sách này, các nội dung cài đặt, thuật toán, ứng dụng … chỉ dành
cho OpenCV phiên bản 2.x trên nền tảng hệ điều hành Window.
3. Hướng dẫn sử dụng thư viện OpenCV trên Window

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 6


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

Trước hết ta cần download thư viện OpenCV về máy tính, tốt hơn là luôn download bản
mới nhất tại địa chỉ http://sourceforge.net/projects/opencvlibrary/ . Chọn bản đã build sẵn

ng
phù hợp với hệ điều hành đang dùng, bản OpenCV được sử dụng trong cuốn sách này là
bản 2.4.3 với lần update cuối cùng là vào ngày 25 tháng 12 năm 2012. Sau khi download
về máy, tiến hành cài đặt bình thường, ta để mặc định thư mục cài đặt là C:\ thư mục cài
đặt xong sẽ có dạng C:\opencv. Tiếp theo ta sẽ tiến hành tùy chỉnh để có thể làm việc với
OpenCV qua hai IDE thông dụng là Microsoft Visual Studio và Eclipse CDT

Lo
Trên Microsoft Visual Studio
Phiên bản Visual studio sử dụng ở đây là phiên bản Visual Studio 2010, các phiên bản
trước ta hoàn toàn có thể cấu hình một cách tương tự.
Tạo một project mới: New > Project, trong cửa sổ New Project chọn Visual C++, Win32
console application. Đặt tên project là opencv

ăn
ễ nV
uy

Chọn OK, sau đó nhấn Next, hộp thoại tiếp theo xuất hiện, ở hộp thoại này ta chọn
Application type là Console application và Additional option là Empty project, nhấn
Finish để kết thúc quá trình khởi tạo
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 7


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

ng
Lo
ăn
Project mới được tạo ra là project hoàn toàn trống, ta phải thêm vào đó ít nhất một file
nV
nguồn để chương trình có th ể chạy
được, trong Solution Explorer ta
click chuột phải vào Source Files,
chọn Add -> New Item… Hộp thoại
Add New Item hiện ra, ta chọn kiểu
cần thêm vào là C++ File(.cpp) đồng
thời trong ô Name ta đặt tên cho file
thêm vào, giả sử là FirstApp.cpp.
Bây giở trong file này ta có thể thêm

vào các #include và gọi hàm main()


để chạy chương trình.
uy

Để chương trình có thể chạy được


với thư viện OpenCV ta cần tùy chỉnh lại một sô thuộc tính của project như sau
Vào Project -> Properties (hoặc nhấn tổ hợp phím Alt + F7) để mở hộp thoại Properties.
Hộp thoại opencv Property Pages hiện ra, trong mục Configuration Properties chọn
VC++ Directories, tương ứng bên phải, ta tìm mục Include Directories và Library
Ng

Directories. Ta sẽ chỉ đường dẫn hai thư mục này đến các phần tương ứng của thư viện
OpenCV.
Mục Include Directories, ta tùy chỉnh ở ô bên phải tới C:\opencv\build\include
Mục Library Directories trỏ đến thư mục C:\opencv\build\x86\vc10\lib nếu như ta sử
dụng hệ điều hành 32bit hoặc C:\opencv\build\x64\vc10\lib cho hệ điều hành 64bit.

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 8


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

Tiếp theo, trong hộp thoại opencv Property Pages -> Configuration Properties -> Linker
chọn Input, tương ứng ở ô bên phải, thêm vào các giá trị cho mục Additional

ng
Dependencies là opencv_core243d.lib, opencv_imgproc243d.lib, opencv_highgui243d.lib.
Chú ý là
các lib thêm vào sẽ tương ứng với các
header ta khai báo trong chương trình, và
tùy thuộc vào mục đích sử dụng mà ta có

Lo
thể thêm vào các lib các nhau, giả sử ta cần
sử dụng tới các hàm về video, khi đó ta
thêm header #include
<opencv2/video/video.hpp> thì trong phần
Additional Dependencies ta phải khai báo
thêm opencv_video243d.lib. Chữ d đứng
cuối các file trên thể hiện ta đang hoạt
động ở chế độ debug, ta có thể thêm các lib

ăn
không có chữ “d” ở cuối như
opencv_core243.lib … trong chế độ
release. Tuy nhiên khi đang còn h ọc tập và cần nhiều chỉnh sửa ta nên để ở chế độ debug.
Cuối cùng, khi dịch xong một chương trình, để nó có thể chạy được ta cần chú ý tới các
file *.dll. Cách đơn giản nhất là ta copy các file *.dll tương ứng (như
nV
opencv_core243d.dll, opencv_imgproc243d.dll) vào thư mục chứa file chạy của chương
trình (file *.exe). Các file *.dll này nằm trong mục C:\opencv\build\x86\bin với win 32 bit
hoặc C:\opencv\build\x64\bin với win 64 bit. Với các phiên bản OpenCV cũ hơn, ta cần
copy luôn file tbb_debug.dll (trong chế độ debug) hoặc tbb.dll (trong chế độ release) vào
thư mục chứa file *.exe. tbb.dll (Thread building block) là file khá quan trọng, thiếu nó
chương trình sẽ báo lỗi.
Sau khi đã hoàn tất việc chỉ dẫn thư mục chứa header, library và link tới các library tương

ứng, ta có thể include các header của opencv vào chương trình và có thể gọi các hàm làm
việc của OpenCV.
#include <opencv2/core/core.hpp>
uy

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace std;


using namespace cv;
Ng

void main()
{
...
}

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 9


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

Với Eclipse CDT


Khởi động Eclipse, Từ cửa sổ Eclipse chọn New -> C++ Project , hộp thoại C++ Project

ng
xuất hiện, trong hộp thoại ta chọn Project name là opencv, Project type là Hello World
C++ Project (Có thể chọn là Empty
Project), Toolchains là MinGW GCC,
Chọn Finish và ta có một Project mới.
Bây giờ tùy chỉnh cho project này hoạt

Lo
động được với OpenCV.
Trong cửa sổ của Eclipse chọn Project -
>Properties, cửa sổ Properties hiện ra.
Tron cửa sổ Properties chọn C/C++
Build->Settings. Trong tab Tool Settings.
Ở phần GCC C++ Compiller chọn
Include rồi dẫn đường dẫn tới mục
Include của OpenCV

ăn
là C:\opencv\build\include. Trong phần
MinGW C++ Linker chọn Library và
chọn các mục như sau: click vào dấu
cộng ở Library search path (-L) và dẫn
tới thư mục
nV
lib: C:\opencv\build\x86\mingw\lib đối
với Windows 32 bit hoặc
C:\opencv\build\x64\mingw\lib đối với
Windows 64 bit. Tiếp đó click vào dấu "cộng" để thêm Library(-I) vào, các library cần
thêm lần lượt là: opencv_core243, opencv_highgui243, opencv_imgproc243 ... nói chung
là tùy vào nhu cầu sử dụng có thể thêm một hoặc nhiều lib vào.

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 10


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

ng
Lo
ăn
Ta cũng cần phải copy các *.dll tương ứng vào thư mục chứa file chạy *.execủa chương
trình để chương trình có thể chạy thành công.
ễ nV
uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 11


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

Chương II. Các phép xử lý ảnh và ứng dụng cơ bản

ng
1. Chương trình đ ầu tiên

Trong bài này ta sẽ tìm hiểu một ví dụ đầu tiên như một chương trình Hello world để load
và hiển thị một ảnh. Chương trình như sau:
#include "stdafx.h"
#include <iostream>

Lo
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\core\core.hpp>

using namespace std;


using namespace cv;

int main()
{
cout<<"Chuong trinh dau tien"<<endl;

}
ăn
Mat img = imread("vietnam.jpg", CV_LOAD_IMAGE_COLOR);
namedWindow("Viet Nam", CV_WINDOW_AUTOSIZE);
imshow("Viet Nam", img);
waitKey(0);
return 0;
nV
Như đã nói ở trên, trong Opencv với giao diện C++, tất cả các kiểu dữ liệu ảnh, ma trận
điều được lưu ở dạng cv::Mat. Hàm imread sẽ đọc ảnh đầu vào và lưu vào biến img.
Nguyên mẫu của hàm này như sau: cv::Mat imread(const std::string &filename, int flags)
trong đó, filename là đường dẫn tới file ảnh, nếu file ảnh không nằm trong thư mục làm
việc hiện hành thì ta phải chỉ ra đường dẫn tương đối dạng như D:\Anh\vietnam.jpg hoặc
D://Anh//Vietnam.jpg. Flags là tham số loại ảnh mà ta muốn load vào, cụ thể nếu nếu

muốn load ảnh mầu thì ta để CV_LOAD_IMAGE_COLOR, nếu là ảnh xám thì ta để
CV_LOAD_IMAGE_GRAYSCALE…
Sau khi đã load ảnh thành công, muốn hiển thị ảnh lên màn hình ta phải tạo ra một cửa sổ,
uy

hàm namedWindow(const std::string &winname, int flags) sẽ tạo ra cửa sổ với tiêu đề cửa
sổ là một chuỗi string winname. Tham số flags sẽ chỉ ra kiểu cửa sổ muốn tạo: nếu tham
số CV_WINDOW_AUTOSIZE được sử dụng thì kích cỡ cửa sổ tạo ra sẽ được hiển thị
một cách tự động tùy thuộc vào kích thước của ảnh, nếu là tham số
CV_WINDOW_AUTOSIZE_FULLSCREEN kích thước cửa sổ sẽ khít với màn hình máy
tính …
Ng

Cuối cùng, hàm imshow(const std::string &winname, cv::InputArray Mat) sẽ hiển thị ảnh
ra cửa sổ đã được tạo ra trước đó.
Hàm waitKey(int delay) sẽ đợi cho đến khi có một phím được bấm vào trong khoảng thời
gian là delay. Chú ý là nếu không có hàm này thì chương trình sau khi chạy sẽ không
dừng lại màn hình và kết thúc luôn, ta dung hàm này mục đích là để dừng màn hình lại

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 12


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

trong một khoảng thời gian bằng tham số delay (tính theo đơn vị millisecond). Nếu muốn
dừng màn hình lại mãi mãi ta đặt tham số delay bằng 0.

ng
Và sau đây là kết quả chương trình chay:

Lo
ăn
nV

2. Không gian màu, chuyển đổi giữa các không gian màu

Khôn gian màu là một mô hình toán học dùng để mô tả các màu sắc trong thực tế được

biểu diễn dưới dạng số học. Trên thực tế có rất nhiều không gian màu khác nhau được mô
hình để sử dụng vào những mục đích khác nhau. Trong bài này ta sẽ tìm hiểu qua về ba
uy

không gian màu cơ bản hay được nhắc tới và ứng dụng nhiều, đó là hệ không gian màu
RGB, HSV và CMYK.
Không gian màu RGB
RGB là không gian màu rất phổ biến được dùng trong đồ họa máy tính và nhiều thiết bị kĩ
thuật số khác. Ý tưởng chính của không gian màu này là sự kết hợp của 3 màu sắc cơ bản
: màu đỏ (R, Red), xanh lục (G, Green) và xanh lơ (B, Blue) để mô tả tất cả các màu sắc
Ng

khác.
Nếu như một ảnh số được mã hóa bằng 24bit, nghĩa là 8bit cho kênh R, 8bi t cho kênh G,
8bit cho kênh B, thì mỗ kênh này màu này sẽ nhận giá trị từ 0-255. Với mỗi giá trị khác
nhau của các kênh màu kết hợp với nhau ta sẽ được một màu khác nhau, như vậy ta sẽ có
tổng cộng 255x255x255 = 1.66 triệu màu sắc.

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 13


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

Ví dụ: màu đen là sự kết hợp của các kênh màu (R, G, B)
với giá trị tương ứng (0, 0, 0) màu trắng có giá trị (255,

ng
255, 255), màu vàng có giá trị (255, 255, 0), màu tím đậm
có giá trị (64, 0, 128) ...Nếu ta dùng 16bit để mã hóa một
kênh màu (48bit cho toàn bộ 3 kênh màu) thì dãi màu sẽ
trãi rộng lên tới 3*2^16 = ... Một con số rất lớn.

Không gian màu CMYK.

Lo
CMYK là không gian màu được sử dụng phổ biến trong
ngành công nghiệp in ấn.Ý tưởng cơ bản của hệ không
gian này là dùng 4 màu sắc cơ bản để phục vụ cho việc pha trộn mực in. Trên thực tế,
người ta dùng 3 màu là C=Cyan: xanh lơ, M=Magenta: hồng xẫm, và Y=Yellow: vàng để
biểu diễn các màu sắc khác nhau. Nếu lấy màu hồng xẫm cộng với vàng sẽ ra màu đỏ,
màu xẫm kết hợp với xanh lơ sẽ cho xanh lam ... Sự kết hợp của 3 màu trên sẽ cho ra màu

ăn
đen, tuy nhiên màu đen ở đây khôn phải là đen tuyệt đối và thường có độ tương phản lớn,
nên trong ngành in, để tiết kiệm mực in người ta thêm vào màu đen để in những chi tiết
có màu đen thay vì ph ải kết hợp 3 màu sắc trên. Và như vậy ta có hệ màu CMYK. chữ K
ở đây là để kí hiệu màu đen (Black), có nhẽ chữ B đã được dùng để biểu diễn màu Blue
nên người ta lấy chữ cái cuối K để biểu diễn màu đen?
nV
Nguyên lý làm việc của hệ màu này như sau : Trên một
nền giấy trắng, khi mỗi màu này được in lên sẽ loại bỏ dần
đi thành phần màu trắng. 3 màu C, M, Y khác nhau in theo
những tỉ lệ khác nhau sẽ loại bỏ đi thành phần đó một cách
khác nhau và cuối cùng cho ta màu sắc cần in. Khi cần in
màu đen, thay vì phải in cả 3 màu người ta dùng màu đen

để in lên. Nguyên lý này khác với nguyên lý làm việc của


hệ RGB ở chỗ hệ RGB là sự kết hợp của các thành phần
màu, còn hệ CMYK là sự loại bỏ lẫn nhau của các thành
uy

phần màu.

Không gian màu HSV.


HSV và cũng gần tương tự như HSL là không gian màu được dùng nhiều trong việc ch ỉnh
sữa ảnh, phân tích ảnh và một phần của lĩnh vực thị giác máy tính. Hệ không gian này dựa
Ng

vào 3 thông số sau để mô tả màu sắc


H = Hue: màu sắc, S = Saturation: độ đậm đặc, sự bảo hòa, V = value: giá trị cường độ
sáng.
Không gian màu này thường được biểu diễn dưới dạng hình trụ hoặc hình nón . Theo đó,

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 14


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

đi theo vòng tròn từ 0 -360 độ là trường biểu diễn màu sắc(Hue). Trường này bắt đầu từ
màu đỏ đầu tiên (red primary) tới màu xanh lục đầu tiên (green primary) nằm trong

ng
khoảng 0-120 độ, từ 120 - 240 độ là màu xanh lục tới xanh lơ (green primary - blue
primary). Từ 240 - 360 là từ màu đen tới lại màu đỏ.

Lo
Không gian màu HSV Hình tròn biểu diễn màu sắc (HUE)

ăn
Chuyển đổi giữa các không gian màu.

Chuyển đổi RGB sang CMYK và ngược lại.


nV
Như đã nói ở trên, thành phần K là thành phần phụ dùng để in cho những điểm màu có
màu đen trong hệ CYMK, do vậy để chuyển không gian màu từ RGB sang CMYK trước
hết ta chuyển RGB sang CMY sau đó tìm thành phần K còn lại. Cô ng thức chuyển từ
RGB sang CMY:
(C', M', Y') = ((255 - R), (255 - G), (255 - B)).
Việc tính giá trị của K lại là một vấn đề khác vì nó liên quan tới nhà sản xuất công nghệ
in, tuy nhiên về mặt lý thuyết có thể chấp nhận rằng K = min {C'/2,55, M'/2,55, Y'/2,55} ,

như vậy 0<= K <=100.


Nếu K = 100, thì C = M = Y =0 (trương hợp in màu đen)
Nếu 0< K < 100: C = (C'/2.55 - K) * 100 /(100 - K), M = (M'/2.55 - K) * 100 /(100 - K),
uy

Y = (Y'/2.55 - K) *100 /(100 - K) và K = K. Trong đó, C, M, Y, K được làm tròn tới để


lấy chỉ số nguyên.
Chuyển đổi RGB sang HSV và ngược lại
Giả sử ta có một điểm màu có giá trị trong hệ RGB là (R, G, B). ta chuyển sang không
gian HSV như sau:
Ng

Đặt M = Max(R, G, B), m = Min(R, G, B) và C = M - m.


Nếu M = R, H' = (G - B)/C mod 6. Nếu M = G, H' = (B - R)/C + 2. Nếu M = B, H' = (R -
G)/C + 4. Và H = H'x60. Trong trường hợp C = 0, H = 00
V = M.

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 15


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

S = C/V. Trong trường hợp V hoặc C bằng 0, S = 0.


Để chuyển từ HSV sang RGB ta làm như sau:

ng
Giả sử ta có không gian màu HSV với H = [0, 360], S = [0, 1], V = [0, 1]. Khi đó, ta tính
C = VxS. H' = H/60.
X = C(1 - |H' mod2 -1|). Ta biểu diễn hệ (R1, G1, B1) như sau:

(0, 0, 0) ế ℎư đượ á đị ℎ

( , , 0) ế 0≤ <1

Lo

⎪ ( , , 0) ế 1≤ <2
( 1, 1, 1) = (0, , ) ế 2 ≤ <3
⎨ (0, , ) ế 3≤ <4
⎪ ( , 0, )
⎪ ế 4 ≤ <5
⎩ , 0, ) ế 5 ≤ <6

Chương trình chuyển đổi các không gian màu

ăn
Trong OpenCV, các không gian màu được được chuyển đổi qua lại nhờ hàm cvtColor
(convert color), nguyên mẫu hàm này như sau:
cv::cvtColor(cv::InputArray src, cv::OutputArray dst, int code)
Trong đó, src, dst là ảnh gốc và ảnh thu được sau khi chuyển đổi không gian màu. code
nV
mà mã chuyển đổi không gian màu. OpenCV định nghĩa khá nhi ều chuyển đổi giữa các
không gian màu chẳng hạn như code = CV_BGR2GRAY sẽ chuyển ảnh ở không gian màu
RGB sang ảnh xám, code = CV_HSV2BGR sẽ chuyển ảnh ở không gian màu HSV sang
không gian màu RGB …

#include "stdafx.h"
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <opencv2/highgui/highgui.hpp>

using namespace cv;


uy

void main()
{
Mat src = imread("LucBinh.jpg", CV_LOAD_IMAGE_COLOR);
Mat gray, hsv, ycrcb;
cvtColor(src, gray, CV_BGR2GRAY);
cvtColor(src, hsv, CV_BGR2HSV);
Ng

cvtColor(src, ycrcb, CV_BGR2YCrCb);


imshow("src", src);
imshow("gray", gray);
imshow("hsv", hsv);
imshow("ycrcb", ycrcb);
waitKey(0);
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 16
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

ng
Sau đây là hình ảnh khi chuyển đổi các không gian màu trên

Lo
ăn
ễ nV

3. Điều chỉnh độ sang và độ tương phản trong ảnh


uy

Trong bài này ta sẽ tìm hiểu về cấu trúc của một bức ảnh, cách tiếp cận và điều chỉnh tới
từng điểm ảnh.
Một ảnh số được lưu trữ trên máy tính là một ma trận các điểm ảnh (hay pixel). Trong
OpenCV nó được biểu diễn dưới dạng cv::Mat. Ta xét một kiểu ảnh thông thường nhất,
đó là ảnh RGB. Với ảnh này, mỗi pixel ảnh quan sát được là sự kết hợp của các thành
phần màu R (Red), Green (Green) và Blue (Blue). Sự kết hợp này theo những tỉ lệ R, G,
Ng

B khác nhau sẽ tạo ra vô số các màu sắc khác nhau. Giả sử ảnh được mã hóa bằng 8 bit
với từng kênh màu, khi đó mỗi giá trị của R, G, B sẽ nằm trong khoảng [0, 255]. Như vậy
ta có thể biểu diễn tới 255*255*255 ~ 1.6 triệu màu sắc từ ba màu cơ bản trên. Ta có thể
xem cách biểu diễn ảnh trong OpenCV ở định dạng cv::Mat qua hình ảnh sau:

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 17


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

Cột 0 Cột 1 Cột m

ng
Hàng 0 0, 0 0, 0 0, 0 0, 1 0, 1 0, 1 0, m 0, m 0, m
Hàng 1 1, 0 1, 0 1, 0 1, 1 1, 1 1, 1 1, m 1, m 1, m
Hàng 2 2, 0 2, 0 2, 0 2, 1 2, 1 2, 1 2, m 2, m 2, m
Hàng n n, 0 n, 0 n, 0 N, 1 n, 1 n, 1 n, m n, m n, m

Lo
Như vậy, mỗi ảnh sẽ có n hàng và m cột, m gọi là chiều dài của ảnh (width) và n gọi là
chiều cao của ảnh (heigh). Mỗi pixel ở vị trí (i,j) trong ảnh sẽ tương ứng với 3 kênh màu
kết hợp trong nó. Để truy xuất tới từng pixel ảnh với những kênh màu riêng rẽ ta sử dụng
mẫu sau:
img.at<cv::Vec3b>(i,j)[k]
Trong đó, i ,j là pixel ở hàng thứ i và cột thứ j, img là ảnh mà ta cần truy xuất tới các pixel
của nó. cv::Vec3b là kiểu vector uchar 3 thành phần, dung để biểu thị 3 kênh màu tương
ứng. k là kênh màu thứ k, k = 0, 1, 2 tương ứng với kênh màu B, G, R. Chú ý là trong

ăn
OpenCV, hệ màu RGB được biểu diễn theo thứ tự chữ cái là BGR.
Sau đây ta sẽ áp dụng kiến thức trên để làm tăng, giảm độ sang và tương phản của một
ảnh màu, việc làm này cũng hoàn toàn tương t ự đối với ảnh xám, chỉ khác biệt là ảnh ta
dung một kênh duy nhất để biểu diễn ảnh xám.
Chương trình tăng, gi ảm độ sáng và độ tương phản của một ảnh
nV
Giả sử f là một hàm biểu diễn cho một ảnh nào đó, f(x,y) là giá trị của pixel trong ảnh ở vị
trí (x,y). Đặt g(x,y) = αf(x,y) + β. Khi đó, nếu α ≠ 1, thì ta nói ảnh g(x,y) có độ tương phản
gấp α lần so với ảnh f(x,y). Nếu β ≠ 0ta nói độ sáng của ảnh g(x,y) đã thay đ ổi một lượng
là β. Dựa vào công thức trên ta có chương trình thay đ ổi độ sáng và tương phản của ảnh
như sau:

#include "stdafx.h"
#include <iostream>
#include <opencv2\core\core.hpp>

#include <opencv2\highgui\highgui.hpp>

using namespace std;


uy

using namespace cv;

int main()
{
cout<<"Chuong trinh dieu chinh do sang va tuong phan"<<endl;
Mat src = imread("hoa_huong_duong.jpg", 1);
Mat dst = src.clone();
Ng

double alpha = 2.0;


int beta = 30;
for(int i = 0; i < src.rows; i++)
for(int j = 0; j < src.cols; j++)
for(int k = 0; k < 3; k++)

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 18


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

dst.at<Vec3b>(i,j)[k] = saturate_cast<uchar>(
alpha*(src.at<Vec3b>(i,j)[k] ) + beta);
imshow("anh goc", src);

ng
imshow("anh co sau khi chinh do tuong phan va do sang", dst);
waitKey(0);
return 0;
}

Trong chương trình trên, hàm clone() sẽ sao chép một ảnh giống hệt như ảnd gốc cho vào

Lo
ảnh đích (dst = src.clone()). Giá trị của các pixel ảnh f(x,y) và g(x,y) ở đây phải nằm trong
khoảng [0, 255], trong khi phép biến đổi g(x,y) = αf(x,y) + β có thể khiến cho giá trị của
g(x,y) vượt qua ngưỡng đó. Để tránh tình trạng tràn số hoặc kiểu dữ liệu không tương
thích, ta dùng thêm hàm saturate_cast<uchar>(type). Hàm này sẽ biến kiểu dữ liệu
type nếu không phải là uchar thành kiểu dữ liệu uchar
Sau đây là kết quả chương trình với giá trị α = 2.0 và β = 30

ăn
ễ nV

4. Ảnh nhị phân, nhị phân hóa với ngưỡng động


uy

Ảnh nhị phân là ảnh mà giá trị của các điểm ảnh chỉ được biểu diễn bằng hai giá trị 0
hoặc 255 tương ứng với hai màu đen hoặc trắng. Nhị phân hóa một ảnh là quá trình biến
một ảnh xám thành ảnh nhị phân. Gọi f(x,y) là giá trị cường độ sáng của một điểm ảnh ở
vị trí (x,y), T là ngưỡng nhị nhị phân. Khi đó, ảnh xám f sẽ được chuyển thành ảnh nhị
Ng

phân dựa vào công thức f(x,y) = 0 nếu f(x,y) ≤ T và f(x,y) = 255 nếu f(x,y) > T
Hình sau mô tả một ảnh nhị phân với ngưỡng nhị phân T = 100

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 19


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

ng
Lo
Ảnh xám Ảnh nhị phân

Hàm để chuyển nhị phân hóa ảnh trong OpenCV là hàm threshold(). Nguyên mẫu hàm
như sau:

ăn
threshold(cv::InputArray src, cv::OutputArray dst, double thresh, int maxval, int type)
Trong đó, src là ảnh đầu vào một kênh màu (ảnh xám …), dst là ảnh sau khi được nhị
phân hóa, thresh là ngưỡng nhị phân, maxval là giá trị lớn nhất trong ảnh (maxval = 255
đối với ảnh xám), type là kiểu nhị phân có thể là CV_THRESH_BINARY,
CV_THRESH_BINARY_INV, CV_THRESH_OTSU… lần lượt là nhị phân hóa thông
thường, nhị phân hóa ngược và nhị phân hóa theo thuật toán Otsu …
nV
Kết quả của việc nhị phân hóa một ảnh phụ thuộc vào ngưỡng T, có nghĩa là v ới mỗi
ngưỡng T khác nhau thì ta có những ảnh nhị phân khác nhau. Hình sau mô tả 3 ảnh nhị
phân tương ứng với ngưỡng T = 50, T = 100 và T = 150.

uy

T = 50 T = 100 T = 150
Ng

Để thu được một ảnh nhị phân tốt mà không cần phải quan tâm tới các điều kiện ánh sáng
khác nhau (không cần quan tâm tới ngưỡng T), người ta dùng một kĩ thuật sao cho với
mọi ngưỡng xám khác nhau ta luôn thu được một ảnh nhi phân tốt. Kĩ thuật đó gọi là kĩ

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 20


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

thuật nhị phân hóa với ngưỡng động (Dymamic threshold) hay nhị phân thích nghi
(Adaptive threshold)

ng
Có nhiều phương pháp khác khác nhau để thực hiện việc này, tuy nhiên chúng đều dựa
trên ý tưởng chính là chia ảnh ra thành những vùng nhỏ, với mỗi vùng áp dụng việc nhị
phân cho vùng đó với những ngưỡng nhị phân khác nhau.Các ngưỡng nhị phân ở các
vùng được tính toán dựa trên độ lớn mức xám của chính các pixel trên vùng đó. Giả sử ta
tính toán ngưỡng cho một vùng nào đó dựa trên độ trung bình của các pixel trong vùng đó
(ta có thể xem một vùng là một cửa sổ). Ta xét quá trình nhị phân với ngưỡng động trong

Lo
một vùng cửa sổ 5x5:

ăn
nV
Vùng ảnh nhị phân thu được ở trên là vùng ảnh được nhị phân với ngưỡng là trung bình
cộng của tất cả các ô trong cửa sổ T = (55 + 10 + 100 + …)/25 = 65.6.

Chương trình nhị phân hóa với ngưỡng động như sau

// Adaptive Threshold
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

#include <iostream>
uy

using namespace std;


using namespace cv;

int main()
{
cout<<"Nhi phan anh voi nguong dong"<<endl;
Mat src = imread("Thap_But.jpg", CV_LOAD_IMAGE_GRAYSCALE);
Ng

Mat dst;
adaptiveThreshold(src, dst, 255, CV_ADAPTIVE_THRESH_MEAN_C,
CV_THRESH_BINARY, 35, 5);
imshow("Anh xam goc", src);
imshow("Anh nhi phan voi nguong dong", dst);
waitKey(0);

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 21


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

return 1;
}

ng
Trong chương trình trên, hàm thực hiện việc nhị phân hóa với ảnh động là hàm
adaptiveThreshold, Nguyên mẫu của hàm như xau:
cv::adaptiveThreshold(cv::InputArray src, OutputArray dst, double maxValue,
int adaptiveMethod, int thresholdType, int blockSize, double C)
Trong đó, src là ảnh xám cần nhị phân, dst là ảnh kết quả thu được, maxValue là giá trị

Lo
lớn nhất trong ảnh xám (thông thường là 255), adaptiveMethod là cách thức nhị phân với
ngưỡng động, nó chính là cách tính giá trị ngưỡng nhị phân trong từng vùng cần nhị phân,
thresholdType như đã nói ở trên, blockSize là kích thước của sổ áp dụng cho việc tính
toán ngưỡng động, và C là một thông số để bù trừ trong trường hợp ảnh có độ tương phản
quá lớn.
Kết quả khi chạy chương trình như sau:

ăn
nV

Ảnh xám Ảnh nhị phân



uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 22


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

5. Histogram, cân bằng histogram trong ảnh

ng
Histogram của một ảnh là một biểu đồ nói lên
mối quan hệ giữa các giá trị của pixel ảnh (điểm
ảnh) và tần suất xuất hiện của chúng. Nhìn vào
biểu đồ histogram ta có thể đoán được một ảnh
sáng tối như thế nào.
Nếu một ảnh có histogram lệch về phía phải

Lo
biểu đồ, ta nói ảnh đó thừa sáng. Nếu lệch về
phía trái thì ảnh đó thiếu sáng. Hình bên mô tả
histogram của một ảnh xám, ảnh này có
histogram lệch về phía trái của biểu đồ và do đó
ảnh này là khá tối. Đối với ảnh màu, ta có thể
tính toán histogram cho từng kênh màu một.
Sau đây là chương trình tính và v ẽ biểu đồ
histogram của một ảnh màu.

#include <iostream>
ăn
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
nV
using namespace std;
using namespace cv;

int main()
{
std::cout<<"Tim histogram anh mau"<<std::endl;

Mat src = imread("buoi.jpg");


vector<Mat> img_rgb;
Mat img_r, img_g, img_b;
int w = 400, h = 400;

int size_hist = 255;


float range[] = {0, 255};
const float* hist_range = {range};
uy

split(src, img_rgb);
calcHist(&img_rgb[0], 1, 0, Mat(), img_b, 1, &size_hist, &hist_range, true, false);
calcHist(&img_rgb[1], 1, 0, Mat(), img_g, 1, &size_hist, &hist_range, true, false);
calcHist(&img_rgb[2], 1, 0, Mat(), img_r, 1, &size_hist, &hist_range, true, false);

int bin = cvRound((double)w/size_hist);

Mat disp_r(w, h, CV_8UC3, Scalar( 255,255,255) );


Ng

Mat disp_g = disp_r.clone();


Mat disp_b = disp_r.clone();

normalize(img_b, img_r, 0, disp_b.rows, NORM_MINMAX, -1, Mat() );


normalize(img_g, img_g, 0, disp_g.rows, NORM_MINMAX, -1, Mat() );
normalize(img_r, img_b, 0, disp_r.rows, NORM_MINMAX, -1, Mat() );

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 23


Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV
for( int i = 1; i < 255; i++ )
{
line(disp_r, Point(bin*(i), h), Point(bin*(i), h - cvRound(img_r.at<float>(i))),

ng
Scalar(0, 0, 255), 2, 8, 0 );
line(disp_g, Point(bin*(i), h), Point(bin*(i), h - cvRound(img_g.at<float>(i))),
Scalar( 0, 255, 0), 2, 8, 0 );
line(disp_b, Point( bin*(i), h), Point(bin*(i), h - cvRound(img_b.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0 );
}
namedWindow("src", 0);
imshow("src", src);

Lo
imshow("Histogram of Blue chennel", disp_b);
imshow("Histogram of Green chennel", disp_g);
imshow("Histogram of Red chennel", disp_r);

cv::waitKey(0);
return 1;
}

Trong chương trình trên, đ ầu tiên ảnh được đưa vào là một ảnh mầu, để tính histogram
của từng kênh mầu một ta sẽ phân tách ảnh này thành 3 kênh mầu riêng rẽ theo thứ tự là

Hàm ăn
Blue, Green và Red. Hàm cv::split(const cv::Mat src, cv::Mat *mvbegin) sẽ tách thành
src thành các kênh màu tương ứng lưu trong mvbegin.
cv:: calcHist(const cv::Mat *images, int nimages, const int *channels,
cv::InputArray mask, cv::OutputArray hist, int dims, const int histSize, const float
**ranges, bool uniform = true, bool accumulate = false) sẽ tính histogram của ảnh đầu
nV
vào images và lưu kết quả tính toán vào mảng hist. Các thông số khác bao gồm: nimages
là số lượng ảnh đầu vào, channels là danh sách chiều các kênh dung để tính histogram,
mask là một mặt nạ tùy chỉnh, nếu không dung gì thì để là cv::Mat(). dims là chiều của
histogram, bản OpenCV hiện tại hỗ trợ tính toán histogram với số chiều lên tới 32.
histSize là kích thước dãy histogram mỗi chiều, hai tham số cuối có thể để mặc định,
Sau đây là kết quả chương trình

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 24


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

ng
Lo
ăn
Đối với ảnh xám, ta có thể xem như nó là một kênh màu, do vậy để tính histogram của
ảnh xám ta cũng có thể làm hoàn toàn tương tự bằng cách gọi hàm caclHist()
Cân bằng histogram
nV
Cân bằng histogram (histogram equalization) là
phương pháp làm cho biểu đồ histogram của ảnh được
phân bố một cách đồng đều. Đây là một biến đổi khá
quan trọng giúp nâng cao chất lượng ảnh, thông
thường đây là bước tiền xử lý của một ảnh đầu vào
cho các bước tiếp theo.
Để cân bằng histogram, ta dung hàm
equalizeHist(cv::InputArray src, cv::OuputArray dst)

trong đó, src là ảnh đầu vào một kênh màu (ảnh xám
chẳn hạn), dst là ảnh sau khi cân bằng. Ví dụ:
uy

Mat src = imread("src.jpeg", CV_LOAD_IMAGE_GRAYSCALE); // Load anh xam


imshow("Anh xam goc", src);
Mat dst;
equalizeHist(src, dst);
imshow("anh xam sau khi can bang histogram", dst);
Ng

Ta có kết quả sau:

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 25


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

ng
Lo
Để cân bằng histogram của một ảnh màu, trước hết ta chuyển ảnh màu ở dạng RGB sang
HSV, sau đó cân bằng thành phần kênh màu V (Value tức độ sáng) và biến đổi ngược lại.
Chương trình sau cân bằng histogram của ảnh màu

// Can bang histogram

ăn
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
nV
#include <iostream>

using namespace std;


using namespace cv;

int main()
{
cout<<"Chuong trinh can bang histogram"<<endl;
Mat src = imread("Cho_Ben_Thanh.jpg", CV_LOAD_IMAGE_COLOR);

Mat hsv, disp;


cvtColor(src, hsv, CV_BGR2HSV);

vector<Mat> hsv_channels;
// Tach hsv thanh 3 kenh mau
split(hsv, hsv_channels);
uy

// Can bang histogram kenh mau v (value)


equalizeHist(hsv_channels[2], hsv_channels[2]);
// Tron anh
merge(hsv_channels, hsv);
// Chuyen doi hsv sang rgb de hien thi
cvtColor(hsv, disp, CV_HSV2BGR);
imshow("anh mau goc", src);
imshow("anh mau sau khi can bang histogram", disp);
Ng

waitKey(0);

Sau đây là kết quả của chương trình trên

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 26


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

ng
Lo
6. Phóng to, thu nhỏ và xoay ảnh

ăn
Như đã nói ở trên, ảnh số thực chất là một ma trận các điểm ảnh, do đó để có thể phóng
to, thu nhỏ hay xoay một tấm ảnh ta có thể sử dụng các thuật toán tương ứng trên ma trận.

Ta sẽ sử dụng biển đổi affine để quay và thay đổi tỉ lệ to, nhỏ của một ma trận.
nV
Biến đổi affine:

Giả sử ta có vector = [ , ] và ma trận M 2x2. Phép biển đổi affine trong không gian
hai chiều có thể được định nghĩa p’ = Mp trong đó = [ , ] . Viết một cách tường
minh ta có:


=

Hay x’ = αx + δy, y’ = γx + βy .
uy

Xét ma trận = . Nếu δ = γ = 0, khi đó x’ = αx và y’ = βy, phép biến đổi này làm
thay đổi tỉ lệ của ma trận. Nếu là trong ảnh nó sẽ phóng to hoặc thu nhỏ ảnh. Hình sau mô
tả phép biến đổi với tỉ lệ α = β = 2
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 27


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

ng
Lo
cos ( ) −sin ( )
Nếu ta định nghĩa ma trận = thì phép biến sẽ quay p thành p’ với
sin ( ) cos ( )
góc quay là θ.

ăn
nV
α. cos (θ) −sin ( )
Nếu ma trận M được định nghĩa thành = thì phép biến đổi sẽ
sin ( ) . cos ( )
vừa là phép biến đổi theo tỉ lệ và quay.

Bây giờ ta sẽ xét chương trình phóng to, thu nh ỏ và quay ảnh.
// Chuong trinh phong to, thu nho va quay anh
uy

#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;


Ng

int main()
{
Mat src = imread("HoaSen.jpg");
Mat dst = src.clone();

double angle = 45.0;

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 28


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

double scale = 1.5;


Point2f center(src.cols/2, src.rows/2);

ng
Mat mat_rot = getRotationMatrix2D(center, angle, scale);
warpAffine(src, dst, mat_rot, src.size());
imshow("Anh goc", src);
imshow("Anh sau phep bien doi", dst);
waitKey(0);

Lo
return 1;
}

Trong chương trình trên, hàm cv::getRotationMatrix2D(cv::Point center, double


angle, double scale) sẽ tạo ra ma trận với tâm quay center, góc quay angle và tỉ lệ
scale. Ma trận này được tính toán trong Opencv là ma trận như sau:

(1 − ). . − . .

Với = . cos ( ăn=


) và =
. . − (1 − ).

. sin ( )
.

Ta thấy rằng ma trận này là hoàn hoàn tương đương với ma trận của phép biến đổi affine
nV
đã nó i ở trên, ngoại trừ thành phần thứ 3 là thành phần giúp dịch chuyển tâm quay vào
chính giữa của bức ảnh. Chú ý là có sự khác biệt một chút về chiều của hệ tọa độ trong
ảnh, hệ tọa độ trong ảnh lấy góc trên bên trái làm gốc tọa độ (0,0) còn hệ tọa độ thông
thường ta hay lấy điểm dưới bên trái làm gốc, do đó có sự ngược chiều.

Kết quả của chương trình trên v ới tỉ lệ scale = 1.5 và góc quay = 450 như hình sau

uy
Ng

Ngoài hai phép biến đổi là tỉ lệ và quay như trên, ta có thể thực hiện các biến đổi khác của
phép biến đổi affine như phép trượt (shearing), hoặc phép phản chiếu (reflection) bằng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 29


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

việc định nghĩa lại ma trận M. Ta thử định nghĩa lại ma trận M để được một ảnh trượt của
ảnh gốc. Quay lại ma trận M như trên, nếu ta định nghĩa α = β = 1 còn δ nh ận một giá trị

ng
bất kì, khi đó ta s ẽ có:

= +
ta sẽ định nghĩa ma
=
1 0.5 0
trận = để trượt ảnh
0 1 0

Lo
ban đầu thành ảnh mới với hệ số trượt
0
δ = 0.5, chú ý là thành phần thứ ba
0
định nghĩa ma tr ận trong Opencv sẽ
thể hiện dộ dịch chuyển, giống như trong ví dụ trên ta chuyển tâm quay về tâm của bức
ảnh chẳng hạn. Ta sẽ làm giống hệt ví dụ trên, chỉ thay ma trận M là ma trận ta tự định
nghĩa

ăn
double I[2][3] = {1,0.5,0, 0,1,0}; // cac phan tu cua ma tran
Mat mat_rot(2,3,CV_64F, I); // khoi tao ma tran
warpAffine(src, dst, mat_rot, src.size());

và ta có kết quả:
ễ nV
uy

7. Lọc số trong ảnh


Ng

Lọc số trong ảnh có ý nghĩa quan trọng trong việc tạo ra các hiệu ứng trong ảnh, một số
hiệu ứng nhờ sử dụng các bộ lọc như làm mờ ảnh(Blur), làm trơn ảnh(Smooth) …
Nguyên tắc chung của phương pháp lọc ảnh là cho ảnh nhân chập với một ma trận lọc,

Idst = M*Isrc

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 30


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

Isrc, Idst là ảnh gốc và ảnh sau khi thực hiện phép lọc ảnh bằng cách nhân với ma trận lọc
M. Ma trận M đôi khi còn g ọi là mặt nạ (mask), nhân (kernel). Với mỗi phép lọc ta có

ng
những ma trận lọc M khác nhau, không có quy định cụ thể nào cho việc xác định M, tuy
nhiên ma trận này có một số đặc điểm như sau:

- Kích thước của ma trận thường là một số lẻ chẳng hạn 3x3, 5x5 … Khi đó, tâm của
ma trận sẽ nằm ở giao của hai đường chéo và là điểm áp đặt lên ảnh mà ta cần tính

Lo
nhân chập.
- Tổng các phần tử trong ma trận thông thường bằng 1. Nếu tổng này lớn hơn 1, ảnh
qua phép lọc sẽ có độ sáng lớn hơn ảnh ban đầu. Ngược lại ảnh thu được sẽ tối hơn
ảnh ban đầu.

Ví dụ về ma trận lọc (ma trận lọc Sobel theo hướng x, y và ma trận Gausian Blur)

1 4 7 4 1
⎡4 4⎤
−1
−2
−1
0
0
0
+1
+2
+1
ăn −1
0
+1
−2
0
+2

Công thức cụ thể cho việc lọc ảnh như sau:


−1
0
+1

⎢7
⎢4
⎣1
16
26
16
4
26
41
26
7
16
26
16
4
7⎥
4⎥
1⎦

nV
( , )= ( , )∗ ( , )= ( − , − )× ( , )

Trong đó, ta đang tính phép nhân chập cho điểm ảnh có tọa độ (x,y) và vì ta lấy tâm của
ma trận lọc là điểm gốc nên u chạy từ -n (điểm bên trái) và v chạy từ -n (điểm phía trên)
đến n, với n = (kích thước mặt nạ - 1)/2. Để dễ hiểu hơn ta xét một ví dụ về việc làm trơn

nhờ sử dụng một ma trận lọc như sau:

100 100 100 100 100


uy

100 100 100 100 100


1/9 1/9 1/9 100 200 205 203 100 100 144 205 203 100
1/9 1/9 1/9 * 100 195 200 200 100 = 100 195 200 200 100
1/9 1/9 1/9 100 200 205 195 100 100 200 205 195 100
nhân chập
Ng

100 100 100 100 100 100 100 100 100 100
pixel (2,2)

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 31


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

ng
Lo
100 100 100 100 100 100 100 100 100 100
1/9 1/9 1/9 100 200 205 203 100 100 144 167 203 100
1/9 1/9 1/9 * 100 195 200 200 100 = 100 195 200 200 100
1/9 1/9 1/9 100 200 205 195 100 100 200 205 195 100
nhân chập
100 100 100 100 100 100 100 100 100 100
pixel (2,3)

……
ăn
Và kết quả cuối cùng ta có:
nV
100 100 100 100 100 100 100 100 100 100
1/9 1/9 1/9 100 200 205 203 100 100 144 167 145 100
1/9 1/9 1/9 * 100 195 200 200 100 = 100 167 200 168 100
1/9 1/9 1/9 100 200 205 195 100 100 144 166 144 100
nhân chập
100 100 100 100 100

100 100 100 100 100


uy

Ta thấy rằng, ảnh ban đầu với là ảnh có độ tương phản khá lớn (các giá trị độ lớn pixel
chênh lệch lớn: 100, 200, …), sau phép lọc ảnh có độ tương phản giản đi hay bị làm mờ
đi(lúc này độ chênh lệch giá trị giữa các pixel giảm đi: 100, 144, 167 …). Sau đây ta sẽ
xem xét một số bộ lọc trong OpenCV.
Ng

Lọc Blur:

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 32


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

1 1 . . 1
⎡1 1 . . 1⎤
⎢ ⎥
=

ng
Ma trận lọc của phương pháp này có dạng :
∗ ⎢. . . . .⎥
⎢. . . . .⎥
⎣1 1 . . 1⎦
Bộ lọc này có tác dụng làm trơn ảnh, khử nhiễu hạt.
Hàm cài đặt trong OpenCV có dạng:
cv::blur(const Mat& src, const Mat& dst, Size ksize, Point anchor, int borderType)

Lo
trog đó, src và dst là ảnh gốc và ảnh sau phép lọc. ksize là kích thước ma trận lọc,
ksize = Size(rows, cols), anchor là điểm neo của ma trận lọc, nếu để mặc định là (-
1,-1) thì điểm này chính là tâm của ma trận. borderType là phương pháp để ước
lượng và căn chỉnh các điểm ảnh nếu qua phép lọc chúng bị vượt ra khỏi giới hạn
của ảnh. Thông thường giá trị mặc định của nó là 4.
Ảnh qua phép lọc blur

ăn
nV

Lọc Sobel:
Lọc sobel chính là cách tính xấp xỉ đạo hàm bậc nhất theo hướng x và y, nó cũng

chính là cách tính gradient trong ảnh. Bộ lọc này thông thường được áp dụng cho
mục đích tìm biên trong ảnh. Ma trận lọc theo các hướng x, y lần lượt như sau:
uy

−1 0 +1 −1 −2 −1
−2 0 +2 0 0 0
−1 0 +1 +1 +2 +1

Trong OpeCV, hàm cài đặt phép lọc này như sau:
Ng

cv::Sobel(const Mat& src, Mat& dst, int ddepth, int xorder, int yorder, int ksize,
double scale, double delta, int borderType)
Trong đó, src và dst là ảnh gốc và ảnh qua phép lọc. ddepth là độ sâu của ảnh sau
phép lọc, có thể là CV_32F, CV_64F … . xoder và yoder là các đạo hàm theo

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 33


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

hướng x và y, để tính đạo hàm theo hướng nào ta đặt giá trị đó lên 1, ngược lại nếu
giá trị bằng 0, hàm cài đặt sẽ bỏ qua không tính theo hướng đó. Scale và delta là

ng
hai thông số tùy chọn cho việc tính giá trị đạo hàm lưa giá trị vi sai vào ảnh sau
phép lọc, chúng có giá trị mặc định là 1 và 0. borderType là tham số như trên.
Ảnh qua phép lọc Sobel:

Lo
Lọc Laplace:
ăn
Lọc Laplace là cách tính xấp xỉ đạo hàm bậc hai trong ảnh, nó có ý nghĩa quan
trọng trong việc tìm biên ảnh và phân tích, ước lượng chuyển động của vật thể.
nV
=∆ = +
Ma trận lọc có dạng như sau:
0 1 0
1 −4 1
0 1 0
Trong OpenCV, bộ lọc này được cài đặt qua hàm:
cv::Laplacian(const Mat& src, Mat& dst, int ddepth, int ksize=1, double scale=1,

double delta=0, int borderType)


Các thông số này có ý nghĩa giống như các thông số trong bộ lọc Sobel, chỉ khác ở
uy

chỗ ksize là một giá trị int mặc định bằng 1 và khi đó ma trận lọc laplace trên được
áp dụng.
Ảnh qua phép lọc Laplace:
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 34


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

ng
Lo
Ngoài 3 bộ lọc trên, OpenCV còn cài đặt khá nhiều bộ lọc khác như lọc trung vị
(medianBlur), lọc Gause (gaussianBlur), pyrDown, pyrUp … Tuy nhiên, ta hoàn toàn có
thể cài đặt bộ lọc cho riêng mình thông qua hàm cv::filter2D. Nguyên mẫu hàm này như
sau:

ăn
filter2D(const Mat& src, Mat& dst, int ddepth, const Mat& kernel, Point anchor, double
delta, int borderType).
nV
Trong đó, src và dst là ảnh gốc và ảnh thu được qua phép lọc, kernel là ma trân lọc.
Thông số anchor để chỉ ra tâm của ma trận, delta điều chỉnh độ sáng của ảnh sau phép lọc
(ảnh sau phép lọc được cộng với delta và borderType là kiểu xác định những pixel nằm
ngoài vùng ảnh.

Hàm cv::filter2D thực chất là hàm tính toán nhân chập giữa ảnh gốc và ma trận lọc để cho
ra ảnh cuối sau phép lọc. Như vậy qua trên ta thấy rằng để tiến hành việc lọc ảnh ta chỉ
cần định nghĩa một ma trận lọc kernel. Có rất nhiều nghiên cứu về toán học đã định nghĩa

các ma trận này, do đó việc áp dụng vào lọc ảnh sẽ là khá đơn giản.

Chương trình demo phương pháp l ọc ảnh.


uy

Chương trình sau sẽ thực hiện một số bộ lọc ảnh do chính ta định nghĩa, chẳng hạn ta có
−1 −1 0
ma trận lọc = −1 0 1 , ma trận này là ma trận sẽ làm nổi bật ảnh giống như
0 1 1
Ng

ảnh khắc 3D, ta sẽ áp dụng ma trận này để lọc một ảnh.


#include "stdafx.h"
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 35


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

using namespace cv;

void main()

ng
{
Mat src = imread("HoaDao.jpg", CV_LOAD_IMAGE_COLOR);
Mat dst;
double m[3][3] = { -1, -1, 0,
-1, 0, 1,
0, 1, 1

Lo
};
Mat M = cv::Mat(3,3,CV_64F, m, Point(-1,-1), 128.0);
cv::filter2D(src, dst, src.depth(), M);
imshow("Anh goc", src);
imshow("Anh qua phep loc", dst);
cv::waitKey(0);
}

ăn
Kết quả của chương trình như sau:
ễ nV

Một số bộ lọc sau có thể hữu ích cho việc tạo ra các hiệu ứng ảnh đẹp mắt:
uy

1 1 1
= 1 −7 1 , =0
Ng

1 1 1

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 36


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

−1 −1 −1

ng
= −1 8 −1 , =0
−1 −1 −1

Lo
−1
= −1
−1
−1
8
−1
−1
−1 ,
−1 ăn =0
ễ nV

8. Các phép toán hình thái học trong ảnh


uy

Các phép toán hình thái học là những phép toán liên quan tới cấu trúc hình học (hay topo)
của các đối tượng trong ảnh. Các phép toán hình thái học tiêu biểu bao gồm phép giãn nở
(dialation) phép co (erosion), phép mở (opening) và phép đóng (closing).

Phép toán giãn nở (dilation)


Ng

Phép toán giãn nở được định nghĩa ⊕ = ⋃ ớ ⊂ trong đó, A là đối tượng
trong ảnh, B là một cấu trúc phần tử ảnh. Phép toán này có tác dụng làm cho đối tượng
ban đầu trong ảnh tăng lên về kích thước (giản nở ra)

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 37


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

Cấu trúc phần tử ảnh (image structuring element) là một hình khối được định nghĩa sẵn
nhằm tương tác với ảnh xem nó có thỏa mãn một số tính chất nào đó không, một số cấu

ng
trúc phần tử hay gặp là cấu trúc theo khối hình vuông và hình chữ thập

Lo
Ta hãy xét một ảnh với đối tượng trong ảnh được biểu diễn bằng màu nền nâu, sau đó
dùng cấu trúc phần tử hình vuông (màu đỏ) để làm giản nở ảnh, kết quả là ảnh được giản
nở ra và phần giản nở ra ta đánh dấu là dấu x.

x
x
x
x x

ăn
ễ nV
uy

Ứng dụng của phép giãn nở là làm cho đối tượng trong ảnh được tăng lên về kích thước,
các lỗ nhỏ trong ảnh được lấp đầy, nối liền đường biên ảnh đối với những đoạn rời nhỏ …

Phép toán co (erosion)

Phép toán co trong ảnh được định nghĩa ⊖ = { |( ) ⊆ } trong đó A là đối tượng
Ng

trong ảnh, B là cấu trúc phần tử ảnh. Ta cũng sẽ xét một ví dụ như trên với phép co trong
ảnh. Đối tượng trong ảnh được biểu diển bởi màu xám, cấu trúc phần tử ảnh là khối có
viền màu đỏ, x là điểm sau phép thỏa mãn phép co ảnh, 0 là điểm không thỏa mãn.

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 38


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

ng
0

Lo
ăn
Ta thấy rằng sau phép toán này đối tượng trong ảnh bị co lại, chính vì vậy mà nó được
ứng dụng trong việc giảm kích thước của đối tượng, tách rời các đối tượng gần nhau và
làm mảnh, tìm xương đối tượng.
nV
Phép toán mở (opening) và đóng (closing) là sự kết hợ của phép co (erosion) và giản
(dialation) như trên, chúng được định nghĩa như sau:

Phép toán mở : ∘ =( ⊖ )⊕

Phép toán đóng: ∙ =( ⊕ )⊖

Phép toán mở được ứng dụng trong việc loại bỏ các phần lồi lõm và làm cho đường bao

đối tượng trong ảnh trở lên mượt mà hơn.

Phép toán đóng được ứng dụng trong việc làm trơn đường bao đối tượng, lấp đầy các
uy

khoảng trống trên biên và loại bỏ những hố nhỏ (một số pixel đứng thành cụm độc lập)

Trong OpenCV, các phép toán hình thái học trong ảnh được cài đặt trong hàm
cv::morphologyEx, riêng phép giãn nở và phép co có thể gọi trực tiếp từ hàm cv::dilate và
cv::erode:
Ng

morphologyEx(const Mat& src, Mat& dst, int op, const Mat& element, Point anchor,
int iterations, int borderType, const Scalar& borderValue)

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 39


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

Trong đó, src, dst là ảnh đầu vào và ảnh sau phép xử lý hình thái học. op là kiểu lựa chọn
phép hình thái học, chẳng hạn như phép giản nở là MORPH_DILATE, phép đóng là

ng
MORPH_OPEN …. element là cấu trúc phần tử ảnh, có ba cấu trúc cơ bản là theo khối
hình vuông, hình chữ thập và hình elip. Để tạo ra các cấu trúc này ta có thể tự định nghĩa
một ma trận với các hình khối tương ứng hoặc sử dụng hàm getStructuringElement, hàm
này có cấu trúc như sau: getStructuringElement(int shape, Size ksize, Point anchor), với
shape là kiểu hình khối (một trong 3 hình khối trên), ksize là kích thước của hình khối và

Lo
là khích thước của hai số nguyên lẻ, anchor là điểm neo và thông thường nhận giá trị là
((ksize.width – 1)/2, (ksize.height – 1)/2). Thông số tiếp theo anchor cũ ng có ý nghĩa
tương tự. iterations là số lần lặp lại của phép toán hình thái và hai thông số cuối cùng là
về giới hạn biên của những điểm ảnh nằm ngoài kích thước ảnh trong quá trình tính toán,
nó có ý nghĩa như các ví d ụ trong bài trước.

Phép toán về giản nở và co có thể được gọi từ hàm cv::morphologyEx thông qua hai đối

ăn
số op là MORPH_DILATE và MORPH_ERODE hoặc chúng có thể được gọi trực tiếp từ
hàm cv::dilate và cv::erode, Cấu trúc của hai hàm này là tương tự nhau và các tham số
hoàn toàn giống với tham số trong hàm morphologyEx:

dilate(const Mat& src, Mat& dst, const Mat& element, Point anchor=Point(-1, -1),
nV
int iterations, int borderType, const Scalar& borderValue)

Một điều cần chú ý là trái với cách diễn đạt về các phép hình thái như trên, OpenCV có
cách cài đặt ngược lại giữa đối tượng và nền ảnh, nghĩa là trong phép gi ản nở (dilate),
phần được giản nở là nền của ảnh (background) chứ không phải các đối tượng vật thể
trong ảnh, điều đó cũng có nghĩa rằng phép giản nở sẽ làm co lại các đối tượng vật thể
trong ảnh và phép co (erode) sẽ làm co nền của ảnh đồng thời giản nở các đối tượng vật
thể trong ảnh.

Sau đây ta sẽ xét một ví dụ về việc sử dụng các phép toán hình thái trong ảnh trong việc
phát hiện biển số xe ô tô và cách ly các kí tự trong biển số.
uy

Giả sử ta có một ảnh chứa biển số, vì biển số có nền trắng và kí tự màu đen, nên trước
tiên ta nhị phân ảnh đó, sau đó tìm đư ờng bao của các đối tượng (contour) để từ đó xác
định được xem những đường bao nào có khả năng là biển số xe nhất dựa trên tỉ lệ các
cạnh chiều dài, rộng của hình chữ nhật bao quanh đường bao đó (dĩ nhiên đây là m ột cách
Ng

tiếp cận rất đơn giản!). Tuy nhiên, ta chỉ xác định được đường bao quanh đối tượng khi
tập các điểm nằm trên biên của nó tạo thành một vùng kín. Trong nhiều trường hợp của
biển số, việc bị mất một vài điểm ảnh trong quá trình nhị phân trên biên là chuyện thường
xuyên xảy ra và do đó sẽ khó xác định được một đường bao quanh biển số. Để khắc phục

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 40


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

tình trạng này, ta sẽ làm giản nở đường biên (điều đó sẽ tương đương với việc gọi hàm
erode để làm co lại nền ảnh) để các điểm ảnh trên biên trở lên liền lơn.

ng
#include "stdafx.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

Lo
using namespace std;

void main()
{
Mat src1 = imread("BienSo.jpg", CV_LOAD_IMAGE_COLOR);
Mat src2 = src1.clone(); // copy anh
Mat gray, binary;
cvtColor(src1, gray, CV_BGR2GRAY);
threshold(gray, binary, 100, 255, CV_THRESH_BINARY);

ăn
imshow("Anh nhi phan goc", binary);
Mat morpho;
Mat element = getStructuringElement(MORPH_CROSS, Size(3,3),
Point(1,1));
erode(binary, morpho, element, Point(-1,-1), 3);
imshow("Anh sau khi thuc hien phep gian no", morpho);
nV
vector<vector<Point> > contours1;
findContours(binary, contours1, CV_RETR_LIST, CV_CHAIN_APPROX_NONE );
for(size_t i = 0; i < contours1.size(); i++)
{
Rect r = boundingRect(contours1[i]);
if(r.width/(double)r.height > 3.5f && r.width/(double)r.height <
4.5f)
rectangle(src1, r, Scalar(0, 0, 255), 2, 8, 0);
else

rectangle(src1, r, Scalar(0, 255, 0), 1, 8, 0);


}
imshow("Ket qua phat hien truoc phep gian no", src1);
uy

vector<vector<Point> > contours2;


findContours(morpho, contours2, CV_RETR_LIST, CV_CHAIN_APPROX_NONE );
for(size_t i = 0; i < contours2.size(); i++)
{
Ng

Rect r = boundingRect(contours2[i]);
if(r.width/(double)r.height > 3.5f && r.width/(double)r.height <
4.5f)
rectangle(src2, r, Scalar(0, 0, 255), 2, 8, 0);
else
rectangle(src2, r, Scalar(0, 255, 0), 1, 8, 0);
}
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 41
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

imshow("Ket qua phat hien sau khi phep gian no", src2);

waitKey(0);

ng
}

Trong chương trình trên, ta lần lượt thực hiện tìm biên với hai ảnh nhị phân, một ảnh nhị
phân thông thường và một ảnh nhị phân đã đư ợc làm giản nở thông qua hàm erode. Hàm
findContours sẽ tìm đư ờng bao quanh của các đối tượng đã đư ợc nhị nhâp hóa và lưu

Lo
đường bao này vào một vector là các điểm nằm trên đường bao đó, hàm boundingRect sẽ
tìm ra một hình chữ nhật bao một tập điểm của một đường bao được tìm ra từ hàm
findContours. Dựa vào tỉ lệ kích thước chiều dài/rộng của hình chữ nhật này ta có thể xem
xét liệu đường bao mà ta tìm được có phải là một vùng của biển số hay không, nếu phải ta
sẽ vẽ hình chữ nhật này thông qua hàm rectangle với một màu khác (Scalar(0, 0, 255) :
màu đỏ) và nét đậm hơn. Kết quả chạy chương trình như sau

ăn
ễ nV
uy

Ta thấy rằng, điểm đứt trong ảnh được nối liền nhờ sự giản nở của biển cạnh biển số (sự
co lại của nền ảnh) nên ta tìm đư ợc một hình bao khép kín quanh biển số, tuy nhiên ta
cũng nh ận thấy rằng khi các đối tượng vật thể trong ảnh giản nở ra, các kí tự sẽ có xu
Ng

hướng dính vào nhau và việc tách các kí tự ra là khó khăn, chẳng hạn trên hình do có đinh
vit ở giữ mà số 2 và số 9 gần như nối liền. Đây là lúc ta cần thực hiện việc co lại của các
đối tượng (sự giản nở của nền ảnh), và do đó bạn đọc hoàn toàn có thể cài đặt để tách ra
các kí tự này.

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 42


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

9. Tìm biên ảnh dựa trên bộ lọc Canny

ng
Bộ lọc Canny là sự kết hợp của nhiều bước khác nhau để tìm và tối ưu đường biên, kết
quả là cho ra một đường biên khá mảnh và chính xác. Quá trình tìm biên sử dụng phương
pháp canny có thể được thực hiện qua 4 bước sau:

Bước 1: Loại bớt nhiễu trong ảnh.

Lo
Người ta loại nhiễu trong ảnh, làm cho ảnh mờ đi bằng cách nhân chập ảnh với một bộ
lọc Gause, chẳng hạn bộ lọc Gaus 5x5 với hệ số σ = 1.4:

2 4 5 4 2
⎡4 9 12 9 4⎤
1 ⎢ ⎥
= 5 12 15 12 5⎥
159 ⎢
⎢4 9 12 9 4⎥
⎣2 4 5 4 2⎦

ăn
Bước 2: Tính toán giá trị gradient trong ảnh

Vì đường biên trong ảnh là nơi phân cách giữa các đối tượng khác nhau, nên tại đó
gradient của nó sẽ biến đổi mạnh mẽ nhất. Để tính toán gradient trong ảnh, ta có thể sử
dụng bộ lọc Sobel, hoặc trực tiếp nhâp chập ma trận ảnh với các mặ nạ theo hướng x và y
nV
−1 0 +1 −1 −2 −1
chẳng hạn = −2 0 +2 , = 0 0 0
−1 0 +1 +1 +2 +1
Sau đó tính độ lớn gradient trong ảnh :

= + và =

Trong đó, Gx, Gy chính là đạo hàm theo hướng X, Y của điểm ảnh ta đang xét. Góc θ s ẽ
được làm tròn theo các hướng thẳng đứng, nằm ngang và hướng chéo, nghĩa là nó s ẽ được
uy

làm tròn để nhận các giá trị 0, 45, 90 và 135 độ.

Bước 3: Loại bỏ các giá trị không phải là cực đại

Bước này sẽ tìm ra những điểm ảnh có khả năng là biên ảnh nhất bằng cách loại bỏ đi
những giá trị không phải là cực đại trong bước tìm gradient ảnh ở trên. Ta thấy rằng, với
Ng

giá trị của góc θ ở trên thì biên của đối tượng có thể tuân theo bốn hướng, và ta có bốn
khả năng sau:

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 43


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

4. Nếu θ = 00, khi đó, điểm A sẽ được xem là


một điểm trên biên nếu độ lớn gradient tại A

ng
lớn hơn độ lớn gradient của các điểm tại A3,
A7 .
5. Nếu θ = 450, khi đó, điểm A sẽ được xem là
một điểm trên biên nếu độ lớn gradient tại A
lớn hơn độ lớn gradient của các điểm tại A4,

Lo
A8
6. Nếu θ = 900, khi đó, điểm A sẽ được xem là
một điểm trên biên nếu độ lớn gradient tại A
lớn hơn độ lớn gradient của các điểm tại A1,
A5 .
7. Nếu θ = 1350, khi đó, điểm A sẽ được xem là một điểm trên biên nếu độ lớn
gradient tại A lớn hơn độ lớn gradient của các điểm tại A2, A6

ăn
Bước 4: Chọn ra biên của đối tượng trong ảnh

Sau bước trên, ta thu được tập các điểm tương ứng trên đường biên khá mỏng. Vì những
điểm có giá trị gradient lớn bao giờ cũng có xác suất là biên thật sự hơn những điểm có
nV
giá trị gradient bé, đo đó để xác định chính xác hơn nữa biên của các đối tượng, ta sử
dụng các ngưỡng. Theo đó, bộ lọc canny sẽ sử dụng một ngưỡng trên (upper threshold) và
một ngưỡng dưới (lower threshold), nếu gradient tại một điểm trong ảnh có giá trị lớn hơn
ngưỡng trên thì ta xác nhận đó là một điểm biên trong ảnh, nếu giá trị này bé hơn ngưỡng
dưới thì đó không phải điểm biên. Trong trường hợp giá trị gradient nằm giữa ngưỡng
trên và ngưỡng dưới thì nó chỉ được tính là điểm trên biên khi các điểm liên kết bên cạnh
của nó có giá trị gradient lớn hơn ngưỡng trên.

Tìm biên ảnh bằng bộ lọc canny trong OpenCV:

OpenCV cung cấp một hàm cho ta xác định biên trong ảnh của các đối tượng. Nguyên
uy

mẫu của hàm này như sau :

canny(Mat src, Mat dst, int lower_threshold, int upper_threshold, int kernel_size)

Trong đó, src là ảnh cần phát hiện biên (là ảnh xám), dst là ảnh chứa biên được phát hiện,
Ng

lower_threshold, upper_threshold là ngưỡng dưới, ngưỡng trên như đã nói bên trên,
kernel_size là kích thước của mặt nạ dùng cho bước thứ hai để tính toán gradient của các
điểm trong ảnh.

Chương trình demo tìm biên trong ảnh dựa trên bộ lọc canny:

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 44


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

#include "stdafx.h"
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

ng
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

void main()
{
Mat gray = cv::imread("TuoiTho.jpg", CV_LOAD_IMAGE_GRAYSCALE);

Lo
Mat dst1, dst2;
imshow("Anh xam", gray);
GaussianBlur(gray, gray, Size(9,9), 2);
double t1 = 30, t2 = 200;
Canny(gray, dst1, t1, t2, 3, false);
t1 = 100; t2 = 120;
Canny(gray, dst2, t1, t2, 3, false);
imshow("Bien trong anh voi nguong 1", dst1);
imshow("Bien trong anh voi nguong 2", dst2);

}
waitKey(0);

ăn
Trong chương trình trên, trước khi tìm biên bằng phương pháp canny ta đã làm trơn một
nV
ảnh xám bằng bộ lọc GaussianBlur (việc làm này có ý rất lớn trong việc giảm thiểu các
tiểu tiết không mong muốn trong đường biên sau này). Về nguyên tắc, bộ lọc
GaussianBlur cũng là m ột bộ lọc số như đã giới thiệu ở bài trước về lọc số trong ảnh, cấu
trúc của hàm GaussianBlur như sau:

cv::GaussianBlur(cv::InputArray src, cv::OutputArray dst, cv::Size ksize, double sigmaX,


double sigmaY = 0, int borderType = 4), trong đó src và dst sẽ là các mảng dữ liệu đầu
vào và kết quả. Một ảnh cũng có th ể coi là một mảng dữ liệu, đo đó src và dst chính là

ảnh đầu và ảnh thu được sau khi biến đổi, ksize là kích thước của ma trận lọc, sigmaX,
sigmaY là độ lệch chuẩn theo hướng x và y. Nếu sigmaY = 0, độ lệch chuẩn theo hướng y
uy

sẽ được lấy theo sigmaX. BorderType là cách nội suy biên đối với những điểm ảnh tràn ra
ngoài kích thước của ảnh.

Kết quả thu được sau các ngưỡng t1, t2 khác nhau, ta được những biên có độ chi tiết khác
nhau như hình sau.
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 45


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

ng
Lo
ăn
ễ nV
uy

10. Chuyển đổi Hough, Phát hiện đường thẳng, đường tròn trong ảnh

Chuyển đổi Hough (Hough transformation) là một phương pháp được dùng nhiều trong
Ng

phân tích và xử lý ảnh, mục đích chính của phương pháp này là tìm ra nh ững hình dáng
đặc trưng trong ảnh bằng cách chuyển đổi không gian ảnh ban đầu sang một không gian
của các tham số nhằm đơn giản quá trình tính toán, trong bài này ta xét chuyển đổi Hough
cho đường thẳng và đường tròn.

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 46


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

Chuyển đổi Hough cho đường thẳng.

ng
Ta đã biết rằng, một đường thẳng trong không gian hai chiều có thể được biểu diễn dưới
dạng y = kx + m và cặp hệ số góc k, giá trị m có thể được chọn để làm đặc trưng cho một
đường thẳng. Tuy nhiên, cách biểu diễn theo cặp (k, m) khó thỏa mãn với những đường
thẳng thẳng đứng khi mà hệ số góc là một số vô cùng. Để tránh trường hợp này, ta sẽ biểu
diễn đường thẳng trong hệ tọa độ cực.

Lo
Phương trình đư ờng thẳng trong hệ tọa độ cực
có dạng như sau:

= ( )+ ( )

Trong đó, r là khoảng cách từ gốc tọa độ O tới


đường thẳng, θ là góc cực.

= ( )+ ăn
Như vậy, với mỗi điểm (x0, y0) ta có một họ
các đường thẳng đi qua thõa mãn phương trình
( )

Phương trình này biểu diễn một đường cong, như vậy trong một tấm ảnh có n điểm (n
nV
pixel) ta sẽ có n các đường cong. Nếu đường cong của các điểm khác nhau giao nhau, thì
các điểm này cùng thuộc về một đường thẳng.
Bằng cách tính các giao điểm này, ta sẽ xác định
được đường thẳng, đó là nội dung ý tưởng của
thuật toán Hough cho đường thẳng.

Chuyển đổi Hough cho đường tròn


Chuyển đổi Hough cho đường tròn cũng tương


tự như với đường thẳng, phương trình đư ờng
uy

tròn được xác định bởi:

= +
= +

Trong đó, (u,v) là tâm đường tròn, R là bán kính đường tròn, θ là góc có giá trị từ 0 tới
Ng

360 độ. Một đường tròn sẽ hoàn toàn được xác định nếu ta biết được bộ ba thông số
(u,v,R). Từ phương trình trên ta có thể chuyển đổi tương đương

= −
= −

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 47


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

Ta xét với trường hợp đã biết trước giá trị của R. Khi đó, với mỗi điểm ảnh (x,y) ta sẽ xác
định được một giá trị (u,v) và lưu nó vào một mảng. Tâm của đường tròn sẽ là giá trị xuất

ng
hiện trong mảng với tần suất lớn nhất. Trong trường hợp R chưa biết, ta tăng giá trị của R
từ một ngưỡng min tới ngưỡng max nào đó và tiến hành như với trường hợp đã biết trước
giá trị R

Tìm đường thẳng, đường tròn trong ảnh.

Lo
Để tìm đường thẳng trong ảnh, thư viện OpenCV có hỗ trợ hàm cv::HoughLines và
cv::HoughLinesP, hai hàm này về cơ bản đều thực hiện một thuật toán, nhưng đưa ra kết
quả ở hai dạng khác nhau.

Dạng 1: đưa ra kết quả của đường thẳng là một cặp giá trị (r, θ)

cv::HoughLines(src, lines, rho, theta, threshold, param)

ăn
Trong đó, src là ảnh nhị phân chứa biên của các đối tượng cần được phát hiện đường
thẳng (trong thực tế ta có thể sử dụng là một ảnh xám), lines là vector chứa kết quả đầu ra
của (r, θ) đã đư ợc phát hiện, rho là độ phân giải của r (tính theo pixel, thực chất đó là
bước tăng nhỏ nhất của r để tính toán), theta là độ phân giải của góc θ (có giá trị từ 0 tới
nV
360 độ), threshold là giá trị nhỏ nhất của tổng các giao điểm của các đường cong để xác
định đường thẳng, param là một thông số mặc định của OpenCV chưa sử dụng đến (nó có
giá trị bằng 0)

Dạng 2: đưa ra kết quả là tọa độ điểm đầu và điểm cuối của đường thẳng

cv::HoughLinesP(src, lines, rho, theta, threshold, minLenght, maxGap)

Trong đó, các thông số src, rho, theta, threshold giống như dạng 1, lines là vector chứa

tọa độ điểm đầu và tọa độ điểm cuối của đường thẳng phát hiện được (x1, y1, x2, y2),
minLength là độ dài nhỏ nhất để có thể xem nó là một đường thẳng (tính theo đơn vị
uy

pixel), maxGap là khoảng cách lớn nhất của hai điểm cạnh nhau để xác định chúng có
thuộc về một đường thẳng hay không (tính theo đơn vị pixel)

Đối với đường tròn, ta cũng áp dụng hàm tương tự: cv::HougCircles(), nguyên mẫu của
hàm như sau:
Ng

cv::HougCircles(src, circles, method, dp, min_dist, param1, param2, min_radius,


max_radius)

Trong đó, src có thể là một ảnh nhị phân chứa biên của đối tượng hoặc ảnh xám (chương
trình sẽ tự động dò biên bằng phương pháp canny), circles là vector chứa kết quả phát

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 48


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

hiện đường tròn với 3 tham số (a, b, R), method là phương pháp phát hiện đương tròn
(hiện tại phương pháp trong OpenCV là CV_HOUGH_GRADIENT), dp là tỉ số nghịch

ng
đảo của độ phân giải (áp dụng trong phương pháp hiện tại), min_dist là khoảng cách nhỏ
nhất giữa hai tâm đường tròn phát hiện được, param1, param2 là các thông số ngưỡng
trên và ngưỡng dưới phục vụ cho việc phát hiện biên bằng phương pháp canny,
min_radius và max_radius là giới hạn nhỏ nhất, lơn nhất của bán kính đường tròn ta cần
phát hiện(Nếu ta không biến bán kính, để mặc định hai giá trị này bằng không thì chương

Lo
trình sẽ lần tăng giá trị bán kính một cách tự động để tìm ra tất cả các đường tròn trong
ảnh).

Trong đó : …

Chương trình tìm đư ờng thẳng, đường tròn trong ảnh:


#include "stdafx.h"
#include
#include
#include
#include
ăn
<opencv2\core\core.hpp>
<opencv2\imgproc\imgproc.hpp>
<opencv2\highgui\highgui.hpp>
<iostream>

using namespace cv;


using namespace std;
nV
void main()
{
Mat src = imread("DongHo.jpg", 1);
Mat gray;
cvtColor(src, gray, CV_BGR2GRAY);
GaussianBlur(gray, gray, Size(9, 9), 2, 2 );

// Tim duong thang


Mat canny;
Canny(gray, canny, 100, 200, 3, false);
vector<Vec4i> lines;
uy

HoughLinesP(canny, lines, 1, CV_PI/180, 50, 60, 10);

// Tim duong tron


vector<Vec3f> circles;
HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, 100, 200, 100, 0, 0);

// Ve duong thang, duong tron len anh


Ng

for(int i = 0; i < lines.size(); i++ )


{
Vec4i l = lines[i];
line(src, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 2);
}

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 49


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

for(int i = 0; i < circles.size(); i++ )


{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); // Tam

ng
int radius = cvRound(circles[i][2]); // Ban kinh
circle(src, center, radius, Scalar(0,0,255), 2, 8, 0 );
}

imshow("Anh sau khi tim thay duong thang - Duong tron", src);
waitKey(0);

Lo
}

Trong chương trình trên, đ ầu tiên ảnh được tải vào một biến src, sau đó được chuyển đổi
qua ảnh xám gray và được làm trơn đi nhờ hàm GaussianBlur. Đối với việc phát hiện
đường thẳng, ta cần phát hiện ra tập các điểm biên của đối tượng, do vậy ta sử dụng hàm
canny để cho ra ảnh nhị phân chứa tập các điểm biên. Trong trường hợp phát hiện đường
tròn, hàm HoughCircles đã ngầm tìm biên cho ra thông thông qua hai ngưỡng đặt sẵn

ăn
(trong chương trình trên là 200 và 100) và do đó ta không c ần phải thực hiện việc này.
Kết quả thu được được vẽ lên ảnh gốc như hình sau.
ễ nV
uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 50


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

Chương III. Lập trình xử lý ảnh với giao diện MFC

ng
1. Giới thiệu về MFC

MFC (Microsoft Foundation Classes) là bộ thư viện được Microsoft phát triển phục vụ
cho việc lập trình trên Window. Bản chất của thư viện này là cung cấp cho ta các lớp, các
công để làm việc với các hàm API của Window, do vậy việc lập trình trở nên đơn giản
hơn rất nhiều.

Lo
Trong phần này và phần sau, các ví dụ và ứng dụng sẽ được tạo ra nhờ vào giao diện
Dialog của MFC. Việc nghiên cứu một cách đầy đủ và bài bản về MFC là một việc cần
đầu tư về thời gian và công sức. Trong khuôn khổ cuốn sách này, ta chỉ đi xem xét một
phần nhỏ và các thủ thuật để làm việc nhanh nhất được với MFC.

2. Khởi tạo project MFC

ăn
Khởi động Visual Studio, từ menu chọn File -> New - > Project (hoặc nhấn Ctrl + Shift +
N). Hộp thoại New Project hiện ra, họn Visual C++ (có thể sẽ phải chọn mục Other
langguge trước khi hiển ra Visual C++) sau đó chọn MFC Application. Ta đặt tên cho
project trong trường Name (giả sử tên là xyz) sau đó click OK để đến bước tiếp theo. Ở
nV
bước tiếp theo, tiếp tục chọn Next ta được hộp thoại sau

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 51


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

ng
Lo
ăn
nV
Application type chọn Dialog based, Project type chọn MFC standard… Chú ý rằng việc
tick vào chọn Use Unicode libraries sẽ có những ý nghĩa và cách dùng khác nhau, ta s ẽ
xét trường hợp này sau. Ta nhấn next để đi tới hộp thoại tiếp theo, hộp thoại này cho phép
ta tùy chỉnh một số chức năng của cửa sổ như có thêm nút phóng to, thu nhỏ, menu… có
thể để mặc định và chọn next

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 52


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

ng
Lo
ăn
nV
Ta nhấn Next để đi tới tùy chỉnh tiếp một số tùy chọn nâng cao, nếu chưa hiểu rõ ta có thể
để mặc định và nhấn Next. Hộp thoại cuối cùng xuất hiện yêu cầu ta chọn để MFC khởi
tạo lớp. Ta chọn là CxyzDlg và nhấn Finish

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 53


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

ng
Lo
ăn
nV
Đến đây ta đã khởi tạo xong một project MFC có dựa trên nền Dialog, Dialog hiện ra mặc
đinh có một button OK, button Cancel và một label, ta có thể để sử dụng hoặc xóa đi và
thiết kế theo ý riêng của mình.

3. Làm việc với các điều khiển (Control) của MFC


Đặt tên biến cho các control


Khi muốn sử dụng một Control nào, ta kéo
uy

control đó từ Toolbox và cho vào dialog. Để


làm việc với các control một cách dễ dàng, ta
nê đặt tên biến cho các control.
Để đặt tên biến cho một control, ta click chuột
phải vào control, sau đó chọn Add Variable.
Ng

Một hộp thoại Add Member Variable Wizard


hiện ra và trong mục variable name ta đặt tên
cho control đó. Chú ý là đối với các control mà
ID của nó có dạng IDC_STATIC (như Static
Text) thì ta chỉ có thể đặt tên biến được khi đổi

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 54


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

ID của nó, chẳng hạn như đổi thành IDC_STATIC1, IDC_LABEL …

ng
Lo
ăn
Lấy giá trị nhập vào từ một Edit Control
Để lấy giá trịn nhập vào từ một Edit Control, ta sử dụng hàm GetWindowText(). Giả sử
nV
như Edit Control được đặt tên là text_box, khi đó ta có thể lấy giá trị text trong ô Edit
Control như sau:
CString text;
text_box.GetWindowTextW(text);
Giá trị lấy được sẽ được lưu trong một chuỗi CString text. Một điểm cần chú ý từ giờ về
sau là các hàm trong MFC khi được build ở chế độ Unicode và chế độ Multi-byte thì các
gọi các hàm, cách sử dụng các hàm cũng có những điểm khác nhau nho nhỏ, chẳng hạn
như cũng là l ấy giá trị trong một ô Edit Control nhưng nếu ở chế độ Multi-byte ta sẽ dùng

lệnh sau:
CString text;
text_box.GetWindowTextA(text);
uy

Ta có thể tùy chỉnh để trình dịch build theo chế độ Unicode hoặc Multi-byte bằng cách
vào Project -> Properties (hoặc nhấn Alt + F7), hộp thoại Properties hiện ra, chọn
Configuration Properties -> General và tùy chỉnh ở mục Character Set.

Hiển thị text lên các control


Ng

Để hiển thị text lên các control (Button, Edit control, Static Text ..), ta dùng hàm
SetWindowText(CString text)
text_box.SetWindowTextW(_T("text")); // Unicode
text_box.SetWindowTextA("text"); // Multi-byte

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 55


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

Hiển thị ảnh lên một control


Để hiển thị ảnh lên một control, ta dùng hàm SetBitmap(). Đoạn code sau load một ảnh

ng
bitmap từ ổ D và hiển thị lên một button có tên là btn:
HBITMAP image = (HBITMAP)LoadImageA(0, "D:/test.bmp", IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
btn.SetBitmap(image);
Enable, disable một control
Ta sử dụng hàm EnableWindow để cho phép control có được phép sử dụng hay không

Lo
text_box.EnableWindow(flase); // Vo hieu hoa edit control
text_box.EnableWindow(true); // Cho phep edit control hoat dong

Lấy giá trị từ thanh trượt (Slider Control)


Giả sử thanh trượt được đặt tên là slider, khi đó ta có thể lấy giá trị hiện tại của thanh
trượt bằng hàm GetPos() : int value = slider.GetPos();
Ta cũng có thể dùng hàm SetRange để đặt giá trị lớn nhất và bé nhất cho thanh trượt, và
dùng hàm SetPos để đặt vị trí cho thanh trượt:

ăn
slider.SetRange(0, 100); slider.SetPos(50);

Lấy giá trị lựa chọn từ Combo Box


Combo box cho phép ta lựa chọn, chuyển đổi giữa các lựa chọn một cách nhanh chóng.
nV
Để thêm các lựa chọn vào Combo Box ta có thể điển trực tiếp vào mục Data trong
properties của nó, (giả sử ta có các lựa chọn về tỉnh thành ở Việt Nam như HaNoi,
ThanhHoa, DaNang …ta sẽ click chuột phải vào Combo box, chọn properties, trong bảng
properties ở mục Data ta điền vào HaNoi;ThanhHoa;DaNang… các lựa chọn được phân
cách bởi dấu ";" ). Để xem lựa chọn nào đang được chọn, ta dùng hàm GetCurSel().
int choice = combo_box.GetCurSel(); Giá trị trả về là thứ tự các dữ liệu trong mục
Data của Combo box

Dialog mở file và lưu file


Mục đích của loại dialog này là tạo ra một hộp thoại cho phép người dùng chọn đến
uy

đường dẫn để mở file và lưu file. Kết quả cuối cùng mà ta quan tâm nhất là lấy được
đường dẫn mà người dùng lựa chọn.
CString Filter=_T("image files (*.bmp; *.jpg) |*.bmp;*.jpg|All Files
(*.*)|*.*||");
CFileDialog dlg(TRUE, _T("*.jpg"), NULL,
OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,Filter,NULL);// Mo file
Ng

CFileDialog dlg(FALSE, _T("*.jpg"), NULL,


OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,Filter,NULL);// Luu file
Filter sẽ lọc và chỉ hiển thị những file tương ứng mà ta cần quan tâm, trong trường hợp
trên ta đang xét mở hoặc lưu một file ảnh do đó ta để file mở rộng là bmp hoặc jpg. Nếu
muốn hiển thị tất cả các loại file ta chỉ việc để filter là *.*, hộp thoại mở file và lưu file
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 56
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

chỉ khác nhau ở thông số đầu tiên khi tạo đối tượng dlg, nếu là mở file ta đặt là TRUE, lưu
file ta đặt là FALSE.

ng
Ta hiển thị hộp thoại này và lấy đường dẫn người dùng chọn như sau:
if(dlg.DoModal() == IDOK)
{
CString file_name = dlg.GetPathName();// lay duong dan day du

Lo
Xử lý sự kiện khi click chuột vào button
Hầu hết các control trong MFC đều có một hoặc nhiều sự kiện khi người dùng tương tác
với nó, chẳng hạn sự kiện click chuột vào button, sự kiện kéo thanh trượt của slider
control … Để xử lý các sự kiện cho các control, trong mục properties của control tương
ứng ta chọn vào icon sự kiện sau đó chọn các sự

chọn BN_CLICKED

ăn
kiện cần xử lý. Chẳng hạn đối với button, ta
rồi
OnBnClickedButton1, khi đó ta sẽ có một hàm
chọn

được tự động sinh ra và ta có thể xử lý các vấn


đề liên quan tới sự kiện click chuột. Hàm sau sẽ
nV
sinh ra một Message Box khi click vào button1
void CxyzDlg::OnBnClickedButton1()
{
MessageBoxA(NULL, "Button1 duoc
click", "Thong bao", 0);
}

Xử lý sự kiện khi thay đổi lựa chọn Combo Box


Sự kiện thay đổi lựa chọn của Combo Box là CBN_SELENDOK. Đoạn code sau mô tả sự
uy

thay đổi lựa chọn của người dùng trên Combo Box, với mỗi lựa chọn ta sinh ra một
Message Box tương ứng.
void CxyzDlg::OnCbnSelendokCombo1()
{
int index = combo_box.GetCurSel();
switch(index)
{
Ng

case 0:
MessageBoxA(NULL, "Ban chon HaNoi", "Thong bao", 0);
break;
case 1:
MessageBoxA(NULL, "Ban chon ThanhHoa", "Thong bao", 0);
break;
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 57
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

case 2:
MessageBoxA(NULL, "Ban chon DaNang", "Thong bao", 0);
break;

ng
default:
MessageBoxA(NULL, "Khong chon?", "Thong bao", 0);
break;
}

Lo
Thêm menu vào chương trình, x ử lý sự kiện khi click vào menu
Menu trong MFC được xem là resource của chương trình . Việc thêm menu vào dialog đòi
hỏi ta phải thêm nó vào resource của chương trình. Trư ớc hết ta hiển thị cửa sổ xem các
resource của chương trình bằng cách từ menu của Visual Studio chọn View -> Resource
View (hoặc nhận tổ hợp phìm Ctrl + Shift + E). Cửa sổ Resource View hiện ra, ta click

ăn
chuột phải vào đó, chọn Add->Resource… Cửa sổ Add Resource hiện ra, ta chọn Menu
và click vào button New. Ngay sau đó ta sẽ có một
resource chứa menu chống, ta tiến hành điền tên
các menu mà ta muốn chương trình thực hiện. Hình
bên ta điền 3 menu chưc năng nhỏ là Open, Save và
nV
Exit… Khi đã tạo xong menu, nó vẫn chỉ nằm ở
trong resource của chương trình, mu ốn menu này
được gắn vào dialog khi chạy, ta vào properties của
dilog, trong mục Menu chọn IDR_MENU1, với IDR_MENU1 chính là ID của menu ta
vừa tạo ra.
Để xử lý sự kiện click chuột của menu nào, ta click chuột phải vào mune ấy và chọn Add
Event Handler…

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 58


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

ng
Lo
ăn
nV
Ở hộp thoại Event Handler Wizard ta chọn lớp mà ta muốn thêm menu vào sau đó click
vào button Add and Edit để đi tới hàm xử lý sự kiện khi click vào menu, ví dụ sau là ta
gọi hàm OnCancel() để thoát khỏi chương trình
void CxyzDlg::OnUpdateFileExit(CCmdUI *pCmdUI)
{
OnCancel();
}

uy

Ngoài một số điều khiển thông dụng đã nhắc tới ở trên, MFC còn cung cấp rất nhiều các
điều khiển khác giúp cho việc tạo ra giao diện một cách dễ dàng và đẹp mắt hơn. Bạn đọc
tham khảo thêm các tài liệu khác về phần này.

4. Chuyển đổi các kiểu dữ liệu trong MFC


Ng

Các kiểu dữ liệu của MFC về cơ bản là giống với các kiểu dữ liệu trong C, tuy nhiên có
một số trường hợp ta phải chuyển đổi qua lại giữa các kiểu dữ liệu để phù hợp với đầu
vào, đầu ra của một việc nào đó, chẳng hạn khi ta dùng CFileDialog để mở một đường
dẫn sau đó đọc ảnh từ đường dẫn này, kết quả trả về đường dẫn của CFileDialog là một
chuỗi CString tuy nhiên hàm cv::imread lại đọc ảnh từ một chuỗi string, do đó ta phải
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 59
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

chuyển đổi từ CString sang string. Một số chuyển đổi sau đây là hữu ích cho việc hiển thị
giao diện, và lấy dữ liệu từ giao diện người dùng.

ng
Chuyển đổi string sang CString và ngược lại
Với chế độ biên dịch không sử dụng Unicode, ta có thể chuyển đổi hai kiểu dữ liệu này dễ
dàng:

8. Chuyển từ string sang CString:

Lo
std::string str = "chuoi string";
CString cstr = str.c_str();
9. Chuyển từ CString sang string
CString cstr = "chuoi cstring";
std::string str = std::string(cstr);

với chế độ biên dịch có sử dụng Unicode:

ăn
10. Chuyển từ string sang CString:
std::string str = "chuoi string";
CStringW cstr = (CStringW)(str.c_str());
11. Chuyển từ CString sang string
CString cstr = _T("chuoi cstring");
std::wstring wstr = (std::wstring)cstr;
nV
std::string str;
str.assign(wstr.begin(), wstr.end());

Chuyển đổi số sang CString và ngược lại


Cách chuyển đổi này giúp ta lấy số liệu dưới dạng số của các ô Edit Control hoặc hiển thị
số lên các control.

12. Chuyển đổi CString sang số


CString cstr1 = "123";

int num1 = atoi(cstr1); // Chuyen sang so nguyen khong su dung Unicode


int num1 = _wtoi(cstr1); // Chuyen sang so nguyen su dung Unicode
CString cstr2 = "10.5";
uy

float num2 = atof(cstr2) // Chuyen sang so thuc khong su dung Unicode


float num2 = _wtof(cstr2) // Chuyen sang so thuc su dung Unicode
13. Chuyển số sang CString
char s1[20];
int num = 10;
sprintf(s1, "%d", num);
Ng

CString cstr1 = s1; // chuyen so nguyen sang cstring - khong unicode


CString cstr2;
cstr2.Format(_T("%d"), num); // chuyen so nguyen sang cstring unicode
Tương tự với số thực ta có thể chuyển đổi tương tự và thay "%d" bằng "%f".

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 60


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

5. Chương trình tải ảnh và hiển thị ảnh trên giao diện MFC

ng
Ta tạo một mới một project, đặt tên project là HienThiThongTinAnh và thiết kế giao diện
như hình sau:

Group Box

Lo
Picture
control Labels
Label

ăn Button
nV
Trong đó, các Labels hiển thị thông tin của ảnh chính là Static Text, ID IDC_STATIC của
chúng được đổi và chúng được đựt tên biến là l_width, l_height, l_channels, l_path (đối
với các static text dùng để hiển thị kết quả). ID của Picture Control được đổi thành
IDC_STATIC_PICTURE.
Bây giờ, ta sẽ tiến hành xử lý sự kiện cho các button Tai Anh, Thong Tin và Thoat

void CHienThiThongTinAnhDlg::OnBnClickedButton1()
{
// Load anh va hien thi anh

static CString Filter=_T("image files (*.bmp; *.jpg) |*.bmp;*.jpg|All


Files(*.*)|*.*||");
uy

CFileDialog Load(TRUE, _T("*.jpg"), NULL,


OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,Filter,NULL);
Load.m_ofn.lpstrTitle= _T("Load Image");
if (Load.DoModal() == IDOK)
{
path = Load.GetPathName();
Ng

std::string filename(path);
src = cv::imread(filename,1);
cv::namedWindow("Hien thi anh", 1);
HWND hWnd = (HWND) cvGetWindowHandle("Hien thi anh");
HWND hParent = ::GetParent(hWnd);
::SetParent(hWnd, GetDlgItem(IDC_STATIC_PICTURE)->m_hWnd);

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 61


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

::ShowWindow(hParent, SW_HIDE);
// resize va hien thi anh
cv::Mat dst;

ng
cv::resize(src, dst, cv::Size(320, 240), 0, 0, 1);
cv::imshow("Hien thi anh", dst);

Lo
Trong hàm trên hàm cvGetWindowHandle() trả về một HWND của cửa sổ hiển thị ảnh
trong OpenCV, sau đó hàm SetParent() sẽ đặt cửa sổ này vào điều khiển Picture Control
của MFC, đây là cách ta dùng để hiển thị một ảnh lên bất kì một control nào của MFC
thay vì phải chuyển đổi ảnh sang HBITMAP và dùng hàm SetBitmap. Hàm cv::resize()
giúp chuyển từ một ảnh bất kì về ảnh có kích thước 320x240 giúp hiển thị trọn trong kích
thước của Picture Control. Cuối cùng, khi gọi hàm cv::imshow() trong OpenCV, ta sẽ
thấy ảnh được hiển thị trong Picture Control.

ăn
void CHienThiThongTinAnhDlg::OnBnClickedButton2()
{
// Hien thi thong tin anh
if(src.empty())
MessageBoxA("Chua tai anh", "Thong bao", 0);
nV
else
{
int width_ = src.cols;
int height_ = src.rows;
int channels_ = src.channels();

CString result;
result.Format("%d", width_);
l_width.SetWindowTextA(result);

result.Format("%d", height_);
l_height.SetWindowTextA(result);
result.Format("%d",channels_);
uy

l_channels.SetWindowTextA(result);
l_path.SetWindowTextA(path);

}
Ng

Trong hàm trên, đầu tiên ta kiểm tra xem ảnh đã đư ợc tải ở bước trên hay chưa, nếu chưa
thì hiện ra thông báo chưa tải được ảnh, nếu đã t ải được ảnh rồi thì tính toán chiều dài,
chiều rộng, kênh màu của ảnh … sau đó hiển thị thông tin này lên các label tương ứng.
Để hiện lên label hoặc bất kì một điều khiển nào của MFC ta dùng thuộc tính
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 62
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

SetWindowText(). Tuy nhiên đối số đầu vào của hàm này lại có định dạng CString, do vậy
các giá trị như int, float, double .. muốn hiển thị được trên các điều khiển của MFC ta

ng
phải chuyển đổi kiểu dữ liệu như đã nói ở trên.
Ở button Thoat, khi click vào vào chương trình sẽ thoát ra, việc thoát khỏi chương trình là
rất đơn giản, có thể đạt được bằng cách gọi hàm OnCancel().

void CHienThiThongTinAnhDlg::OnBnClickedButton3()
{

Lo
// Thoat khoi chuong trinh
this->OnCancel();

Kết quả chạy chương trình như sau:

ăn
ễ nV
uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 63


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

Chương IV Một số ứng dụng xử lý ảnh trong thực tế.

ng
1. My Photo Editor, phần mềm chỉnh sữa ảnh đơn giản

Mục đích của bài này là tổng hợp lại các kiến thức và kĩ năng cơ b ản của các thuật toán
xử lý ảnh thong qua việc viết một chương trình tương có kh ả năng chỉnh sửa ảnh tương tự
như phần mềm Photoshop, tuy nhiên xét về mọi mặt thì đơn gi ản hơn rất nhiều. Một số
chức năng của phần mềm mà ta có thể sử dụng để chỉnh sửa ảnh như: chuyển đổi qua lại

Lo
giữa các không gian màu(HSV, YCmCb, RGB…), Tăng giảm độ sang/độ tương phản,
đảo ngược ảnh(ảnh âm bản), nhị phân ảnh, quay ảnh, thay đổi kích thước ảnh, cân bằng
histogram trong ảnh, làm mờ ảnh, méo ảnh … Ngoài ra ta có thể xem xét các thông tin về
ảnh, xem biểu histogram ảnh và lịch sử chỉnh sủa ảnh và xem ảnh trước khi đồng ý chỉnh
sửa. Giao diện chương trình như sau:

ăn
ễ nV
uy

Để quản lý chương trình đư ợc đơn giản, ta chia chương trình làm 3 phần, một phần
chuyên về các hàm xử lý ảnh được lưu trong một lớp và đặt tên là ImgProcessing. Phần
cho phép ta xem trước kết quả xử lý là một Dialog có chứa các thanh trượt giúp ta điều
chỉnh các tham số đồng thời xem trước kết quả của quá trình xử lý. Phần này được định
Ng

nghĩa trong l ớp ImgPreview. Cuối cùng là phần chứa giao diện chính của chương trình là
Dialog được thiết kế từ đầu, ta định nghĩa các biến và xử lý sự kiện trong file My Photo
EditorDlg.h và Photo EditorDlg.cpp. Ta sẽ lần lượt xem qua các phần này.

Phần chứa các hàm xử lý ảnh chính

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 64


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

Các hàm xử lý trong ImgProcessing có dạng void TenHam(cv::Mat &src, cv::Mat& dst,
type value) trong đó, src là ảnh đưa vào xử lý, dst là ảnh chứa kết quả và value thì tùy

ng
thuộc vào đặc trưng của từng hàm mà có các kiểu và giá trị khác nhau. Ta định nghĩa
header của lớp ImgProcessing như sau:
// heade file

#pragma once

Lo
#include <opencv2\core\core.hpp>

using namespace cv;

class ImgProcessing
{
public:
ImgProcessing(void);
~ImgProcessing(void);
// Ham xu ly

ăn
void Grayscale(Mat&, Mat&);
void ColorSpace(Mat&, Mat&, int);
void Brightness(Mat&, Mat&, int);
….
nV
}
Và file cài đặt ImgProcessing.cpp có dạng như sau (đối với hàm tăng giảm độ sang):
// Brightness
void ImgProcessing::Brightness(Mat &src, Mat &dst, int value)
{
if(src.channels() == 1)
{
for(int i = 0; i < src.rows; i++)
for(int j = 0; j < src.cols; j++)

dst.at<uchar>(i,j) =
saturate_cast<uchar>(src.at<uchar>(i,j) + value);
}
uy

if(src.channels() == 3)
{
for(int i = 0; i < src.rows; i++)
for(int j = 0; j < src.cols; j++)
for(int k = 0; k < 3; k++)
dst.at<Vec3b>(i,j)[k] =
Ng

saturate_cast<uchar>(src.at<Vec3b>(i,j)[k] +
value);
}
}

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 65


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

Các hàm khác có thể cài đặt một cách tương tự giống như nhiều bài trước đã đề cập.

ng
Phần xem trước kết quả chỉnh sửa

Phần này nằm trong lớp ImgPreview kế thừa tử lớp CDialogEx của MFC. Để tạo ra lớp
này, cách đơn giản nhất là thêm vào project một dialog và thêm lớp vào cho dialog đó. Cụ
thể, từ cửa sổ Resource View -> My Photo Editor -> My Photo Editor.rc -> Dialog hãy
click chuột phải và chọn Insert Dialog, thiết kế một dialog có dạng như sau:

Lo
Picture Control

ăn Slider Control
Static text
nV
Button

Để thêm lớp vào dialog này ta click chuột phải vào dialog sau đó chọn Add Class, hộp
thoại MFC Add Class Wizard hiện ra, ta đặt tên cho class ở mục Class name là
ImgProcessing và ở mục Base class ta chọn là CDialogEx. Nhấn Finish để hoàn thành

quá trình khởi tạo lớp và ta có hai file mới được thêm vào, đó là ImgProcessing.h và
ImgProcessing.cpp . Tiếp đó ta thêm biến cho các điều khiển(Control) bằng cách click
uy

chuột phải vào các điều khiển sau đó chọn Add Variable . Các biến của slider tùy chọn
tham số có tên là silder1, slider2, slider3 , các biến là nhãn (Static text) dung để đặt tên
hiển thị cho slider có tên lần lượt là l_disp1, l_disp2, l_disp3 và cuối cùng tên của biến
hiển thị kết quả khi kéo trượt slider là param1, param2, param3.
Ng

Vì trong chương trình có rất nhiều các hàm xử lý cần đến việc phải xem trước kết quả và
việc với mỗi hàm xử lý cần đến một dialog để xem trước là rất phức tạp vì ta phải tạo ra
số lượng dialog lớn tương ứng với lượng hàm xử lý. Để khác phục tình trạng này, ta chỉ
tạo ra một dialog preview duy nhất và tùy thuộc vào yêu cầu xử lý của từng hàm mà ta

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 66


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

cho nó một kiểu nhất định. Do vậy ta sẽ định nghĩa một kiểu liệt kê các kiểu xử lý trong
file ImpProcessing.h như sau:

ng
enum {m_brightness = 0, m_contrast = 1,m_threshold = 2, m_rotation = 3,
m_size = 4, m_blur = 5, m_noise = 6, m_distort = 7, m_edge = 8};
Và khi dialog Image Preview được khởi tạo, trong hàm OnInitDialog() ta, sẽ căn cứ vào
yêu cầu của hàm cần xử lý để tạo ra cửa sổ thích hợp
BOOL ImgPreview::OnInitDialog()

Lo
{
CDialogEx::OnInitDialog();

// Khoi tao cac thanh phan


this->slider1.SetRange(0, 450);
this->slider1.SetPos(225);
this->slider2.SetRange(0, 450);
this->slider2.SetPos(225);
this->slider3.SetRange(0, 450);

ăn
this->slider3.SetPos(225);

this->slider2.EnableWindow(false);
this->slider3.EnableWindow(false);

switch(type)
nV
{
case m_brightness:
{
this->param1.SetWindowTextA("Brightness");
}
break;

case m_contrast:
{
this->param1.SetWindowTextA("Contrast");

this->slider1.SetRange(0, 100);
this->slider1.SetPos(10);
}
uy

….
}
}

Trong đó, type là là kiểu xử lý được truyền từ chương trình chính tương ứng với từng hàm
xử lý khác nhau. type được định nghĩa trong file ImageProcessing.h là
Ng

private:
int type;
và được gán giá trị thông qua phương thức SetType(int type_) :
void ImgPreview::SetType(int type_)
{
this->type = type_;
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 67
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

}
Khi thanh trượt điều chỉnh các thong số thay đổi vị trí, ảnh phải được xử lý và hiển thị

ng
trực quan lên Picture Control. Sự kiện thay đổi vị trí thanh trượt được cài đặt như sau:
void ImgPreview::OnNMCustomdrawSlider1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
switch(type)
{

Lo
case m_brightness:
{
i_param1 = slider1.GetPos() - (int)slider1.GetRangeMax()/2;
l_disp1.SetWindowTextA(ToString(i_param1));
process.Brightness(img_display, img_dst, i_param1);
cv::imshow("Image Preview", img_dst);

}
break;

case m_contrast:
{
ăn
d_param1 = double(slider1.GetPos() / 10.0);
l_disp1.SetWindowTextA(ToString(d_param1));
process.Contrast(img_display, img_dst, d_param1);
nV
cv::imshow("Image Preview", img_dst);
}
break;

}
}

Trong đó, img_dst là một ảnh thu nhỏ của ảnh gốc, được thay đổi kích thước để hiển thị

phù hợp trong khung hình thông qua hàm ImageDisplay()


uy

Khi việc xem trước hoàn tất, nếu ta đồng ý với kết quả đó và bấm vào nút OK, một biến
thành viên is_ok sẽ được gán giá trị cho chương trình chính biết rằng người dung đã đồng
ý với việc chỉnh sữa đó, ngược lại nếu nút Cancel được bấm, biến này sẽ có giá trị false.
void ImgPreview::OnBnClickedButton1()
{
Ng

// Button OK
this->is_ok = true;
ImgPreview::OnCancel();
}

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 68


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

Phần chương trình chính

ng
Phần này chứa giao diện người dung trong quá trình xử lý ảnh. Nó bao gồm các thao tác
mở file ảnh, thực hiện các hàm xử lý như đã nêu ở trên, hiển thị các cửa sổ view khác
nhau và lưa file ảnh sau khi hoàn tất xử lý.

Ta thiết kế giao với hệ thống menu, button và các cửa sổ hiển thị ảnh được tạo ra từ
Picture Control … như hình 1. Các Control khác trong MFC là khá quen thuộc, đối với hệ

Lo
thống menu, để thêm vào dialog đâu tiên ta thiết kế hệ thống menu bằng cách click chuột
phải vào Project sau đó chọn Add -> Resource-> Menu. Sau khi đã hoàn thành h ệ thống
menu ta hãy vào dialog chính, ở mục Properties của dialog này chọn Menu và chọn ID
của menu mà ta vừa tạo ra, giả sử là IDR_MENU1

ăn
Để xử lý sự kiện click chuột cho menu, ta hãy quay lại phần thiết kế menu, click chuột
nV
phải vào menu cần xử lý sau đó chọn Add Event Handler. Sự kiện click chuột vào menu
Open có dạng như sau:
void CMyPhotoEditorDlg::OnUpdateFileOpen1(CCmdUI *pCmdUI)
{
// Open Image

static CString Filter=_T("image files (*.bmp; *.jpg) |*.bmp;*.jpg|All


Files (*.*)|*.*||");
CFileDialog Load(TRUE, _T("*.bitmap"), NULL,

OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,Filter,NULL);
Load.m_ofn.lpstrTitle= _T("Load Image");
if (Load.DoModal() == IDOK)
uy

{
img_path = Load.GetPathName();
img_name = Load.GetFileName();
src = cv::imread(img_path);

}
Ng


}
Sự kiện mở file ảnh của nút Open cũng có th ể được định nghĩa một cách tương tự.

Sau khi file ảnh được mở, ta có thể tiến hành thực hiện các phép xử lý ảnh thống qua hệ
thống menu vừa thiết kế ở trên. Đối với các phép xử lý cho kết quả trực tiếp mà không
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 69
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

cần phải xem trước kết quả (như chuyển sang màu xám, làm ngược ảnh …) thì sự kiện
click chuột vào menu đó được sử lý có dạng như sau:

ng
void CMyPhotoEditorDlg::OnUpdateAdjustmentsInvert(CCmdUI *pCmdUI)
{
// Invert Image
process.Invert(src, src);
ImageDisplay(src);

Lo
}

Đối với những menu cần xem trước kết quả thì ta chỉ sử lý ảnh khi đã nh ận được tham số
từ dialog preview, như vậy trước khi xử lý ta phải khởi tạo một dialog preview, truyền
tham số cho dialog này và nhận tham số để xử lý. Sự kiện của những menu đó có dạng
như sau:
void CMyPhotoEditorDlg::OnUpdateAdjustmentsThreshold(CCmdUI *pCmdUI)
{
// Threshold
ImgPreview dlg;
ăn
dlg.SetType(m_threshold);
dlg.src = src;
dlg.DoModal();
nV
if(dlg.is_ok)
{
process.Threshold(src, src, dlg.i_param1);
ImageDisplay(src);
}
}
Trong đoạn code trên, ta truyền hai tham số cho lớp ImgPreview là m_threshold để cho
lớp này biết kiểu cần xử lý là kiểu gì và src là ảnh gốc cần được xem trước trong quá trình
xử lý. Hàm ImageDisplay() có tác dụng tùy chỉnh kích thước để hiện thị ảnh trong phù

hợp trong khung hình sau đó hiển thị ảnh đó lên. Process là một đối tượng của lớp
ImgProcessing được khai báo trong file My Photo EditorDlg.h
uy

Bên cạnh quá trình xử lý, ta thiết kế thêm các cửa sổ nhìn khác đ ể nắm được thông tin chi
tiết hơn về ảnh đang xử lý , đó là cửa sổ cho phép xem thông tin chi tiết về ảnh (kích
thước chiều dài, rộng, …), cửa sổ hiện thị thông tin histogram ảnh (một hoặc nhiều kênh
màu) và cửa sổ cho phép xem lịch sử chỉnh sửa ảnh. Các cửa sổ này thông qua hệ thống
Ng

menu view để cho phép nó hoạt động hay không hoạt động và ta sử dụng các biến kiểm
tra xem cửa sổ này có được kích hoạt hay không trong các hàm xử lý. Nếu nó được kích
hoạt (có check ở menu view) thì sau mỗi hàm xử lý ta phải cập nhật thông tin cho nó, nếu
không thì bỏ qua bước này, sẽ tiết kiệm được thời gian xử lý.
void CMyPhotoEditorDlg::OnUpdateViewHistogram(CCmdUI *pCmdUI)

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 70


Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV
{
// View histogram
is_histogram = !is_histogram;

ng
CMenu *m_histogram = GetMenu();
m_histogram->GetSubMenu(3);
if(is_histogram)
{
m_histogram->CheckMenuItem(ID_VIEW_HISTOGRAM, MF_CHECKED);
UpdateHistogram(0);
}

Lo
else
{
m_histogram->CheckMenuItem(ID_VIEW_HISTOGRAM, MF_UNCHECKED);
cv::imshow("Histogram", hist_init);
}

}
Và trong các hàm xử lý ta kiểm tra biến is_histogram để update cửa sổ view

{
// Invert Image
ăn
void CMyPhotoEditorDlg::OnUpdateAdjustmentsInvert(CCmdUI *pCmdUI)

process.Invert(src, src);
ImageDisplay(src);
if(is_histogram) UpdateHistogram(0);
if(is_history) history_list.AddString("Invert Image");
nV
}
Trong đó hàm UpdateHistogram sẽ tính toán histogram và hiển thị histogram của ảnh lên
cửa sổ, history_list là một Edit Control sẽ hiển thị lịch sử của bước vừa thao tác .

Khi quá trình chỉnh sữa ảnh hoàn tất ta có thể lưu ảnh lại thông qua nút Save hoặc menu
Save. Khi lưu ảnh, ta cho phép người sử dụng được lựa chọn chất lượng ảnh thông qua tỉ
số nén ảnh. Tỉ số này càng cao thì ảnh nén có dung lượng càng thấp, tuy nhiên thông tin
ảnh có thể bị mất nhiều. Ngược lại nếu tỉ số nén thấp thì ảnh sẽ chi tiết tuy nhiên dung

lượng lại lớn. Ta thiết kế một dialog SaveOption và xử lý sự kiện như đối với lớp
ImgPreview
uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 71


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

Một phần nữa cũng rất quan trọng trong quá trình xử lý đó là xác định tính hợp lệ của
từng menu. Theo đó, để hệ thống tránh gặp những lỗi phát sinh thì menu chỉ được phép sử

ng
dụng khi đã có các đi ều kiện có sẵn. Chẳng hặn như khi ảnh chưa được tải vào chương
trình thì toàn bộ các menu sẽ ẩn, chỉ có menu và nút Open và được phép hoạt động nếu
không khi click vào bất kì menu xử lý nào hệ thống sẽ gặp lỗi bộ nhớ do ảnh xử lý là ảnh
rỗng. Hoặc khi đang làm việc với ảnh xám (grayscale) thì menu chuyển đổi sang các
không gian màu khác (HSV chẳng hạn) phải bị ẩn nếu không muốn chương trình g ặp sự

Lo
cố và thoát ra ngoài ý muốn. Ta xác định tính hợp lệ của hệ thống menu thông qua các
biến và hàm ValidateMenu(). Ví dụ như biến is_load sẽ kiển tra xem ảnh được tải hay
chưa, biến is_gray kiểm tra ảnh có phải là ảnh xám hay không… các biến này được đặt
ngay sau các hàm tương ứng của chúng :
void CMyPhotoEditorDlg::OnUpdateFileOpen1(CCmdUI *pCmdUI)
{
// Open Image

}

ăn
if (Load.DoModal() == IDOK)
{

is_load = true;

nV
}
Và hàm ValidateMenu() có dạng như sau:
void CMyPhotoEditorDlg::ValidateMenu()
{
// Kiem tra tinh hop le cua menu trong tung truong hop
CMenu *m_file = GetMenu();
m_file->GetSubMenu(0);

CMenu *m_image = GetMenu();


m_image->GetSubMenu(1);


if(!is_load)
uy

{
m_file->EnableMenuItem(ID_FILE_SAVE1, MF_DISABLED|MF_GRAYED);
m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_DISABLED|MF_GRAYED);
save_btn.EnableWindow(false);

}

else
Ng

{
m_file->EnableMenuItem(ID_FILE_SAVE1, MF_ENABLED);
m_image->EnableMenuItem(ID_MODE_GRAYSCALE, MF_ENABLED);

}
if(is_gray)

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 72


Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV
{
m_image->EnableMenuItem(ID_MODE_HSV, MF_DISABLED|MF_GRAYED);
m_image->EnableMenuItem(ID_MODE_YCrCb, MF_DISABLED|MF_GRAYED);

ng

}
}
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

Lo
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.

ăn
ễ nV

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
uy

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ế.
Ng

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

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 73


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

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

ng
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ự.

Lo
ăn
nV
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
uy

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
Ng

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

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 74


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

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

ng
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.

Lo
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à

ăn
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
nV
đ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.
uy

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
Ng

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

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 75


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

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

ng
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ó

Lo
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

ă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ô
nV
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
uy

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
Ng

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ờ

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 76


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

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

ng
Lo
ăn
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
nV
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),
uy

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).
Ng

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.

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 77


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

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

ng
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ẽ

Lo
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<float> calculate_feature(Mat src)


{
Mat img;
if(src.channels() == 3)
cvtColor(src, img, CV_BGR2GRAY);
threshold(img, img, 100, 255, CV_THRESH_BINARY);

vector<float> r;

ăn
resize(img, img, Size(40, 40));
int h = img.rows/4;
int w = img.cols/4;
int S = count_pixel(img);
nV
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)


uy

{
float f = r[i] + r[i+1] + r[i+2] + r[i+3];
r.push_back(f);
}

for(int i = 0; i < 4; ++i)


Ng

{
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]);

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 78


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

r.push_back(r[3] + r[6] + r[9] + r[12]);


...
return r; //32 dac trung

ng
}

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;

Lo
int white = 0;
for(int i = 0; i < img.rows; ++i)
for(int j = 0; j < img.cols; ++j)
{
if(img.at<uchar>(i,j) == 0)
black++;
else
white++;
}

}
if(black_pixel)

else
ăn
return black;

return white;
nV
Đ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;
uy

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;
Ng

vector<string> folders = list_folder("D:/Data");


for(size_t i = 0; i < folders.size(); ++i)
{
cout<<i<<"..."<<endl;
vector<string> files = list_file(folders.at(i));

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 79


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

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)

ng
{
src = imread(files.at(j));
if(src.empty()) continue;
vector<float> feature = calculate_feature(src);
if(feature.size() < number_of_feature)
{

Lo
cout<<"error " <<endl;
continue;
}

for(size_t t = 0; t < feature.size(); ++t)


data.at<float>(index, t) = feature.at(t);
label.at<float>(index, 0) = i;
index++;
}
}

ăn
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
nV
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.


uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 80


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

ng
Lo
ăn
nV
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ư sa u:
// lprcore.h header
#pragma once
#include <opencv2/core/core.hpp>
#include <opencv2/ml/ml.hpp>

using namespace std;


using namespace cv;
uy

class LprCore
{
public:
LprCore(void);
~LprCore(void);
void set_image(Mat);
void set_svm_model(string);
Ng

void do_process();
vector<string> get_text_recognition();
vector<Mat> get_plate_detection();
vector<Mat> get_character_isolation();
vector<vector<Mat> > get_character();
vector<double> get_process_time();

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 81


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

void clear();
private:
bool done;

ng
bool ready;
SVM svm;
Mat image;
vector<Mat> plates;
vector<Mat> draw_character;
vector<vector<Mat> > characters;

Lo
vector<string> text_recognition;
vector<double> 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

ă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()
{
nV
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<string> LprCore::get_text_recognition()
{
if(done)
return this->text_recognition;

vector<Mat> LprCore::get_plate_detection()
uy

{
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
Ng

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<Mat> plates:

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 82


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

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

ng
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,

Lo
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 < 20 || (double)r.width/r.height > 4.5
|| (double)r.width/r.height < 3.5)

}
...
continue;

ăn
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
nV
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<Rect> 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);
uy

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);
}
}
Ng

}

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ì

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 83


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

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í

ng
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

Lo
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)
{

}
ready = true;
ăn
this->svm.load(file_name.c_str());

Hàm cài đặt cho nhận dạng kí tự như sau:


nV
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<float> 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<float>(i, 0) = feature[i];
float r = this->svm.predict(m); // du doan mau moi
uy

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
Ng

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

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 84


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

return c;
}

ng
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()
{

Lo
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);

}
}

ăn
this->text_recognition.push_back(result);

t = (double)cvGetTickCount() - t;
t = (double)t/(cvGetTickFrequency()*1000.); //convert to second
nV
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:

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 85


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

ng
Lo
ăn
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
nV
// LPRDlg.h : header file
#pragma once
#include <opencv2/core/core.hpp>
#include "LprCore.h"
#include "afxwin.h"

// CLPRDlg dialog
class CLPRDlg : public CDialogEx
{
public:

CLPRDlg(CWnd* pParent = NULL);


enum { IDD = IDD_LPR_DIALOG };
...
uy

private:
cv::Mat src;
cv::Mat plate;
cv::Mat character;
LprCore lpr;
string file_name;
Ng

string text_recognition;
// Implementation
protected:
...
DECLARE_MESSAGE_MAP()
public:
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 86
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

afx_msg void OnUpdateFileOpenimage(CCmdUI *pCmdUI);


afx_msg void OnBnClickedButton1();
afx_msg void OnBnClickedButton2();

ng
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

Lo
đượ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)
{

}
}
ăn
file_name = to_string(dlg.GetPathName());
src = imread(file_name);
if(src.empty()) return;
...
nV
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<Mat> plates = lpr.get_plate_detection();
uy

vector<Mat> characters = lpr.get_character_isolation();


vector<double> t = lpr.get_process_time();
vector<string> text = lpr.get_text_recognition();
if(plates.size() > 0)
{
plate = plates[0];
resize(plate, disp_plate, Size(280,50));
Ng

imshow("plate", disp_plate);
character = characters[0];
resize(character, disp_character, Size(280,50));
imshow("character", disp_character);

text_recognition = text[0];

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 87


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

text_result.SetWindowTextW(to_wstring(text_recognition));
num_plate.SetWindowTextW(to_wstring((int)plates.size()));
time_detection.SetWindowTextW(to_wstring(t[0]) + " ms");

ng
...

}
else
{
...

Lo
}
}

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.

ăn
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 …
nV
Hình sau mô tả kết quả chạy chương trình:

uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 88


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

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

ng
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 …

Lo
ăn
ễ nV
uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 89


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

3. MyCam, một số hiệu ứng ảnh với video.

ng
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

Lo
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ụ đề …

ăn
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
Track Video

Track Audio

Track Phụ đề
nV
Track thời gian
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.
uy

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
Ng

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
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 90
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

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).

ng
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

Lo
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.

ăn
Để đọ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ị:
nV
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)
uy

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ử >>
Ng

để lấy ra các frame từ đối tượng capture:


cv::Mat img;
capture << img;

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 91


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

Để 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:

ng
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,

Lo
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;

ăn
cv::VideoWriter writer = cv::VideoWriter("D:/test.avi", CV_FOURCC('X', 'V',
'I', 'D'), 25, img.size(), true);
while(1)
{
capture >> img;
nV
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:
uy

Mycamcore.h:
#pragma once
#include <opencv2\core\core.hpp>

class MyCamCore
Ng

{
public:
MyCamCore(void);
~MyCamCore(void);

void Gray(cv::Mat&, cv::Mat&);

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 92


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

void Invert(cv::Mat&, cv::Mat&);


void ToHSV(cv::Mat&, cv::Mat&);
void Blur(cv::Mat&, cv::Mat&);

ng


};

Và mycamcore.cpp:

Lo
#include "MyCamCore.h"
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;

MyCamCore::MyCamCore(void)
{
}

ăn
MyCamCore::~MyCamCore(void)
{
nV
}
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)


{

}
uy

void MyCamCore::ToHSV(Mat &src, Mat &dst)


{

}

void MyCamCore::Blur(Mat &src, Mat &dst)


Ng

{

}


}

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 93


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

ng
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

Lo
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

ăn
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)
{
nV
static Mat mask = 255*Mat::ones(src.rows, src.cols, CV_8UC1);
std::vector<Mat> src_bgr;
std::vector<Mat> 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);

}
uy

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

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 94


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

ng
Lo
ăn
nV
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
uy

#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include "MyCamCore.h"
#include "afxwin.h"
Ng

class CMyCamDlg : public CDialogEx


{

public:
CMyCamDlg(CWnd* pParent = NULL);
enum { IDD = IDD_MYCAM_DIALOG };

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 95


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

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};

ng
protected:
virtual void DoDataExchange(CDataExchange* pDX);

private:
// Các biến, các hàm chức năng trong chương trìn h

Lo
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;

MyCamCore mycam;
void Start(); ăn
std::string img_file_name;
cv::VideoWriter writer;
cv::VideoCapture capture;

void InitAppearance();
bool ReadConfig(std::string);
nV
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
uy

CButton btn_snapshot;
CButton btn_capture;
CButton btn_setting;
CButton btn_rgb;
CButton btn_gray;
...
// Các hàm khi click vào button
Ng

afx_msg void OnBnClickedSnapshot();


afx_msg void OnBnClickedCapture();
afx_msg void OnBnClickedSetting();
afx_msg void OnBnClickedRgb();
afx_msg void OnBnClickedGray();
...

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 96


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

};

ng
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

Lo
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:

ăn
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:
nV
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,


uy

"../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);
...
Ng

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ị

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 97


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

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

ng
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);

Lo
if(!ifs) return false;
std::vector<std::string> lines;
std::string line;
while(ifs >> line)
{
lines.push_back(line);
}
if(lines.size() < 2) return false;
fps = atof(lines.at(0).c_str());

} ăn
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
nV
ở 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);
uy

time_t rawtime;
time(&rawtime);
std::string t = (std::string)ctime(&rawtime);
int pos = t.find(":");
if(pos != t.npos)
{
t.replace(pos, 1, "_");
Ng

t.replace(pos + 3, 1, "_");
t.pop_back();
}
std::string path = output_folder + "/";
return path + t;
}
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 98
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

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ừ

ng
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:

Lo
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;

ăn
switch(type)

case RGB :
dst = src;
break;
case GRAY :
mycam.Gray(src, dst);
nV
break;
case INVERT:
mycam.Invert(src, dst);
break;
case HSV :
mycam.ToHSV(src, dst);
break;
case ... :
...

break;

default:
uy

dst = src;
break;

}
cv::imshow("Video", dst);
if(is_captured && writer.isOpened())
writer << dst;
Ng

cv::waitKey(1);
}
}

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 99


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

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

ng
btn_invert):
void CMyCamDlg::OnBnClickedInvert()
{
type = INVERT;

}
16. Hàm khi click vào button Snapshot (chụp ảnh):

Lo
void CMyCamDlg::OnBnClickedSnapshot()
{
img_file_name = CreateFileName() + ".jpg";
std::vector<int> p(2);
p.at(0) = CV_IMWRITE_JPEG_QUALITY;
p.at(1) = 90;
if(!dst.empty())
{
cv::imwrite(img_file_name, dst, p);

}
}

ăn
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.
nV
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/
uy

stop.bmp",0, 0, 0, LR_LOADFROMFILE)));
btn_snapshot.EnableWindow(false);
btn_setting.EnableWindow(false);
btn_folder.EnableWindow(false);
}

else
Ng

{
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);
Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 100
Ứng dụng xử lý ảnh trong thực tế với thư viện OpenCV

if(writer.isOpened())
writer.release();

ng
}
}

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

Lo
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()
{

} ăn
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
nV
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;
uy

if(output_folder == "") output_folder = "..\\Output";

std::ofstream ofs("config.txt");
if(!ofs) return;
ofs << fps <<std::endl;
ofs << output_folder;
ofs.flush();
Ng

ofs.close();

is_stoped = false;
Start();

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 101


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

Hàm này sẽ mở ra một dialog để người dung đặt các giá trị cho chương trình như

ng
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:

Lo
ăn
ễ nV
uy
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 102


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

ng
Lo
ăn
nV
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.

uy

------------------------------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 …
Ng

Tác giả: Nguyễn Văn Long – long.06clc@gmail.com Page 103

You might also like