Xla TT Vs8 Format1 3

You might also like

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 228

MỤC LỤC

MỤC LỤC................................................................................................1
DANH MỤC CÁC ẢNH..........................................................................4
CHƯƠNG 1..............................................................................................8
XỬ LÝ ẢNH BẰNG PHÉP BIẾN ĐỔI ĐIỂM-ĐIỂM.........................8
1.1. Xử lý ảnh.............................................................................................8
1.1.1. Định nghĩa về Xử lý ảnh..................................................................8
1.1.2. Mục đích của việc xử lý ảnh............................................................8
1.1.3. Giới thiệu về scikit-image................................................................9
1.1.4. Định nghĩa về ảnh số.....................................................................10
1.1.5. Xử lý ảnh bằng scikit – image.......................................................11
1.1.6. Định nghĩa ảnh RGB và ảnh xám..................................................11
1.1.7. Hiển thị ảnh....................................................................................13
1.1.8. Thư viện NumPy cho xử lý ảnh.....................................................14
1.1.9. Kích thước ảnh...............................................................................16
1.1.10. Ứng dụng Numpy để xử lý ảnh....................................................17
1.1.11. Lược đồ mức xám........................................................................18
1.2. Phân ngưỡng.....................................................................................21
1.2.1. Phân loại các bài toán phân ngưỡng..............................................23
1.2.2. Phân ngưỡng Otsu..........................................................................25
CHƯƠNG 2............................................................................................39
XỬ LÝ ẢNH DỰA TRÊN NHỮNG ĐIỂM ẢNH LÂN CẬN VÀ
BIẾN ĐỔI TRONG KHÔNG GIAN....................................................39
2.1. Bộ lọc nhân chập...............................................................................39
2.1.1. Phát hiện cạnh................................................................................40
2.1.2. Làm mịn bằng bộ lọc Gaussian......................................................43
2.2. Tăng cường độ tương phản của ảnh..................................................45
2.2.1. Độ tương phản và sự tương quan với lược đồ mức xám...............46
2.2.2. Tăng cường độ tương phản............................................................47
2.2.3. Cân bằng lược đồ mức xám toàn cục.............................................48
2.2.4. Cân bằng lược đồ mức xám cục bộ................................................49
2.3. Chỉnh kích thước và xoay ảnh..........................................................51
2.3.1. Xoay ảnh........................................................................................52
2.3.2. Thay đổi theo tỷ lệ ảnh..................................................................54
2.3.3. Thay đổi kích thước ảnh................................................................56

1
2.4. Phép biến đổi hình thái học:.............................................................58
2.4.1. Phép biến đổi giãn nở và xói mòn.................................................59
2.4.2. Ứng dụng thư viện scikit-image cho bài toán hình thái học..........62
CHƯƠNG 3............................................................................................81
PHỤC HỒI VÀ RÚT TRÍCH THÔNG TIN TRONG ẢNH.............81
3.1. Phục hồi ảnh......................................................................................81
3.1.1. Vai trò của tham số mask trong việc khôi phục ảnh......................86
3.1.2. Nhiễu..............................................................................................88
3.1.3. Tạo nhiễu bằng thư viện scikit-image............................................89
3.1.4. Khử nhiễu.......................................................................................90
3.2. Siêu điểm ảnh....................................................................................94
3.3. Tìm đường bao................................................................................102
3.3.1. Tìm đường bao.............................................................................102
3.3.2. Kích thước của một đường bao....................................................105
CHƯƠNG 4..........................................................................................120
ĐẶC TRƯNG CỦA ẢNH VÀ BÀI TOÁN NHẬN DIỆN................120
4.1. Tìm cạnh với Canny........................................................................120
4.1.1. Phát hiện cạnh..............................................................................120
4.2. Phát hiện góc...................................................................................124
4.2.1. Phát hiện góc và ứng dụng...........................................................124
4.2.2. Phát hiện góc bằng đặc trưng Harris............................................129
4.3. Nhận dạng khuôn mặt.....................................................................132
4.3.1. Nhận diện khuôn mặt và ứng dụng..............................................132
4.3.2. Nhận dạng khuôn mặt với thư viện scikit-image.........................133
4.4. Bảo vệ quyền riêng tư cùng với ứng dụng phát hiện khuôn mặt....138
CHƯƠNG 5..........................................................................................156
HỌC TẬP CHUYỂN GIAO...............................................................156
5.1. Cơ sở lý thuyết................................................................................156
5.2. Tinh chỉnh toàn bộ mô hình:...........................................................160
5.2.1. Dataloader....................................................................................161
5.2.2. Huấn luyện mô hình.....................................................................166
5.2.3. Kiến trúc của một mạng CNN.....................................................170
5.2.4. Hiển thị kết quả............................................................................175
5.3. Cố định một phần của mô hình ban đầu trong suốt quá trình tinh
chỉnh.......................................................................................................177
PHỤ LỤC 1: CÀI ĐẶT MÔI TRƯỜNG PYTHON.........................185
CÀI ĐẶT ANACONDA......................................................................191

2
QUẢN LÝ MÔI TRƯỜNG.................................................................197
CÀI ĐẶT JUPYTER NOTEBOOK...................................................201
CÀI ĐẶT PYCHARM.........................................................................202
PHỤ LỤC 2: MÔ TẢ CÁC HÀM DÙNG TRONG CHƯƠNG 1....207
PHỤ LỤC 3: MÔ TẢ CÁC HÀM DÙNG TRONG CHƯƠNG 2....215
PHỤ LỤC 4: MÔ TẢ CÁC HÀM DÙNG TRONG CHƯƠNG 3....221

3
DANH MỤC CÁC ẢNH

Hình 1.1. Ma trận ảnh 2D..........................................................................9


Hình 1.2. Ảnh gốc Rocket từ thư viện.....................................................10
Hình 1.3. Ảnh gốc và ảnh xám................................................................11
Hình 1.4. Ảnh gốc và kết quả các kênh màu RGB..................................14
Hình 1.5. Ảnh gốc và ảnh kết quả các kênh màu RGB sau khi đổi ảnh
gốc thành ảnh xám...................................................................................15
Hình 1.6. Ảnh Marid từ tải từ thư viện skimage......................................15
Hình 1.7. Ảnh kết quả sau khi lật ảnh ngang và theo hướng bên trái......17
Hình 1.8. Ảnh gốc và Histogram của ảnh................................................18
Hình 1.9. Ảnh gốc và lược đồ mức giá trị theo kênh màu RGB..............18
Hình 1.10. Ảnh gốc và lược đồ mức xám của các kênh màu RGB.........20
Hình 1.11. Ảnh gốc và ảnh đã lấy ngưỡng..............................................22
Hình 1.12. Ảnh gốc và ảnh đã lấy ngưỡng bằng nhiều phương pháp khác
nhau..........................................................................................................24
Hình 1.13. Ảnh gốc và ảnh lấy ngưỡng toàn cục.....................................25
Hình 1.14. Ảnh gốc và ảnh lấy ngưỡng cục bộ........................................27
Hình 1.15. Các ảnh gốc cho bài tập 1.1...................................................28
Hình 1.16. Ảnh gốc cho bài tập 1.2.........................................................29
Hình 1.17. Ảnh gốc cho bài tập 1.3........................................................30
Hình 1.18. Ảnh gốc cho bài tập 1.4.........................................................32
Hình 1.19. Ảnh gốc cho bài tập 1.5.........................................................33
Hình 1.20. Ảnh gốc cho bài tập 1.6.........................................................34
Hình 1.21. Ảnh gốc cho bài tập 1.7.........................................................35
Hình 1.22. Ảnh gốc cho bài tập 1.8.........................................................36
Hình 2.1. Phép lọc dựa trên các điểm ảnh lân cận...................................39
Hình 2.2. Ảnh gốc và các cạnh được phát hiện.......................................40
Hình 2.3. Kết quả phát hiện cạnh dùng bộ lọc Sobel...............................40
Hình 2.4. Ảnh gốc và ảnh kết quả làm mờ dùng bộ lọc Gaussian...........43
Hình 2.5. Tăng cường ảnh X-quang........................................................44
Hình 2.6. Ảnh gốc và lược đồ mức xám của ảnh. Độ rộng của lược đồ
mức xám càng lớn thì ảnh sẽ có độ tương phản cao hơn.........................45
Hình 2.7. Ảnh gốc có độ tương phản thấp và Histogram của ảnh...........46

4
Hình 2.8. Ảnh gốc có độ tương phản thấp và các ảnh sau khi được tăng
cường theo ba phương pháp: kéo giãn độ tương phản, cân bằng lược đồ
mức xám, và cân bằng lược đồ cục bộ.....................................................47
Hình 2.9. Ảnh gốc và ảnh sau khi được cân bằng Histogram..................48
Hình 2.10. Ảnh gốc và ảnh sau khi được cân bằng thích ứng.................49
Hình 2.11. Ứng dụng của việc thay đổi kích thước ảnh..........................51
Hình 2.12. Ảnh gốc và các ảnh sau khi được xoay..................................51
Hình 2.13. Ảnh gốc và ảnh sau khi được thay đổi kích thước.................53
Hình 2.14. Ảnh kết quả sau khi thay đổi kích thước có và không có sử
dụng bộ khử răng cưa..............................................................................55
Hình 2.15. Ảnh gốc và ảnh sau khi thay đổi kích thước..........................55
Hình 2.16. Ảnh gốc và ảnh sau khi thay đổi kích thước..........................56
Hình 2.17. Ảnh gốc và ảnh sau khi thay đổi kích thước..........................57
Hình 2.18. Ảnh gốc và ảnh nhị phân được tạo bằng cách lấy ngưỡng....58
Hình 2.19. Ảnh nhị phân và ảnh xám......................................................58
Hình 2.20. Ảnh gốc và các ảnh được giãn ra và bào mòn đi...................59
Hình 2.21. Đối tượng và phần tử cấu trúc tương ứng..............................60
Hình 2.22. Các dạng phần tử cấu trúc......................................................60
Hình 2.23. Ảnh gốc và ảnh kết quả sau khi xói mòn bằng cấu trúc hình
chữ nhật 12x6...........................................................................................62
Hình 2.24. Ảnh gốc và ảnh sau khi bào mòn bằng cấu trúc mặc định.....63
Hình 2.25. Ảnh gốc và ảnh kết quả sau khi mở rộng...............................64
Hình 2.26. Ảnh gốc của bài tập 2.1.........................................................65
Hình 2.27. Ảnh gốc của bài tập 2.2.........................................................66
Hình 2.28. Ảnh gốc của bài tập 2.3.........................................................68
Hình 2.29. Ảnh gốc của bài tập 2.4.........................................................69
Hình 2.30. Ảnh gốc của bài tập 2.5.........................................................70
Hình 2.31. Ảnh gốc của bài tập 2.6.........................................................71
Hình 2.32. Ảnh gốc của bài tập 2.7.........................................................73
Hình 2.33. Ảnh gốc của bài tập 2.8.........................................................75
Hình 2.34. Ảnh gốc bài tập 2.9................................................................76
Hình 2.35. Ảnh gốc cho bài tập 2.10.......................................................78
Hình 2.36. Ảnh gốc cho bài tập 2.11.......................................................79
Hình 3.1. Ảnh bị hỏng và kết quả ảnh sau khi phục hồi..........................81
Hình 3.2. Các trường hợp cần tái tạo ảnh................................................82
Hình 3.3. Ảnh bị hỏng và các vị trí pixel lỗi...........................................83
Hình 3.4. Ảnh bị hỏng và kết quả ảnh sau khi phục hồi..........................85

5
Hình 3.5. Ảnh bị lỗi và ảnh mask hiển thị vùng bị lỗi.............................86
Hình 3.6. Ảnh gốc bị nhiễu và ảnh các hạt nhiễu màu khi phóng to.......87
Hình 3.7. Ảnh gốc bị nhiễu và ảnh các hạt nhiễu màu khi phóng to.......88
Hình 3.8. Ảnh gốc và ảnh sau khi được thêm nhiễu................................89
Hình 3.9. Ảnh bị nhiễu và ảnh kết quả xử lý nhiễu.................................90
Hình 3.10. Ảnh nhiễu và ảnh xử lý nhiễu dùng bộ lọc tổng phương sai. 92
Hình 3.11. Ảnh gốc bị nhiễu và ảnh xử lý nhiễu dùng bộ lọc tổng phương
sai.............................................................................................................93
Hình 3.12. Ảnh gốc và ảnh được phân đoạn............................................94
Hình 3.13. Ảnh được phân đoạn để tách đối tượng khỏi nền..................95
Hình 3.14. Ảnh gốc bị nhiễu và ảnh 1 điểm ảnh đơn lẻ..........................96
Hình 3.15. Ảnh phân đoạn thành 100 nhóm điểm ảnh............................96
Hình 3.16. Phân đoạn giám sát và phân đoạn không giám sát................98
Hình 3.17. Ảnh gốc và ảnh sau khi được phân đoạn...............................99
Hình 3.18. Ảnh gốc và ảnh được phân đoạn với n_segments = 300.....101
Hình 3.19. Ảnh gốc và ảnh kết quả tìm đường bao các domino............101
Hình 3.20. Ảnh được lấy ngưỡng và ảnh đường bao.............................102
Hình 3.21. Kết quả các bước lấy đường bao của ảnh............................103
Hình 3.22. Ảnh gốc và ảnh kết quả khi thay đổi tham số level.............104
Hình 3.23. Ảnh gốc của bài tập 3.1.......................................................106
Hình 3.24. Ảnh gốc của bài tập 3.2.......................................................107
Hình 3.25. Ảnh gốc của bài tập 3.3.......................................................109
Hình 3.26. Ảnh gốc của bài tập 3.4.......................................................110
Hình 3.27. Ảnh gốc của bài tập 3.5.......................................................111
Hình 3.28. Ảnh gốc của bài tập.............................................................112
Hình 3.29. Ảnh nhị phân của bài tập 3.8...............................................114
Hình 3.30. Ảnh gốc của bài tập 3.9.......................................................115
Hình 3.31. Ảnh kết quả bài tập 3.9........................................................117
Hình 4.1. Ảnh gốc và ảnh kết quả phát hiện cạnh dùng bộ lọc Canny. .120
Hình 4.2. Ảnh kết quả phát hiện cạnh cùng Sobel và Cany..................120
Hình 4.3. Ảnh gốc và ảnh kết quả phát hiện cạnh dùng Canny.............121
Hình 4.4. Kết quả cạnh khi sigma thay đổi............................................123
Hình 4.5. Ảnh gốc và ảnh kết quả phát hiện góc...................................124
Hình 4.6. Các điểm trọng tâm và kết quả phát hiện cạnh dùng Sobel...125
Hình 4.7. Ảnh góc..................................................................................126
Hình 4.8. Ảnh các góc phù hợp giữa ảnh gốc và ảnh đã giảm tỉ lệ.......127
Hình 4.9. Ảnh các góc phù hợp giữa ảnh góc và ảnh khi xoay.............128

6
Hình 4.10. Ảnh gốc được truy xuất xuất từ hàm angle_harris...............129
Hình 4.11. Ảnh gốc và đáp ứng của đặc trưng Harris...........................129
Hình 4.12. Ảnh các góc được phát hiện.................................................131
Hình 4.13. Ứng dụng nhận dạng khuôn mặt..........................................132
Hình 4.14. Ảnh nhận dạng khuôn mặt và ảnh tách khuôn mặt được nhận
dạng........................................................................................................133
Hình 4.15. Các cửa sổ sẽ trượt đi để tìm vị trí khuôn mặt.....................134
Hình 4.16. Nhiều kích thước cửa sổ sẽ được thử nghiệm để hiển thị kích
thước khuôn mặt....................................................................................135
Hình 4.17. Ảnh gốc và ảnh nhận dạng khuôn mặt.................................136
Hình 4.18. Ứng dụng phát hiện cạnh, làm mờ khuôn mặt.....................138
Hình 4.19. Ảnh gốc và kết quả làm mờ khuôn mặt...............................141
Hình 4.20. Ảnh gốc của bài tập 4.1.......................................................142
Hình 4.21. Ảnh gốc cho bài tập 4.2.......................................................143
Hình 4.22. Ảnh gốc cho bài tập 4.3.......................................................144
Hình 4.23. Ảnh gốc cho bài tập 4.4.......................................................146
Hình 4.24. Ảnh gốc cho bài tập 4.5.......................................................147
Hình 4.25. Ảnh gốc cho bài tập 4.6.......................................................148
Hình 4.26. Ảnh gốc cho bài tập 4.7.......................................................150
Hình 4.27. Ảnh gốc cho bài tập 4.8.......................................................151
Hình 4.28. Ảnh gốc cho bài tập 4.9.......................................................153
Hình 5.1. Một số ảnh trong tập huấn luyện...........................................164
Hình 5.2. Kết quả dự đoán khi tinh chỉnh toàn bộ mạng học sâu..........176
Hình 5.3. Kết quả dự đoán khi cố định một phần mạng học sâu...........183

7
CHƯƠNG 1.
XỬ LÝ ẢNH BẰNG PHÉP BIẾN ĐỔI ĐIỂM-ĐIỂM

Trong chương này, người học sẽ thảo luận về định nghĩa xử lý


ảnh cũng như tìm hiểu về các công cụ xử lý ảnh thường dùng trong ngôn
ngữ lập trình Python. Sự khác biệt giữa ảnh xám và ảnh màu sẽ được mô
tả cụ thể. Bên cạnh đó, các kỹ thuật rút trích thông tin từ ảnh dựa trên
thống kê lược đồ mức xám cũng sẽ được trình bày. Các thống kê này sẽ
được sử dụng trong ứng dụng chọn ngưỡng để phân tách đối tượng và
khung nền của ảnh. Kỹ thuật phân ngưỡng được mở rộng từ kỹ thuật
phân ngưỡng toàn cụ sang kỹ thuật phân ngưỡng cục bộ để có thể phân
tách đối tượng tốt hơn khi thông tin ánh sáng thay đổi. Cuối cùng, các bài
tập và gợi ý được cung cấp để giúp người học thực hành lập trình.

LÝ THUYẾT
1.1. Xử lý ảnh

1.1.1. Định nghĩa về Xử lý ảnh

Xử lý ảnh là phương pháp thực hiện thao tác trên ảnh, nhằm tăng
cường ảnh hoặc trích xuất thông tin hữu ích, phân tích và đưa ra quyết
định. Ta có thể tính toán, xử lý bằng cách định lượng thông tin trong ảnh.
Xử lý ảnh là một lĩnh vực của thị giác máy tính.
Ngày nay, xử lý ảnh có nhiều ứng dụng rộng rãi như phân tích ảnh
y tế, trí tuệ nhân tạo, phục hồi ảnh, giám sát và nhiều lĩnh vực khác.

8
1.1.2. Mục đích của việc xử lý ảnh

Thông thường, xử lý ảnh là một khâu của một ứng dụng cụ thể.
Trong mỗi ứng dụng, vai trò của khâu xử lý ảnh có thể khác nhau. Mặc
dù vậy, vai trò của xử lý ảnh có thể được chia thành năm nhóm chính:

● Trực quan hoá dữ liệu: quan sát các đối tượng mà mắt thường
không nhìn thấy được. Ví dụ, trong ảnh y tế, các tập tin y tế
được lưu dưới dạng đặc biệt có dải động cao. Trong khi các thiết
bị thông thường chỉ có thể hiển thị thông tin 8 bits. Chính vì
vậy, các ảnh y tế phải được xử lý trước khi có thể hiển thị trên
màn hình.
● Làm sắc nét và phục hồi ảnh: Các ảnh gốc có thể có chất lượng
không tốt, một số chi tiết bị mất đi. Việc tăng độ sắc nét và phục
hồi ảnh có thể làm cho ảnh trông đẹp hơn dưới góc nhìn của con
người.
● Truy xuất ảnh: Đây là chức năng tìm ảnh giống với ảnh cho
trước. Chức năng này cho phép truy vấn nhanh những ảnh giống
với ảnh mẫu cho trước và giúp ích cho việc tìm kiếm thông tin.
Tính năng này thường được dùng trong ứng dụng giám sát khi
một đối tượng cho trước cần được tìm trong kho dữ liệu lớn.
● Đo lường: Một số ảnh đặc biệt như ảnh y tế có thể giúp ta đo
lường thể tích được khối u dựa vào thông tin của ảnh. Ngoài ra,
trong một số trường hợp đặc biệt thông tin ảnh 2D có thể giúp
truy xuất được các thông tin 3D như vị trí và khoảng cách.
● Nhận dạng ảnh: Chức năng này dùng để phân biệt các đối tượng
trong một ảnh và vị trí của chúng.

9
1.1.3. Giới thiệu về scikit-image

Có rất nhiều thư viện có thể được sử dụng để thực hiện việc xử lý
ảnh như OpenCV, OpenGL. Các thư viện này được viết dựa trên ngôn
ngữ C để tối ưu hoá về mặt tốc độ. Ngoài ra, trong những ứng dụng
không đòi hỏi tốc độ xử lý thời gian thực và cần tích hợp với môi trường
web, các ngôn ngữ khác như Python có thể được sử dụng. Trong khuôn
khổ giáo trình này, gói thư viện Scikit-image được lựa chọn để giới thiệu
vì nó là một thư viện xử lý ảnh rất dễ sử dụng. Scikit-image được viết
trên nền tảng của OpenCV và ngôn ngữ Python dựa theo cấu trúc của
một gói thư viện nổi tiếng là Scikit-learn. Nó tích hợp một số ứng dụng
máy học và hàm xử lý ảnh cơ bản để người học có thể dễ dàng tiếp cận.
Sau khi thực tập với thư viện này, người đọc được khuyến khích sử dụng
thêm thư viện OpenCV để thực hiện các tác vụ phức tạp hơn.

1.1.4. Định nghĩa về ảnh số

Ảnh số là một mảng hay ma trận 2 chiều gồm các phần tử ảnh được
sắp xếp theo cột và hàng. Mỗi phần tử trong ma trận này được gọi là
điểm ảnh. Các điểm ảnh chứa thông tin về màu sắc và cường độ ánh
sáng. Hình 1.1 dưới đây là một ví dụ về ma trận cho ảnh xám 2D. Ảnh
đầu tiên bên trái (1.1a) là một ảnh được số hóa. Các con số mà ta nhìn
thấy trên ảnh ở giữa (1.1b) tương ứng với giá trị mỗi điểm ảnh. Cuối
cùng một ảnh (1.1c) có thể được xem như một ma trận các giá trị mức
xám.

10
(a) b) (c)

Hình 1.1. Ma trận ảnh 2D


1.1.5. Xử lý ảnh bằng scikit – image

Thư viện scikit-image cung cấp một số ảnh có sẵn được lưu trong
gói data. Ví dụ, có một ảnh màu tên là “rocket” đã được lưu sẵn. Để sử
dụng được ảnh này trước hết cần khai báo sử dụng gói data từ trong thư
viện skimage. Việc khai báo này có thể thực hiện bằng câu lệnh “from
skimage import data”. Sau đó, một phương pháp rocket có thể giúp tải
ảnh “rocket” từ trong gói thư viện và lưu lại trong biến rocket_image.

Mã:

from skimage import data


rocket_image = data.rocket()

11
Hình 1.2. Ảnh gốc Rocket từ thư viện
1.1.6. Định nghĩa ảnh RGB và ảnh xám

Ảnh xám chỉ có sắc độ đen và trắng. Thông thường, cường độ mức
xám được lưu trữ dưới dạng số nguyên 8 bit cho 256 mức xám khác
nhau.

Ảnh màu thường được biểu diễn bằng ba kênh màu RGB (kênh đỏ,
xanh lục và xanh lam của ảnh). Mỗi kênh màu được mô tả bằng một
mảng 2 chiều, và mỗi giá trị trong các mảng này thường được lưu trữ
dưới dạng số nguyên 8 bit giống như ảnh xám.

Điểm khác biệt chính của ảnh màu và ảnh xám nằm ở chỗ, ảnh màu
có ba kênh màu, trong khi ảnh xám có một kênh duy nhất. Ta có thể
chuyển đổi ảnh RGB thành ảnh xám bằng cách sử dụng hàm rgb2gray()
và chuyển ảnh xám thành ảnh màu bằng cách sử dụng hàm gray2rgb().
Hai hàm này được cung cấp trong thư viện color. Một ví dụ về sự khác
biệt giữa ảnh gốc và ảnh màu có thể tìm được ở Hình 1.3

Ví dụ mẫu:

12
from skimage import color
grayscale = color.rgb2gray(original)
rgb = color.gray2rgb(grayscale)

Hình 1.3. Ảnh gốc và ảnh xám


1.1.7. Hiển thị ảnh

Để hiển thị ảnh, có thể sử dụng hàm imshow trong thư viện
matplotlib.pylot. Ngoài ra, các thông tin về tên ảnh và tông màu hiển thị
cũng cần được xử lý để đảm bảo ảnh được hiển thị với chất lượng tốt.
Chính vì vậy, một chương trình con show_image() được cung cấp để hiển
thị ảnh một cách dễ dàng hơn. Trong ngôn ngữ Python, từ khoá def cho
biết đây là một chương trình con tùy biến. Các tham số title và
cmap_type có thể được dùng để điều khiển tên sẽ được hiển thị và tông
màu sử dụng một cách tương ứng. Nếu để mặc định, giá trị title sẽ được
gán là 'Image', và giá trị cmap_type sẽ được gán là 'gray'.

Ví dụ về chương trình con show_image():

13
def show_image(image, title='Image', cmap_type='gray'):
plt.imshow(image, cmap=cmap_type)
plt.title(title)
plt.axis('off')
plt.show()

Vì vậy, giả sử người dùng muốn hiển thị một ảnh đã được chuyển
đổi sang ảnh xám, chỉ cần gọi hàm show_image với tham số đầu tiên là
ảnh xám cần hiển thị và đặt tên cho ảnh đó là "Anh_Xam" bằng cách khai
báo tham số thứ hai. Tham số thứ ba không khai báo, tức là giá trị
cmap_type sẽ được gán là 'gray' một cách mặc định.

Ví dụ mẫu về chuyển đổi ảnh màu thành ảnh xám:

from skimage import color


grayscale = color.rgb2gray(original)
show_image(grayscale, " Anh_Xam ")
1.1.8. Thư viện NumPy cho xử lý ảnh

Các ảnh được thể hiện dưới dạng ma trận hai chiều. Trong Python,
các ma trận thường được định nghĩa dưới dạng các ma trận NumPy. Các
hàm xử lý ảnh đều giả thiết ảnh đầu vào là một ma trận Numpy.

Giả sử có một ảnh được đọc bằng hàm imread() của thư viện
matplotlib. Hàm type() trong Python có thể được sử dụng để kiểm tra
kiểu dữ liệu của nó. Đoạn mã sau đây cho thấy ảnh đọc về là một đối
tượng Ndarray. Ảnh có thể được biểu diễn bởi mảng đa chiều NumPy
(hoặc "NdArrays"); đây không chỉ là một mảng chứa các giá trị thông
thường mà còn là một đối tượng. Các đối tượng này được hỗ trợ bởi
nhiều phương pháp xử lý giúp cho việc tính toán ma trận trở nên đơn
giản hơn.

14
Ví dụ mẫu về đọc ảnh có sẵn trong máy tính:

# Đọc ảnh sử dụng Matplotlib


madrid_image = plt.imread('/madrid.jpeg')
type(madrid_image)

Ảnh màu là một mảng NumPy với ba chiều. Chiều thứ nhất và
chiều thứ hai là hàng và cột của ảnh, chiều thứ ba đại diện cho các kênh
màu. Ta có thể tách các mảng đa chiều này thành nhiều mảng hai chiều,
và mỗi mảng hai chiều là một kênh màu này riêng biệt. Ví dụ, để tách
thông tin kênh màu đỏ của ảnh, các điểm ảnh theo chiều dọc và chiều
ngang sẽ được giữ lại, và chỉ chọn các giá trị của lớp màu đầu tiên. Thư
viện Matplotlib có thể hiển thị chúng với bản đồ màu mặc định.

Ví dụ mẫu về trích thông tin về các kênh màu:


# Thu dữ liệu kênh màu đỏ
red = image[: , : , 0]
# Thu dữ liệu kênh màu xanh lục
green = image[: , : , 1]
# Thu dữ liệu kênh màu xanh lam
blue = image[: , : , 2]

Hình 1.4. Ảnh gốc và kết quả các kênh màu RGB
Hàm imshow trong thư viện Matplotlib.pylot có thể hiển thị các
kênh màu nhưng tông màu mặc định sẽ là màu xanh. Bằng cách sử dụng

15
cmap=’gray’, các kênh màu này có thể được hiển thị như các ảnh đen
trắng. Ví dụ về ảnh hưởng của tham số điều khiển cmap có thể được
quan sát trong Hình 1.4 và 1.5. Ngoài ra, lệnh show() cho phép dừng
chương trình để hiển thị ảnh.

Ví dụ mẫu về hiển thị ảnh nếu muốn ảnh hiển thị dưới tông màu
đen trắng. Tham số cần điều khiển là cmap.

plt.imshow(red, cmap="gray")
plt.title('Red')
plt.axis('off')
plt.show()

Hình 1.5. Ảnh gốc và ảnh kết quả các kênh màu RGB sau khi đổi ảnh
gốc thành ảnh xám
1.1.9. Kích thước ảnh

Có hai thuộc tính quan trọng của các đối tượng NumPy là kích
thước (size) và hình dạng (shape) của ma trận đó. Ví dụ, đối với bức
tranh Madrid được đọc từ dataset của thư viện skimage. Bức tranh này
có độ phân giải là 426 hàng và 640 cột. Bởi vì là ảnh màu, nó có ba kênh
màu để biểu diễn màu sắc. Vì vậy, kích thước của ảnh này là (426, 640,
3). Để có thể truy vấn được các thông tin này, một thuộc tính của đối
tượng Numpy được sử dụng là shape. Ngược lại, nếu cần truy vấn thông

16
tin về tổng số các điểm ảnh, thuộc tính size của đối tượng sẽ được sử
dụng.

Hình 1.6. Ảnh Marid từ tải từ thư viện skimage


Ví dụ mẫu về truy vấn kích thước ảnh.

# Truy xuất kích thước ảnh


madrid_image.shape
# Truy xuất số lượng các điểm ảnh
madrid_image.size

1.1.10. Ứng dụng Numpy để xử lý ảnh

Vì các ảnh đọc được là các ma trận Numpy, do đó các hàm có sẵn
của thư viện Numpy cũng có thể được áp dụng để xử lý ảnh. Ví dụ về
tính năng lật và xoay ảnh. Đây là một tính năng cơ bản mà các phần mềm
hiển thị ảnh có thể cung cấp cho người dùng. Bằng cách dùng hàm
flipud(), ảnh sẽ được lật theo chiều dọc. Bằng cách sử dụng hàm
flipplr(), ảnh sẽ được lật theo chiều ngang. Để sử dụng được các hàm
này, trước tiên cần dùng lệnh “import numpy as np” để khai báo sử dụng
thư viện Numpy.

17
Ví dụ mẫu về cách gọi các hàm trong numpy:

import numpy as np
# Xoay ảnh theo chiều dọc
vertically_flipped = np.flipud(madrid_image)
show_image(vertically_flipped, 'Vertically flipped image')
# xoay ảnh theo hướng trái
horizontally_flipped = np.fliplr(madrid_image)
show_image(horizontally_flipped, 'Horizontally flipped image')

18
Hình 1.7. Ảnh kết quả sau khi lật ảnh ngang và theo hướng bên trái
1.1.11. Lược đồ mức xám

Lược đồ mức xám của một ảnh là một biểu diễn thống kê số lượng
điểm ảnh của mỗi mức xám. Các ảnh thông thường nhận các mức xám là
số nguyên từ 0 đến 255. Trong hình 1.8b, ảnh quan sát rất tối. Vì vậy khi
quan sát lược đồ mức xám tương ứng (ở hình 1.8a), hầu hết các điểm ảnh
có cường độ thấp, từ 0 đến 50. Trong khi ảnh 1.8d sáng hơn và hầu hết
các điểm ảnh từ có mức xám từ 200 đến 255 (ở hình 1.8b).

19
Hình 1.8. Ảnh gốc và Histogram của ảnh
Đối với ảnh màu, mỗi kênh màu có thể được xem như một ảnh
xám. Do đó, các lược đồ mức xám cũng có thể được dùng trên từng kênh
màu riêng rẽ. Trong ví dụ hình 1.9, các kênh màu được tách riêng, và
mỗi kênh sẽ hiển thị lược đồ một cách độc lập.

Hình 1.9. Ảnh gốc và lược đồ mức giá trị theo kênh màu RGB
Bằng cách quan sát lược đồ mức xám, nhiều thông tin có thể được
rút trích. Các lược đồ mức xám này có thể được sử dụng để lấy ngưỡng
ảnh, để thay đổi độ sáng và độ tương phản cũng như cân bằng độ sáng
của ảnh.

20
Để hiển thị thông tin lược đồ mức xám của ảnh, thư viện
Matplotlib sử dụng hàm hist. Nó nhận một mảng đầu vào x và các tham
số điều khiển bin. Tham số bin điều khiển số lượng các mức xám được
thống kê trong một khoảng range xác định. Trong ví dụ tiếp theo, kênh
màu đỏ của ảnh sẽ được tách ra khỏi ảnh màu image. Sau đó, hàm hist.
sẽ được dùng để hiển thị lược đồ mức xám của mảng. Hàm ravel trả về
một mảng liên tục 1D từ các giá trị của ma trận 2D (kênh màu đỏ của
ảnh). Trong ví dụ dưới đây, tham số điều bins được đặt bằng 256 vì ảnh
có 256 mức xám từ từ 0 đến 255. Có nghĩa là cần 256 giá trị để hiển thị
biểu đồ. Cuối cùng, lệnh plt.show() được dùng để tạm ngưng chương
trình và hiển thị kết quả. Trong ví dụ dưới đây, phương thức ravel() sẽ
thay thế cho hàm ravel để chuyển đổi đối tượng Numpy nhiều chiều
thành mảng NumPy một chiều mà hàm hist yêu cầu.

Ví dụ mẫu:

# Red color of the image


red = image[: , : , 0]
# Obtain the red histogram
plt.hist(red.ravel(), bins=256)
blue = image[: , : , 2]
plt.hist(blue.ravel(), bins=256)
plt.title('Blue Histogram')
plt.show()

21
Hình 1.10. Ảnh gốc và lược đồ mức xám của các kênh màu RGB
1.2. Phân ngưỡng

Phân ngưỡng là một kỹ thuật được sử dụng để chuyển một ảnh xám
thành ảnh nhị phân trong đó vùng nền (mức 0) và đối tượng (mức 1) của
ảnh sẽ được tách biệt rõ ràng. Bằng cách so sánh giá trị mức xám của một
điểm ảnh với một ngưỡng cho trước, điểm ảnh đó sẽ được gán thuộc về
đối tượng cần quan tâm hoặc phông nền. Nếu giá trị mức xám của điểm
ảnh nhỏ hơn giá trị ngưỡng, nó sẽ được chuyển thành màu trắng (phông
nền) và ngược lại chuyển thành màu đen (đối tượng cần quan tâm).
Phân ngưỡng là một thuật toán rất phổ cập vì nó cho phép tách đối
tượng quan tâm ra khỏi nền, sau đó đối tượng sẽ được xử lý để rút ra
những thông tin có ích. Ví dụ, trong bài toán nhận diện các đặc trưng trên

22
khuôn mặt để điểm danh nhân viên, việc đầu tiền là cần tách phần khuôn
mặt ra khỏi khung nền. Hay trong các bài toán nhận diện sản phẩm bị lỗi
và loại lỗi; công việc đầu tiên vẫn là phải tách đối tượng ra để tiến hành
nhận diện lỗi. Việc phân ngưỡng chỉ hoạt động tốt khi các ảnh xám có độ
tương phản cao. Do đó, để phân ngưỡng các ảnh màu cần chuyển ảnh
màu thành ảnh xám trước khi áp dụng các thuật toán phân ngưỡng. Quy
trình thực hiện các bước như sau:
● Tải ảnh cần xử lý
● Thiết lập giá trị ngưỡng (thresh). Trong ví dụ sau đây ngưỡng
được chọn là 127 (điểm nằm giữa từ 0 đến 255)
● Tạo ra ảnh nhị phân bằng cách so sánh ảnh mới ngưỡng đã chọn
bằng cách sử dụng một toán tử so sánh lớn hơn so với thresh.
● Cuối cùng, hiển thị ảnh đã phân ngưỡng bằng hàm
show_image().

Ví dụ mẫu:

# Thiết lập giá trị của ngưỡng


thresh = 127
# So sánh ảnh với ngưỡng đã chọn
binary = image > thresh
# Hiển thị kết quả đạt được
show_image(image, 'Original')
show_image(binary, 'Thresholded')

23
Hình 1.11. Ảnh gốc và ảnh đã lấy ngưỡng
Trong trường hợp muốn nghịch đảo ảnh phân ngưỡng (tức là đảo
ngược các mức đen và trắng trong ảnh), toán tử "<=" có thể được sử
dụng thay cho toán tử ">". Ảnh kết quả cũng là ảnh nhị phân nhưng nền
màu đen và chủ thể màu trắng.

Ví dụ mẫu về kỹ thuật phân ngưỡng:

# Thiết lập giá trị của ngưỡng


thresh = 127
# So sánh ảnh với ngưỡng đã chọn
binary = image <= thresh
# Hiển thị kết quả đạt được
show_image(image, 'Original')
show_image(inverted_binary,'Inverted Thresholded')

1.2.1. Phân loại các bài toán phân ngưỡng

Thư viện scikit-image hỗ trợ hai kỹ thuật phân ngưỡng. Phân


ngưỡng toàn cục dựa trên biểu đồ mức xám của toàn bộ ảnh để chọn một
ngưỡng thresh duy nhất. Ngưỡng này sẽ được áp dụng cho mọi điểm ảnh
trong ảnh. Phương pháp này thường phù hợp với những ảnh có nền tương
đối đồng đều. Kỹ thuật phân ngưỡng thứ hai là kỹ thuật cục bộ. Phương

24
pháp này so sánh mỗi điểm ảnh với một ngưỡng khác nhau. Do đó
ngưỡng ở đây là một ảnh có kích thước bằng với ảnh gốc, và giá trị mỗi
ngưỡng ứng với điểm ảnh sẽ dựa trên những những điểm lân cận trên ảnh
gốc tương ứng. Phương án cục bộ này thường tối ưu cho cho những ảnh
mà độ sáng của khung nền thay đổi thường xuyên và có độ sáng nền
không đồng đều. So với phân ngưỡng toàn cục, phân ngưỡng cục bộ có
kết quả tốt hơn. Tuy nhiên cần lưu ý rằng phân ngưỡng cục bộ sẽ chậm
hơn phân ngưỡng toàn cục. Ảnh bên dưới là ảnh so sánh hai kỹ thuật
phân ngưỡng này. Khi ảnh nền bị hiện tượng đổ bóng do phân bố ánh
sáng không đều, phân ngưỡng cục bộ tốt hơn.

Ngoài phân ngưỡng toàn cục và phân ngưỡng cục bộ, thư viện
scikit-image còn hỗ trợ việc thử nhiều mức phân ngưỡng khác nhau để
quan sát kết quả thu được. Hàm try_all_threshold của thư viện filters
trong gói scikit-image cung cấp giải pháp kiểm tra kết quả phân ngưỡng
với nhiều ngưỡng khác nhau. Ta sử dụng hàm try_all_threshold bằng
cách truyền ảnh image và tham số điều khiển verbose. Ở đây ảnh image
là ảnh xám với dữ liệu uint8; còn verbose = False để hàm này không in
ra nhiều thông tin về các ngưỡng đang được thực thi. Cuối cùng, hàm
shown_plot() giúp hiển thị tất cả các kết quả.

Ví dụ mẫu:

from skimage.filters import try_all_threshold

25
# Thực thi tất cả các phương pháp phân ngưỡng.
fig, ax = try_all_threshold(image, verbose=False)
# Hiển thị kết quả
show_plot(fig, ax)

Trong ví dụ trên, bảy thuật toán phân ngưỡng toàn cục được sử sử
dụng. Kết quả dưới đây hiển thị đầu tiên là ảnh gốc, sau đó là các ảnh kết
quả của các phương pháp tạo ngưỡng.

Hình 1.12. Ảnh gốc và ảnh đã lấy ngưỡng bằng nhiều phương pháp
khác nhau
1.2.2. Phân ngưỡng Otsu
Khi ảnh cần phân ngưỡng là ảnh có nền đồng nhất phương pháp
phân ngưỡng toàn cục sẽ hoạt động tốt nhất. Mặc dù vậy, việc chọn giá
trị ngưỡng thresh toàn cục cần phải được tính tới. Nếu giá trị ngưỡng này
được chọn tốt, kết quả bài toán phân ngưỡng sẽ đạt được tốt nhất. Để đạt
được điều đó, hàm threshold_otsu() từ thư viện filters có thể được sử
dụng. Hàm này sử dụng phương pháp Otsu nổi tiếng để xác định thresh.
Sau đó, giá trị thresh sẽ được áp dụng cho tất cả điểm ảnh để phân chia
thành phần nền và thành phần đối tượng chính. Hình 1.14 cho thấy kết

26
quả của việc phân ngưỡng bằng phương pháp Otsu. So với việc chọn
ngưỡng ngẫu nhiên là 127 ở hình 1.11; kết quả này phân tách điểm khung
nền và đối tượng tốt hơn nhiều. Cũng trong hình 1.13, đường kẻ màu đỏ
thể hiện ngưỡng được tìm thấy bởi phương pháp Otsu. Ta thấy rõ ràng
rằng ngưỡng đã tách phần trên của lược đồ mức xám thành một phần độc
lập.

Ví dụ mẫu:

# Khai báo hàm phân ngưỡng OTSU


from skimage.filters import threshold_otsu
# Tính toán giá trị ngưỡng OTSU của ảnh
thresh = threshold_otsu(image)
# Áp dụng kỹ thuật lấy ngưỡng.
binary_global = image > thresh
# Hiển thị kết quả
show_image(image, 'Original')
show_image(binary_global, 'Global thresholding')

Hình 1.13. Ảnh gốc và ảnh lấy ngưỡng toàn cục


Nếu ảnh không có độ tương phản cao hoặc phông nền có các giá trị
mức xám khác nhau, việc sử dụng phương pháp phân ngưỡng cục bộ sẽ

27
cho kết quả tốt hơn. Việc phân ngưỡng cục bộ có thể được thực hiện
bằng hàm threshold_local(), cũng từ thư viện filters. Với hàm này, ta
tính toán các ngưỡng trong các vùng điểm ảnh lân cận của mỗi điểm ảnh
đang được khảo sát. Vì vậy, cần khai báo tham số điều khiển block_size
để điều khiển giá trị xung quanh mỗi điểm ảnh. Các block_size còn được
gọi là các vùng lân cận của điểm ảnh. Ngoài ra còn có tham số offset tùy
chọn, đó là một hằng số được trừ đi bởi giá trị trung bình để tính giá trị
ngưỡng cục bộ. Trong ví dụ dưới đây, ở trong hàm threshold_local,
tham số block_size được gán là 35 điểm ảnh và offset là 10. Sau đó,
ngưỡng cục bộ được sử dụng để xác định điểm ảnh tương ứng là khung
nền hay là đối tượng được quan tâm.

Ví dụ mẫu về phân ngưỡng cục bộ:

# khai báo hàm local threshold


from skimage.filters import threshold_local
# khai báo biến block size
block_size = 35
# xác định các ngưỡng cục bộ với offset=10
local_thresh = threshold_local(text_image, block_size,
offset=10)
# Ứng dụng ngưỡng cục bộ để xác định ảnh nhị phân
binary_local = text_image > local_thresh
# hiển thị ảnh gốc và ảnh nhị phân
show_image(image, 'Original')
show_image(binary_local, 'Local thresholding')

28
Hình 1.14 hiển thị ảnh gốc và ảnh kết quả, có thể thấy rằng phương
pháp phân ngưỡng cục bộ hoạt động tốt trong ảnh này khi ảnh có các
mức sáng không đều do nguyên nhân của việc đổ bóng.

Hình 1.14. Ảnh gốc và ảnh lấy ngưỡng cục bộ

29
BÀI TẬP
Bài tập 1.1: Kích thước ảnh

Câu hỏi: Cho hai ảnh coffee_image và coins_image được tải từ thư


viện data scikit-image bằng cách sử dụng đoạn mã:
coffee_image = data.coffee()
coins_image = data.coins()

Hãy sử dụng phương thức shape() của đối tượng kiểu NumPy để


xác định kích thước của từng ảnh. Nhận xét về sự khác nhau giữa kích
thước các ảnh này?

Hình 1.15. Các ảnh gốc cho bài tập 1.1


Hoàn thiện đoạn mã sau để minh chứng cho câu trả lời ở trên.

# Tải các gói thư viện cần thiết


from skimage import ____, __data__

30
Bài tập 1.2: Chuyển ảnh RGB thành ảnh xám

Câu hỏi: Trong bài tập này, hãy lấy một ảnh từ gói thư viện
data thuộc scikit-image và chuyển ảnh đó sang ảnh xám, sau đó hiển thị
ảnh thu được.

Hình 1.16. Ảnh gốc cho bài tập 1.2


Hãy sử dụng hàm show_image(image, title='Image') hiển thị ảnh
bằng Matplotlib. Có thể kiểm tra thêm về các thông số của hàm bằng
cách sử dụng: ?show_image() hoặc help(show_image). Điền vào chỗ
trống trong dòng mã sau để hoàn thành bài tập ở trên.

# Tải các thư viên cần thiết


from skimage import ____, __color__
# Tải ảnh rocket
rocket = data._rocket___()

31
# Chuyển đổi ảnh thành ảnh xám
gray_scaled_rocket = color._rgb2gray___(__rocket__)
# Hiển thị ảnh gốc
show_image(rocket, 'Original RGB image')
# Hiển thị ảnh xám
show_image(gray_scaled_rocket, 'Grayscale image')

Hướng dẫn:

● Import thư viện data và color từ Scikit-image. Thư viện đầu


tiên cho ta các ảnh, và thư viện thứ hai cung cấp các hàm chuyển đổi
màu.
● Tải ảnh rocket
● Chuyển ảnh RGB-thành ảnh xám và hiển thị nó.

Bài tập 1.3: Lật ảnh

Câu hỏi: Giả sử có một ảnh đã bị lật ngược. Hãy thực hiện chỉnh
sửa để khôi phục đúng chiều của ảnh ban đầu bằng cách xoay ảnh.

Hình 1.17. Ảnh gốc cho bài tập 1.3

32
Hướng dẫn: Sử dụng các phương thức mà đối tượng NumPy có thể
cung cấp, lật ảnh theo chiều ngang và chiều dọc. Sau đó, hiển thị ảnh đã
chỉnh sửa bằng hàm show_image(). Thư viện NumPy đã được import
dưới tên gắn là np.

Hãy điền vào đoạn mã dưới đây để hoàn thành đề bài.

# Xoay ảnh theo chiều ngược.


seville_vertical_flip = ____ np.flipud.____(flipped_seville)

Bài tập 1.4: Lược đồ mức xám

Câu hỏi: Trong bài tập này, người học sẽ phân tích số lượng các
điểm màu đỏ trong ảnh. Để làm điều này cần tính toán lược đồ giá trị của
kênh màu đỏ. Ví dụ, cho ảnh 1.19, hãy sử dụng hàm hist() để khảo sát
tần suất của các điểm ảnh có màu đỏ. Các mức cần hiển thị là 256 mức
độ đậm nhạt khác nhau của màu đỏ. Gợi ý, hãy sử dụng hàm ravel() để
biến các kênh màu đỏ thành một mảng một chiều. Thư viện
Matplotlib.pylot được viết tắt là plt và thư viện Numpy được viết tắt
dưới tên np. Ngoài ra, nếu muốn có được kênh màu xanh lục của ảnh,
thực hiện lệnh sau: green = image[:, :, 1] . Về mặt ứng dụng, việc trích
xuất các kênh màu của ảnh là một phần cơ bản của việc tăng cường ảnh.
Bằng cách này, có thể cân bằng màu đỏ và xanh lam để làm cho ảnh
trông lạnh hơn hoặc ấm hơn.

33
Hình 1.18. Ảnh gốc cho bài tập 1.4
Hãy thực hiện yêu cầu trên bằng cách hoàn thiện đoạn mã sau:

# Trích xuất kênh màu đỏ


red_channel = image[_:___, __:__, 0____]
# Vẽ lược đồ kênh màu đỏ với khoảng màu [0, 256]
plt._ hist___(_ red.ravel()___.____, bins=_256___)
# Đặt tên cho khung hình và hiển thị
plt.title('Red Histogram')
plt.show()

Bài tập 1.5: Phân ngưỡng toàn cục

Câu hỏi: Trong bài tập này, người học sẽ chuyển một bức ảnh xám
sang dạng nhị phân để có thể tách đối tượng ảnh khỏi nền. Đầu tiên cần
import các thư viện cần thiết, tải ảnh, tính được giá trị phân ngưỡng tối
ưu bằng hàm threshold_otsu() và sử dụng nó vào ảnh. Sử dụng
hàm show_image() để hiển thị kết quả ảnh nhị phân.

34
Hình 1.19. Ảnh gốc cho bài tập 1.5
Lưu ý rằng phải chuyển ảnh màu sang ảnh xám sử dụng hàm
rgb2gray().

Hãy thực hiện yêu cầu trên bằng cách hoàn thiện đoạn mã sau:

# Khai báo sử dụng hàm  otsu threshold


from skimage._ filters ___ import _ threshold_otsu ___
# Chuyển ảnh màu sang ảnh xám bằng hàm rgb2gray
chess_pieces_image_gray = color.rgb2gray___(chess_pieces_im
age____)
# Tính toán ngưỡng bằng phương pháp otsu
thresh = threshold_otsu(chess_pieces_image_gray)
# Lấy ngưỡng cho ảnh xám
binary = chess_pieces_image_gray ___> thresh ____
# Hiển thị ảnh kết quả nhị phân
show_image(binary, 'Binary image')

Bài tập 1.6: Phân ngưỡng cục bộ khi nền ảnh không rõ ràng và có độ
sáng phân bố không đồng đều

35
Câu hỏi: Nếu ảnh cần xử lý có phần nền tương đối đồng nhất,
phương pháp phân ngưỡng toàn cục có thể được sử dụng như trong bài
tập trước bằng cách sử dụng hàm threshold_otsu(). Tuy nhiên, nếu phần
nền của ảnh có ánh sáng không đồng đều, sử dụng phương pháp phân
ngưỡng cục bộ threshold_local() sẽ có thể cho kết quả tốt hơn.
Trong bài tập này, phương pháp phân ngưỡng sẽ được áp dụng để
xử lý hình 1.20. Hai phương pháp toàn cục và cục bộ sẽ được so sánh để
tìm ra cách tốt hơn trong việc phân ngưỡng cho hình 1.20.

Hình 1.20. Ảnh gốc cho bài tập 1.6


Hãy thực hiện yêu cầu trên bằng cách hoàn thiện đoạn mã sau:

# Khai báo sử dụng hàm otsu threshold 


from skimage.____ import ____
# Thực hiện xác định ngưỡng toàn cục cho ảnh
global_thresh = ____(page_image)
# Lấy ảnh nhị phân dựa vào phương pháp toàn cục
binary_global = page_image ____ ____
# lấy ảnh nhị phân dựa vào phương pháp cục bộ
show_image(binary_global, 'Global thresholding')

36
Bài tập 1.7: Ảnh hưởng của việc lựa chọn thông số lấy ngưỡng đối với
việc phân ngưỡng ảnh

Câu hỏi: Thư viện Scikit-image cung cấp cho một hàm để kiểm tra
nhiều phương pháp lấy ngưỡng khác nhau để từ đó có thể lựa chọn xem
kết quả nào là tốt nhất. Hàm đó trả về các kết quả đầu ra của nhiều
ngưỡng toàn cục khác nhau. Hãy sử dụng hàm này để phân ngưỡng ảnh ở
hình 1.21. Nhận xét kết quả và lựa chọn ảnh được phân ngưỡng tốt nhất.

Hình 1.21. Ảnh gốc cho bài tập 1.7


Hướng dẫn: lấy ngưỡng toàn cục cho ảnh trên, trong đó thư viện
matplotlib.pyplot đã được tải dưới tên plt. Sử dụng hàm
try_all_threshold() để thử nhiều ngưỡng toàn cục khác nhau.

Hãy thực hiện yêu cầu trên bằng cách hoàn thiện đoạn mã sau:

# Khai báo sử dụng hàm try_all


from skimage.____ import ____
# khai báo sử dụng hàm chuyển đổi ảnh sang ảnh xám 
from skimage.____ import ____
# Chuyển đổi ảnh gốc sang ảnh xám
grayscale = ____

37
# Sử dụng hàm phân ngưỡng
fig, ax = ____(____, verbose=False)
# Hiển thị kết quả.
plt.show()

Hướng dẫn

● Import hàm try_all_threshold() 


● Import hàm chuyển ảnh RGB sang ảnh xám
● Chuyển ảnh màu thành ảnh xám
● Sử dụng hàm try_all_threshold() cho ảnh xám

Bài tập 1.8: Ứng dụng phân ngưỡng ảnh

Câu hỏi: Trong bài tập này, người học sẽ quyết định loại ngưỡng
nào là tốt nhất để có thể được sử dụng nhằm nhị phân hóa một ảnh cho
trước. Mục đích là có thể nhìn thấy hình dạng của các đồ vật như các trái
tim bằng giấy đến chiếc kéo một cách rõ ràng.

Hình 1.22. Ảnh gốc cho bài tập 1.8


Hãy xác định phương pháp phân ngưỡng nào nên được áp dụng, từ
đó thực hiện yêu cầu trên bằng cách hoàn thiện đoạn mã sau:

38
# Khai báo các thư viện, hàm cần dùng
from skimage.____ import ____
from skimage.color import ____
#Chuyển ảnh màu thành ảnh xám
gray_tools_image = ____
# Tính giá trị ngưỡng
thresh = ____(gray_tools_image)
# Thực hiện phân ngưỡng
binary_image = gray_tools_image > ____
# HIển thị kết quả
show_image(binary_image, 'Binarized image')

39
CHƯƠNG 2.
XỬ LÝ ẢNH DỰA TRÊN NHỮNG ĐIỂM ẢNH LÂN
CẬN VÀ BIẾN ĐỔI TRONG KHÔNG GIAN

Trong chương một, người học đã tìm hiểu về cách xử lý ảnh dựa theo
từng điểm ảnh. Ở chương hai, các kỹ thuật xử lý ảnh dựa và những điểm
ảnh lân cận sẽ được khảo sát. Đối với ảnh xám thông thường, kỹ thuật xử
lý lân cận nổi tiếng nhất là nhân chập. Chương này sẽ áp dụng kỹ thuật
nhân tập để rút trích cạnh (thông tin tần số cao) và làm mờ ảnh (thông tin
tần số thấp). Bên cạnh đó, việc xử lý ảnh nhị phân dựa trên các điểm lân
cận cũng sẽ được trình bày. Kỹ thuật này được thể hiện trong các phép
toán hình thái học. Ngoài ra, các phép toán hình học trong xử lý ảnh cũng
sẽ được thảo luận. Cuối cùng kỹ thuật tăng cường ảnh dựa vào xử lý lược
đồ mức xám sẽ được trình bày để. Nội dung chính của chương sẽ gồm
các phần nhỏ sau đây:
● Bộ lọc nhân chập
● Tăng cường ảnh
● Biển đổi hình học của ảnh
● Biến đổi hình thái học

2.1. Bộ lọc nhân chập

Lọc là một kỹ thuật để sửa đổi hoặc tăng cường ảnh dựa vào thông
tin của những điểm ảnh lân cận. Về bản chất, phép toán lọc là phép toán
nhân chập một ma trận lọc với ảnh gốc. Bộ lọc có thể được giúp tăng
cường hoặc loại bỏ một số đặc tính nhất định, chẳng hạn như tăng cường

40
các cạnh của ảnh, làm mịn, làm sắc nét và phát hiện cạnh. Trong phần
này sẽ đề cập đến việc làm mịn và phát hiện cạnh.

Hình 2.1 mô tả cách thức các phép lọc hoạt động. Điểm ảnh màu
xám đậm thể hiện điểm ảnh mà ta đang quan tâm, các điểm ảnh màu xám
nhạt thể hiện các lân cận được sử dụng để tính giá trị ngõ ra. Điểm ảnh
màu xám đậm ở ngõ ra là kết quả của việc tính toán từ các điểm ảnh lân
cận của ảnh gốc.

Hình 2.23. Phép lọc dựa trên các điểm ảnh lân cận
2.1.1. Phát hiện cạnh

Một trong những ứng dụng kinh điển của phép toán lọc đó là bài
toán phát hiện cạnh. Kỹ thuật này có thể được sử dụng để tìm ranh giới
của các đối tượng trong ảnh. Kỹ thuật phát hiện cạnh thường đi kèm với
bài toán phân đoạn và để trích xuất xem có bao nhiêu đối tượng trong
ảnh. Tuy nhiên thuật toán phát hiện cạnh còn có thể dùng để xử lý một
ảnh xám. Trong trường hợp này, các cạnh của đối tượng được tăng cường
và giữ lại trong khi các thành phần khác bị loại bỏ.

41
Hình 2.24. Ảnh gốc và các cạnh được phát hiện
Tính năng phát hiện cạnh hoạt động dựa trên sự không liên tục về
độ sáng của các điểm ảnh lân cận. Bộ lọc Sobel là một bộ lọc có thể tính
được sự không liên tục của các mức xám trong ảnh. Do đó, đây là bộ lọc
thường được sử dụng để hiển thị các đối tượng trong ảnh. Trong hình 2.3,
bộ lọc Sobel được sử dụng để phát hiện hình dạng của đối tượng trong
ảnh.

Hình 2.25. Kết quả phát hiện cạnh dùng bộ lọc Sobel
Để thực hiện phép phát hiện cạnh bằng bộ lọc Sobel, ta có thể sử
dụng hàm sobel trong gói thư viện skimage.filters. Trước khi áp dụng
hàm sobel ảnh cần được chuyển về ảnh xám. Trong ví dụ tiếp theo, ảnh
về các đồng tiền xu đã được lưu sẵn trong gói thư viện data với tên
image_coins. Hàm sobel truyền ảnh xám cần xử lý dưới dạng tham số

42
đầu vào và thực hiện lọc với bộ lọc Sobel một cách tự động. Vì vậy,
trong trường hợp ảnh đầu vào là ảnh màu, cần chuyển ảnh đó sang ảnh
xám. Để có thể hiện thị ảnh gốc và ảnh kết quả cùng lúc trên một khung
hình duy nhất, ta sử dụng hàm con plot_comparison. Trong hàm con này,
việc hiển thị ảnh gốc và ảnh kết quả bằng hàm subplots trong gói thư
viện Matplotlib.pylot. Hình 2.2 và 2.3 cho thấy bộ lọc phát hiện các
cạnh trong ảnh gốc và làm nổi bật các biên dưới dạng các đường khép
kín trong kết quả hiển thị ngõ ra.

Ví dụ mẫu sử dụng bộ lọc sobel:

# Khai báo sử dụng hàm sobel


from skimage.filters import sobel
# áp dụng hàm sobel để lọc cạnh
edge_sobel = sobel(image_coins)
# hiển thị kết quả ảnh gốc và ảnh đã được lọc cạnh
plot_comparison(image_coins, edge_sobel, "Edge with Sobel")

Trong ví dụ trên, hàm plot_comparison là một chương trình con dùng để


hiển thị hai ảnh trên cùng một khung hình vẽ, chi tiết của chương trình
con plot_comparison được viết như sau. Việc tổ chức hàm
plot_comparison như một chương trình con cho phép ta sử dụng lại đoạn
mã này và tập trung hơn vào phần xử lý ảnh.

def plot_comparison(original, filtered, title_filtered):


fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(8, 6),
sharex=True, sharey=True)
ax1.imshow(original, cmap=plt.cm.gray)
ax1.set_title('original')

43
ax1.axis('off')
ax2.imshow(filtered, cmap=plt.cm.gray)
ax2.set_title(title_filtered)
ax2.axis('off')

2.1.2. Làm mịn bằng bộ lọc Gaussian

Trong khi bộ lọc Sobel là bộ lọc giúp rút trích thông tin tần số cao,
một kỹ thuật lọc khác là lọc Gaussian giúp rút trích tần số thấp và làm
mịn ảnh. Bộ lọc Gaussian có giá trị điểm cao tại vị trí trung tâm và giá trị
đó sẽ nhỏ dần khi di chuyển ra xa vị trí trung tâm của bộ lọc. Kỹ thuật
này thường được sử dụng để làm mờ ảnh hoặc giảm nhiễu. Ảnh dưới đây
cho ta thấy hiệu quả của việc dùng bộ lọc Gaussian, đặc biệt có thể thấy
rõ ảnh hưởng tại các chi tiết nhỏ như trong Hình 2.4. Tuy có thể giúp lọc
được nhiễu, nhưng tùy vào thông số điều khiển băng thông bộ lọc, bộ lọc
Gaussian sẽ làm mờ các cạnh và giảm độ tương phản.

(a)

44
(b)

(c)
Hình 2.26. Ảnh gốc và ảnh kết quả làm mờ dùng bộ lọc Gaussian
Đoạn mã ví dụ minh hoạ sử dụng bộ lọc Gaussian:

# khai báo các thư viện thường dùng


from skimage.filters import gaussian
# Áp dụng bộ lọc Gaussian
gaussian_image = gaussian(amsterdam_pic, multichannel=True)
# Hiển thị và so sánh ảnh gốc cùng ảnh kết quả
plot_comparison(amsterdam_pic, gaussian_image, "Blurred with
Gaussian filter")

45
Để có thể sử dụng bộ lọc Gaussian, ta có thể sử dụng hàm
Gaussian từ gói thư viện filters của gói scikit image. Tham số đầu tiên
được truyền vào là ảnh gốc; tham số multichannel là một biến boolean
mà nếu nhận giá trị True/False sẽ cho phép xử lý ảnh màu/ ảnh xám.
Cuối cùng, so sánh ảnh gốc và ảnh kết quả.

2.2. Tăng cường độ tương phản của ảnh

Một trong những bài toán kinh điển của xử lý ảnh là tăng cường độ
tương phản của ảnh. Kỹ thuật này cho phép tăng cường sự khác biệt giữa
một điểm ảnh và những điểm ảnh lân cận. Tăng cường ảnh có thể cực kỳ
hữu ích trong nhiều lĩnh vực. Ví dụ trong hình 2.7, thông thường các ảnh
y tế như X-quang này có thể có độ tương phản thấp, khó phát hiện các
chi tiết quan trọng. Khi cải thiện độ tương phản, các chi tiết sẽ trở nên rõ
ràng hơn, dễ dàng phát hiện mọi thứ trên ảnh hơn.

Hình 2.27. Tăng cường ảnh X-quang


2.2.1. Độ tương phản và sự tương quan với lược đồ mức xám

Độ tương phản của một ảnh có sự liên quan mật thiết với lược đồ
mức xám. Nếu một ảnh có lược đồ mức xám chỉ tập trung ở một vùng

46
hẹp, ta khó có thể thấy rõ các chi tiết vì mọi điểm ảnh đều có mức xám
gần bằng nhau. Do đó, "độ lan rộng" của biểu đồ mức xám có thể xem
như là một chỉ dấu để đánh giá độ tương phản của ảnh. Nếu độ rộng này
lớn, có thể xem ảnh có độ tương phản cao. Ngược lại, ảnh sẽ có độ tương
phản thấp. Hình 2.6 cho thấy một ví dụ về ảnh gốc và lược đồ mức xám
tương ứng của nó. Độ rộng của lược đồ càng lớn thì ảnh càng rõ.

Hình 2.28. Ảnh gốc và lược đồ mức xám của ảnh. Độ rộng của lược đồ
mức xám càng lớn thì ảnh sẽ có độ tương phản cao hơn
Trong hình 2.7 thể hiện một ví dụ về sự co hẹp của biểu đồ mức
xám là nguyên nhân của ảnh có độ tương phản kém. Ảnh có độ tương
phản thấp có sự khác biệt nhỏ giữa các giá trị mức xám của các điểm ảnh
tối và sáng. Thường thì lược đồ mức sáng sẽ bị lệch sang phải (nếu là ảnh
sáng), và bị lệch sang trái (khi ảnh là ảnh tối), hoặc nằm quanh điểm giữa
(chủ yếu là màu xám).

47
Hình 2.29. Ảnh gốc có độ tương phản thấp và Histogram của ảnh
2.2.2. Tăng cường độ tương phản

Để tăng cường độ tương phản, một giải pháp thường dùng là kéo
giãn biểu đồ mức xám sao cho lược đồ mức xám của ảnh sau khi biến đổi
sẽ trải rộng và đều trong khoảng [0-255]. Kỹ thuật cân bằng lược đồ mức
xám cho phép trải rộng các giá trị cường độ mức xám để xác suất xuất
hiện các mức xám là giống nhau. Trong thư viện scikit-image, có ba
phương pháp thông dụng được sử dụng để tăng cường độ tương phản của
ảnh. Các phương pháp đó là kéo giãn độ tương phản, cân bằng lược đồ
mức xám toàn cục, và cân bằng lược đồ cục bộ. Một ví dụ về hiệu quả
của ba phương pháp này được thể hiện trong hình 2.8. Trong ba phương
pháp này, phương pháp kéo dãn lược đồ mức xám là phương pháp đơn
giản nhất khi dải dữ liệu hẹp của ảnh gốc được kéo dãn tuyến tính về
khoảng từ [0-255]. Phương pháp này chỉ hiệu quả khi giá trị lớn nhất và
nhỏ nhất của ảnh gốc bị giới hạn trong một khoảng rất hẹp. Vì điều kiện
này thường khó xảy ra trong thực tế nên kỹ thuật kéo dãn lược đồ mức
xám ít khi được sử dụng. Vì vậy trong chương này, người học chủ yếu

48
khảo sát hai phương pháp là cân bằng lược đồ mức xám toàn cục và cân
bằng lược đồ mức xám cục bộ.

Hình 2.30. Ảnh gốc có độ tương phản thấp và các ảnh sau khi được
tăng cường theo ba phương pháp: kéo giãn độ tương phản, cân bằng
lược đồ mức xám, và cân bằng lược đồ cục bộ.
2.2.3. Cân bằng lược đồ mức xám toàn cục

Hình 2.9 mô tả một ví dụ về việc tăng cường độ tương phản của


một ảnh sử dụng phương pháp cân bằng lược đồ mức xám toàn cục.

(a)

49
(b)

Hình 2.31. Ảnh gốc và ảnh sau khi được cân bằng Histogram
Để ứng dụng kỹ thuật cân bằng lược đồ mức xám toàn cục, ta khai
báo sử dụng khối thư viện exposure từ thư viện scikit-image. Sau đó sử
dụng hàm equalize_hist, để thực hiện cân bằng lược đồ mức xám toàn
cục cho ảnh gốc. Từ kết quả quan sát được trong hình 2.9, ta nhận thấy
mặc dù độ tương phản tăng lên nhưng ảnh trông không tự nhiên, thậm chí
không giống như ảnh đã được tăng cường. Điều này là do phương pháp
tăng cường ảnh toàn cục chỉ tập trung vào lược đồ mức xám của toàn ảnh
chứ không quan tâm tới các chi tiết nhỏ. Ta có thể thấy chiếc xe đã trở
nên rất tối màu trong khi bãi cát có phân phối màu không đồng đều.

from skimage import exposure


# Nhận kết quả cân bằng lược đồ mức xám
image_eq = exposure.equalize_hist(image)
# Hiển thị ảnh gốc và kết quả.
show_image(image, 'Original')
show_image(image_eq, 'Histogram equalized')

2.2.4. Cân bằng lược đồ mức xám cục bộ

Một kỹ thuật cân bằng lược đồ mức xám khác là cân bằng dựa trên
lược đồ mức xám cục bộ. Phương pháp này chia ảnh thành nhiều biểu đồ
mức xám theo vị trí của các điểm ảnh. Mỗi biểu đồ tương ứng với một
phần riêng biệt của ảnh và chúng được sử dụng để phân phối lại các giá
trị độ đậm nhạt của biểu đồ mức xám của ảnh theo từng vùng nhỏ chứ
không phải của toàn ảnh. Phương pháp nổi tiếng nhất trong nhánh này là
cân bằng lược đồ động có xét đến giới hạn độ tương phản (CLAHE).

50
Phương pháp này được được thiết kế để ngăn chặn sự khuếch đại quá
mức các thông tin nhiễu mà các phương pháp khác có thể tạo ra. Trong
Hình 2.10, ta có thể thấy rằng phương pháp CLAHE tránh được hiện
tượng một số vùng trở nên quá sáng trong khi một số vùng trở nên quá
tối. Quan sát kỹ hơn và so sánh các kết quả thì sẽ thấy rằng phương pháp
thích ứng CLAHE cho kết quả trông tự nhiên hơn. Điều này là do nó
không lấy biểu đồ chung của toàn bộ ảnh mà hoạt động trên các vùng nhỏ
được gọi là ô hoặc vùng lân cận.

(a)

(b)
Hình 2.32. Ảnh gốc và ảnh sau khi được cân bằng thích ứng
Để sử dụng kỹ thuật CLAHE, ta khai báo sử dụng khối thư viện
exposure từ thư viện scikit-image giống như ví dụ ở trên. Tuy nhiên,
thay vì sử dụng hàm equalize_hist, ta có thể sử dụng hàm

51
equalize_adapthist. Hàm này tính toán các biến đổi độ tương phản cho
từng ô riêng lẻ. Để tránh bị quá tăng cường, một tham số điều khiển được
sử dụng là tham số clip_limit. Tham số này được chuẩn hóa giữa 0 và 1;
và nếu giá trị của tham số này càng cao thì độ tương phản càng lớn. Mặc
dù vậy, độ tương phản lớn sẽ dẫn tới hiện tượng quá tăng cường và làm
cho ảnh trông không tự nhiên.

Đoạn mã ví dụ minh hoạ:

from skimage import exposure


# Áp dụng cân bằng lược đồ mức xám cục bộ
image_adapteq = exposure.equalize_adapthist(image,
clip_limit=0.03)
# Hiển thị ảnh gốc và ảnh kết quả
show_image(image, 'Original')
show_image(image_adapteq, 'Adaptive equalized')

2.3. Chỉnh kích thước và xoay ảnh

Trong phần này, người học sẽ thảo luận về cách xoay và thay đổi
kích thước ảnh. Các kỹ thuật này khi đứng một mình sẽ không có ứng
dụng gì nhiều. Mặc dù vậy, chúng lại là một phần không thể thiếu khi kết
hợp với các mô hình sử dụng trí tuệ nhân tạo trong xử lý ảnh. Một ví dụ
điển hình là các bài toán phân loại và phát hiện đối tượng trong kỹ thuật
học sâu. Hiện nay có rất nhiều mô hình được huấn luyện sẵn để thực hiện
công việc này. Mặc dù vậy, các mô hình đó thường đòi hỏi các ảnh đầu
vào có kích thước cố định; trong khi thực tế ảnh thu nhận được có kích
thước rất đa dạng. Chính vì vậy, các ảnh cần được thay đổi kích thước về
cùng một chuẩn đã được sử dụng khi huấn luyện mô hình học sâu. Hình

52
2.11 cho thấy một ứng dụng của việc thay đổi kích thước ảnh như là một
bước tiền xử lý trước khi áp dụng các thuật toán trí tuệ nhân tạo. Bên
cạnh đó, việc xoay ảnh cũng sẽ giúp tạo ra nhiều ảnh giả lập mới từ một
ảnh ban đầu. Đây cũng là một cách để giúp có thêm nhiều dữ liệu cho
việc huấn luyện mô hình học sâu.

Hình 2.33. Ứng dụng của việc thay đổi kích thước ảnh
2.3.1. Xoay ảnh

Việc xoay ảnh có thể được thực hiện dễ dàng bằng cách sử dụng
thư viện Numpy. Để mô tả việc xoay ảnh ta sử dụng thông số điều khiển
góc quay và hướng của góc quay. Ví dụ như khi xoay ảnh 90 độ theo
chiều kim đồng hồ hoặc xoay ảnh ngược chiều kim đồng hồ. Hình 2.12
cho một minh họa về kết quả của quá trình xoay ảnh theo cả hai hướng.

Ảnh gốc Ảnh xoay 90 độ Ảnh xoay 90 độ


cùng chiều kim đồng hồ ngược chiều kim đồng hồ
Hình 2.34. Ảnh gốc và các ảnh sau khi được xoay

53
Nhằm thuận tiện cho việc xoay ảnh, thư viện scikit-image cung
cấp gói transform. Gói thư viện này cung cấp nhiều hàm thực hiện các
chức năng khác nhau trong việc biến đổi ảnh. Ví dụ như hàm rotate
nhằm giúp xoay ảnh theo một góc điều khiển cho trước. Trong đoạn mã
dưới đây ta xoay ảnh xoay 90 độ theo chiều kim đồng hồ. Tham số đầu
tiên của hàm rotate là ảnh cần xoay và tham số thứ hai là góc quay. Để
quay theo chiều kim đồng hồ, tham số điều khiển góc quay sẽ phải mang
dấu âm. Điều này được định nghĩa trong tài liệu tham khảo của câu lệnh
rotate và cần thảm khảo tài liệu gốc để biết về các cập nhập mới nhất.

Đoạn mã ví dụ minh hoạ xoay ảnh theo chiều kim đồng hồ:

from skimage.transform import rotate


# Xoay ảnh 90 độ theo chiều kim đồng hồ
image_rotated = rotate(image, -90)
show_image(image_rotated, 'Original')
show_image(image_rotated, 'Rotated 90 degrees clockwise')

Trong trường hợp muốn xoay ngược chiều kim đồng hồ, tham số
góc quay điều khiển sẽ mang dấu dương. Đoạn mã sau đây ví dụ về cách
xoay ảnh ngược chiều kim đồng hồ bằng cách khai báo góc quay là 90.

Ví dụ minh họa xoay ảnh 90 độ ngược chiều kim đồng hồ:

from skimage.transform import rotate


# Xoay ảnh 90 độ ngược chiều kim đồng hồ
image_rotated = rotate(image, 90)
show_image(image_rotated, 'Original')
show_image(image_rotated, 'Rotated 90 degrees anticlockwise')

54
2.3.2. Thay đổi theo tỷ lệ ảnh

Bên cạnh hàm rotate để xoay ảnh, gói thư viện transform còn
cung cấp một số hàm khác. Ví dụ, để thay đổi kích thước ảnh theo một
hệ số tỷ lệ nhất định, ta sẽ sử dụng hàm rescale. Tham số điều khiển là
một số thực để định nghĩa mức độ scale chung của ảnh. Ngoài ra, nếu
muốn thay đổi giá trị kích thước cho mỗi trục, ta có thể gán riêng tỷ lệ
thay đổi kích thước cho từng trục.

Hình 2.35. Ảnh gốc và ảnh sau khi được thay đổi kích thước
Trong đoạn mã dưới đây, hàm rescale từ thư viện transform được
sử dụng để làm cho ảnh nhỏ hơn 4 lần so với kích thước ban đầu. Tham
số điều khiển ở đây được chọn là 1/4. Việc chọn tham số anti_aliasing
nhận giá trị True cho phép áp dụng bộ lọc để làm mịn ảnh trước khi giảm
tỷ lệ ảnh hay không. Ngoài ra, tham số multichannel=True cho phép xử
lý các ảnh màu. Nếu ảnh đưa vào là ảnh xám, ta có thể không cần quan
tâm tới yếu tố đó.

Ví dụ minh họa:

55
from skimage.transform import rescale
#Thay đổi tỷ lệ thành nhỏ hơn 4 lần
image_rescaled = rescale(image, 1/4, anti_aliasing=True,
multichannel=True)
show_image(image, 'Original image')
show_image(image_rescaled, 'Rescaled image')

Để hiểu rõ hơn vai trò của tham số điều khiển anti_aliasing, hãy
cùng quan sát Hình 2.14. Trong ảnh kỹ thuật số, hiện tượng răng cưa là
một hiệu ứng gợn sóng khi phóng to ảnh. Điều này làm cho thông tin
không được liền lạc. Nguyên nhân chính của hiện tượng này là do ảnh
gốc có độ phân giải kém. Quan sát kỹ kết quả thay đổi kích thước ảnh khi
không sử dụng anti_aliasing, ta thấy hiện tượng răng cưa ở ảnh kết quả
hiện lên ngay các cạnh của vật thể. Trong khi đó, nếu sử dụng kỹ thuật
khử răng cưa bằng cách đặt anti_aliasing=True thì kết quả đạt được sau
khi thay đổi tỷ lệ ảnh rất mịn.

Hình 2.36. Ảnh kết quả sau khi thay đổi kích thước có và không có sử
dụng bộ khử răng cưa

56
2.3.3. Thay đổi kích thước ảnh

Tương tự như thay đổi tỷ lệ ảnh, việc thay đổi kích thước ảnh sẽ sẽ
làm thay đổi độ phân giải của ảnh và được sử dụng để làm cho ảnh phù
hợp với một kích thước nhất định. Điểm khác biệt chính của hai phương
pháp này là phương pháp thay đổi kích thước ảnh cho phép xác định kích
thước của ảnh kết quả thay vì lựa chọn một hệ thống thay đổi tỷ lệ.

Hình 2.37. Ảnh gốc và ảnh sau khi thay đổi kích thước
Ta có thể thực hiện việc thay đổi kích thước ảnh bằng cách dùng
hàm resize trong thư viện transform. Hàm này yêu cầu tham số đầu điều
khiển đầu tiên là ảnh gốc và tham số điều khiển thứ hai là một tuple lưu
giữ chiều cao và chiều rộng mong muốn để thay đổi kích thước ảnh. Ta
cũng có thể tùy chọn xác định phương pháp khử răng cưa giống như đã
thảo luận ở hàm scale.

Ví dụ mẫu thay đôỉ kích thước ảnh.


from skimage.transform import resize
# Chiều cao và chiều rộng khi thay đôỉ kích thước ảnh
height = 400
width = 600

57
# Đổi kích thước ảnh với chiều cao và chiều rộng đã định nghĩa
trước
image_resized = resize (image, (height, width),
anti_aliasing=True)
# HIển thị ảnh gốc và ảnh sau biến đổi
show_image(image, 'Original image')
show_image(image_resized, 'Resized image')
Kết quả của ví dụ mẫu ở trên được thể hiện ở Hình 2.16. Ảnh gốc
đã được thay đổi kích thước thành chiều cao là 400 và chiều rộng là 600.

Hình 2.38. Ảnh gốc và ảnh sau khi thay đổi kích thước
Ta cũng có thể điều khiển các biến chiều dài và chiều rộng của ảnh
thông qua các phép chia tỷ lê với kích thước ban đầu lấy từ ảnh gốc. Kết
quả ở hình 2.17 và ví dụ mẫu dưới đây cho thấy hàm scale và hàm resize
có quan hệ mật thiết với nhau.
Ví dụ mẫu:
from skimage.transform import resize
# Tính toán kích thước ảnh mới khi giảm kích thước ảnh cũ 4 lần
height = image.shape[0] / 4
width = image.shape[1] / 4

58
# Thay đổi kích thước ảnh
image_resized = resize (image, (height, width),
anti_aliasing=True)
show_image(image_resized, 'Resized image')

Hình 2.39. Ảnh gốc và ảnh sau khi thay đổi kích thước
2.4. Phép biến đổi hình thái học:

Trong chương trước, ta đã thảo luận về phương pháp phân ngưỡng


để tách đối tượng cần quan tâm và khung nền. Tuy nhiên thực tế rất khó
chọn được một ngưỡng mà có thể phân tách các đối tượng và phần nền.
Như trong hình 2.23, luôn luôn có một số ít những điểm ảnh chưa được
phân ngưỡng tốt. Để có thể phát hiện các đối tượng trong một ảnh tốt
hơn, ta có thể dựa vào các đặc điểm hình dạng của ảnh. Các thông tin về
hình dạng này được mô tả trong các phép biến đổi hình thái học.

59
Hình 2.40. Ảnh gốc và ảnh nhị phân được tạo bằng cách lấy ngưỡng
Thông thường, các phép toán hình thái học sẽ cố gắng loại bỏ
những điểm không hoàn hảo này bằng cách tính đến hình thức và cấu
trúc của các đối tượng trong ảnh. Các phép toán hình thái học đặc biệt
phù hợp với ảnh nhị phân; nhưng một số trường hợp có thể mở rộng sang
các ảnh xám. Hình 2.19 cho thấy hai ví dụ về ảnh nhị phân và ảnh xám.
Trong khi ảnh nhị phân chỉ có hai giá trị là 0 và 1, ảnh xám sẽ có nhiều
mức xám hơn.

Hình 2.41. Ảnh nhị phân và ảnh xám


2.4.1. Phép biến đổi giãn nở và xói mòn

Hai phép biến đổi hình thái học cơ bản là giãn nở ra và xói mòn.
Phép giãn nở có mục tiêu là thêm các điểm ảnh màu trắng vào biên của
các đối tượng đã phân đoạn trong ảnh nhị phân. Ngược lại, phép toán xói
mòn là loại bỏ các điểm ảnh trên biên của đối tượng. Số lượng điểm ảnh

60
được thêm vào hoặc xóa khỏi các đối tượng trong ảnh nhị phân phụ thuộc
vào kích thước và hình dạng của phần tử cấu trúc được sử dụng để xử lý
ảnh.

Hình 2.42. Ảnh gốc và các ảnh được giãn ra và bào mòn đi
Phần tử cấu trúc là một ảnh nhị phân có kích thước nhỏ được sử
dụng để trượt trên ảnh đầu vào. Phần tử cấu trúc này được so sánh với
đối tượng ảnh mà ta đã phân đoạn. Trong hình 2.21, phần tử cấu trúc là
hình vuông với kích thước 2*2. Tại vị trí "A", đối tượng của ảnh giao
hoàn toàn với thành phần cấu trúc. Tại vị trí "B" đối tượng giao một phần
với thành phần cấu trúc. Và tại vị trí "C", không có sự giao nhau giữa ảnh
đối tượng và thành phần có cấu trúc. Đối với phép giãn nở, chỉ khi đối
tượng không giao nhau với thành phần cấu trúc thì giá trị trả về là 0; tất
cả các trường hợp khác đều trả về là 1. Ngược lại, đối với phép xói mòn,
chỉ khi đối tượng giao nhau hoàn toàn với thành phần cấu trúc thì giá trị
trả về là 1; các trường hợp khác đều trả về là 0.

61
Hình 2.43. Đối tượng và phần tử cấu trúc tương ứng
Các thành phần cấu trúc có thể có kích thước và hình dạng khác
nhau tùy theo ứng dụng. Trong hình 2.22, thành phần cấu trúc có thể có
giá trị là một hình vuông 5x5 hoặc 3x3. Hình dạng của bit 1 và 0 xác
định hình dạng của phần tử cấu trúc. Thông thường, các thành phần cấu
trúc phải có hình dạng tương tự với hình dạng của đối tượng mà ta muốn
chọn. Vì các đối tượng cần phân tách có nhiều loại hình dạng khác nhau
nên các thành phần cấu trúc cũng có nhiều hình dạng khác nhau. Các
hình dạng cơ bản có thể kể đến là hình vuông, hình thoi, hay hình chữ
nhật. Bên cạnh đó, ta có thể lựa chọn điểm gốc của thành phần có cấu
trúc để tiến hành so sánh. Ô màu hồng trong hình 2.22 là trung tâm hoặc
điểm gốc của phần tử cấu trúc. Điểm gốc này giúp xác định vị trí điểm
ảnh đang được xử lý của phép toán hình thái học.

Hình 2.44. Các dạng phần tử cấu trúc

62
2.4.2. Ứng dụng thư viện scikit-image cho bài toán hình thái học

Để có thể thực hiện các phép toán hình thái học, việc đầu tiên cần
làm là định nghĩa thành phần cấu trúc. Thư viện Scikit-image cung cấp
nhiều lựa chọn khác nhau để định nghĩa phần tử có cấu trúc này. Các
thành phần cấu trúc cơ bản được cung cấp sẵn trong thư viện
morphology. Ví dụ, nếu muốn khởi tạo phần tử có cấu trúc có dạng hình
vuông, người dùng có thể sử dụng hàm square. Hoặc nếu muốn khởi tạo
phần tử có cấu trúc có dạng hình chữ nhật với chiều rộng và chiều cao cụ
thể, người dùng có thể sử dụng hàm rectangle. Ví dụ dưới đây cho thấy
cách sử dụng gói thư viện morphology để định nghĩa các phần tử cấu
trúc. Ngoài ra các phần tử cấu trúc nào có thể được định nghĩa thủ công
bằng cách sử dụng các ma trận Numpy.

from skimage import morphology rectangle =


square = morphology.square(4) morphology.rectangle(4, 2)
[[1 1 1 1]
[1 1 1 1] [[1 1]
[1 1 1 1] [1 1]
[1 1 1 1]] [1 1]
[1 1]]
Để sử dụng phép toán xói mòn, thư viện morphology hỗ trợ hàm
binary_erosion. Hàm này thực hiện phép xói mòn với đầu vào là một
ảnh nhị nhân và một thành phần có cấu trúc cho trước. Đầu tiên, người
dùng cần khai báo các thư viện morphology cần sử dụng thông qua lệnh
import. Ảnh nhị phân image_horse được tải lên trực tiếp từ trong thư
viện. Đây là một ảnh nhị phân giống như thể hiện ở hình 2.23. Sau đó
người dùng cần định nghĩa thành phần cấu trúc. Trong ví dụ ở dưới,
thành phần cấu trúc này là một hình chữ nhật có kích thước (12,6). Để sử

63
dụng hàm binary_erosion, cần truyền thanh số điều khiển ảnh đầu vào
và tham số điều khieenr phần tử cấu trúc. Nếu phần tử cấu trúc không
được chọn, hàm sẽ sử dụng phần tử có cấu trúc mặc định hình chữ thập.

Ví dụ mẫu sử dụng phép biến đôỉ hình thái học.

from skimage import morphology


# Định nghĩa thành phần cấu trúc là một hình chữ nhật 12x6
selem = morphology.rectangle(12,6)
# Lấy ảnh kết quả bằng phép toán xói mòn binary_erosion
eroded_image = morphology.binary_erosion(image_horse,
selem=selem)
# Hiển thị kết quả
plot_comparison(image_horse, eroded_image, 'Erosion')

Hình 2.23 hiển thị ảnh kết quả sau khi thực hiện phép toán xói mòn
và ảnh gốc để so sánh. Ta thấy rằng ảnh kết quả bị thiếu một số pixel
nhưng vẫn là hình con ngựa.

Hình 2.45. Ảnh gốc và ảnh kết quả sau khi xói mòn bằng cấu trúc
hình chữ nhật 12x6

64
Tùy thuộc vào ứng dụng, việc lựa chọn thành phần cấu trúc phải
được chọn khác nhau. Giả sử không lựa chọn thành phần cấu trúc là một
hình chữ nhật như ví dụ trên mà bỏ trống tham số điều khiển thành phần
cấu trúc selem; lúc này thành phần cấu trúc sẽ được hiểu là một giá trị
mặc định. Trong hàm này, giá trị selem mặc định là hình chữ thập kích
thước 3x3. Dựa vào kết quả ở hình 2.24, ta thấy số lượng các điểm ảnh bị
xói mòn ít hơn và tổng thể ảnh vẫn giữ được hình dạng giống bạn đầu.
Tuy nhiên các chi tiết nhỏ như ở phần tai ngựa không được xóa bớt. Do
đó, tùy theo yêu cầu của từng ứng dụng cụ thể, việc chọn lựa các thành
phần cấu trúc này có thể thay đổi.

Ví dụ mẫu về cách sử dụng phép toán xói mòn:

# Xói mòn nhị phân với thành phần cấu trúc mặc định
eroded_image = morphology.binary_erosion(image_horse)

Hình 2.46. Ảnh gốc và ảnh sau khi bào mòn bằng cấu trúc mặc định
Bên cạnh phép xói mòn, phép toán giãn nở cũng là một phép toán
cơ bản trong biến đổi hình thái học. Giống như tên gọi, phép toán này
"mở rộng" các đối tượng trong ảnh nhị phân. Thư viện morphology hỗ

65
trợ hàm Binary_dilation, để thực hiện phép toán giãn nở cho ảnh nhị
phân. Ví dụ dưới thực hiện phép toán giãn nở trên ảnh image_horse.
Phần tử cấu trúc không được khai báo, tức là nhận giá trị mặc định là một
hình chữ thập kích thước 3*3.

Ví dụ mẫu về cách sử dụng phép giãn nở.

from skimage import morphology


# Giãn nở với ảnh nhị phân
dilated_image = morphology.binary_dilation(image_horse)
# Hiển thị kết quả đạt được.
plot_comparison(image_horse, dilated_image, 'Erosion')

Hình 2.47. Ảnh gốc và ảnh kết quả sau khi mở rộng

66
BÀI TẬP
Bài tập 2.1: Phát hiện cạnh
Câu hỏi: Hãy hoàn thiện đoạn mã sau để phát hiện các cạnh của
ảnh gốc bằng cách sử dụng bộ lọc Sobel

Hình 2.48. Ảnh gốc của bài tập 2.1


Điền vào đoạn mã còn thiếu sau đây

# Khai báo thư viện cần sử dụng


from ____ import ____
# khai báo sử dụng bộ lọc sobel
from skimage.____ import ____
# Chuyển ảnh gốc thành ảnh xám
soaps_image_gray = ____.____(soaps_image)
# Ứng dụng bộ lọc phát hiện cạnh
edge_sobel = ____(____)
# Hiển thị và so sánh ảnh gốc cũng như ảnh đã được phát hiện
cạnh
show_image(soaps_image, "Original")
show_image(edge_sobel, "Edges with Sobel")

67
Hướng dẫn:

● Import thư viện color để chuyển ảnh thành ảnh xám


● Import hàm sobel() từ module filters 
● Tạo ảnh xám soaps_image sử dụng phương thức thích hợp từ
thư viện color
● Sử dụng bộ lọc phát hiện biên Sobel trên ảnh xám
soaps_image_gray

Bài tập 2.2: Lọc nhiễu cho ảnh


Câu hỏi: Sử dụng bộ lọc thông thấp để làm mờ ảnh và lọc nhiễu.
Sau khi lọc nhiễu hãy đánh giá tỷ lệ nhiễu của ảnh.

Hình 2.49. Ảnh gốc của bài tập 2.2

68
Hoàn thành đoạn mã chưa hoàn thiện:

# Khai báo sử dụng bộ lọc Gaussian


____
# Áp dụng bộ lọc
gaussian_image = ____
# Hiển thị ảnh gốc và ảnh kết quả
show_image(____, "Original")
____ (____, "Reduced sharpness Gaussian")
# Đánh giá mức độ nhiễu của ảnh
_____________
_____________

Hướng dẫn:

● Import bộ lọc Gaussian


● Sử dụng bộ lọc cho ảnh building_image, thiết lập giá trị cho
thông số đa kênh
● Hiển thị ảnh gốc building_image và kết quả gaussian_image
● Thực hiện phép đo đánh giá tỷ lệ nhiễu của các ảnh

Bài tập 2.3: Khảo sát lược đồ mức xám


Câu hỏi: Trình bày sự tương quan giữa độ tương phản của ảnh và
lược đồ mức xám của ảnh. Hãy cho các ví dụ minh họa cụ thể?

69
Hình 2.50. Ảnh gốc của bài tập 2.3
Hướng dẫn:

● Lược đồ mức xám là gì?


● Độ tương phản là gì?
● Vai trò của hàm np.max()  and np.min() của thư viện NumPy
trong quá trình đánh giá độ tương phản của ảnh.
● Tính độ tương phản cho ảnh ví dụ của bài tập 2.3
Bài tập 2.4: Cân bằng Histogram
Câu hỏi: Cải thiện chất lượng của ảnh chụp từ trên không của một
thành phố. Do ảnh có độ tương phản thấp nên mắt người không thể phân
biệt được tất cả các yếu tố trong đó. Hãy sử dụng kỹ thuật cân bằng
Histogram để tăng cường độ tương phản cho ảnh.

70
Hình 2.51. Ảnh gốc của bài tập 2.4
Đoạn mã chưa hoàn chỉnh. Hãy hoàn chỉnh các dòng mã sau:

# Khai báo các thư viện cần dùng


from ____ import ____
# Tiến hành cân bằng lược đồ mức xám
image_eq =  ____
# hiển thị ảnh gốc và ảnh kết quả
show_image(image_aerial, 'Original')
show_image(____, 'Resulting image')

Bài tập 2.5: Khảo sát lược đồ mức xám của ảnh y sinh

71
Câu hỏi: Ảnh y sinh thường được lưu trữ bằng các định dạng khác
DICOM với ảnh thông thường. Hãy khảo sát gói dữ liệu phù hợp để đọc
ảnh DICOM, sau đó áp dụng kỹ thuật cân bằng lược đồ mức xám toàn
cục để cải thiện độ tương phản.

Hình 2.52. Ảnh gốc của bài tập 2.5


Hướng dẫn: Đầu tiên đọc ảnh bằng các thư viện cho phép đọc ảnh
DICOM (ví dụ như PyDicom). Kiểm tra lược đồ mức xám của ảnh sau
đó sử dụng cân bằng Histogram tiêu chuẩn để cải thiện độ tương phản.
Để cân bằng lược đồ mức xám của ảnh ta sử dụng hàm hist() từ
Matplotlib. Lưu ý các giá trị trả về của ảnh DICOM không nằm trong
khoảng [0-255] của các ảnh thông thường.

Hãy hoàn thiện đoạn mã sau:

# khai báo thư viện cần thiết


from ____ import ____
# Đọc ảnh DICOM
_____________________
#Thực hiện cân bằng lược đồ mức xám cho ảnh DICOM

72
_____________________
# chuẩn hóa ảnh DICOM về ngưỡng [0-255]
_____________________
# Thực hiện cân bằng lược đồ mức xám
_____________________
# hiển thị và so sánh kết quả
_____________________

Bài tập 2.6: Tăng cường độ tương phản sử dụng cân bằng lược đồ mức
xám cục bộ

Câu hỏi: Trong bài tập này người học sẽ tăng cường độ tương phản
của tách cà phê bằng cách sử dụng kỹ thuật tăng cường ảnh dự trên cần
bằng lược đồ mức sáng cục bộ. Sau đó so sánh kết quả có được so với
phương pháp cân bằng lược đồ mức xám truyền thống.

Hình 2.53. Ảnh gốc của bài tập 2.6

73
Sử dụng hàm show_image() để hiển thị ảnh sử dụng Matplotlib.
Thay đổi các tham số image và title để hiển thị ảnh ứng với những
phương pháp xử lý khác nhau.

Hoàn thiện đoạn mã sau:

# Khai báo các gói thư viện cần thiết


from skimage import data, ____
# Tải ảnh gốc
original_image = ____.coffee()
# ứng dụng phương pháp tăng cường cảnh cục bộ để tăng cường
độ tương phản của ảnh
adapthist_eq_image = ____.____(original_image, ____=____)
# ứng dụng phương pháp tăng cường cảnh truyền thống để tăng
cường độ tương phản của ảnh
_____________________
# Hiển thị kết quả và so sánh
show_image(original_image)
show_image(adapthist_eq_image, '#adapthist eq image ')
show_image(________,  'hist eq image')

Hướng dẫn:

● Import thư viện chứa hàm the Contrast Limited Adaptive


Histogram Equalization.
● Lấy ảnh tách cà phê từ thư viện ảnh.
● Gọi hàm áp dụng phương pháp cân bằng thích ứng trên ảnh gốc
và đặt tham số clip_limit bằng 0,03.

74
Bài tập 2.7: Giảm thiểu hiệu ứng răng cưa trong ảnh; xoay và thay đổi tỷ
lệ của ảnh
Câu hỏi: Hiện tượng răng cưa là một hiệu ứng làm cho các tín hiệu
trông như bị nhiễu. Trong ứng dụng xử lý ảnh, hiện tượng răng cưa là
hiện tượng các điểm ảnh trở nên không thể phân biệt hoặc bị biến dạng.
Hiện tượng này thường xảy ra khi ta phóng to hoặc thu nhỏ ảnh. Để khảo
sát hiện tượng này, hãy bắt đầu với ảnh gốc trong hình 2.32. Hãy thực
hiện các yêu cầu sau:

● Xoay ảnh về dạng thẳng đứng.


● Tăng kích thước ảnh lên 2 lần không sử dụng khử răng cưa
● Tăng kích thước ảnh lên 2 lần và sử dụng kỹ thuật khử răng cưa
● Cắt một vùng ảnh kích thước 100x100 tương ứng ở hai ảnh trên
và so sánh kết quả của chúng

Hình 2.54. Ảnh gốc của bài tập 2.7

75
Hoàn thiện đoạn mã sau:

# Khai báo các thư viện cần thiết


from skimage.____ import ____, ____
# đọc ảnh
OriginalImage= _____________
# xoay ảnh thẳng đứng so với ảnh gốc
RotatedImage= _____________
# Tăng kích thước lên 2 lần sử dụng kỹ thuật khử răng cưa\
Zoom_2x_A= _____________
# tăng kích thước lên 2 lần không sử dụng kỹ thuật khử răng cưa
Zoom_2x_B= _____________
# Tách một khung ảnh 100*100 cho cả hai trường hợp trên
Crop_A=____________
Crop_B=____________
# hiển thị kết quả và so sánh
ComparasionImage(______)

Bài tập 2.8: Thay đổi kích thước ảnh


Câu hỏi: Hãy phóng to ảnh lên 3 lần. Nhận xét về chất lượng của
ảnh sau khi phóng to.

76
Hình 2.55. Ảnh gốc của bài tập 2.8
Hoàn thiện đoạn mã sau:

# Khai báo các thư viện cần sử dụng


from skimage.____ import ____
# Khai báo dữ liệu các ảnh gốc
from skimage import ____
# Tải ảnh từ thư viện ảnh gốc
rocket_image = ____.____()
# Tăng kích thước ảnh lên 3 lần
enlarged_rocket_image = ____(rocket_image, ____, ____=____, 
multichannel=____)
# hiển thị ảnh để đánh giá
show_image(rocket_image)
show_image(enlarged_rocket_image, "3 times enlarged image")

Hướng dẫn:

77
● Import thư viện và hàm cần thiết
● Import thư viện data
● Tải ảnh rocket() từ data
● Phóng to ảnh rocket_image  lên 3 lần, dùng bộ lọc khử răng
cưa. Đặt tham số multichannel thành True, nếu không sẽ gặp lỗi
“timing out!”
● Ảnh phóng to trông như bị mờ. Hãy giải thích nguyên nhân làm
ảnh bị mờ và cách khắc phục
Bài tập 2.9: Thay đổi tỷ lệ ảnh thông qua điều chỉnh trực tiếp số hàng số
cột của ảnh
Câu hỏi: Các ảnh thu thập được thường có kích thước bất kỳ, tuy
nhiên các thuật toán xử lý thường yêu cầu cùng một kích thước cho ảnh
đầu vào. Do đó không thể sử dụng phép chia tỷ lệ để thay đổi kích thước
ảnh trong trường hợp này. Để có thể đảm bảo mọi ảnh sau khi thay đổi tỷ
lệ đều có cùng kích thước, cần điều khiển trực tiếp số hàng và số cột của
ảnh. Trong bài tập này, hãy thực hiện giảm 1/4 kích thước ảnh thông qua
điều khiển số hàng và số cột của ảnh sau khi xử lý.

Hình 2.56. Ảnh gốc bài tập 2.9


Hãy hoàn thiện đoạn mã sau:

78
# Khai báo thư viện cần dùng
from skimage.___ import ____
# khai báo chiều dài và chiều rộng của cột sau khi xử lý thay đổi
kích thước ảnh
height = int(____ / 2)
width = int(____ / 2)
# Thực hiện thay đổi kích thước ảnh
image_resized = ____(dogs_banner, (____, ____), anti_aliasing=
True)
# Hiển thị kết quả và so sánh
show_image(dogs_banner, 'Original')
show_image(image_resized, 'Resized image')

Hướng dẫn:

● Import thư viện và hàm để thay đổi kích thước;


● Đặt chiều cao và chiều rộng tương ứng để nó bằng một nửa chiều
cao của ảnh;
● Thay đổi kích thước bằng cách sử dụng chiều cao và chiều rộng
tỷ lệ đã tính toán.

Bài tập 2.10: Xử lý Hình thái học


Câu hỏi: Một ứng dụng rất thú vị của thị giác máy tính trong thực
tế là nhận dạng ký tự viết tay (OCR) để phân biệt các ký tự văn bản in
trong ảnh kỹ thuật số của tài liệu. Để việc nhận diện ký tự dễ dàng hơn,
các phương pháp kinh điển cố gắng loại bỏ các nét dư thừa bên ngoài và
rối liền các nét đứt bên trong ký tự viết tay. Việc này hoàn toàn thực hiện
được bởi các phép toán hình thái học. Trong bài tập này, người học hãy

79
hoàn thiện ảnh gốc để loại bỏ các điểm ảnh gần biên của chữ cái R và
điền kiến khoảng trống trong nét đứt của chữ R ở hình 2.40

Hình 2.57. Ảnh gốc cho bài tập 2.10


Hoàn thiện đoạn mã sau:

# Khai báo thư viện morphology


from ____
# điền kín khoảng trống trong nét đứt của chữ R
____________________________
# Thực hiện việc xói mòn các điểm ảnh để loại bỏ những điểm
ảnh thừa 
eroded_image_shape = ____.____(____) 
# Hiển thị kết quả
show_image(upper_r_image, 'Original')
show_image(eroded_image_shape, 'Eroded image')
Hướng dẫn:
● Import thư viện từ scikit image.
● Áp dụng phép toán hình thái học để tạo ra ký tự R hoàn chỉnh từ
chữ viết tay.

Bài tập 2.11: Cải thiện kết quả phân ngưỡng ảnh

80
Câu hỏi: Sau khi thực hiện việc lấy ngưỡng, chúng ta sẽ có một ảnh
nhị phân. Thông thường ảnh này sẽ bị nhiễu vì một số điểm ảnh của ảnh
gốc không được phân tách tốt. Trong bài tập này, người học sẽ cải thiện
kết quả phân ngưỡng của một ảnh. Hãy chỉ ra những chỗ được phân
ngưỡng chưa tốt trong ảnh nhị phân và cải thiện kết quả này bằng thuật
toán giãn nở.

Hình 2.58. Ảnh gốc cho bài tập 2.11


Hoàn thiện đoạn mã sau:

#Khai báo thư viện cần sử dụng


from skimage import ____
# Giãn nở ảnh
dilated_image = ____
# Hiển thị kết quả và so sánh
show_image(world_image, 'Original')
show_image(dilated_image, 'Dilated image')

81
CHƯƠNG 3.
PHỤC HỒI VÀ RÚT TRÍCH THÔNG TIN TRONG ẢNH

Trong chương một và chương hai, các kỹ thuật xử lý ảnh dựa trên
từng điểm đơn và dựa trên các điểm lân cận đã được khảo sát. Trong
chương ba, các ứng dụng cơ bản của xử lý ảnh được trình bày. Các ứng
dụng nhỏ này chưa hoàn thiện để coi là một ứng dụng độc lập nhưng có
thể là một khâu nhỏ trong toàn hệ thống. Hai ứng dụng được thảo luận là
phục rồi rảnh và trích xuất thông tin trong ảnh. Đối với bài toán phục hồi
ảnh, kỹ thuật khôi phục ảnh bị hỏng và kỹ thuật lọc nhiễu cho ảnh sẽ
được thảo luận. Đối với bài toán rút trích thông tin trong ảnh, có hai kỹ
thuật được trình bày là kỹ thuật siêu điểm ảnh và kỹ thuật đếm đối tượng
trong ảnh dựa vào kỹ thuật phát hiện đường bao. Nội dung chính của
chương này gồm có:

● Phục hồi ảnh


● Khử nhiễu ảnh
● Phân tích siêu điểm ảnh
● Phân tích đường bao của đối tượng trong ảnh
LÝ THUYẾT
3.1. Phục hồi ảnh

Trong quá trình sử dụng, các ảnh tự nhiên thường bị hỏng hoặc
rách một phần nào đó. Để có thể khôi phục ảnh bị hỏng hoặc bị lỗi, các
kỹ thuật khôi phục ảnh cần được áp dụng. Các nguyên nhân gây ra ảnh bị
lỗi thường là do bộ nhớ máy tính bị hỏng, hoặc có thể là một bức tranh
theo thời gian đã bị trầy xước

82
Về mặt toán học, việc khôi phục ảnh có thể được hiểu là xác định
các điểm ảnh bị hỏng, sau đó sử dụng các điểm ảnh lân cận để điền vào
các điểm ảnh bị hỏng đó. Các tiếp cận trực tiếp như vậy thường dẫn tới
việc ảnh tái tạo sẽ bị mờ và không tự nhiên. Để tránh tình trạng này, các
điểm ảnh sẽ được khôi phục dần dần thông qua một quá trình lặp. Đầu
tiên, những điểm ảnh bên ngoài đường biên sẽ được khôi phục trước.
Việc khôi phục thường dựa vào độ mượt và độ sắc nét của ảnh. Do đó
tránh được việc các chi tiết về cạnh bị mất đi. Sau khi khôi phục các
đường biên, các điểm ảnh ở vùng bên trong sẽ trở thành đường biên mới
và tiếp tục được khôi phục. Quá trình này được gọi là chức năng lan
truyền điểm (PSF) để khôi phục thông tin ảnh; nó thường rất tốn thời
gian và chi phí tính toán. Hình 3.1 cho thấy một ví dụ về ảnh bị hỏng và
ảnh được khôi phục. Các điểm ảnh màu đen minh họa các điểm ảnh bị
hỏng, mục tiêu của kỹ thuật này là tái tạo lại ảnh gốc giống như trong
hình 3.1.

Hình 3.59. Ảnh bị hỏng và kết quả ảnh sau khi phục hồi

83
Bên cạnh việc sửa chữa ảnh bị hư hỏng, kỹ thuật khôi phục hoặc tái
tạo ảnh cũng được sử dụng để xóa các đối tượng không mong muốn ra
ngoài khung hình , xóa logo và thậm chí xóa các đối tượng nhỏ như hình
xăm mà ta không muốn hiển thị trên ảnh. Hình 3.2 mô tả một số ứng
dụng mà ở đó kỹ thuật khôi phục ảnh được áp dụng.

Hình 3.60. Các trường hợp cần tái tạo ảnh


Có rất nhiều thuật toán phục vụ cho việc phục hồi ảnh, tuy nhiên
thuật toán nổi tiếng nhất là thuật toán inpainting. Thuật toán này tái tạo

84
ảnh hoàn toàn tự động bằng cách khai thác thông tin trong các vùng
không bị hư hỏng của ảnh. Các kết quả khôi phục ảnh trong hình 3.1 và
3.2 đều được thực hiện dựa theo thuật toán inpainting.

Để thực thi thuật toán này, thư viện scikit-image hỗ trợ hàm
inpaint_biharmonic từ thư viện restoration. Tham số thứ nhất của hàm
là ảnh cần xử lý, tham số thứ hai cần khai báo là vị trí của các điểm ảnh
bị hỏng. Trong hình 3.3, các ảnh bị hỏng là các điểm màu đen. Bằng cách
sử dụng một mặt nạ có kích thước giống với ảnh gốc để đánh dấu các vị
trí bị hỏng này, hàm inpaint_biharmonic có thể khôi phục lại thông tin
màu của các điểm ảnh bị hỏng. Quy ước mặt nạ là một ảnh nhị phân nhận
giá trị 0 nếu điểm ảnh không bị hỏng và giá trị 1 nếu điểm ảnh bị hỏng.
Vì vậy nếu đơn thuần là những điểm đen thể hiện những điểm ảnh bị
hỏng, thì phương pháp phân ngưỡng toàn cục hoàn toàn có thể giúp tạo
ra ảnh mặt nạ này một cách tự động. Tuy nhiên trong trường hợp các
điểm ảnh bị hỏng có kết cấu phức tạp, ta cần tạo ta các mặt nạ này một
cách thủ công.

Hình 3.61. Ảnh bị hỏng và các vị trí pixel lỗi

85
Quy trình thực hiện tái tạo ảnh được thực hiện như sau khi sử dụng
thư viện scikit-image:

● Khai báo các thư viện và hàm cần thiết.


● Tải ảnh bị hỏng.
● Chuẩn bị ma trận mặt nạ với vị trí của các điểm ảnh bị hỏng trong
ảnh.  Trong ví dụ tiếp theo, ta sử dụng hàm getmask() để khởi tạo ma
trận mặt nạ. Lưu ý rằng các điểm ảnh hỏng sẽ được đánh dấu mức 1 và
mỗi ảnh khác nhau cần có ma trận mặt nạ khác nhau. Mặt nạ này có thể
tạo tự động hoặc thủ công tùy tình huống.
● Sau khi đã tạo được mặt nạ các điểm ảnh bị hỏng, hàm
inpaint_biharmonic được gọi để khôi phục ảnh bị hỏng. Tham số đầu
tiên được truyền vào là ảnh bị hỏng. Tham số thứ hai được truyền vào là
mặt nạ. Cuối cùng, tham số multichannel, sẽ được chọn là true nếu là
ảnh màu và ngược lại.

Đoạn mã ví dụ về cách tái tạo lại ảnh:

from skimage.restoration import inpaint


# Lấy mặt nạ là một ảnh nhị phân
mask = get_mask(defect_image)
# Khôi phục ảnh gốc bằng hàm inpaint_biharmonic
restored_image = inpaint.inpaint_biharmonic(defect_image,
mask, multichannel=True)
# Hiển thị ảnh gốc và ảnh đã chỉnh sửa.
show_image(defect_image, 'Image to restore')
show_image(restored_image, 'Image restored')

Sau đó quan sát kết quả dưới đây:

86
Hình 3.62. Ảnh bị hỏng và kết quả ảnh sau khi phục hồi
Kết quả ở hình 3.4 cho thấy ảnh được khôi phục tốt ở những vùng
ít thông tin chi tiết giống như thảm cỏ, bầu trời. Mặc dù vậy, nếu ở những
vùng có nhiều thông tin chi tiết như tán cây xem lẫn với tường nhà, kết
quả đạt được không thực sự ấn tượng.

3.1.1. Vai trò của tham số mask trong việc khôi phục ảnh

Như trình bày ở trên, tham số điều khiển mask đóng vai trò rất
quan trọng trong việc khôi phục ảnh gốc. Trong bức ảnh hình 3.5, ta cố ý
thêm làm hỏng một số điểm ảnh bằng cách xóa chúng thành màu
đen. Hoặc trong trường hợp muốn xóa bỏ một đối tượng ra khỏi khung
hình, ta có thể khoanh vùng đối tượng đó để đánh dấu mặt nạ cần xử lý
theo cách thủ công. Phương án thủ công thường tốn nhiều chi phí về
nhân lực để giải quyết, vì vậy phương án trích xuất tự động nên được cân
nhắc. Một giá trị ngưỡng và phương pháp phân đoạn là phương án đơn
giản nhất để có thể tạo ra mặt nạ. Cũng trong hình 3.5, một ví dụ minh
họa về giá trị của tham số điều khiển mask được hiển thị. Các điểm ảnh
hỏng được đánh dấu bằng màu trắng còn các điểm ảnh không bị ảnh

87
hưởng được thể hiện bằng màu đen. Bất kể dùng phương pháp thủ công
hay phương pháp lập trình để tạo ra giá trị của tham số mask thì giá trị
thu được đều cần tuân thủ quy luật này.

Hình 3.63. Ảnh bị lỗi và ảnh mask hiển thị vùng bị lỗi
Đoạn mã ví dụ dưới đây minh họa cách tạo ra một ma trận mặt nạ
để điều khiển tham số mask:

def get_mask(image):
''' Tạo ra mặt nạ mask bao gồm nhiều điểm ảnh bị hỏng bằng
phương pháp thủ công '''
mask = np.zeros(image.shape[:-1])
mask[101:106, 0:240] = 1
mask[152:154, 0:60] = 1
mask[153:155, 60:100] = 1
mask[154:156, 100:120] = 1
mask[155:156, 120:140] = 1
mask[212:217, 0:150] = 1
mask[217:222, 150:256] = 1
return mask

88
3.1.2. Nhiễu

Nhiễu là những tín hiệu không mong muốn tác động tới quá trình
thu nhận tín hiệu. Trong lĩnh vực xử lý ảnh, nhiễu thường được sinh ra
khi chụp ảnh trong một trường ánh sáng yếu. Trong điều kiện làm việc
bình thường, các phần cứng thu nhận ảnh (các camera) đã được thiết kế
để khử nhiễu tự động. Mặc dù vậy, như ví dụ trong hình 3.6, khi phóng to
một bức ảnh lên ta có thể thấy một số điểm ảnh không giống với các lân
cận của nó. Đây chính là hiện tượng nhiễu trong ảnh.

Hình 3.64. Ảnh gốc bị nhiễu và ảnh các hạt nhiễu màu khi phóng to
Trong một ví dụ khác, nhiễu ảnh có thể xem như là kết quả của các
lỗi trong quá trình thu nhận ảnh. Các giá trị điểm ảnh không phản ánh
đúng cường độ thực của ảnh thực. Trong hình 3.7, ta có thể thấy được sự
biến đổi của độ sáng và màu sắc không tương ứng với ảnh thật mà là do
máy ảnh tạo ra. Với cùng một đối tượng có màu sắc cố định, nhưng mỗi
điểm ảnh lại nhận được một thông tin màu khác nhau.

89
Hình 3.65. Ảnh gốc bị nhiễu và ảnh các hạt nhiễu màu khi phóng to
3.1.3. Tạo nhiễu bằng thư viện scikit-image

Thư viện scikit-image cung cấp gói util.random_noise để tạo ra


các tín hiệu nhiễu. Để có thể thêm tín hiệu nhiễu vào một ảnh gốc đã
được tải; trước hết, cần import thư viện util và hàm random_noise. Hàm
này cho phép tạo ra nhiều loại nhiễu ngẫu nhiên khác nhau cho ảnh.
Trong ví dụ dưới đây, ảnh mới đặt tên là noisy_image. Để so sánh ảnh
gốc và nhiễu, ta hiển thị chúng bằng cách đặt cạnh nhau sử dụng hàm
show_image().

Ví dụ mẫu tạo ra ảnh nhiễu:

# Khai báo thư viện và hàm cần thiết


from skimage.util import random_noise
# Thêm nhiễu vào ảnh gốc
noisy_image = random_noise(dog_image)
# Hiển thị ảnh gốc và ảnh bị nhiễu
show_image(dog_image)
show_image(noisy_image, 'Noisy image')

90
Nếu để mặc định như trong ví dụ trên, nhiểu được tạo ra là nhiễu
Gaussian. Bên cạnh đó, có rất nhiều loại nhiễu khác như nhiễu muối tiêu,
nhiễu Poisson,... ta có thể điều khiển nó bằng cách tham khảo tài liệu
hướng dẫn của lệnh.

Hình 3.66. Ảnh gốc và ảnh sau khi được thêm nhiễu
3.1.4. Khử nhiễu

Trong các ví dụ trên, ta khảo sát cách thêm nhiễu vào trong một
ảnh. Mặc dù vậy, công việc trong thực tế thường là loại bỏ hoặc giảm
nhiễu trong một ảnh chất lượng kém thay vì thêm nhiễu vào một ảnh chất
lượng tốt. Trong ví dụ hình 3.10, ta có thể thấy ảnh gốc là một ảnh đã bị
nhiễu và ảnh kết quả là một ảnh được giảm nhiễu bằng nhiều thuật toán
trong scikit-image. Độ phân giải của ảnh càng cao thì thời gian loại bỏ
nhiễu càng lâu.

91
Hình 3.67. Ảnh bị nhiễu và ảnh kết quả xử lý nhiễu
Các bộ lọc làm mờ ảnh, một cách tự nhiên, có thể giúp giảm nhiễu
trong ảnh nhưng đồng thời cũng làm mờ các chi tiết. Một bộ lọc nhiễu tốt
cần loại bỏ nhiễu nhưng vẫn giữ lại sự sắc nét cho các tín hiệu. Các bộ
lọc nhiễu tương ứng có thể kể tới bao gồm: bộ lọc tổng phương sai và bộ
lọc bilateral. Bộ lọc tổng phương sai cố gắng giảm thiểu các biến đổi
tổng thể của ảnh. Do đó, kết quả của phép lọc này thường chia ảnh thành
từng mảnh khác nhau trong cùng một cấu trúc. Ngược lại, bộ lọc
Bilateral làm mịn ảnh trong khi vẫn giữ được biên. Ý tưởng chính của
bộ lọc Bilateral là sử dụng thông tin trên cả hai miền màu sắc và miền
không gian. Nếu như hai điểm ảnh gần nhau nhưng có màu sắc khác
nhau thì không thể tương tác lẫn nhau. Chỉ những điểm ảnh gần nhau và
có màu sắc giống nhau mới có thể tương tác với nhau. Bằng các này, bộ
lọc Bilateral khắc phục nhược điểm của bộ lọc Gaussian khi mà bất kỳ
điểm ảnh nào gần nhau cũng có thể tương tác được với nhau. Nhờ đặc
điểm này, bộ lọc Bilateral có thể loại bỏ nhiễu ở những vùng các điểm
ảnh giống nhau nhưng sẽ không gây mờ các cạnh nơi những điểm ảnh có
màu sắc khác nhau.

92
Để lọc nhiễu ảnh, thư viện scikit-image hỗ trợ gói restoration. Gói
thư viện này chứa hàm denoise_tv_chambolle để lọc nhiễu bằng phương
pháp lọc tổng phương sai. Khi dùng hàm này, ta có thể lựa chọn đặt giá
trị giảm nhiễu weight. Khi weight càng lớn, khả năng lọc nhiễu càng lớn
tuy nhiên cũng có thể làm ảnh mượt hơn tức là nhiều chi tiết sẽ bị mất đi.
Tùy thuộc vào ảnh cần xử lý, tham số multichannel cũng cần phải lựa
chọn cho phù hợp. Cụ thể, nếu ảnh cần xử lý là ảnh màu, tham số cần
chọn là True; hoặc nếu tham số cần chọn là ảnh xám thì tham số này
nhận giá trị là False. Bước cuối cùng là hiển thị ảnh gốc và so sánh với
kết quả của nó sau lọc nhiễu.

Ví dụ mẫu về cách lọc nhiêũ nhưng không làm mờ ảnh:

from skimage.restoration import denoise_tv_chambolle


# Áp dụng phương pháp lọc nhiễu
denoised_image = denoise_tv_chambolle(noisy_image,
weight=0.1, multichannel=True)
# Hiển thị kết quả để so sánh
show_image(noisy_image, 'Noisy image')
show_image(denoised_image, 'Denoised image')

Ta thấy ảnh khử nhiễu vẫn giữ được đường biên nhưng ảnh hơi mịn
và mờ ở những vùng thông tin tần số thấp (ví dụ: Bức tường ở hình 3.11)

93
Hình 3.68. Ảnh nhiễu và ảnh xử lý nhiễu dùng bộ lọc tổng phương sai
Nếu muốn khử nhiễu dùng bộ lọc Bilateral, ta có thể dễ dàng thực
hiện thông qua hàm denoise bilateral cũng từ thư viện restoration. Các
bước tiến hành cũng giống như việc khử nhiễu bằng bộ lọc tổng phương
sai. Bây giờ quan sát ảnh gốc và kết quả để so sánh.

Ví dụ mẫu sử dụng bộ lọc bilateral

from skimage.restoration import denoise_bilateral


# Áp dụng bộ lọc bilateral filter
denoised_image = denoise_bilateral(noisy_image,
multichannel=True)
# Hiển thị kết quả để so sánh
show_image(noisy_image, 'Noisy image')
show_image(denoised_image, 'Denoised image')

94
Ảnh gốc và ảnh sau khử nhiễu được hiển thị ở Hình 3.11. Kết quả
ảnh ít mịn hơn khi dùng bộ lọc tổng phương sai và giữ được biên tốt hơn
nhiều

Hình 3.69. Ảnh gốc bị nhiễu và ảnh xử lý nhiễu dùng bộ lọc tổng
phương sai
3.2. Siêu điểm ảnh

Trong chương trước, người học đã khảo sát về kỹ thuật phân đoạn
để tách ảnh thành hai phần là đối tượng và khung nền. Như trong hình
3.12, việc kết hợp các điểm ảnh đơn lẻ thành một nhóm cho phép biểu
diễn ảnh một cách có ý nghĩa và dễ phân tích hơn. Ở đây, mỗi đồng xu đã
được gom lại thành một nhóm. Mặc dù vậy, ta vẫn chưa thể tách các
đồng xu thành cách đối tượng riêng lẻ mà chỉ mới dừng ở mức chia thành
hai phần thuộc của một ảnh nhị phân.

95
Hình 3.70. Ảnh gốc và ảnh được phân đoạn
Trong thực tế sử dụng, một ảnh có thể gồm nhiều thành phần với
mỗi thành phần thể hiện bằng các màu sắc khác nhau và các điểm ảnh
này phải được đặt gần nhau trong không gian. Trong khi đó kỹ thuật
phân ngưỡng không xét tới yếu tố không gian cũng như nhiều mức xám
khác nhau mà chỉ áp dụng một mức xám để tạo ra ảnh nhị phân. Những
điểm ảnh ở xa nhau nhưng vẫn có cùng một cách xử lý. Ví dụ như trong
hình 3.13, trước khi một khối u được phân tích trong chụp cắt lớp, nó
phải được phát hiện và bằng cách nào đó cô lập khỏi phần còn lại của
ảnh. Rõ ràng rằng việc chỉ sử dụng 1 ngưỡng duy nhất để phân ngưỡng
và không xét tới thông tin về vị trí đã tạo ra một số điểm ảnh thừa không
thuộc về khối u. Hoặc trong ứng dụng nhận diện khuôn mặt, trước khi
nhận ra một khuôn mặt, ta cần tách một khuôn mặt ra khỏi nền của nó.
Phương pháp phân ngưỡng truyền thống đã tách cả phần bức tường và
phần màu da vào thành một nhóm mặc dù bản thân chúng không có mối
quan hệ với nhau.

96
Hình 3.71. Ảnh được phân đoạn để tách đối tượng khỏi nền
Từ những ví dụ trên, có thể kết luận được rằng chỉ quan sát một
điểm ảnh đơn lẻ không thể cho ta một thông tin chính xác về ảnh. Cũng
giống như trong hình 3.14, ta không thể trả lời bức ảnh có nội dung gì khi
chỉ quan sát thông tin màu của một điểm ảnh. Chính vì vậy, việc kết hợp
nhiều điểm ảnh có cùng đặc tính lại với nhau để mô tả một thông tin nhất
định là một cách tốt hơn để truy vấn thông tin của ảnh. Đại diện được tạo
thành bởi sự kết hợp của nhiều điểm ảnh gọi là một siêu điểm ảnh (super
pixel). Trong hình 3.15, một ví dụ về siêu điểm ảnh được thể hiện. Có ba
đối tượng chính trong hình này, đó là phần nền màu đen, phần mặt bàn
và ly cà phê. Phần khung nền vốn được tạo ra bởi nhiều điểm ảnh đơn lẻ
nhưng giờ đây chỉ được mô tả bằng ba siêu điểm ảnh. Thực chất, ta có

97
thể mô tả vùng này bằng một điểm ảnh duy nhất, tuy nhiên do giới hạn
kích thước lớn nhất của một siêu điểm ảnh (thông số này có thể điều
chỉnh trong lúc lập trình) nên vùng nền này được thể hiện bằng ba siêu
điểm ảnh với kích thước gần tương tự nhau. Phần mặt bàn có diện tích
lớn nhất và được chia thành nhiều siêu điểm ảnh. Ta có thể thấy rằng các
điểm ảnh nằm trong cùng một siêu điểm ảnh có vị trí gần nhau và thông
tin màu sắc tương tự nhau. Đối tượng chính của ảnh này là tách cà phê.
Đối tượng này có nhiều thành phần như đĩa, ly, v.v. Và kết quả phân tách
cho thấy siêu điểm ảnh đã chia thành nhiều điểm ảnh nhỏ với kích thước
rất khác nhau nhưng đều thỏa mãn điều kiện là các thông tin màu của các
điểm ảnh trong một siêu điểm ảnh là giống nhau.

Hình 3.72. Ảnh gốc bị nhiễu và 1 điểm ảnh đơn lẻ

Hình 3.73. Ảnh phân đoạn thành 100 nhóm điểm ảnh

98
Về mặt ứng dụng, các siêu điểm ảnh có thể được áp dụng cho nhiều
nhiệm vụ của thị giác máy tính như theo dõi trực quan và phân loại ảnh.
Chúng có thể tính toán các đặc trưng trên các vùng có ý nghĩa hơn và có
thể khái quát một ảnh từ hàng nghìn điểm ảnh xuống một số vùng cụ thể
hơn cho các thuật toán tiếp theo. Nhờ thế, các chương trình máy tính có
thể tính toán xử lý với hiệu năng cao hơn.

Kỹ thuật siêu điểm ảnh này cũng có thể được xem như là một thuật
toán phân ngưỡng không giám sát. Trong các chương trước, khi khảo sát
về bài toán phân ngưỡng, ta thấy rằng giá trị ngưỡng phải được chọn
trước một cách thủ công, hoặc ít nhất là người dùng phải lựa chọn một
phương pháp chọn ngưỡng phù hợp. Sau đó ngưỡng được chọn sẽ dùng
để chia ảnh thành đối tượng và nền. Điều đó có nghĩa là con người phải
tác động một cách tương đối vào quá trình tách đối tượng. Ngược lại, kỹ
thuật siêu điểm ảnh tự chọn những điểm ảnh mà nếu kết hợp lại với nhau
có thể tạo ra một đối tượng mà có thể có một ý nghĩa nào đó. Các kỹ
thuật xử lý không cần sự can thiệp của con người được gọi là kỹ thuật
không giám sát. Hình 3.16 hiển thị kết quả của kỹ thuật phân ngưỡng có
giám sát (bằng phương pháp Otsu) và phương pháp phân ngưỡng không
giám sát. Kết quả cho thấy việc phân đoạn không giám sát có khi cho kết
quả tốt hơn cả phân đoạn có giám sát vì các điểm ảnh ở mặt đất được kết
nối liền mạch với nhau chứ không xuất hiện nhiều điểm nhiễu ảo.

99
Hình 3.74. Phân đoạn giám sát và phân đoạn không giám sát
Thông thường, các thuật toán phân đoạn không giám sát dựa vào
một thuật toán phân cụm nổi tiếng của học máy là K-means. Kỹ thuật này
sử dụng thông tin màu và thông tin vị trí để mô tả các đặc trưng của ảnh.
Ứng với một số lượng cụm cho trước, các hạt nhân sẽ được khởi tạo ngẫu
nhiên trên toàn ảnh. Sau đó thuật toán k-means được sử dụng để chia các
điểm ảnh giống nhau vào cùng một nhóm. Để có thể thực hiện việc phân
tách các siêu điểm ảnh, thư viện skimage hỗ trợ hàm slic thuộc gói thư
viện con segmentation. Hàm này trả về các vùng được phân đoạn, và các
điểm ảnh trong mỗi vùng sẽ được gán một giá trị còn được gọi là nhãn
(labels). Trong ví dụ dưới đây, hàm slic được sử dụng với các tham số
mặc định và thu được 100 nhóm các phân đoạn khác nhau. Nhằm thuận
tiện hơn trong việc quan sát các nhóm đã được phân đoạn, hàm
label2rgb từ thư viện color có thể được sử dụng để tạo ra một ảnh trong
đó các phân đoạn thu được từ hàm slic sẽ được kết hợp với thông tin màu
trong ảnh gốc để tạo ra một màu mới đại diện cho tất cả các điểm ảnh
nằm trong các nhóm này. Kết quả trả về là một ảnh có kích thước giống
với ảnh gốc, chỉ có màu sắc của mỗi điểm ảnh sẽ được thay bằng màu sắc
của đại diện của từng siêu điểm ảnh. Mặc định, giá trị màu trung bình sẽ

100
được sử dụng để tạo ra màu đại diện cho mỗi siêu điểm ảnh. Mặc dù vậy,
có nhiều cách khác để tạo ra màu đại diện này, người học có thể tham
khảo phụ lục IV để hiểu chi tiết hơn về cách sử dụng hàm slic.

Ví dụ mẫu:

# Khai báo các thư viện cần thiết


from skimage.segmentation import slic
from skimage.color import label2rgb
# Thực hiện phân đoạn ảnh bằng hàm slic
segments = segmentation.slic(image)
# Kết hợp ảnh phân đoạn với ảnh gốc để đánh giá kết quả
segmented_image = label2rgb(segments, image, kind='avg')
show_image(image)
show_image(segmented_image, "Segmented image")

Cuối cùng, Hình 3.17 hiển thị kết quả ảnh được phân đoạn. Có thể
thấy cách các vùng cục bộ có sự phân bố màu sắc và kết cấu tương tự là
một phần của cùng một nhóm superpixel.

Hình 3.75. Ảnh gốc và ảnh sau khi được phân đoạn

101
Nếu muốn có nhiều phân đoạn hơn, giả sử là 300, ta có thể điều
chỉnh tham số tùy chọn, n_segments. Giá trị mặc định của nó là 100
phân đoạn.

Ví dụ mẫu về cách phân đoạn sử dụng siêu điểm ảnh với tham số
điều khiển là số lượng các siêu điểm ảnh:

# Khai báo thư viện cần dùng


from skimage.segmentation import slic
from skimage.color import label2rgb
# Lấy ảnh đã được phân đoạn
segments = segmentation.slic(image, n_segments= 300)
# Kết hợp kết quả phân đoạn với ảnh gốc, giá trị trung bình được
sử dụng làm đại diện cho một nhóm các điểm ảnh
segmented_image = label2rgb(segments, image, kind='avg')
show_image(image)
show_image(segmented_image, "Segmented image")

Hình 3.18 hiển thị kết quả ảnh gốc và ảnh sau khi phân đoạn khi sử
dụng tham số điều khiển n_segments=300. Kết quả cho thấy ảnh sau khi
phân đoạn được chia thành nhiều nhóm hơn, nhiều thông tin chi tiết được
thể hiện hơn. Ví dụ như chiếc muỗng trong hình 3.17 không thực sự được
nhận ra vì đã bị mất đi phần cán. Tuy nhiên ở hình 3.18, phần cán của
chiếc muỗng đã có thể được phát hiện. Cùng với việc nhiều chi tiết được
phân biệt, thì nhiều nhóm cũng xuất hiện hơn. Điều này dẫn tới việc hiệu
năng xử lý của các thuật toán phía sau bị giảm. Do đó, việc lựa chọn
tham số điều khiển phù hợp là rất cần thiết để có thể có được kết quả xử
lý tốt.

102
Hình 3.76. Ảnh gốc và ảnh được phân đoạn với n_segments = 300
3.3. Tìm đường bao

3.3.1. Tìm đường bao

Trong phần này, người học sẽ tìm đường bao cho các đối tượng
trong một ảnh. Trong xử lý ảnh, đường bao là một tập hợp các điểm bao
quanh một đối tượng. Mỗi điểm sẽ thể hiện bằng giá trị tọa độ (x,y) trên
ảnh. Kỹ thuật này đặt biệt có ích khi cần đếm số lượng các đối tượng
trong ảnh vì mỗi đối tượng sẽ có một đường bao khác nhau. Trong hình
3.19, khi tìm thấy các đường bao, ta có thể xác định tổng số điểm của
quân bài domino. Như trong ví dụ này, có 29 điểm trên các quân cờ
domino và ta có thể đếm chúng hoàn toàn tự động. Vì vậy, ta có thể đo
kích thước, phân loại hình dạng hoặc xác định số lượng đối tượng trong
một ảnh.

103
Hình 3.77. Ảnh gốc và ảnh kết quả tìm đường bao các domino
Để có thể thực hiện giải thuật tìm đường bao, đầu tiên cần phải
chuyển ảnh cần xử lý thành ảnh nhị phân. Trong hình 3.20, một ảnh nhị
phân được tạo ra bằng phương pháp lấy ngưỡng. Trong ảnh đó, các đối
tượng phải được chuyển thành mức 1 (màu trắng) còn các điểm ảnh trên
khung nền phải ở mức 0 (màu đen).

Hình 3.78. Ảnh được lấy ngưỡng và ảnh đường bao


Để tìm đường bao sử dụng scikit-image, ảnh cần trải qua một số
bước tiền xử lý như sau:

● Cần chuyển ảnh màu sang ảnh xám bằng cách sử dụng hàm
rgb2gray từ thư viện color. Bước này là để chuẩn bị cho phân ngưỡng.
● Sau khi có ảnh xám, ta sẽ áp dụng phương pháp phân ngưỡng để
có thể chuyển ảnh xám sang ảnh dạng nhị phân. Tùy vào đặc điểm của
ảnh đầu vào mà phương pháp lấy ngưỡng toàn cục hay cục bộ có thể
được áp dụng. Giá trị ngưỡng cũng có thể được áp dụng một cách thủ
công. Áp dụng ngưỡng để làm điều đó nên ta được một ảnh được lấy
ngưỡng.
● Cuối cùng, để tìm đường bao của ảnh, có thể sử dụng hàm
find_contours, trong thư viện measure của scikit-image. Hàm này tìm
các đường bao hoặc nối các pixel có độ sáng bằng nhau thành mảng 2D.

104
Thư viện measure được import từ skimage. Hàm find_contours. được
khai báo bằng lệnh "from skimage.measure import find_countours". Hàm
này có tham số đầu tiên là ảnh nhị phân từ phép lấy ngưỡng, và tham số
thứ hai là một hằng số điều khiển mức độ kết nối giữa các điểm trong
cùng một đường biên. Trong ví dụ dưới đây, tham số thứ hai này được
gán một giá trị hằng số không đổi là 0.8. Hàm này trả về một danh sách
với tất cả các đường bao của ảnh. Mỗi đường bao là một mảng numpy
array chứa các điểm trên đường bao đó.

Ví dụ mẫu về cách tìm đường bao của các đối tượng trong ảnh:

# Tạo ảnh xám từ ảnh màu ban đầu


image = color.rgb2gray(image)
# Tính ngưỡng bằng phương pháp otsu
thresh = threshold_otsu(image)
# Áp dụng ngưỡng để tạo ra ảnh nhị phân
thresholded_image = image > thresh
# khai báo thư viện cần thiết
from skimage import measure
# Tính các đường bao của ảnh
contours = measure.find_contours(thresholded_image, 0.8)

Hình 3.79. Kết quả các bước lấy đường bao của ảnh

105
Trong ví dụ trên, ta thấy tham số thứ hai của hàm find_contours là
một tham số điều khiển khả năng tạo ra các đường biên của ảnh. Tham số
này nhận giá trị ở mức 0 và 1. Giá trị này càng gần 1 thì phương pháp
phát hiện các đường bao càng nhạy, do đó các đường bao phức tạp hơn
sẽ được phát hiện. Tùy thuộc các ảnh đầu vào và kết quả phát hiện đường
biên tương ứng mà ta cần chọn giá trị điều khiển phù hợp. Trong hình
3.22, các kết quả tìm đường biên ứng với các tham số điều khiển khác
nhau được thể hiện. Ta có thể thấy nếu giá trị điều khiển này càng lớn thì
càng có nhiều đường biên được phát hiện.

Hình 3.80. Ảnh gốc và ảnh kết quả khi thay đổi tham số level
3.3.2. Kích thước của một đường bao

Sau khi thực hiện việc tìm đường bao, hàm find_contours sẽ trả về
một danh sách, trong đó mỗi phần tử trong danh sách là đường bao. Mỗi
đường là một mảng dạng (n, 2), bao gồm n hàng và 2 cột. Mỗi hàng
tương ứng với một điểm và mỗi cột tương ứng với tọa độ (x,y) trong ảnh.
Theo cách này, một đường bao được mô tả như là một tập hợp được tạo
thành bởi nhiều điểm nối với nhau. Đường bao càng lớn thì càng có
nhiều điểm nối với nhau và chu vi hình thành càng rộng. Ở đây ta có thể
thấy kích thước của các đường bao được tìm thấy trong ảnh của quân cờ
domino.

106
Hai đường bao đầu tiên có kích thước là 433 điểm, chúng thể hiện
đường viền bên ngoài của các domino vì chúng dài nhất. Có nghĩa, đây là
những vật thể lớn nhất, xét theo hình dạng của chúng. Những đường biên
tiêp theo có kích thước 401 điểm thể hiện các đường viền bên trong của
quân cờ domino. Các đường biên có tiếp theo gồm 123 điểm và là đường
phân chia ở giữa các Domino. Cuối cùng, ta thấy phần lớn các đường bao
còn lại có kích thước là 59. Đây là các dấu chấm của Domino. Nếu đếm
số lượng các đường bao này, ta nhận được tổng số là 7, giá trị này tượng
trưng cho tổng số dấu chấm cho cả hai thẻ Domino.

(433, 2)
(433, 2) --> Biên ngoài
(401, 2)
(401, 2) --> Biên trong
(123, 2)
(123, 2) --> Đường phân chia thẻ Domino
(59, 2)
(59, 2)



(59, 2) --> Dấu chấm Số lượng dấu

107
BÀI TẬP
Bài tập 3.1: Khôi phục ảnh bị hỏng
Câu hỏi: Trong bài tập này, hãy khôi phục một ảnh có các điểm ảnh
bị hỏng.

Hình 3.81. Ảnh gốc của bài tập 3.1


Ảnh ban đầu là một ảnh thuộc thư viện data. Ảnh này có thể truy
xuất bởi hàm data.astronaut(). Một số điểm ảnh đã được thay thế bằng
giá trị bằng các điểm đen nhằm mục đích mô phỏng một ảnh bị hỏng.
Hãy viết chương trình để mô phỏng quá trình này và lưu ảnh mới dưới
tên defect_image.

Sau khi tạo ra ảnh bị hỏng giả lập, hãy viết chương trình tạo ra mặt
nạ nhị phân là một ảnh đen trắng với các điểm ảnh hỏng sẽ nhận giá trị là
True. Mặt nạ này được đặt tên là mask. Hãy sử dụng hàm inpainting để
hồi phục lại các phần ảnh bị giảm chất lượng.

Haỹ hoàn thiện đoạn mã sau:

# Tải các thư viện cần thiết

108
from ____.____ import ____
# tải ảnh gốc
Image =data.astronaut()
# làm hỏng một số điểm ảnh
Image [__,__]=_________
# Tạo mặt nạ các điểm ảnh bị hỏng
mask=_________________
# Khôi phục ảnh gốc
Resutl=___________________________

Hướng dẫn: Import hàm inpaint trong thư viện restoration trong
skimage.

Bài tập 3.2: Xóa logo


Câu hỏi: Trong bài tập này, người học thực tập xóa một logo không
mong muốn ra khỏi ảnh ban đầu.

Hình 3.82. Ảnh gốc của bài tập 3.2

109
Đầu tiên hãy viết mã để tạo ra mặt nạ đánh dấu vị trí của logo ở
trong ảnh. Mặt nạ này có thể được thiết kế thủ công hoặc tự động bằng
cách đánh giá thông số màu của ảnh. Sau đó áp dụng kỹ thuật khôi phục
ảnh để thay thế các điểm ảnh tại vị trí của logo.

Hãy hoàn thành đoạn mã sau:

# Khởi tạo mặt nạ


mask = ____(____[:-1])
# Đặt các điểm ảnh tại vị trí của logo lên 1
mask[____,_____] = ____
#Loại bỏ logo
image_logo_removed = inpaint.____(__, __,multichannel=True)
# Hiển thị kết quả để so sánh và đánh giá
show_image(image_with_logo, 'Image with logo')
show_image(image_logo_removed, 'Image with logo removed')

Hướng dẫn:

● Khởi tạo một mặt nạ có cùng dạng với ảnh, sử dụng np.zeros()


● Trong mặt nạ, thiết lập giá trị 1 cho khu vực sẽ được khôi phục.
● Sử dụng khôi phục ảnh image_with_logo sử dụng mask
Bài tập 3.3: Khởi tạo ảnh nhiễu
Câu hỏi: Trong bài tập này, hãy tạo ra các ảnh nhiễu bằng cách
thêm nhiễu vào ảnh gốc sau đây.

110
Hình 3.83. Ảnh gốc của bài tập 3.3
Hãy hoàn thiện đoạn mã sau:

# Khai báo các thư viện cần thiết


from skimage.____ import ____
# Thêm nhiễu vào trong ảnh
noisy_image = ____
# Hiển thị ảnh gốc và ảnh sau khi thêm nhiễu
show_image(____, 'Original')
___(____, 'Noisy image')

Hướng dẫn:

● Import thư viện util  và dùng hàm thêm nhiễu ngẫu nhiên


● Thêm nhiễu vào ảnh
● Hiển thị ảnh gốc và ảnh kết quả
Bài tập 3.4: Giảm nhiễu

111
Câu hỏi: Trong bài tập này, hãy khử nhiễu của một ảnh cho trước
bằng phương pháp sử dụng bộ lọc tổng phương sai.

Hình 3.84. Ảnh gốc của bài tập 3.4


Hãy hoàn thiện đoạn mã sau:

# Khai báo các thư viện cần thiết


from skimage.____ import ____
# Ứng dụng bộ lọc nhiễu tổng phương sai để loại bỏ nhiễu
denoised_image = ____(____, multichannel=____)
# Hiển thị kết quả và so sánh với ảnh gốc ban đầu
show_image(____, 'Noisy')
____(____, 'Denoised image')
# Đánh giá tỷ lệ tín hiệu / nhiễu cho ảnh trước và sau khi lọc
nhiều
original_noise_ratio=_______(original_image)
Improve_noise_ratio=_______(denoised_image)

Hướng dẫn:

112
● Import hàm denoise_tv_chambolle từ thư viện của nó.
● Sử dụng giảm nhiễu dùng bộ lọc tổng phương sai.
● Hiển ảnh gốc nhiễu và ảnh đã giảm nhiễu.
Bài tập 3.5: Giảm nhiễu bằng bộ lọc bilataral filter.
Câu hỏi: Trong bài tập này, hãy giảm nhiễu bức tranh phong cảnh
sau sao cho nhiễu có thể được khử mà không làm mờ các đường biên của
ảnh.

Hình 3.85. Ảnh gốc của bài tập 3.5


Hãy hoàn thiện đoạn mã sau:

# Khai báo các thư viện cần thiết


from skimage.____ import ____
# Ứng dụng bộ lọc nhiễu tổng phương sai để loại bỏ nhiễu
denoised_image = ____(____, multichannel=____)
# Hiển thị kết quả và so sánh với ảnh gốc ban đầu
show_image(____, 'Noisy')
____(____, 'Denoised image')
# Đánh giá tỷ lệ tín hiệu / nhiễu cho ảnh trước và sau khi lọc
nhiều

113
original_noise_ratio=_______(original_image)
Improve_noise_ratio=_______(denoised_image)

Hướng dẫn:

● Import hàm denoise_bilateral
● Dùng giảm nhiễu từ bộ lọc bilateral
● Hiển thị ảnh gốc và ảnh kết quả được giảm nhiễu

Bài tập 3.6: Siêu điểm ảnh


Câu hỏi: Hãy tính tổng số pixels của ảnh face_image trong bài tập
này. Tổng số pixels là độ phân giải của ảnh, được tính bằng Chiều Cao x
Chiều Rộng.

Hình 3.86. Ảnh gốc của bài tập 3.6


Hướng dẫn: Sử dụng thuộc tính .shape của thư viện Numpy để
kiểm tra chiều rộng và chiều cao của ảnh. Một số gợi ý như sau:

● 191 * 191 = 36,481 pixels

114
● 265 * 191 = 50,615 pixels
● 1265 * 1191 = 1,506,615 pixels
● 2265 * 2191 = 4,962,615 pixels

Bài tập 3.7: Siêu điểm ảnh

Câu hỏi: Trong bài tập này, hãy áp dụng thuật toán phân đoạn
không giám sát cho ảnh face_image. Mục đích của bài tập này là xác
định những siêu điểm ảnh thể hiện khuôn mặt để có thể tách phần khuôn
mặt ra cho các ứng dụng học máy tiếp theo.  Vì vậy, hãy thực hiện giảm
số điểm ảnh của ảnh ở bài tập 6 xuống ngưỡng 400 siêu điểm ảnh.

Hãy hoàn hiện đoạn mã sau:

#Tải các thư viện cần thiết


from skimage.____ import ____
# khai báo hàm label2rgb từ thư viện color 
from skimage.____ import ____
# Lấy kết quả phân vùng thành 400 vùng siêu điểm ảnh
segments = ____(____, ____= ____)
# Kết hợp ảnh gốc với ảnh phân đoạn để đối sánh
segmented_image = ____(____, ____, kind='avg')
# Hiển thị kết quả và so sánh
show_image(_____,'original image')
show_image(segmented_image, "Segmented image, 400 superpi
xels")

Hướng dẫn:

● Import hàm slic() từ thư viện segmentation.


● Import hàm label2rgb() từ thư viện color.

115
● Thực hiện phân đoạn với 400 ngưỡng bằng cách sử dụng hàm
slic().
● Đặt các phân đoạn lên trên ảnh gốc để so sánh với hàm
label2rgb()

Bài tập 3.8: Hình dạng đường bao của ảnh nhị phân
Câu hỏi: Trong bài tập này, hãy xác định chu vi và diện tích của đối
tượng trong hình.

Hình 3.87. Ảnh nhị phân của bài tập 3.8


Hướng dẫn: Để làm được điều đó, cần chuẩn bị một ảnh nhị phân
thể hiện đối tượng và khung nền. Diện tích của đối tượng thể hiện bởi số
lượng các điểm ảnh thuộc về đối tượng. Trong khi chu vi của đối tượng
thể hiện bởi số điểm trên đường bao của ảnh. Hãy sử dụng kỹ thuật tìm
đường bao để xác định chu vi của đối tượng. Sử dụng hàm
show_image_contour(image, contours) để tìm đường bao của đối tượng.

● Nhập dữ liệu và thư viện cần thiết để tìm đường bao.


● Hiển thị ảnh con ngựa.

116
● Tìm đường bao của ảnh con ngựa bằng cách sử dụng giá trị
ngưỡng không đổi là 0,8.

Dưạ vào hướng dẫn trên, hãy hoàn thành đoạn mã sau:

# Khai báo những thư viện cần dùng


from skimage import ____, ____
# Tải ảnh nhị phân cần xử lý
horse_image = ___.horse()
# Tìm đường biên với tham số level phù hợp.
contours = measure.____(____, ____)
# Hiển thị kết quả với đường bao phù hợp
show_image_contour(horse_image, contours)
# Tính diện tích và chu vi của đối tượng
Area=_________
Count=________

Bài tập 3.9: Tìm đường bao của một ảnh màu
Câu hỏi: Trong bài tập ngày, hãy mở rộng bài tập số 8 sang xử lý
một ảnh màu. Một số bước tiền xử lý sẽ cần áp dụng để tìm được các
đường bao và lấy thông tin từ nó. Cho ảnh gốc như hình 3.33; hãy viết
chương trình đếm phát hiện các đường biên trong ảnh màu.

Hình 3.88. Ảnh gốc của bài tập 3.9

117
Hướng dẫn: Trong trường hợp này, ảnh chưa phải là xám hoặc nhị
phân nên cần thực hiện một số bước xử lý ảnh trước khi tìm đường
bao. Đầu tiên, ta sẽ chuyển ảnh thành ảnh xám 2D và tiếp theo là sử dụng
ngưỡng để chuyển ảnh xám thành ảnh nhị phân. Cuối cùng, các đường
bao được hiển thị cùng với ảnh gốc. Lưu ý là các thư viện color,
measure, và filters sẽ cung cấp các hàm cần thiết. Ngoài ra, thư viện io
cung cấp hàm imread để đọc một ảnh nếu ảnh đó không có sẵn trong thư
viện data.

Hãy hoàn thành đoạn mã sau:

# Tải các thư viện cần thiết


Import _______________
# Đọc ảnh từ trong ổ cứng máy tính
image_dices= imread(_________)
#chuyển đổi ảnh màu sang ảnh xám
image_dices = color.___(image_dices)
# Tìm ngưỡng
Threshod= _____________
# Tạo ảnh nhị phân
_____= image_dices > Threshod
# Xác định các đường biên
_______________________
# Hiển thị kết quả
_______________________
Bài tập 3.10: Đếm số dấu chấm trong ảnh xúc xắc
Câu hỏi: Trong bài tập trước, người học đã tìm đường bao của ảnh
xúc xắc màu tím. Bài tập này, hãy cùng xác định số điểm được tung bởi
xúc xắc bằng cách đếm số dấu chấm trong ảnh.

118
Hình 3.89. Ảnh kết quả bài tập 3.10
Hướng dẫn:

● Các đường bao được tìm thấy trong bài tập trước được tìm thấy
bởi hàm contours. Hàm này trả về danh sách các đối tượng đã tìm được.

● Sử dụng kỹ thuật ở bài tập 8 để đếm chu vi của các đối tượng đã
tìm thấy.

● Xác định chu vi của các nút tròn thể hiện điểm số trong ảnh.

● Kiểm tra để lọc ra các đối tượng có chu vi phù hợp. Nếu đếm
chúng, chúng là số chấm chính xác trong ảnh.

● Sử dụng hàm show_image_contour(image, contours) để hiển thị


kết quả trong ảnh.

Dựa trên hướng dẫn trên để hoàn thành ví dụ mẫu sau:

# Lấy ra thông tin về chi vi của các đối tượng


shape_contours = [cnt.shape[0] for cnt in ____]
# Xác định chu vi lớn nhất của một điểm
max_dots_shape = ____
# Đếm số lượng điểm
dots_contours = [cnt for cnt in contours if np.shape(cnt)[0] < __]

119
# Hiển thị các đường bao đã phát hiện 
show_image_contour(binary, contours)
# In ra số điểm của viên xúc xắc.
print("Dice's dots number: {}. ".format(len(____)))

120
CHƯƠNG 4.
ĐẶC TRƯNG CỦA ẢNH VÀ BÀI TOÁN NHẬN DIỆN

Trong chương bốn, người học sẽ thảo luận về các đặc trưng của
ảnh và áp dụng các đặc trưng cho bài toán nhận điện. Các đặc trưng được
thảo luận trong chương này gồm có đặc trưng về cạnh, đặc trưng về góc.
Bài toán nhận diện được thảo luận và bài toán nhận diện khuôn mặt. Cuối
cùng, các khối rời sẽ được kết hợp với nhau để tạo thành một ứng dụng
bảo vệ danh tính cá nhân trên mạng xã hội. Chi tiết các phần được liệt kê
như sau:

● Đặc trưng về cạnh và ứng dụng


● Đặc trưng về góc và ứng dụng
● Phát hiện khuôn mặt
● Ứng dụng bảo vệ danh tính
LÝ THUYẾT
4.1. Tìm cạnh với Canny

4.1.1. Phát hiện cạnh

Hầu hết thông tin hình dạng của một ảnh được thể hiện bởi các
cạnh bao quanh các đối tượng. Trong ảnh này, ta thấy cách các cạnh lưu
giữ thông tin về các quân cờ domino. Biểu diễn một ảnh theo các cạnh
của nó có ưu điểm là lượng dữ liệu được giảm đáng kể trong khi vẫn giữ
lại hầu hết các thông tin về ảnh, chẳng hạn như các hình dạng của các đối
tượng trong khi loại bỏ thông tin về mức xám hay màu sắc. Hình 4.1
minh họa cho việc biểu diễn một ảnh chỉ bằng cách sử dụng các cạnh của
nó. Các thông tin quan trọng như số điểm cũng như hình dáng của các
domino được giữ lại trong khi thông tin về mức sáng được lược bỏ.

121
Hình 4.90. Ảnh gốc và ảnh kết quả phát hiện cạnh dùng bộ lọc Canny
Trong chương trước, người học đã thảo luận về cách phát hiện các
cạnh bằng kỹ thuật lọc Sobel. Trong chương này, người học sẽ tìm hiểu
về một trong những kỹ thuật phát hiện cạnh bằng bộ lọc Canny. Đây là
bộ lọc kinh điển được sử dụng nhiều nhất trong phát hiện cạnh. Đây được
coi là phương pháp phát hiện cạnh tiêu chuẩn trong xử lý ảnh. Nó có độ
chính xác cao hơn khi phát hiện các cạnh và thời gian thực thi nhanh hơn
so với thuật toán Sobel. Hình 4.2 so sánh sự khác biệt giữa phát hiện
cạnh bằng Canny và phát hiện cạnh bằng bộ lọc Sobel.

Hình 4.91. Ảnh kết quả phát hiện cạnh cùng Sobel và Cany
Trong thư viện scikit-image, gói thư viện feature cung cấp hàm
canny để phát hiện cạnh. Hàm này yêu cầu ảnh đầu vào phải là một
mảng 2 chiều (ảnh xám). Vì vậy, để sử dụng hàm này, ta cần chuyển đổi

122
ảnh từ định dạng RGB sang ảnh xám. Việc này được thực hiện bằng cách
sử dụng hàm rgb2gray từ thư viện color từ các chương trước. Sau đó, áp
dụng phương pháp phát hiện Canny trên ảnh xám và thu được kết quả.

Ví dụ mẫu về cách sử dụng thư viện canny:

from skimage.feature import canny


# Chuyển đổi ảnh màu thành ảnh xám
coins = color.rgb2gray(coins)
# Áp dụng bộ lọc phát hiện cạnh canny
canny_edges = canny(coins)
# Hiển thị cạnh sau bộ lọc
show_image(canny_edges, "Edges with Canny")

Từ kết quả trong hình 4.3, các cạnh được làm nổi bật bởi các đường
trắng dày và một số chi tiết bên trong ảnh được hiển thị rõ ràng hơn phần
còn lại của ảnh. Ta cũng có thể đếm được số đồng xu trong ảnh thông
qua việc đếm các đường tròn hoặc các đường elip trong ảnh.

Hình 4.92. Ảnh gốc và ảnh kết quả phát hiện cạnh dùng Canny
Không giống với phương pháp lọc sobel đã thảo luận ở chương 2,
gói thư viện feature bổ sung nhiều bước xử lý khác nhau để có kết quả

123
phát hiện cạnh tốt hơn chứ không phải chỉ đơn thuần là thực hiện nhân
chập. Bước đầu tiên của thuật toán này là áp dụng bộ lọc Gaussian để
loại bỏ nhiễu trong ảnh. Đó là bộ lọc từ hàm gaussian trong thư viện
filters. Vì vậy, trong hàm canny, ta có thể tùy chọn cường độ của bộ lọc
Gaussian sẽ được áp dụng trong ảnh. Cường độ này sẽ quyết định độ mịn
của ảnh ngõ ra và được điều khiển thông qua tham số sigma. Giá trị của
tham số sigma càng thấp, hiệu ứng làm mịn của bộ lọc Gaussian lên ảnh
càng ít; vì vậy các thông tin về cạnh sẽ được giữ lại và hàm canny sẽ
phát hiện ra nhiều cạnh hơn. Tất nhiên việc phát hiện nhiều cạnh hơn
không phải lúc nào cũng tốt vì một số cạnh chỉ đơn giản là các chi tiết
nhỏ chứ không phải là cạnh của những đối tượng quan trọng. Ngược lại,
nếu đặt giá trị này cao hơn, nhiều nhiễu hơn sẽ bị loại bỏ, kết quả là ảnh
sẽ ít sắc nét hơn; do đó số lượng cạnh được phát hiện bởi hàm canny sẽ ít
hơn. Giá trị mặc định của tham số sigma là 1. Trong ví dụ tiếp theo,
sigma được khởi tạo bằng 0.5 và kết quả được thể hiện trong hình 4.4.
Có thể thấy rằng giá trị sigma nhỏ hơn cho phép phát hiện được nhiều
cạnh hơn.

Ví dụ mẫu về cách sử dụng đặc trưng Canny

# Ứng dụng phép phát hiện cạnh Canny với hệ số sigma bằng
0.5
canny_edges_0_5 = canny(coins, sigma=0.5)
# Hiển thị kết quả có được
show_image(canny_edges, "Sigma of 1")
show_image(canny_edges_0_5, "Sigma of 0.5")

124
Hình 4.93. Kết quả cạnh khi sigma thay đổi
4.2. Phát hiện góc

4.2.1. Phát hiện góc và ứng dụng

Phát hiện góc được sử dụng để trích xuất một số loại thuộc tính
nhất định và suy ra nội dung của ảnh. Kỹ thuật này Nó thường được sử
dụng trong phát hiện chuyển động, đánh dấu ảnh, theo dõi đối tượng,
ghép ảnh toàn cảnh, xây dựng mô hình 3D và nhận dạng đối tượng.
Tương tự như bộ lọc Canny và trước đó nữa là bộ lọc Sobel trong chương
2; kỹ thuật phát hiện góc cho ta một ma trận có kích thước tương đương
với ảnh ban đầu. Trong ma trận đặc trưng đó, nếu một điểm ảnh giống
như một góc, thì giá trị của ma trận đặc trưng tại vị trí đó sẽ cao. Các
thông tin như khả năng một điểm ảnh là nằm trên cạnh; hay khả năng
một điểm ảnh là một góc đều có thể coi là các đặc trưng để mô tả ảnh.
Kết hợp với kỹ thuật học máy, các đặc trưng này sẽ có thể sử dụng để
phân loại đối tượng và nhiều tác vụ khác. Hình 4.5 cho ta một ví dụ về
kết quả của thuật toán phát hiện góc. Các vị trí giống như góc của các vật
thể sẽ được phát hiện và đánh dấu bằng các điểm chữ thập đỏ.

125
Hình 4.94. Ảnh gốc và ảnh kết quả phát hiện góc
Như đã trình bày ở trên, cả phép lọc cạnh và phát hiện góc đều là
các biến thể cơ bản của một kỹ thuật phức tạp hơn nhiều gọi là rút trích
đặc trưng của ảnh. Các đặc trưng là các điểm quan trọng cung cấp thông
tin về nội dung của ảnh phong phú. Các thông tin này thường là các
thông tin bất biến ngay cả khi ta thực hiện các ngoại cảnh bên ngoài của
ảnh thay đổi như ánh sáng, góc quay của camera hay môi trường nhiễu.
Nhờ đặc tính bất biến này, các thông tin đó có thể được coi là ổn định để
sử dụng trong các phép xử lý khác như học máy và phân tích nội dung
ảnh. Lấy ví dụ, hình 4.6 chứa thông tin về các điểm cần quan tâm và cạnh
của các đối tượng; các điểm này sẽ không bị thay đổi nếu ánh sáng bên
ngoài thay đổi. Do đó, kết quả của các phép phát hiện cạnh hay phát hiện

126
góc có thể được dùng để mô tả thông tin của ảnh trong một số trường hợp
cụ thể.

Hình 4.95. Các điểm trọng tâm và kết quả phát hiện cạnh dùng Sobel

127
Để có thể phát hiện góc, trước tiên cần định nghĩa về góc. Trong xử
lý ảnh, một góc có thể được hiểu là giao điểm của hai cạnh bất kỳ. Về
mặt trực quan, các góc cũng có thể là điểm nối của các đường bao. Ví dụ
về góc được thể hiện trong hình 4.7. Các góc có thể là giao điểm của các
ô vuông trong bàn cờ, hoặc là giao điểm giữa các mặt phẳng trong tòa
nhà 3D. Thực tế, có rất nhiều ứng dụng sử dụng kỹ thuật phát hiện góc để
trích xuất các thông tin 3D từ các ảnh 2D. Một số ứng dụng nổi tiếng có
thể kể đến là cân chỉnh mô hình máy ảnh hoặc xác định sự tương quan
giữa hai máy ảnh.

Hình 4.96. Ảnh minh hoạ cho khái niệm về góc


Ví dụ như có hai máy ảnh đặt gần nhau chụp cùng một khung cảnh,
và ta cần tìm một hàm số thể hiện sự tương quan giữa hai ảnh chụp bằng
hai máy ảnh này. Ta có thể cho hai máy ảnh chụp cùng một cảnh vậy với
nhiều góc cạnh. Sau khi sử dụng kỹ thuật phát hiện góc ở mỗi ảnh, ta có
thể tìm được các góc tương quan ở hai ảnh này giống như hình 4.8. Dựa
trên thông tin về các cặp điểm tương quan này, hàm chuyển đổi thông tin
giữa hai máy ảnh có thể được nội suy. Lợi ích của quá trình này đó là khi

128
xuất hiện một ảnh bất kỳ, ta có thể chuyển đổi thông tin giữa hai máy
ảnh, và có thể ước lượng thông tin khoảng cách từ máy ảnh tới điểm đó
trong không gian 3D. Điều này rất được sử dụng trong điều khiển robot.

Hình 4.97. Ảnh các góc phù hợp giữa ảnh gốc và ảnh đã giảm tỉ lệ
Hình 4.9 thể hiện một ứng dụng thể hiện tính ổn định của các thuật
toán liên quan tới phát hiện cạnh. Ở đây, ảnh gốc đã được xoay một cách
chủ động. Khi mô tả ảnh mới bằng đặc trưng của các góc, ta có thể dễ
dàng tìm được sự tương quan giữa các góc trong ảnh mới và ảnh bị thay
đổi một cách rất chính xác. Điều này thể hiện các đặc trưng về góc rất ổn
định cho dù máy ảnh bị thay đổi góc quay.

129
Hình 4.98. Ảnh các góc phù hợp giữa ảnh góc và ảnh khi xoay
4.2.2. Phát hiện góc bằng đặc trưng Harris

Một trong những đặc trưng kinh điển nhất để mô tả góc là Harris
Corner. Đây là một đặc trưng để phát hiện góc và được sử dụng rộng rãi
trong các thuật toán thị giác máy tính. Để thực hiện phép phát hiện cạnh
thư viện scikit-image.features hỗ trợ hàm corner_harris. Hàm này yêu
cầu ảnh đầu vào là ảnh xám, vì vậy trước tiên ta cần chuyển đổi ảnh từ
rgb sang ảnh xám đã thực hiện trong chương 1. Kết quả trả về là một ma
trận thể hiện khả năng của một điểm ảnh có thể là một góc. Trong hình
4.10, ảnh gốc được truy xuất xuất từ hàm angle_harris của thư viện
scikit-image.features. Kết quả xử xử lý bằng hàm corner_harris được
thể hiện trong hình 4.11. Ta thấy cách mà chỉ một số đường màu đen
được hiển thị. Đây là những điểm gần đúng ở góc.

Ví dụ mẫu quá trình phat hiện góc.

from skimage.feature import corner_harris


# Chuyển ảnh thành ảnh xám

130
image = rgb2gray(image)
#Áp dụng bộ lọc conner lên ảnh
measure_image = corner_harris(image)
# hiển thị đáp ứng Harris của ảnh gốc
show_image(measure_image)

Hình 4.99. Ảnh gốc được truy xuất xuất từ hàm angle_harris

Hình 4.100. Ảnh gốc và đáp ứng của đặc trưng Harris

131
Để tìm các góc trong ảnh đáp ứng của đặc trưng Harris, ta cần phải
tìm các đỉnh nơi có khả năng cao nhất để trở thành một góc trong một
vùng lân cận cục bộ nào đó. Việc này có thể thực hiện dễ dàng bằng cách
sử dụng hàm corner_peaks. Hàm này sẽ trả về tọa độ của các đỉnh của
đáp ứng đặc trưng Harris. Một số tham số có thể tùy chỉnh của hàm
corner_peaks là khoảng cách tối thiểu giữa hai đỉnh lân cận. Khoảng
cách này tính bằng điểm ảnh và được điều khiển thông qua tham số
min_distance. Trong ví dụ này khoảng cách tối thiểu giữa các góc được
đặt là 5 điểm ảnh. Trong ví dụ này, có tổng cộng 122 góc được tìm thấy
từ ảnh đáp ứng của đặc trưng Harris.

Ví dụ mẫu về cách phát hiện góc dựa trên đặc trưng Harris:

# Tìm tọa độ của các góc thông qua ảnh đáp ứng của đặc trưng
Harris
coords = corner_peaks(corner_harris(image), min_distance=5)
print("A total of", len(coords), "corners were detected.")

Bằng cách sử dụng chương trình con


show_image_with_detected_corners(), các góc sẽ được hiển thị đồng bộ
với ảnh gốc trên một khung hình.

Ví dụ mẫu về cách kết hợp cả ảnh và góc để phát hiện:

# Hiển thị ảnh gốc và các góc trên cùng một khung hình
show_image_with_detected_corners(image, coords)

132
Hình 4.101. Ảnh các góc được phát hiện
Chi tiết của chương trình con show_image_with_corners() được
liệt kê như sau:

def show_image_with_corners(image, coords, title="Corners


detected"):
plt.imshow(image, interpolation='nearest', cmap='gray')
plt.title(title)
plt.plot(coords[:, 1], coords[:, 0], '+r', markersize=15)
plt.axis('off')
plt.show()

4.3. Nhận dạng khuôn mặt

4.3.1. Nhận diện khuôn mặt và ứng dụng

Nhận diện khuôn mặt là một trong những ứng dụng nổi tiếng nhất
của công nghệ xử lý ảnh. Trước khi có sự ra đời của kỹ thuật học sâu, mô
hình nhận diện khuôn mặt là hình mẫu của việc kết hợp xử lý ảnh và phát
hiện đối tượng trong ảnh. Cho trước một ảnh với nhiều cá nhân, mô hình

133
nhận diện khuôn mặt cho ra các hình vuông đánh dấu khuôn mặt của có
trong ảnh. Ta có thể thấy tính năng này trở nên phổ cập trên các nền tảng
mạng xã hội như Facebook. Hình 4.13 cho một ví dụ về việc phát hiện
khuôn mặt trong ảnh. Hoặc trong ngữ cảnh cần đảm bảo quyền riêng tư
trên mạng xã hội, ta có thể chủ động phát hiện khuôn mặt và làm mờ đi
trước khi đăng ảnh lên mạng xã hội. Hoặc trong một số ứng dụng như
nhận diện cảm xúc của một người, thì bước tiền xử lý đầu tiên là phải xác
định vị trí của khuôn mặt người đó trước khi triển khai việc phân biệt
cảm xúc. Như trong hình 4.2, khi muốn nhận diện cảm xúc của bé trai,
trước hết cần xác định vị trí khuôn mặt của bé trai và trích ra phần ô
vuông chứa biểu cảm khuôn mặt, Phần ảnh được trích ra đó sẽ dùng để
nhận diện cảm xúc.

Hình 4.102. Ứng dụng nhận dạng khuôn mặt


4.3.2. Nhận dạng khuôn mặt với thư viện scikit-image

Với scikit-image, ta có thể phát hiện khuôn mặt bằng cách sử dụng
bộ phân loại dựa trên công gnheej máy học chỉ với một vài dòng mã.
Trong phần này, tài liệu không trình bày sâu về các khái niệm học máy

134
mà chỉ hướng dẫn cách sử dụng mô hình phân loại được triển khai sẵn
trong thư viện scikit-image.

Hình 4.103. Ảnh nhận dạng khuôn mặt và ảnh tách khuôn mặt được
nhận dạng
Để sử dụng tính năng nhận dạng khuôn mặt, đầu tiên cần khai báo
lớp Cascade trong thư viện skimage.feature. Để có thể sử dụng được
class này, cần khai báo một đối tượng được kế thừa từ class với các tham
số điều khiển cho trước. Trong trường hợp này, tham số điều khiển là
một mô hình phát hiện khuôn mặt đã được huấn luyện và khai báo sẵn
trong một tập tin .xml. Mô hình huấn luyện này được lưu trữ trong gói
thư viện data của scikit-image. Khi khai báo một biến là mô hình phân
loại và sử dụng mô hình này làm tham số điều khiển cho class Cascade,
ta sẽ có một đối tượng có khả năng thực hiện các công việc mà mô hình
máy học đã khai báo trong thư viện .xml. Đoạn mã dưới đây mô tả cách
khởi tạo một đối tượng có khả năng nhận diện khuôn mặt.

Ví dụ mẫu về cách khơỉ tạo đôí tương phát hiện khuôn mặt.

# Khai báo class Cascade

135
from skimage.feature import Cascade
# Tải mô hình nhận diện khuôn mặt.
trained_file = data.lbp_frontal_face_cascade_filename()
# Tạo ra đối tượng có khả năng nhận diện khuôn mặt từ mô hình
được huấn luyện trước.
detector = Cascade(trained_file)

Để có thể phát hiện khuôn mặt, đối tượng detector gọi phương
pháp detector_multi_scale, với tham số đầu vào và ảnh cần xử lý.
Phương thức này tìm kiếm đối tượng được định nghĩa trong bộ phân loại
trained_file, trong trường hợp này là một khuôn mặt. Nó tạo ra một cửa
sổ sẽ di chuyển qua ảnh cho đến khi nó tìm thấy thứ gì đó giống với
khuôn mặt người giống như mô tả ở hình 4.15.

Hình 4.104. Các cửa sổ sẽ trượt đi để tìm vị trí khuôn mặt


Việc tìm kiếm sẽ tiến hành trên nhiều mức cửa sổ khác nhau. Ta sẽ
có thể điều khiển kích thước nhỏ nhất của cửa sổ để phát hiện những
khuôn mặt nhỏ hoặc ở xa. Ngoài ra kích thước tối đa của một cửa sổ

136
cũng có thể điều khiển để phát hiện các khuôn mặt lớn hơn hoặc ở gần
máy ảnh.

Hình 4.105. Nhiều kích thước cửa sổ sẽ được thử nghiệm để hiển thị
kích thước khuôn mặt
Trong ví dụ dưới đây, phương pháp detector_multi_scale, lấy ảnh
đầu vào làm tham số điều khiển đầu tiên. Tham số điều khiển thứ hai là
một hệ số tỷ lệ, theo đó kích thước của cửa sổ tìm kiếm được nhân lên
trong mỗi bước của quá trình tìm kiếm. Nếu giá trị này nhỏ (gần bằng 1)
thì quá trình tìm sẽ diễn ra chậm nhưng đầy đủ khi tất cả mọi kích thước
khuôn mặt đều được xét tới. Nếu giá trị này lớn, kết quả tính toán sẽ
nhanh hơn nhưng có khả năng một số khuôn mặt sẽ bị bỏ sót. Thông
thường, các giá trị trong khoảng từ 1 đến 1,5 cho kết quả tốt. Tiếp theo
đó, kích thước tối thiểu và tối đa của cửa sổ được xác định.

Bộ nhận dạng sẽ trả về tọa độ của cửa sổ chứa khuôn mặt. Khi hiển
thị kết quả, ta thấy đó là một danh sách các khuôn mặt được phát hiện.
Mỗi khuôn mặt được mô tả như là một Dict, trong đó có các từ khoá là r,
c, weight, heigh. Từ khoá r đại diện cho vị trí hàng, c là vị trí của cột của

137
góc trên cùng bên trái của cửa sổ được phát hiện; width là chiều rộng của
cửa sổ được phát hiện và height là chiều cao của cửa sổ được phát hiện.
Hàm show_detected_face hiển thị ảnh gốc với khuôn mặt được phát hiện
được đánh dấu bằng các đường màu đỏ, tạo thành một hình chữ nhật
chứa khuôn mặt.

Ví dụ mẫu về cách sử dụng đối tượng phát hiện khuôn mặt:

# Phát hiện khuôn mặt bằng phương pháp detector_multi_scale


detected = detector.detect_multi_scale(img=image, scale_factor=
1.2, step_ratio=1, min_size=(10, 10), max_size=(200, 200))
print(detected)
# Hiển thị ảnh với khuôn mặt được phát hiện
show_detected_face(image, detected)
Detected face: [{'r': 115, 'c': 210, 'width': 167, 'height': 167}]

Hình 4.106. Ảnh gốc và ảnh nhận dạng khuôn mặt


Chi tiết về hàm show_detected_face được mô tả ở đây.

def show_detected_face(result, detected, title="Face image"):


plt.imshow(result)

138
img_desc = plt.gca()
plt.set_cmap('gray')
plt.title(title)
plt.axis('off')
for patch in detected:
img_desc.add_patch(
patches.Rectangle(
(patch['c'],patch['r']),patch['width'],patch['height'],c
olor='r', linewidth=2))
plt.show()

4.4. Bảo vệ quyền riêng tư cùng với ứng dụng phát hiện khuôn mặt.

Bất kỳ kỹ thuật xử lý ảnh nào, nếu chỉ đứng một mình, sẽ khó tạo
được một ứng dụng cụ thể. Để có thể tạo ra ứng dụng, nhiều bước xử lý
phải nối tiếp nhau và mỗi bước đều cần lựa chọn tham số điều khiển phù
hợp. Ví dụ, trước khi phát hiện cạnh cần chuyển ảnh màu sang ảnh xám,
hoặc trước khi đếm số đối tượng trong ảnh cần chuyển ảnh xám sang ảnh
nhị phân. Đối với ứng dụng bảo vệ quyền riêng tư, ta cần phát hiện
khuôn mặt rồi tiến hành làm mờ nó. Hình 4.18 cho ta một ví dụ về các
bảo vệ thông tin các nhân của một người nhờ việc làm mờ khuôn mặt của
người đó trong các video.

139
Hình 4.107. Ứng dụng phát hiện cạnh, làm mờ khuôn mặt
Đoạn ví dụ mẫu sau hướng dẫn cách làm mờ khuôn mặt của các
các nhân trong ứng dụng bảo vệ thông tin người dùng. Đầu tiên ta cần
phát hiện các khuôn mặt có trong ảnh sử dụng bộ phát hiện Cascade; sau
đó áp dụng bộ lọc Gaussian cho các khuôn mặt được nhận dạng. Các
khuôn mặt được phát hiện trong ảnh sẽ lưu trong một danh sách có tên
detected. Vòng lặp for truy vấn từng phần tử trong danh sách dưới tên
biến là d. Sau đó ảnh tương ứng được mô tả trong d được trích xuất và
cắt khỏi khung hình bằng hàm getFace(d). Khuôn mặt sau khi rút trích sẽ
được lưu trữ ở biến face.

Ví dụ mẫu về ứng dụng bảo mật danh tính:

# Khai báo sử dụng bộ phân loại Cascade và bộ lọc Gaussian


from skimage.feature import Cascade
from skimage.filters import gaussian
# Phát hiện khuôn mặt
detected = detector.detect_multi_scale(img=image, scale_factor
=1.2, step_ratio=1, min_size=(50, 50), max_size=(100, 100))

140
# Với từng khuôn mặt được phát hiện
for d in detected:
# tách khuôn mặt ra khỏi ảnh gốc.
face = getFace(d)

Chi tiết hàm getFace được giải thích như sau. Đầu tiên, lấy vị trị tại
hàng r và cột c là toạ độ khởi đầu. Sau đó đếm thêm width hàng và
height cột để xác định vùng cần quan tâm của khuôn mặt. Cuối cùng,
truy vấn vào tất cả các hàng và các cột của ảnh để có thể lấy về khuôn
mặt đã chọn.

def getFace(d):
''' Extracts the face rectangle from the image using the
coordinates of the detected.'''
# X and Y starting points of the face rectangle
x, y = d['r'], d['c']
# The width and height of the face rectangle
width, height = d['r'] + d['width'], d['c'] + d['height']
# Extract the detected face
face= image[x:width, y:height]
return face

Bây giờ khuôn mặt đã bị cắt khỏi ảnh, áp dụng bộ lọc Gaussian để
làm mờ khuôn mặt và làm cho nó không thể nhận ra. Ảnh kết quả này
được gán cho biến gaussian_face. Bước cuối cùng, đoạn mã sau sẽ hợp
nhất khuôn mặt mờ trở lại ảnh. Việc kết hợp này được thực thi bởi hàm
mergeBlurryFace.

# phát hiện khuôn mặt

141
detected=detector.detect_multi_scale(img=image, scale_factor=
1.2, step_ratio=1, min_size=(50, 50), max_size=(100, 100))
# Ứng với mỗi khuôn mặt trích thực hiện
for d in detected:
# lấy khuôn mặt cần xử lý từ ảnh gốc
face = getFace(d)
# Dùng bộ lọc gaussian làm mờ ảnh
gaussian_face = gaussian(face,multichannel=True,sigma =
10)
# kết hợp ảnh đã được làm mờ lại vào ảnh gốc ban đầu
resulting_image = mergeBlurryFace(image, gaussian_face)

Chương trình con mergeBlurryFace được viết như sau:

def mergeBlurryFace(original, gaussian_image):


# truy vấn vào toạ độ góc trên cùng bên trái của khuôn mặt
x, y = d['r'], d['c']
# Truy vấn và toạ độ góc dưới cùng bên phải của khuôn mặt
width, height = d['r'] + d['width'], d['c'] + d['height']
original[ x:width, y:height] = gaussian_image
# trả về ảnh đã được làm mờ
return original

Bằng cách này, khuôn mặt của các các nhân trong khung hình được
tự động ẩn đi và danh tính cá nhân được bảo vệ. Lưu ý rằng bộ phân loại
ở đây chỉ được tạo để phát hiện mặt trước của khuôn mặt, không phải
khuôn mặt ngang. Vì vậy nếu ai đó quay đầu quá nhiều sang một bên, bộ
phân loại sẽ không nhận ra. Nếu muốn làm được điều đó, cần tạo bộ phân
loại mới với với các tệp xml của các khuôn mặt huấn luyện mới.

142
Hình 4.108. Ảnh gốc và kết quả làm mờ khuôn mặt

143
BÀI TẬP
Bài tập 4.1: Tìm cạnh với thuật toán Canny
Câu hỏi: Trong bài tập này, xác định các hình dạng trong hình quả
bưởi bằng cách phát hiện các cạnh, sử dụng thuật toán Canny.

Hình 4.109. Ảnh gốc của bài tập 4.1


Hoàn thiện đoạn mã sau:

# Tải các thư viện cần thiết 


from skimage.____ import ____
#  chuyển đổi ảnh thành ảnh xám
grapefruit = ____(____)
#  Áp dụng bộ lọc cạnh
canny_edges = ____
#  Hiển thị kết quả
show_image(canny_edges, "Edges with Canny")
Hướng dẫn:

● Import bộ dò cạnh Canny từ thư viện features


● Chuyển ảnh sang ảnh xám, sử dụng hàm từ thư viện color
● Sử dụng phát hiện cạnh dùng Canny vào hình grapefruit.

144
Bài tập 4.2: Khảo sát ảnh hưởng của việc lọc nhiễu lên bộ lọc phát hiện
cạnh

Câu hỏi: Hãy thực hiện lại yêu cầu của bài 1. Tuy nhiên hãy thêm
phần xử lý lọc nhiễu trước khi phát hiện cạnh. Sử dụng bộ lọc Gaussian
với các mức độ làm mờ khác nhau để làm cho ảnh mượt mà hơn thông
qua cách điều khiển giá trị tham số sigma.

Hình 4.110. Ảnh gốc cho bài tập 4.2


Hướng dẫn:

● Dùng công cụ dò cạnh canny cho ảnh gốc với sigma là 1,8.
● Dùng công cụ dò cạnh canny cho ảnh gốc với sigma là 2,2.
● Hiển thị các ảnh kết quả.
● So sánh và giải thích hiện tượng

Hoàn thiện đoạn code sau:

#  Ứng dụng phát hiện cạnh với  sigma =1.8


canny_edges = canny(grapefruit, sigma=____)

145
Bài tập 4.3: Phát hiện góc bằng bộ lọc Harris

Câu hỏi: Tìm các góc của tòa nhà sử dụng phát hiện góc Harris. Sử
dụng các hàm show_image() và show_image_with_corners() đã được cho
trước. Các hàm của thư viện color cũng được khai báo để chuyển ảnh
sang ảnh xám.

Hình 4.111. Ảnh gốc cho bài tập 4.3


Hãy hoàn thiện đoạn code sau:

#  Khai báo các thư viện cần thiết


from skimage.____ import ____, corner_peaks
# Đọc ảnh từ file thư viện cho sẵn:
building_image=__________
#  Chuyển đổi ảnh màu sang ảnh xám
building_image_gray = ____
# Ứng dụng bộ lọc Harris để xác định các góc có khả năng:
measure_image = ____
# Tìm đỉnh của các góc dựa trên đáp ứng của bộ lọc Harris
coords = ____(____, min_distance=2)
#  Hiển thị kết quả và so sánh
show_image(building_image, "Original")
show_image_with_corners(building_image, coords)

146
Hướng dẫn:

● Import hàm corner_harris() từ thư viện feature.


● Chuyển ảnh building_image thành ảnh xám.
● Sử dụng Bộ dò Harris để thu được ảnh phản hồi đo với các góc có
thể
● Tìm các đỉnh của các góc

Bài tập 4.4: Khảo sát ảnh hưởng của tham số min_distance trong việc
phát hiện góc

Câu hỏi: Trong bài tập này, hãy khảo sát ảnh hưởng của tham số
điều khiển min_distance của hàm corner_peaks() trong việc phát hiện
các góc đối tượng trong của ảnh. Các hàm show_image(),
show_image_with_corners() và các thư viện bắt buộc đã được khai báo
trước trước cho bài tập này.

Hình 4.112. Ảnh gốc cho bài tập 4.4

147
Hãy hoàn thiện đoạn mã sau:

# Thay đổi tham số điều khiển min_distance và quan sát số lượng


các góc có thể tìm được
coords_w_min_2 = ___(measure_image, min_distance=___)
print("With a min_distance set to 2, we detect a total", len(coords
_w_min_2), "corners in the image.")

Hướng dẫn:

● Tìm các đỉnh của các góc với tham số min_distance là 2 pixels.
● Tìm các đỉnh của các góc với tham số min_distance là 40 pixels.
● Hiển thị ảnh gốc và ảnh kết quả với các góc được phát hiện
● So sánh và giải thích kết quả đạt được

Bài tập 4.5: Nhận diện khuôn mặt

Câu hỏi: Kiểm tra xem có một người hiện diện hay không trong
ảnh được chụp vào ban đêm.

Hình 4.113. Ảnh gốc cho bài tập 4.5

148
Sử dụng bộ phân loại Cascade từ thư viện feature đã được import.
Hàm show_detected_face() được dùng lại từ các ví dụ trên để hiển thị
khuôn mặt được đánh dấu trong ảnh, đồng thời trích xuất khuôn mặt đó
và hiển thị riêng biệt.

Hoàn thiện đoạn mã sau:

#  Tải mô hình đã được huấn luyện sẵn


trained_file = ____.lbp_frontal_face_cascade_filename()
#  khởi tạo đối tượng phát hiện khuôn mặt
detector = ____(____)
#  Thay đổi các tham số điều khiển giá trị lớn nhất và giá trị nhỏ
nhất của khuôn mặt và đánh giá sự thay đổi
detected = detector.detect_multi_scale(img = night_image,
scale_factor=1.2, step_ratio=1,min_size=(___), max_size=(___))
#  Hiển thị kết quả.
show_detected_face(night_image, detected)

Hướng dẫn:

● Tải mô hình phát hiện khuôn mặt từ thư viện data.


● Khởi tạo detector với tệp được học.
● Phát hiện các khuôn mặt trong ảnh, đặt kích thước tối thiểu của
cửa sổ tìm kiếm là 10 pixel và tối đa là 200 pixel.

Bài tập 4.6: Nhận dạng nhiều khuôn mặt

Câu hỏi: Trong bài tập này, hãy phát hiện nhiều khuôn mặt trong
một ảnh và hiển thị chúng riêng lẻ.

149
Hình 4.114. Ảnh gốc cho bài tập 4.6
Giả thiết rằng bộ phân loại Cascade từ thư viện feature đã được
khai báo; hàm show_detected_face() được tận dụng để hiển thị khuôn
mặt được đánh dấu trong ảnh đồng thời trích xuất khuôn mặt đó và hiển
thị riêng biệt.

Hãy hoàn thiện đoạn mã sau:

# Load the trained file from data
trained_file = ____.___()
# Initialize the detector cascade
detector = ____
# Detect faces with scale factor to 1.2 and step ratio to 1
detected = detector.____(img=friends_image, scale_factor=____,
step_ratio=____, min_size=(10, 10), max_size=(200, 200))
# Show the detected faces
show_detected_face(friends_image, detected)

Hướng dẫn:

150
● Tải file .lbp_frontal_face_cascade_filename() từ thư viện data
● Khởi tạo detector với tệp được học
● Phát hiện các khuôn mặt trong ảnh, gán scale_factor là 1.2
và step_ratio là 1

Bài tập 4.7: Phân đoạn và phát hiện khuôn mặt

Câu hỏi: Hãy thực hiện phân đoạn khuôn mặt bằng kỹ thuật siêu
điểm ảnh. Sau đó dùng những siêu điểm ảnh này để thực thi bài toán phát
hiện khuôn mặt. Hãy đánh giá kết quả thực thi và thời gian thực thi để so
sánh đóng góp của kỹ thuật siêu điểm ảnh vào bài toán phát hiện khuôn
mặt.

Hình 4.115. Ảnh gốc cho bài tập 4.7


Sử dụng hàm slic() để phân đoạn, tiền xử lý ảnh trước khi chuyển
nó đến bộ nhận dạng khuôn mặt. Sử dụng bộ phân loại Cascade và hàm
slic() từ thư viện segmentation để phát hiện khuôn mặt. Giả sử hàm
show_detected_face() để hiển thị đã được khai báo. Bộ nhận dạng đã
được khởi tạo và được lưu dưới tên biến detector.

151
Hãy hoàn thành đoạn mã sau:

# Tách ảnh gốc thành 100 vùng


segments = ____
#  Tạo ra ảnh phân đoạn bằng hàm  label2rgb
segmented_image = ____(____, ____, kind='avg')
#  Phát hiện khuôn mặt dựa vào ảnh phân đoạn
detected = detector.____(img=____, scale_factor=1.2, step_ratio
=1, min_size=(10, 10), max_size=(1000, 1000))
# Hiển thị kết quả và so sánh
show_detected_face(segmented_image, detected)

Hướng dẫn:

● Áp dụng phân đoạn siêu điểm ảnh bằng cách sử dụng hàm slic()
● Lấy được ảnh được phân đoạn bằng cách sử dụng label2rgb(),
chuyển tham số segments và profile_image qua
● Phát hiện các khuôn mặt, sử dụng bộ nhận dạng với phương pháp
đa tỷ lệ.

Bài tập 4.8: Bảo vệ quyền riêng tư

Câu hỏi: Trong bài tập này, ta sẽ phát hiện khuôn mặt trong ảnh và
vì quyền riêng tư, sẽ ẩn dữ liệu bằng cách tự động làm mờ khuôn mặt của
người trong ảnh. Có thể sử dụng bộ lọc Gaussian để làm mờ. Bộ nhận
dạng khuôn mặt đã được khai báo sẵn với tên biến detector và tất cả các
thư viện cần thiết đã được báo.

152
Hình 4.116. Ảnh gốc cho bài tập 4.8
Hãy hoàn thiện đoạn code sau:

#  Phát hiện khuôn mặt


detected = ___.___(img=____, scale_factor=1.2, step_ratio=1, 
min_size=____, max_size=(100, 100))
#  Với mỗi khuôn mặt đã phát hiện, hãy
for d in ____:  
     # Tác khuôn mặt ra khỏi ảnh gốc
     face = getFaceRectangle(d)
         # làm mờ khuôn mặt
     blurred_face = ____(face, multichannel=____, sigma = ____)
         #  kết hợp ảnh đã được làm mờ với ảnh gốc
   resulting_image = mergeBlurryFace(group_image, blurred_face) 
show_image(resulting_image, "Blurred faces")

Hướng dẫn:

● Dò tìm các khuôn mặt trong ảnh bằng detector, đặt kích thước tối
thiểu của cửa sổ tìm kiếm là 10 x 10 điểm ảnh.
● Đi qua từng khuôn mặt được phát hiện bằng vòng lặp for.

153
● Áp dụng bộ lọc gaussian để phát hiện và làm mờ khuôn mặt, sử
dụng sigma là 8.

Bài tập 4.9: Trong bài tập này, hãy phục hồi một bức ảnh bị hư hỏng rất
nặng. Bức ảnh này đã bị làm hỏng do nhiễu, biến dạng và thiếu thông tin
do dữ liệu bị lỗi.

Hình 4.117. Ảnh gốc cho bài tập 4.9


Khắc phục sự cố ảnh này bằng cách:

● Xoay nó để được thẳng đứng bằng hàm rotate()


● Giảm nhiễu với hàm denoise_tv_chambolle()
● Tái cấu trúc những phần bị hỏng với hàm
inpaint_biharmonic()  từ thư viện inpaint
● Hàm show_image() đã được tải.

Hãy hoàn thiện đoạn mã sau:

#  Khai báo các thư viện cần thiết

154
from skimage.restoration import denoise_tv_chambolle, ____
from skimage import transform
# Xoay lại ảnh ban đầu
upright_img = ____(damaged_image, 20)
# Lọc nhiễu
upright_img_without_noise = ____(upright_img,weight=0.1, 
multichannel=True)
#  Khôi phục lại phần bị mất
mask = get_mask(upright_img)
result = ________._______(upright_img_without_noise, mask, 
multichannel=True)
show_image(result)

Hướng dẫn:

● Import thư viện cần thiết để áp dụng khôi phục ảnh.


● Xoay ảnh bằng cách gọi hàm rotate().
● Sử dụng thuật toán chambolle để loại bỏ nhiễu khỏi ảnh.
● Với mặt nạ được cung cấp, sử dụng phương pháp biharmonic để
khôi phục các phần bị thiếu của ảnh và có được ảnh cuối cùng.

155
CHƯƠNG 5.
HỌC TẬP CHUYỂN GIAO

Trong chương bốn, người học đã thảo luận về một ứng dụng của kỹ
thuật học máy vào phát hiện khuôn mặt. Kỹ thuật học máy này sử dụng
các đặc trưng được thiết kế cẩn thận để rút trích những thông tin có tính
ổn định cao dùng trong phát hiện khuôn mặt. Dù vậy, xử lý ảnh hiện đại
sử dụng học sâu để bản thân dữ liệu tự thiết kế các đặc trưng phù hợp cho
chính nó và xây dựng các mô hình phân loại phù hợp. Trong chương này,
người học sẽ thảo luận về cách sử dụng kỹ thuật học sâu để huấn luyện
một mô hình phân loại. Do việc huấn luyện mô hình học sâu đòi hỏi rất
nhiều chi phí về dữ liệu và phần cứng. Nên trong giới hạn chương này,
kỹ thuật học tập chuyển giao sẽ được sử dụng nhằm giảm thiểu chi phí về
dữ liệu. Ngoài ra gói thư viện PyTorch sẽ được sử dụng để thực hiện quá
trình học tập chuyển giao.

5.1. Cơ sở lý thuyết

Việc sử dụng AI và mạng học sâu CNN đã tạo ra nhiều bước tiến
nhảy vọt trong việc xử lý ảnh cũng như thị giác máy tính. Một trong
những ràng buộc để đảm bảo sự thành công của một mô hình học sâu là
cần nhiều dữ liệu và các cấu hình máy tính mạnh để học các đặc trưng có
ý nghĩa. Đây là chi phí mà không phải cá nhân hay tổ chức nào cũng có
thể giải quyết được. Một trong những giải pháp này là sử dụng kỹ thuật
học tập chuyển giao. Trong kỹ thuật này, thay vì huấn luyện toàn bộ mô
hình học sâu ngay từ đầu (khởi tạo tham số ngẫu nhiên), người dùng sẽ
tái sử dụng lại các cấu trúc học sâu đã được kiểm nghiệm cùng với các
trọng số đã được học dựa vào thông tin từ những bộ dữ liệu lớn. Một ví

156
dụ như là mạng Resnet121 được huấn luyện bởi bộ dữ liệu ImageNet
gồm 1.2 triệu ảnh. Cuối cùng, các trọng số này được sử dụng như một bộ
rút trích đặc trưng cố định để tiếp tục huấn luyện nhiệm vụ mới. Có ba
ngữ cảnh về học tập chuyển giao thường được sử dụng như sau:

● Mạng CNN như một bộ rút trích đặc trưng: Sử dụng một
mạng CNN được huấn luyện sẵn (Alexnet, Densenet, YoLo…), ta sẽ bỏ
đi lớp cuối cùng (lớp thực hiện các nhiệm vụ ban đầu của mô hình đã
được huấn luyện). Như vậy, các thông tin có được tại ngõ ra của mô hình
mới là các đặc trưng có ngữ nghĩa cao mà đã học được từ một bộ dữ liệu
lớn. Ví dụ, nếu mô hình khởi tạo là mạng Alexnet, thì ở lớp cuối cùng có
4096 đặc trưng đã được rút trích cho mọi ảnh đầu vào. Lúc này, ta sẽ sử
dụng các mô hình này như một bộ rút trích đặc trưng cho dữ liệu mới.
Sau khi rút trích đặc trưng, bất kỳ bộ phân loại nào (ví dụ như SVM hoặc
mạng nơ ron) có thể được sử dụng để học từ các đặc trưng đã được rút
trích để thực hiện công việc của nó.
● Tinh chỉnh Mạng CNN. Chiến lược thứ hai là không chỉ thay
thế và đào tạo lại bộ phân loại trên đầu ConvNet trên tập dữ liệu mới, mà
còn tinh chỉnh trọng số của mạng được đào tạo trước bằng cách tiếp tục
lai tạo. Có thể tinh chỉnh tất cả các lớp của ConvNet hoặc có thể giữ cố
định một số lớp trước đó (do lo ngại về việc trang bị quá phù hợp) và chỉ
tinh chỉnh một số lớp cao của mạng CNN. Điều này được thúc đẩy bởi
nhận xét rằng các đặc trưng ở các lớp thấp của mạng CNN chứa nhiều
tính năng chung hơn (ví dụ: bộ phát hiện cạnh hoặc bộ phát hiện đốm
màu) và sẽ hữu ích cho nhiều tác vụ, nhưng các lớp cao của ConvNet trở
nên dần dần cụ thể hơn đối với chi tiết của các lớp chứa trong tập dữ liệu
gốc.

157
● Sử dụng mô hình được huấn luyện sẵn: Do các mạng CNN
thường mất hai hoặc ba ngày để huấn luyện trên nhiều GPU đối với dữ
liệu ImageNet. Mọi người thường lưu lại các checkpoint để có thể tiếp
tục sử dụng khi cần.

Với nhiều chiến lược học chuyển đổi như trên, câu hỏi đặt ra là làm
thế nào để có thể xác định chiến lược học phù hợp. Có hai yếu tố quan
trọng để giúp trả lời câu hỏi này. Yếu tố thứ nhất là kích thước của tập dữ
liệu và yếu tố thứ hai là sự tương đồng của nó đối với dữ liệu gốc. Dưới
đây là bốn quy tắc chung cho việc ra quyết định rằng kỹ thuật nào sẽ
được sử dụng:

● Tập dữ liệu mới nhỏ hơn và tương tự như tập dữ liệu gốc. Khi
dữ liệu huấn luyện quá nhỏ, sẽ không phải là ý kiến hay nếu tinh chỉnh
mạng CNN gốc quá nhiều vì nó sẽ bị quá phù hợp với dữ liệu nhỏ mà
mất đi tính tổng quát của bộ dữ liệu lớn. Vì dữ liệu mới tương tự với dữ
liệu gốc, ta có thể kỳ vọng các đặc trưng ở lớp cao hơn trong mạng CNN
cũng có liên quan đến tập dữ liệu mới này. Do đó, cách tốt nhất có thể là
đào tạo một bộ phân loại tuyến tính dựa trên các đặc trưng có thể rút trích
từ mạng CNN gốc mà không thay đổi chúng.
● Tập dữ liệu mới lớn và tương tự như tập dữ liệu gốc. Vì có
nhiều dữ liệu hơn, ta có thể tự tin hơn rằng bộ dữ liệu này sẽ đủ để tinh
chỉnh mọi tham số có trong mạng gốc một cách đầy đủ.
● Tập dữ liệu mới nhỏ nhưng rất khác so với tập dữ liệu ban đầu.
Vì dữ liệu nhỏ, nên tốt nhất là chỉ đào tạo một bộ phân loại tuyến tính. Vì
tập dữ liệu cũ và dữ liệu mới rất khác nhau, nên các đặc trưng rút trích ở
lớp cao của mô hình cũ có thể không phải là những đặc trưng phù hợp
cho dữ liệu mới. Lúc này, các đặc trưng ở lớp thấp có thể sẽ rút được

158
nhiều thông tin chung cho các loại dữ liệu nhiều hơn. Tuy nhiên, nếu chỉ
sử dụng các đặc trưng ở lớp thấp thì số lượng các đặc trưng có thể quá
nhiều so với số lượng mẫu có sẵn ở tập dữ liệu mới. Lúc này các kỹ thuật
giảm chiều dữ liệu như PCA có thể được sử dụng để giảm chiều dữ liệu
trước khi thực hiện quá trình huấn luyện.
● Tập dữ liệu mới lớn và rất khác so với tập dữ liệu ban đầu. Vì
tập dữ liệu mới rất lớn, ta có thể hi vọng rằng mô hình có thể được huấn
luyện ngay từ đầu. Tuy nhiên, trong thực tế, việc khởi tạo với các trọng
số từ một mô hình được đào tạo trước vẫn rất có lợi. Trong trường hợp
này, ta sẽ có đủ dữ liệu và sự tự tin để tinh chỉnh toàn bộ mạng.

Một số kinh nghiệm thực tiễn: Khi thực hiện việc học tập chuyển
giao, một số lưu ý có thể nên lưu tâm như sau:

● Tiền xử lý dữ liệu: Một số mô hình được huấn luyện trước đòi


hỏi việc chuẩn hóa dữ liệu thông qua giá trị mean và giá trị độ lệch
chuẩn. Ví dụ như mạng Densenet, các ảnh phải được tải lên ở trong định
dạng giá trị trong khoảng [0,1], sau đó được chuẩn hoá bằng giá trị mean
= [0.485, 0.456, 0.406] và std = [0.229, 0.224, 0.225].
● Ràng buộc từ các mô hình được đào tạo trước. Các mô hình có
sẵn thường được huấn luyện với những ảnh có kích thước xác định. Mặc
dù vậy, ứng dụng các ảnh có thể được sử dụng với kích thước khác. Tuy
phép toán nhân chập có thể hoạt động với các kích thước ảnh khác nhau
nhưng khi thực hiện việc làm phẳng và kết nối tất cả các nút thì sẽ có sự
khác biệt.
● Tốc độ học: Thông thường, khi tinh chỉnh mô hình tốc độ học
tập nên được đặt nhỏ hơn giá trị được đặt trong quá trình huấn luyện.
Nguyên nhân của việc này là vì ta đang kỳ vọng là các trọng số của mạng

159
CNN đang tương đối tốt và chúng tôi không muốn tinh chỉnh quá nhiều
(đặc biệt là trong khi Bộ phân loại tuyến tính mới đang được khởi tạo
ngẫu nhiên).

5.2. Tinh chỉnh toàn bộ mô hình:

Như đã phân tích ở trên, có hai kỹ thuật thường được sử dụng, một
là tinh chỉnh toàn bộ mô hình khi có đủ dữ liệu, và một là giữ lại một số
lớp ban đầu với giả định rằng các lớp nơ-ron thấp chứa những đặc trưng
phù hợp cho mọi ảnh.

Ví dụ tiếp theo hướng dẫn cách huấn luyện lại toàn bộ mô hình cho
bài toán phân loại kiến và ong. Đầu tiên các thư viện cần sử dụng phải
được khai báo như đoạn mã sau:

from __future__ import print_function, division

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
import copy
plt.ion() # interactive mode

160
5.2.1. Dataloader

Một mô hình học sâu có thể có hàng triệu tham số cần phải học, do
đó nó hoàn toàn có thể được điều chỉnh để phù hợp với với dữ liệu được
đưa vào huấn luyện. Điều này sẽ không có lợi khi một mô hình chỉ nhớ
chính xác những gì mà nó đã học trong quá trình huấn luyện chứ không
thể nhận ra các biến thể khác của chính nó; chưa kể tới việc phải nhận ra
sự khác biệt khi phải thực hiện phân loại cho một mẫu mới chưa từng
thấy.

Để vượt qua thách thức này, một giải pháp phổ biến là tách dữ liệu
thành tập huấn luyện (được sử dụng để giảm thiểu hàm tổn thất) và tập
đánh giá (được đánh giá riêng biệt trong quá trình huấn luyện) mà không
ảnh hưởng trực tiếp đến trọng số của mô hình. Tuy nhiên, tập đánh giá
thường được sử dụng để sửa đổi các tham số điều khiển (các tham số bên
ngoài mô hình học sâu và quản lý quá trình huấn luyện của nó) chọn mô
hình tốt nhất mỗi kỷ nguyên hoặc tác động gián tiếp đến việc đào tạo
theo một số cách khác. Vì lý do này, tập dữ liệu kiểm tra độc lập thường
được tạo ra để đánh giá độ chính xác cuối cùng sau khi quá trình đào tạo
hoàn tất.

Pytorch hỗ trợ các gói thư viện torchvision và torch.utils.data được


thiết kế chuyên dụng để tải dữ liệu từ ổ cứng của máy tính vào vùng nhớ
RAM để thực hiện quá trình huấn luyện. Khi sử dụng các công cụ này,
mỗi bộ dữ liệu cần được phân vào trong những thư mục cụ thể. Các thư
viện này sẽ tạo một đối tượng hỗ trợ việc tải các dữ liệu trong những thư
mục này lên theo những nhóm ngẫu nhiên để thực hiện quá trình học.

161
Trong ví dụ tiếp theo, bộ dữ liệu được chia thành hai tập tin và
‘train’ và ‘eval’. Mỗi tập tin chứa hai thư mục là ‘ong’ và ‘kiến’. Có
khoảng 120 ảnh trong tập huấn luyện và 75 ảnh trong tập kiểm tra. So với
tập dữ liệu ImageNet (hơn 1000 class- 1.2 triệu ảnh) được dùng để huấn
luyện mô hình gốc, bộ dữ liệu mới này chỉ có hai loại là ong và kiến.
Hơn nữa, nếu xét về mặt số lượng đây là một tập dữ liệu rất nhỏ để có thể
học được một mô hình có độ tổng quát hóa nếu các tham số của mô hình
được đào tạo từ đầu. Vì vậy phương thức học tập chuyển giao được sử
dụng.

Đầu tiên các class datasets, models, và transforms từ thư viện


torchvision được sử dụng để tạo ra các bộ tiền xử lý cũng như tải dữ liệu
phục vụ cho quá trình huấn luyện. Bởi vì các ảnh có kích thước khác
nhau, chúng cần phải được chuẩn hoá về cùng một kích thước thước sau
khi vừa được tải lên từ ổ cứng. Sau đó, chúng có thể được xoay theo
nhiều hướng khác nhau để tạo ra nhiều dữ liệu đa dạng. Các dữ liệu ảnh
khi vừa đọc lên sẽ có kiểu dữ liệu numpy array, để có thể sử dụng trong
mạng học sâu, chúng cần được chuyển thành dạng tensor. Pytorch hỗ trợ
nhiều công cụ thể chuyển đổi một numpy array thành một tensor. Lệnh
transforms.Compose giúp kết hợp các bước xử lý này thành một chuỗi
duy nhất. trong quá trình huấn luyện, để giảm kích mô hình và tăng độ đa
dạng của dữ liệu, ta đơn thuần chỉ cần chọn một vùng thông tin cố định
để đưa vào mạng CNN. Ngược lại trong quá trình kiểm tra, cần đánh giá
với một ảnh hoàn chỉnh. Do đó khi sử dụng lệnh transforms.Compose
để tải dữ liệu trong quá trình huấn luyện, ta chỉ cần dùng chức năng
RandomResizedCrop(), và RandomHorizontalFlip() để tạo ra càng
nhiều dữ liệu càng tốt. Ngược lại, trong qúa trình đánh giá chỉ cần thực

162
hiện việc thay đổi kích thước (transforms.Resize(256)) và lấy dữ liệu
(transforms.CenterCrop(224)). Cuối cùng, do mô hình Resnet mà ta sử
dụng sau này đòi hỏi dữ liệu đọc về phải ở trong khoảng [0,1] và chuẩn
hoá với mean = [0.485, 0.456, 0.406], và std = [0.229, 0.224, 0.225].

data_transforms = {
'train': transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224,
0.225])
]),
'val': transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224,
0.225])
]),
}

Để có thể tải lên dữ liệu trong suốt quá trình huấn luyện, ta cần sử
dụng class Dataloader. Một dataloader sử dụng dữ liệu chứa trong các
thư mục riêng lẻ để tải lên. Đoạn mã sau mô tả cách sử dụng dataloader,
đầu tiên tên của các thư mục con (bao gồm 'train', 'val') bên trong thư
mục chứa dữ liệu gốc ('hymenoptera_data) được sử dụng để khai báo một
đối tượng image_datasets thông qua class ImageFolder. Sau đó, đối

163
tượng image_datasets sẽ được sử dụng như một tham số để khởi tạo
dataloder tương ứng. Ở đây vì có hai tập tin được khai báo trong list danh
sách ['train', 'val'], nên image_datasets và dataloaders đều là một list chứa
hai đối tượng tương ứng tạo ra với hai thư mục ['train', 'val'] nằm trong
thư mục cha data_dir = 'hymenoptera_data'.

data_dir = 'hymenoptera_data'
image_datasets ={x:datasets.ImageFolder(os.path.join(data_dir,
x),
data_transforms[x])
for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x],
batch_size=4, shuffle=True, num_workers=0)
for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes

device = torch.device("cuda:0" if torch.cuda.is_available() else


"cpu")

Để hiểu hơn về dữ liệu hiện có, đoạn mã sau sử dụng dataloader


ứng với thư mục train để tải lên một số ảnh và hiển thị chúng. Do khi sử
dụng dataloader, dữ liệu đã được trừ đi giá trị mean=[0.485, 0.456,
0.406], và chia với độ lệch chuẩn std=[0.229, 0.224, 0.225] nên trước khi
hiển tại ta phải biến đổi ngược bằng cách nhân với std và cộng với mean
như câu lệnh inp = std * inp + mean.

def imshow(inp, title=None):


#""Imshow for Tensor."""

164
inp = inp.numpy().transpose((1, 2, 0)) #chuyển từ tensor sang
nunpy array
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
inp = std * inp + mean #trả về dữ liệu gốc nguyên nhân bởi
bước chuẩn hoá của Resnet
inp = np.clip(inp, 0, 1) #đảm bảo dữ liệu ở trong khoảng [0,1]
plt.imshow(inp)
if title is not None:
plt.title(title)
plt.pause(0.001) #tạm ngừng để quan sát dữ liệu

# Lấy một nhóm các dữ liệu được sử dụng trong một lần lăp
inputs, classes = next(iter(dataloaders['train']))
# tạo một lưới các ảnh đã được load
out = torchvision.utils.make_grid(inputs)
imshow(out, title=[class_names[x] for x in classes]) # hiển thị dữ
liệu và tên tương ứng

Hình 5.118. Một số ảnh trong tập huấn luyện

165
5.2.2. Huấn luyện mô hình

Để huấn luyện mô hình, bước đầu tiên cần xác định các tham số
điều khiển của quá trình huấn luyện. Tỷ số học phản ánh mức độ cập
nhật của mô hình cho mỗi lần lặp. Nếu nó quá nhỏ, quá trình huấn luyện
sẽ diễn ra chậm. Nếu nó quá lớn, trọng số sẽ bị điều chỉnh quá nhiều và
bỏ lỡ mức giảm tối thiểu cần thiết, hoặc thậm chí làm cho quá trình học
trở nên không ổn định. Một epoch là một quá trình chạy đầy đủ thông
qua toàn bộ dữ liệu huấn luyện. Một số mô hình yêu cầu hàng nghìn
epoch để huấn luyện; nhưng thường thì không nên để thời gian huấn
luyện quá lâu vì sẽ gây ra hiện tượng quá phù hợp. Do đó trong quá trình
huấn luyện tỷ lệ học sẽ được giảm dần tùy theo số epoch sử dụng.

Để giải quyết bài toán dữ liệu lớn, nơi mà không phải tất cả dữ liệu
đều có thể được tải vào RAM như trong ví dụ này, thì các bộ dữ liệu cần
được chia nhỏ ra và học trong nhiều lần. Nếu số lượng mẫu trong mỗi lần
học rất nhỏ, thông tin học được sẽ không ổn định. Lúc này tỷ lệ học cần
phải nhỏ để tránh bị học sai hướng. Nếu số lượng mẫu trong mỗi lần học
rất lớn, lúc này tỷ lệ học phải được tăng cường.

Quá trình huấn luyện là một quá trình lặp đi lặp lại. Trong mỗi lần
lặp, các nhóm mẫu được chọn ngẫu nhiên bởi đối tượng Dataloader sẽ
được đưa qua mô hình để dự đoán kết quả. Kết quả đó sẽ được so sánh
với nhãn. Sai số này sẽ được thể hiện thông qua một hàm mục tiêu. Nếu
mô hình học càng tốt thì hàm mục tiêu sẽ càng nhỏ. Hàm mục tiêu được
dùng để huấn luyện trong ví dụ này là hàm cross-entropy. Đây là hàm
mục tiêu phổ biến cho bài toán phân loại khi sai số được tính khi một
mẫu được phân loại là dương nhưng không được dự đoán là một mẫu
dương. Hàm mục tiêu được tính cho từng mẫu và nhân rộng ra cho tất

166
của các mẫu của nhóm dữ liệu được dùng để huấn luyện. Sai số sẽ được
lan truyền ngược về để tìm cách thay đổi các trọng số nhằm giảm hàm
mục tiêu. Cuối cùng, một bộ giải hàm mục tiêu sẽ được áp dụng để cập
nhập trọng số.

Sau khi tất cả các nhóm dữ liệu nhỏ đã lần lượt được sử dụng để
hoàn thành việc huấn luyện, ta nói mô mình đã hoàn thành một epoch.
Lúc này, một epoch mới sẽ được bắt đầu để tiếp tục huấn luyện mô hình
cho tới khi mạng nơ-ron hội tụ.

Đoạn mã sau mô tả một chương trình con của quá trình huấn luyện:

def train_model(model, criterion, optimizer, scheduler,


num_epochs=25):
since = time.time()

best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0

for epoch in range(num_epochs):


print('Epoch {}/{}'.format(epoch, num_epochs - 1))
print('-' * 10)

# Mỗi epoch sẽ thực hiện việc huấn luyện và đánh giá 1 lần
for phase in ['train', 'val']:
print(phase)
if phase == 'train':
model.train() # đặt chế độ huấn luyện cho mô hình
else:

167
model.eval() # đặt chế độ đánh giá cho mô hình

running_loss = 0.0
running_corrects = 0

# lặp lại để quét qua toàn bộ dữ liệu. Tuỳ thuộc vào chế
độ chạy nào mà sử dụng bộ loader tương ứng.
for inputs, labels in dataloaders[phase]:
inputs = inputs.to(device) # chuyển dữ liệu vào phần
cứng tương ứng
labels = labels.to(device)

# khởi tạo gradient bằng 0


optimizer.zero_grad()

# forward
# Cho phép tính gradient trong quá trình huấn luyện
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)
# sử dụng lan truyền ngược để cập nhập trong quá
trình huấn luyện
if phase == 'train':
loss.backward()
optimizer.step()

168
# Thống kê lại sai số trong quá trình huấn luyện
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
if phase == 'train': # trong suốt quá trình huấn luyện
thực hiện việc giảm tỷ lệ học
scheduler.step()
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() /
dataset_sizes[phase]
print('{} Loss: {:.4f} Acc: {:.4f}'.format(
phase, epoch_loss, epoch_acc))
# Lưu lại mô hình có độ chính xác cao nhất trong suốt
quá trình học để tránh overfitting do việc huấn luyện quá lâu
if phase == 'val' and epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
# In kết qủa()
time_elapsed = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format(
time_elapsed // 60, time_elapsed % 60))
print('Best val Acc: {:4f}'.format(best_acc))
# load best model weights
model.load_state_dict(best_model_wts)
return model

169
5.2.3. Kiến trúc của một mạng CNN

Trước khi có thể thực sự thực hiện việc huấn luyện, cần xây dựng
cấu trúc của mạng nơron. Ở đây, ta sử dụng kiến trúc của mạng resnet18.
Về mặt tổng quát, đây mà một mạng nơron gồm nhiều lớp tích chập xếp
chồng lên nhau, sau đó đi qua lớp làm phẳng và lớp nơron ngõ ra. Ảnh
ban đầu sẽ đi qua lớp tích chập. Về mặt nhận thức, một ảnh có thể được
nhận diện bởi các đường cong, các cạnh nhỏ. Sau đó, các cạnh – đường
cong nhỏ có thể kết hợp với nhau tạo ra các đường cong, hình dạng và
các cấu trúc phức tạp hơn và có độ trừu tượng cao hơn. Bằng cách chỉ kết
hợp thông tin từ các pixel lân cận, mạng nơ ron nhân chập cố gắng rút
trích thông tin từ các đường cong nhỏ có ý nghĩa của dữ liệu. Sau đó, ở
các lớp tích chập sâu hơn, các thông tin nhỏ này sẽ được kết hợp để học
được những hình dáng chủ đạo xuất hiện trong bộ dữ liệu. Cuối cùng, các
đặc trưng này được làm phẳng (sắp xếp thành một vector), và các lớp
nơron thông thường sẽ được sử dụng để tạo nên chức năng phân lại cho
ảnh.

Một lưu ý quan trọng khi sử dụng các mô hình thuộc họ mạng nơ
ron nhân tạo là cần phải sử dụng hàm tích cực để có thể tạo ra các mô
hình phân loại phi tuyến. Lấy ví dụ, nếu có một hàm tuyến tính y = 3x +
2 và một hàm hợp z = 4y – 7, thì z vẫn là một hàm tuyến tính theo x như
sau : z = 12x + 1. Do đó nếu chỉ đơn giản ghép nối các nút với nhau trong
bộ phân loại mà không có các hàm tích cực thì dù có ghép nhiều lớp lại
với nhau cũng chỉ tạo ra được một hàm tuyến tính. Trong mạng Resnet
này đa số các hàm tích cực là hàm ReLU.

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features

170
# Do chỉ có hai loại cần phân biệt là kiến và ong nên ngõ ra là 2.
# Một cách thay thế, lớp cuối cùng này có thể được viết như sau
nn.Linear(num_ftrs, len(class_names)).
model_ft.fc = nn.Linear(num_ftrs, 2)
model_ft = model_ft.to(device)
criterion = nn.CrossEntropyLoss()
# khởi tạo bộ tối ưu hoá giúp huấn luyện mô hình
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001,
momentum=0.9)
# Bộ đặt lịch để giảm tỷ lệ học, tỷ lệ học sẽ được giảm theo tỷ lệ
0.1 cho mỗi 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft,
step_size=7, gamma=0.1)
Đoạn mã xây dựng cấu trúc mạng dựa trên kiến trúc của
Resnet18, đồng thời xác định bộ tối ưu và đặt lịch giảm learning
rate

Sau khi khai báo mô hình, ta có thể dùng hàm train_model() đã viết
ở trên để thực hiện quá trình huấn luyện. Kết quả trả về như sau

model_ft = train_model(model_ft, criterion, optimizer_ft,


exp_lr_scheduler, num_epochs=25)
Epoch 0/24
----------
train Loss: 0.6185 Acc: 0.6680
val Loss: 0.2424 Acc: 0.9020
Epoch 1/24

171
----------
train Loss: 0.4168 Acc: 0.8115
val Loss: 0.5123 Acc: 0.8170
Epoch 2/24
----------
train Loss: 0.5845 Acc: 0.7746
val Loss: 0.2984 Acc: 0.9020
Epoch 3/24
----------
train Loss: 0.4086 Acc: 0.8525
val Loss: 0.3848 Acc: 0.8889
Epoch 4/24
----------
train Loss: 0.7077 Acc: 0.7746
val Loss: 0.4570 Acc: 0.8758
Epoch 5/24
----------
train Loss: 0.5902 Acc: 0.7869
val Loss: 0.3498 Acc: 0.8824
Epoch 6/24
----------
train Loss: 0.3980 Acc: 0.8238
val Loss: 0.1900 Acc: 0.9216
Epoch 7/24
----------
train Loss: 0.3610 Acc: 0.8197

172
val Loss: 0.1896 Acc: 0.9477
Epoch 8/24
----------
train Loss: 0.4042 Acc: 0.8402
val Loss: 0.2177 Acc: 0.9412
Epoch 9/24
----------
train Loss: 0.3805 Acc: 0.8402
val Loss: 0.2063 Acc: 0.9281
Epoch 10/24
----------
train Loss: 0.3316 Acc: 0.8607
val Loss: 0.2210 Acc: 0.9346
Epoch 11/24
----------
train Loss: 0.3173 Acc: 0.8566
val Loss: 0.2385 Acc: 0.9346
Epoch 12/24
----------
train Loss: 0.3578 Acc: 0.8197
val Loss: 0.2261 Acc: 0.9216
Epoch 13/24
----------
train Loss: 0.2414 Acc: 0.8852
val Loss: 0.1819 Acc: 0.9346
Epoch 14/24

173
----------
train Loss: 0.3007 Acc: 0.8607
val Loss: 0.2125 Acc: 0.9216
Epoch 15/24
----------
train Loss: 0.2707 Acc: 0.8770
val Loss: 0.1974 Acc: 0.9346
Epoch 16/24
----------
train Loss: 0.2903 Acc: 0.8852
val Loss: 0.2080 Acc: 0.9412
Epoch 17/24
----------
train Loss: 0.2318 Acc: 0.9221
val Loss: 0.2056 Acc: 0.9281
Epoch 18/24
----------
train Loss: 0.2982 Acc: 0.8770
val Loss: 0.2084 Acc: 0.9216
Epoch 19/24
----------
train Loss: 0.2441 Acc: 0.8975
val Loss: 0.2144 Acc: 0.9216
Epoch 20/24
----------
train Loss: 0.2837 Acc: 0.8689

174
val Loss: 0.2166 Acc: 0.9346
Epoch 21/24
----------
train Loss: 0.3256 Acc: 0.8607
val Loss: 0.2067 Acc: 0.9346
Epoch 22/24
----------
train Loss: 0.2603 Acc: 0.9057
val Loss: 0.1954 Acc: 0.9346
Epoch 23/24
----------
train Loss: 0.2701 Acc: 0.8852
val Loss: 0.1976 Acc: 0.9281
Epoch 24/24
----------
train Loss: 0.2465 Acc: 0.8975
val Loss: 0.2324 Acc: 0.9085
Training complete in 30m 8s
Best val Acc: 0.947712

5.2.4. Hiển thị kết quả

Trong quá trình huấn luyện, ta đã đánh giá định tính chất lượng của
mô hình. Tuy nhiên để hiểu rõ hơn hoạt động của mô hình, cần hiển thị
các ảnh dùng để kiểm tra và các dự đoán tương ứng của chúng đoạn mã
sau là chương trình con giúp hiển thị kết quả và dự đoán tương ứng.

def visualize_model(model, num_images=6):


was_training = model.training # lưu lại các tham số của chế độ

175
huấn luyện
model.eval() # chuyển sang chế độ đánh giá
images_so_far = 0
fig = plt.figure()

with torch.no_grad():
for i, (inputs, labels) in enumerate(dataloaders['val']): # khởi
tạo bộ tối ưu hoá giúp huấn luyện mô hình
inputs = inputs.to(device)
labels = labels.to(device)

outputs = model(inputs)
_, preds = torch.max(outputs, 1)

for j in range(inputs.size()[0]):
images_so_far += 1
ax = plt.subplot(num_images//2, 2, images_so_far)
ax.axis('off')
ax.set_title('predicted:
{}'.format(class_names[preds[j]]))
imshow(inputs.cpu().data[j])

if images_so_far == num_images:
model.train(mode=was_training)
return
model.train(mode=was_training)

176
visualize_model(model_ft) # Gọi chương trình con để hiển thị kết
quả dự đoán

Hình 5.119. Kết quả dự đoán khi tinh chỉnh toàn bộ mạng học sâu
5.3. Cố định một phần của mô hình ban đầu trong suốt quá trình
tinh chỉnh

Rõ ràng là khi mô hình chỉ có ít dữ liệu, ta không nên tinh chỉnh


toàn bộ mô hình mà chỉ nên học các trọng số của mô hình tuyến tính
được thêm vào ở lớp cuối cùng. Do đó, trong ví dụ tiếp theo, người học
sẽ thảo luận về cách để đóng băng các trọng số của mô hình Resnet cũ và
chỉ cho phép học các trọng số nhất định.

177
Giống như thí nghiệm trước, ta khởi tạo một mô hình dựa trên
restnet18 với các trọng số được huấn luyện sẵn thông qua câu lệnh:

model_conv = torchvision.models.resnet18(pretrained=True)

Các trọng số của đối tượng model_conv lúc này sẽ được trả về
bằng phương thức parameters(). Để ngăn cản các trọng số này thay đổi
trong quá trình học, hãy thiết lập thuộc tính requires_grad của các trọng
số này thành giá trị False như sau:

for param in model_conv.parameters():


param.requires_grad = False

Tiếp theo đó, ta sẽ gắn lớp cuối cùng vào bên trên mô hình hiện tại
để thực hiện quá trình dự đoán cho hai class “kiến” và “ong”. Đoạn code
sau mô tả toàn bộ quy trình tương tự như đã làm ở phần trước.

model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
param.requires_grad = False

# Các trọng số có thuộc tính requires_grad=True theo mặc định


num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs, 2)

model_conv = model_conv.to(device)

criterion = nn.CrossEntropyLoss()

# Lúc này, chỉ các trọng số được khởi tạo với

178
requires_grad=True sẽ được học
optimizer_conv = optim.SGD(model_conv.fc.parameters(),
lr=0.001, momentum=0.9)

# Giảm learning rate theo tỷ lệ 0.1 cho mỗi 7 epoch


exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv,
step_size=7, gamma=0.1)

Để thực hiện huấn luyện đối tượng model_conv, ta sử dụng lại hàm
train_model như sau:

model_conv = train_model(model_conv, criterion,


optimizer_conv, exp_lr_scheduler, num_epochs=25)
Epoch 0/24
----------
train Loss: 0.5524 Acc: 0.7090
val Loss: 0.3939 Acc: 0.8170
Epoch 1/24
----------
train Loss: 0.4309 Acc: 0.8074
val Loss: 0.2322 Acc: 0.9150
Epoch 2/24
----------
train Loss: 0.5841 Acc: 0.7828
val Loss: 0.1693 Acc: 0.9477
Epoch 3/24
----------

179
train Loss: 0.4486 Acc: 0.8115
val Loss: 0.3306 Acc: 0.8627
Epoch 4/24
----------
train Loss: 0.5285 Acc: 0.7623
val Loss: 0.1660 Acc: 0.9542
Epoch 5/24
----------
train Loss: 0.4210 Acc: 0.8279
val Loss: 0.1567 Acc: 0.9542
Epoch 6/24
----------
train Loss: 0.4626 Acc: 0.8156
val Loss: 0.1868 Acc: 0.9281
Epoch 7/24
----------
train Loss: 0.3325 Acc: 0.8484
val Loss: 0.1685 Acc: 0.9542
Epoch 8/24
----------
train Loss: 0.2813 Acc: 0.8770
val Loss: 0.1929 Acc: 0.9346
Epoch 9/24
----------
train Loss: 0.3516 Acc: 0.8607
val Loss: 0.1691 Acc: 0.9412

180
Epoch 10/24
----------
train Loss: 0.3883 Acc: 0.8238
val Loss: 0.1728 Acc: 0.9412
Epoch 11/24
----------
train Loss: 0.3375 Acc: 0.8566
val Loss: 0.2043 Acc: 0.9281
Epoch 12/24
----------
train Loss: 0.3761 Acc: 0.8525
val Loss: 0.2072 Acc: 0.9346
Epoch 13/24
----------
train Loss: 0.3433 Acc: 0.8197
val Loss: 0.1739 Acc: 0.9412
Epoch 14/24
----------
train Loss: 0.4304 Acc: 0.8238
val Loss: 0.1749 Acc: 0.9346
Epoch 15/24
----------
train Loss: 0.3130 Acc: 0.8852
val Loss: 0.2691 Acc: 0.9085
Epoch 16/24
----------

181
train Loss: 0.3180 Acc: 0.8648
val Loss: 0.1861 Acc: 0.9412
Epoch 17/24
----------
train Loss: 0.4263 Acc: 0.8320
val Loss: 0.1982 Acc: 0.9412
Epoch 18/24
----------
train Loss: 0.3248 Acc: 0.8525
val Loss: 0.1898 Acc: 0.9281
Epoch 19/24
----------
train Loss: 0.3148 Acc: 0.8730
val Loss: 0.1996 Acc: 0.9412
Epoch 20/24
----------
train Loss: 0.2946 Acc: 0.8689
val Loss: 0.1604 Acc: 0.9346
Epoch 21/24
----------
train Loss: 0.3077 Acc: 0.8566
val Loss: 0.1887 Acc: 0.9412
Epoch 22/24
----------
train Loss: 0.3663 Acc: 0.8566
val Loss: 0.1706 Acc: 0.9412

182
Epoch 23/24
----------
train Loss: 0.3500 Acc: 0.8566
val Loss: 0.2150 Acc: 0.9281
Epoch 24/24
----------
train Loss: 0.2657 Acc: 0.8607
val Loss: 0.1718 Acc: 0.9542
Training complete in 0m 35s
Best val Acc: 0.954248

Kết quả huấn luyện cho thấy bằng cách cố định các trọng số của
mạng Resnet18 và chỉ thay đổi trọng số của lớp cuối cùng, ta có thể có
kết quả huấn luyện tốt hơn.

visualize_model(model_conv)

183
Hình 5.120. Kết quả dự đoán khi cố định một phần mạng học
sâu

BÀI TẬP
● Hãy vẽ và giải thích kiến trúc của mạng Resnet18
● Hãy in ra các giá trị trả về của câu lệnh
model_conv.parameters(). So sánh kết quả tìm được với cấu trúc
mạng Resnet18 ở trên
● Hãy viết chương trình để cho phép tinh chỉnh lại các trọng số ở
lớp kề cuối của mạng Resnet18. Kết quả huấn luyện lại cho thấy
điều gì?

184
PHỤ LỤC 1: CÀI ĐẶT MÔI TRƯỜNG PYTHON

Giới thiệu về Python


Python được sáng tạo bởi Guido van Rossum vào cuối những năm
1980s, đây là một ngôn ngữ lập trình cực kỳ phổ biến được sử dụng để
phát triển website và thiết kế nhiều ứng dụng khác nhau.

Python là ngôn ngữ lập trình hướng đối tượng đa năng. Ngôn ngữ
này sở hữu cấu trúc dữ liệu cấp cao mạnh mẽ và hệ thống thư viện lớn.
Python sử dụng cơ chế cấp phát bộ nhớ tự động với cú pháp đơn giản và
rõ ràng, giúp người học dễ tiếp cận và làm quen, kể cả đối với những
người mới bắt đầu học lập trình.

Phiên bản Ngày phát


hành

Python 1.0 (bản phát hành chuẩn đầu tiên) 01/1994


Python 1.6 (Phiên bản 1.x cuối cùng) 05/09/2000

Python 2.0 (Giới thiệu list comprehension) 16/10/2000


Python 2.7 (Phiên bản 2.x cuối cùng) 03/07/2010

Python 3.0 (Loại bỏ cấu trúc và mô-đun trùng 03/12/2008


lặp) 03/05/2021
Python 3.9.5 (Bản mới nhất tính đến thời điểm
hiện tại)

Bảng P1.1: Các phiên bản và thời điểm phát hành của
python

Cài đặt Python

185
Bước 1: Truy cập vào trang chủ của Python
https://www.python.org/

Hình P1.1: Cài đặt Python bước 1


Bước 2: Chọn phiên bản ở mục Downloads và nhập chọn Python
3.9.5 để download

Hình P1.2: Cài đặt Python bước 2

186
Bước 3: Sau download hoàn tất. Chúng ta nhấn chọn chạy file
python-3.9.5-amd64.exe để bắt đầu tiến trình cài đặt.

Hình P1.3: Cài đặt Python bước 3


Bước 4: Chọn vào ô Add Python 3.9 to PATH và chọn Install Now

Hình P1.4: Cài đặt Python bước 4.1

187
Chờ đợi cho quá trình cài đặt hoàn thành

Hình P1.5: Cài đặt Python bước 4.2


Bước 5: Khi cửa sổ hiển thị Setup was successful là ta đã cài đặt
thành công môi trường Python

> Chọn Close

Hình P1.6: Cài đặt Python bước 5

Bước 6: Tiếp đến, ta mở Command Prompt (CMD) để kiểm tra.

188
Để mở Command Prompt bạn dùng tổ hợp phím Windows + R để
mở hộp thoại Run

Sau đó, gõ cmd > Enter để mở Command Prompt

Hình P1.7: Cài đặt Python bước 6.1


Cửa sổ Command Prompt hiển thị như bên dưới:

Hình P1.8: Cài đặt Python bước 6.2

189
Bước 7: Trong cửa sổ Command Prompt, gõ python > Enter để
kiểm tra

Nếu hiển thị ra được shell để tương tác với Python như hình ở dưới
có nghĩa là phần cài đặt đã hoàn tất.

Hình P1.9: Cài đặt Python bước 7

190
CÀI ĐẶT ANACONDA

Giới thiệu Anaconda

Anaconda là một Distribution miễn phí và mã nguồn mở của


Python và R giúp đơn giản hóa việc cài đặt, quản lý và triển khai
packages (numpy, scipy, tensorflow, ...).

Anaconda phục vụ cho nhiều mục đích, đặc biệt trong Data Science
(Khoa học dữ liệu), Machine learning (Máy học), Big Data (Dữ liệu lớn),
Image Processing (Xử lý ảnh), ...

Anaconda hiện nay đã có hơn 20 triệu người dùng và hơn 7500


packages khoa học dữ liệu dành cho Windows, Linux và MacOS.

Cài đặt Anaconda

Bước 1: truy cập vào trang web https://www.anaconda.com/

Hình P2.1: Cài đặt Anaconda bước 1


Bước 2: Chọn Individual Edition trong tab Product

191
Hình P2.2: Cài đặt Anaconda bước 2
Bước 3: Chọn xuống mục Anaconda Installers: và chọn tải phiên
bản thích hợp, ở đây chọn hệ điều hành windows bản python 3.8 và 64-
bit Graphical.

Hình P2.3 Cài đặt Anaconda bước 3


Bước 4: Chạy file cài đặt

192
Hình P2.4: Cài đặt Anaconda bước 4
Bước 5: Chấp nhập các yêu cầu thiết lập và tiến hình cài đặt

Hình P2.5: Cài đặt Anaconda bước 5.1

193
Hình P2.6: Cài đặt Anaconda bước 5.2
Bước 6: Chọn tài khoản và vị trí để cài đặt. Ở đây chọn
recommended và ổ C:/

Hình P2.7: Cài đặt Anaconda bước 6.1

Hình P2.8: Cài đặt Anaconda bước 6.2


Bước 7: Chọn thêm các cài đặt nâng cao. Chọn Install và chờ đợi

194
Hình P2.9: Cài đặt Anaconda bước 7.1

Hình P2.10: Cài đặt Anaconda bước 7.2


Bước 8: Chọn Next > Next > Finish để hoàn thành cài đặt

195
Hình P2.11: Cài đặt Anaconda bước 8

Hình P2.12: Cài đặt anaconda bước 8.2

196
QUẢN LÝ MÔI TRƯỜNG

Với Anaconda có nhiều packages khoa học phụ thuộc vào các
phiên bản cụ thể của các packages khác. Các nhà khoa học dữ liệu
thường sử dụng nhiều phiên bản của nhiều package và sử dụng nhiều môi
trường để phân tách các phiên bản khác nhau này.

Chương trình dòng lệnh (command-line program conda) vừa là


trình quản lý các package vừa là trình quản lý môi trường (environment
manager). Điều này giúp các nhà khoa học dữ liệu đảm bảo rằng mỗi
phiên bản của mỗi package có tất cả các phụ thuộc mà nó yêu cầu và hoạt
động chính xác.

Anaconda Navigator cung cấp cho người dùng một giao diện đồ
họa để quản lý các environment (môi trường) và package. Ta sẽ có
environment mặc định là base (root) chứa các package cơ bản.

Ở ngăn giao diện Home là nơi quản lý các Application (ứng dụng)
tại một environment (trong vòng đỏ).

Bước 1: Mở Anaconda Navigator

197
Hình P3.1: Quản lý môi trường Anaconda bước 1
Bước 2: Chọn Environments > Chọn Create

Hình P3.2: Quản lý môi trường Anaconda bước 2


Bước 3: Chọn Create, chờ đợi đến khi tạo được 1 môi trường mới

198
Hình P3.3: Quản lý môi trường Anaconda bước 3.1

Hình P3.4: Quản lý môi trường Anaconda bước 3.2


Bước 4: Quay lại Home > Chọn Install CMD.exe Prompt > Refresh
> Lauch

199
Hình P3.5: Quản lý môi trường Anaconda bước 4
Bước 5: Thêm 1 thư viện

Giả sử thêm thư viện OpenCV: dùng lệnh conda install -c conda-
forge opencv

Hình P3.6: Quản lý môi trường Anaconda bước 5

200
CÀI ĐẶT JUPYTER NOTEBOOK

Bước 1: Mở Anaconda Navigator

Hình P4.1: Cài đặt Jupyter Note Book bước 1


Bước 2: Chọn Install > Chọn Lauch để sử dụng

Hình P4.2: Cài đặt Jupyter Note Book bước 2

201
CÀI ĐẶT PYCHARM

Bước 1: Mở Anaconda Navigator

Hình P5.1: Cài đặt Pycharm bước 1


Bước 2: Chọn Download now

Hình P5.2: Cài đặt Pycharm bước 2

202
Bước 3: Mở file vừa tải xong

Hình P5.3: Cài đặt Pycharm bước 3


Bước 4: Nhấn next để bắt đầu cài đặt

Hình P5.4: Cài đặt Pycharm bước 4


Bước 5: Chọn nơi cài đặt

203
Hình P5.5: Cài đặt Pycharm bước 5
Bước 6: Tùy chọn cài đặt

Hình P5.6: Cài đặt Pycharm bước 6

204
Bước 7: Chọn thư mục lưu trữ trong Start menu

Hình P5.7: Cài đặt Pycharm bước 7


Bước 8: Nhấn install Pycharm

Hình P5.8: Cài đặt Pycharm bước 8

205
Bước 9: Hoàn tất cài đặt Pycharm, ấn Finish

Hình P5.9: Cài đặt Pycharm bước 9


Bước 10: Chọn Continue

Hình P5.10: Cài đặt Pycharm bước 10

206
Bước 11: Chọn Free

Hình P5.11: Cài đặt Pycharm bước 11


Bước 12: Hoàn thành bước cuối cùng cài đặt Pycharm

Hình P5.12: Cài đặt Pycharm bước 12


PHỤ LỤC 2: MÔ TẢ CÁC HÀM DÙNG TRONG CHƯƠNG 1

Chi tiết về các hàm chính sử dụng trong chương 1 bao gồm:

207
matplotlib.pyplot.imshow(X, cmap=None, norm=None, aspect=Non
e, interpolation=None, alpha=None, vmin=None, vmax=None, origi
n=None, extent=None, *, filternorm=True, filterrad=4.0, resample
=None, url=None, data=None, **kwargs)
Chức năng: Hiển thị dữ liệu dưới dạng ảnh 2D
X: Dữ liệu ảnh. Có thể là các dạng mảng
(M, N) với ảnh xám.
(M, N, 3) với ảnh RGB
(M, N, 4) với ảnh RGBA
cmap: string hoặc colormap. Tham số này bị bỏ qua đối với
dữ liệu RGB (A). Để hiển thị như ảnh xám, gán biến cmap thành
‘gray’, giá trị mặc định của camp là Viridis.
norm: Chuẩn hóa dữ liệu. Tham số này chuẩn hoá dữ liệu đầu
vào từ 0 đến 1 [0,1]. Mặc định giá trị thấp nhất là 0, và giá trị cao nhất
là trong ảnh 1. Tham số này sẽ được bị bỏ qua đối với dữ liệu RGB
(A).
aspect: {'equal', 'auto'} hoặc float. Giá trị mặc định là 'equal'.
Giá trị này định nghĩa tỉ lệ khung hình của trục. Tham số này đặc biệt
có liên quan đến ảnh vì nó xác định liệu các điểm ảnh có đo cùng một
giá trị chiều ngang và chiều dọc trong thực tế hay không. Nếu khai
báo tham số này là ‘Equal’ - tức là độ dài các cạnh của các điểm ảnh
là bằng nhau (hình vuông). Nếu khai báo tham số này là ‘Auto’ -
không bảo toàn tỷ lệ khung hình.
interpolation: str. Tham số này điều khiển phương pháp nội
suy của ảnh. Giá trị mặc định là 'antialiased'. Các giá trị được hỗ trợ
của tham số này là:'none', 'antialiased', 'nearest', 'bilinear', 'bicubic',

208
'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser',
'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos',
'blackman'. Nếu interpolation là 'None', thì không có sử dụng
phương thức nội suy trong khi hiển thị ảnh.
alpha: Tham số này nhận giá trị là một số thực (float) hoặc
mảng. Đây là tham số tùy chọn và có thể không khai báo. Tham số
này định nghĩa giá trị hòa trộn khi ảnh được hiển thị trên cùng một
khung hình. Giá trị alpha là một giá trị nằm giữa mức 0 và 1. Nếu
alpha là một mảng, các giá trị được áp dụng từng theo từng điểm ảnh
và alpha phải có cùng kích thước với ảnh đầu vào X.
vmin, vmax: Đây là một tham số tùy chọn kiểu số thực
(float). Tham số này thường sử dụng khi ảnh đầu vào là dữ liệu vô
hướng và không sử dụng tham số norm. vmin và vmax xác định
phạm vi dữ liệu mà bản đồ màu bao phủ. Các giá trị nhỏ hơn vmin sẽ
được hiển thị giống như vmin, và các giá trị lớn hơn vmax sẽ được
hiển thị giống như vmax trên ảnh. Khi sử dụng dữ liệu RGB (A), các
tham số vmin / vmax bị bỏ qua.
origin: {'upper', 'lower'}, Tham số này quyết định vị trí đặt
gốc toạ độ [0, 0]. Giá trị mặc định của tham số này là 'upper'. Khi
khai báo là 'upper' sẽ đặt chỉ số [0, 0] của mảng ở góc trên bên trái
hoặc ngược lại sẽ đặt góc dưới bên phải của trục. Thông thường, gốc
toạ độ [0,0] sẽ được đặt ở góc trên bên trái của ảnh.
matplotlib.pyplot.imread(fname, format=None)
Đọc một ảnh từ một tệp vào một mảng
fname: string: file ảnh: tên file, hoặc đối tượng giống file
được mở ở chế độ đọc nhị phân

209
format: Định dạng tệp ảnh. Nếu không được cung cấp, định
dạng được suy ra từ tên tệp. Nếu không suy luận được thì sẽ thử định
dạng PNG.
Hàm trả về dữ liệu ảnh: numpy.array.
Có thể là mảng
(M, N) với ảnh xám.
(M, N, 3) với ảnh RGB
(M, N, 4) với ảnh RGBA
matplotlib.pyplot.hist(x, bins=None, range=None, density=False,
weights=None, cumulative=False, 
bottom=None, histtype='bar', align='mid', orientation='vertical', 
rwidth=None, log=False, color=None, 
label=None, stacked=False, *, data=None, **kwargs)
Chức năng: Vẽ lược đồ thống kê của một mảng x. Trong đó
yêu cầu các phần từ của x phải khác giá trị NaN
x: Mảng hoặc nhiều mảng. Các mảng này không bắt buộc phải
có cùng kích thước
bins: là một giá trị số nguyên hoặc một mảng, hoặc chuỗi ký
tự. Giá trị mặc định là 10. Nếu bins là:
● một số nguyên, nó xác định số khoảng cách cần làm thống kê,
chiều rộng của các khối là bằng nhau trong dãy.
● một dãy số, nó xác định các cạnh của khối, bao gồm cạnh trái
của khối đầu tiên và cạnh phải của khối cuối cùng; trong trường hợp
này, các khối có thể có khoảng cách không bằng nhau. Ví dụ, nếu
bins là: [1,2,3,4] thì khối thứ nhất là [1, 2) và khối hai là [2, 3) và
khối cuối cùng là [3, 4].

210
● một chuỗi, thì đó là một trong những giá trị sau: 'auto', 'fd',
'doane', 'scott', 'stone', 'rice', 'stereges' hoặc 'sqrt '. Các giá trị này là
những phương pháp khác nhau để xác định khoản giá trị khi làm
thống kê.
range: thuộc kiểu dữ liệu tuple hoặc none. Trong trường hợp
không khai báo, giá trị mặc định nhận được là none. Tham số này
điều khiển phạm vi giới hạn dưới và trên của khoảng cần khảo sát.
Các giá trị thấp hơn và cao hơn các ngưỡng được định nghĩa trong
tham số này sẽ bị bỏ qua. Nếu không khai báo, giá trị này mặc định là
(x.min (), x.max ()). Tham số range không có tác dụng nếu bins là
một chuỗi.
density: thuộc kiểu dữ liệu bool, mặc định: False. Nếu nhận
giá trị là true, hàm sẽ vẽ và trả về mật độ xác suất:
(density=counts/(sum(counts)*np.diff(bins)))
cumulative: thuộc kiểu dữ liệu bool.
Nếu nhận giá trị là True, biểu đồ được tính toán trong đó mỗi
cột sẽ làm hàm cộng dồn của hàm mật độ xác suất. Trong trường hợp
density cũng là True thì giá trị trả về là một mảng có phần tử cuối
cùng được chuẩn hóa bằng 1.
histtype: Tham số này nhận các giá trị {'bar', 'barstacked',
'step', 'stepfilled'}, mặc định: 'bar'. Tham số xác định loại biểu đồ để
vẽ:
● 'bar' là một biểu đồ kiểu thanh truyền thống. Nếu nhiều dữ
liệu được cung cấp, các thanh được sắp xếp cạnh nhau.
● 'barstacked' là một biểu đồ dạng thanh trong đó nhiều dữ liệu
được xếp chồng lên nhau;

211
● 'step' tạo một đường theo mặc định là không được lấp đầy;
● 'stepfilled' tạo một lineplot mặc định được lấp đầy.

align: {'left', 'mid', 'right'}, mặc định: 'mid'. Canh chỉnh theo
chiều ngang của các thanh biểu đồ.
● 'left': các thanh được canh giữa trên các cạnh khối bên trái;
● 'mid': các thanh được canh giữa giữa các cạnh khối;
● 'right': các thanh được canh giữa trên các cạnh khối bên phải.

orientation: {'vertical', 'horizontal'}, mặc định: 'vertical'.


Hướng của biểu đồ, mặc định sẽ hiển thị theo chiều dọc, nếu chọn
'horizontal' thì thanh bên trái sẽ nằm phía dưới và biểu đồ sẽ nằm
ngang
rwidth: thuộc kiểu dữ liệu float hoặc None, mặc định: None.
Chiều rộng tương đối của khi hiển thị các khoảng. Nếu None, tự động
tính chiều rộng. Bị bỏ qua nếu histtype là 'step' or 'stepfilled'
log: thuộc kiểu dữ liệu bool, mặc định: False. Nếu True, trục
biểu đồ sẽ được đặt thành tỷ lệ logarit
color: màu hoặc giống mảng màu hoặc None, mặc định: None.
Màu hoặc chuỗi màu, một màu trên mỗi tập dữ liệu. Mặc định sử
dụng chuỗi màu tiêu chuẩn.
label: str hoặc None. Mặc định: None. Chuỗi hoặc nhiều chuỗi
liên tiếp để tương thích với nhiều tập dữ liệu.
stacked: bool, mặc định là False. Nếu True, nhiều dữ liệu
được xếp chồng lên nhau, nếu False, nhiều dữ liệu xếp cạnh bên
nhau, nếu histtype là ‘bar’ hoặc chồng lên nhau nếu histtype là
‘step’.
skimage.filters.threshold_otsu(image=None,nbins=256, *,

212
hist=None)
Chức năng: Tìm giá trị ngưỡng phân đoạn bằng phương pháp
của Otsu.
image: là một ma trận ndarray thể hiện ảnh xám
nbins thuộc kiểu dữ liệu int, tham số này có thể khai báo hoặc
không. Tham số sẽ xác định số lượng các khung trong quá trình lượng
tử hóa trước để xây dựng lược đồ mức xám. Lược đồ này được dùng
trong thuật toán Otsu.
skimage.filters.threshold_local (image, block_size,
method='gaussian', offset=0, mode = 'reflect', param=None,
cval=0)
Chức năng: Tính toán ảnh mặt nạ ngưỡng dựa trên vùng lân
cận cục bộ. Kết quả trả về là một ảnh có cùng kích thước với ảnh gốc.
Mỗi giá trị là ngưỡng khi xem xét thông tin của chỉ một vùng lân cận
cục bộ.
image: là một ma trận ndarray thể hiện ảnh xám
block_size: thuộc kiểu dữ liệu int hoặc chuỗi int. Giá trị này
xác định kích thước của vùng lân cận được sử dụng trong thuật toán.
method: tham số này có thể nhận một trong các giá trị
{‘generic’, ‘gaussian’, ‘mean’, ‘median’}, và có thể không cần khai
báo. Tham số này xác định kỹ phương pháp được sử dụng để xác định
ngưỡng động cho vùng lân cận cục bộ trong ảnh. Mặc định
‘gaussian’
● ‘generic’: sử dụng hàm tùy chỉnh (xem tham số param)
● ‘gaussian’: áp dụng bộ lọc gaussian (xem thông số param cho
giá trị sigma tùy chỉnh)

213
● ‘mean’: áp dụng bộ lọc trung bình số học
● ‘median’: áp dụng bộ lọc xếp hạng trung bình.

skimage.filters.try_all_threshold (image, figsize=(8,5),


verbose=True)
Chứng năng: Hàm này thử nhiều phương pháp phân ngưỡng khác
nhau và hiển thị chúng. Các phương pháp được thực thi bởi hàm này
bao gồm: isodata, li, mean, minimum, otsu, triangle, yen
image(N, M) là một ma trận ndarray thể hiện ảnh xám
figsize: thuộc kiểu dữ liệu tuple và có thể bỏ qua. Tham số
này xác định kích thước ảnh dùng để hiển thị kết quả (tính bằng inch).
verbose: thuộc kiểu dữ liệu bool, và có thể bỏ qua. Tham số
này lựa chọn in ra tên cho mỗi phương pháp phân ngưỡng.

214
PHỤ LỤC 3: MÔ TẢ CÁC HÀM DÙNG TRONG CHƯƠNG 2

Chi tiết về các hàm chính sử dụng trong chương 2 bao gồm:

skimage.filters.sobel(image,mask=None,axis=None, mode='reflect',
cval=0.0)
Chức năng: Tìm biên ảnh sử dụng bộ lọc Sobel
image: là một ma trận ndarray thể hiện ảnh xám
mask: là một mảng thuộc kiểu dữ liệu bool và có thể bỏ qua.
Tham số này nhận giá trị mặc định là None. Tham số này gắn ảnh
ngõ ra với mặt nạ. Các giá trị nơi mask = 0 sẽ được chuyển thành 0.
axis: thuộc kiểu dữ liệu int hoặc chuỗi int, và có thể bỏ qua.
Tham số này nhận giá trị mặc định là None. Tham số điều khiển
hướng tính toán của bộ lọc. Nếu không được khai báo, độ lớn của
cạnh sẽ được tính theo cả hai hướng như sau:
sobel_mag = np.sqrt(sum([sobel(image, axis=i)**2
mode: thuộc kiểu dữ liệu chuỗi str, và có thể bỏ qua. Tham số
này nhận giá trị mặc định là ‘reflect’. Tham số này xác định biên cho
tích chập. Đây có thể là một chế độ biên đơn hoặc một chế độ biên
trên mỗi trục. Có các chế độ {‘reflect’, ‘constant’, ‘nearest’,
‘mirror’, ‘wrap’}
cval: thuộc kiểu dữ liệu float, và có thể bỏ qua, Tham số này
nhận giá trị mặc định là 0.0. Khi mode là 'constant', tham số này là
hằng số được sử dụng trong các giá trị bên ngoài biên của các dữ liệu
ảnh.
skimage.filters.gaussian(image, sigma = 1, output = None, mode =
'nearest', cval = 0, multichannel=None, preserve_range=False,

215
truncate=4.0): Bộ lọc Gauss nhiều kênh
Chức năng: Trả về kết quả được lọc bằng bộ lọc gaussian
image: Ảnh đầu vào
sigma: Đại lượng vô hướng hoặc chuỗi các đại lượng vô
hướng, và có thể bỏ qua không khai báo. Tham số này mô tả độ lệch
chuẩn của bộ lọc Gaussian theo các trục. Giá trị càng lớn thì ảnh sẽ
càng mờ.
output: Kết quả trả về, và có thể bỏ qua không khai báo, mặc
định None. Tham số này chỉ định một mảng để lưu kết quả đầu ra của
thuật toán.
mode: chế độ làm việc, và có thể bỏ qua không khai báo. mặc
định None. Các giá trị có thể sử dụng để khai báo cho tham số này là
{‘reflect’, ‘constant’, ‘nearest’, ‘mirror’, ‘wrap’}.
cval: thuộc kiểu dữ liệu float, và có thể bỏ qua. Tham số này
nhận giá trị mặc định là 0.0. Khi mode là 'constant', tham số này là
hằng số được sử dụng trong các giá trị bên ngoài biên của các dữ liệu
ảnh
multichannel: thuộc kiểu dữ liệu Bool, và có thể bỏ qua.
Tham số này nhận giá trị mặc định là None. Nếu nhận giá trị là True,
mỗi kênh sẽ được lọc riêng biệt (các kênh không bị trộn lẫn với
nhau).
preserve_range: thuộc kiểu dữ liệu Bool, và có thể bỏ qua.
Tham số này nhận giá trị mặc định là False. Tham số này giúp duy trì
được khoảng dữ liệu không đổi sau khi tiến hành lọc ảnh so với ảnh
gốc.
skimage.exposure.equalize_hist(image, nbins = 256, mask = none)

216
Hàm này có chức năng cân bằng lược đồ mức xám toàn cục cho ảnh
đầu vào image.
image: ảnh đầu vào dạng mảng Numpy
nbins: Số lượng các chân của lược đồ mức xám. Nếu nbin=1,
ảnh nhận được chỉ có màu trắng. Tham số này có thể bỏ qua đối với
ảnh số nguyên uint8. Mặc dù vây, nếu ảnh có dải động đặc biệt lớn
hoặc ảnh được lưu dưới dạng số thập phân thì tham số này phải được
lựa chọn phù hợp.
mask: Tham số này quyết định những điểm ảnh nào sẽ được
xử lý và những điểm ảnh nào không được xử lý. Đây là một ma trận
có cùng kích thước với ảnh. Các giá trị nhận được là 0 và 1. Nếu giá
trị một điểm ảnh là 0 tức là điểm ảnh đó sẽ được xử lý bằng tăng
cường ảnh. Trong trường hợp không khai báo giá trị mask = True
được áp dụng cho toàn bộ ảnh.

skimage.exposure.equalize_adapthist(image, kernel_size = None,


clip_limit = 0.01, nbins = 256)
image: ảnh đầu vào dạng mảng Numpy
kernel_size: int hoặc array_like. Xác định hình dạng của các
vùng được sử dụng trong thuật toán. Nếu có lặp lại, nó phải có cùng
số phần tử như image.ndim (không có kênh màu). Nếu là số nguyên,
nó được truyền tới từng kênh ảnh. Mặc định, kernel_size là 1/8 chiều
cao, 1/8 chiều rộng. Giá trị kernel_size càng lớn càng tăng cường chi
tiết ảnh
clip_limit: float, tùy chọn. Giới hạn cắt, được chuẩn hóa giữa
0 và 1 (giá trị cao hơn cho độ tương phản nhiều hơn).
skimage.transform.rotate(image,angle, resize=False,

217
center=None, order=None, mode ='constant',cval=0, clip=True,
preserve_range= False): Xoay ảnh
Hàm này có chức năng xoay ảnh đầu vào image theo một góc cho
trước.
image: ảnh đầu vào dạng mảng Numpy
angle: góc quay ngược chiều kim đồng hồ
resize: Xác định xem hình dạng của ảnh đầu ra sẽ được tự
động tính toán để ảnh xoay phù hợp chính xác. Mặc định là False.
center: Tâm xoay. Nếu center = None, ảnh sẽ được xoay
xung quanh tâm của nó, tức là center = (cols / 2 - 0.5, row / 2 - 0.5).
Chú ý tham số này là (cols, row), trái với thứ tự thông thường.
order: Thứ tự của phép nội suy spline, mặc định là 0 nếu
image.dtype là bool và 1 nếu ngược lại. Phải nằm trong khoảng 0-5
mode: {‘reflect’, ‘constant’, ‘nearest’, ‘mirror’, ‘wrap’}, mặc
định là ‘reflect’.
preserve_range: thuộc kiểu dữ liệu Bool, và có thể bỏ qua.
Tham số này nhận giá trị mặc định là False. Tham số này giúp duy trì
được khoảng dữ liệu không đổi sau khi tiến hành lọc ảnh so với ảnh
gốc.
skimage.transform.rescale(image,scale,order=None, mode'reflect',
cval=0, clip=True, preserve_range=False, multichannel=False,
anti_aliasing=None, anti_aliasing_sigma =None)
skimage.transform.resize(image, output_shape, order=None,
mode='reflect', cval=0, clip=True, preserve_range =False,
anti_aliasing=True, anti_aliasing_sigma=None)
Hàm này có chức năng thay đổi kích thước ảnh theo một kích thước

218
cho trước.
image: Ảnh đầu vào
scale: là một giá trị thuộc kiểu dữ liệu float. Tham số này xác
định tỉ lệ thay đổi của ảnh. Các hệ số tỷ lệ có thể được thay đổi theo
từng hướng dọc ngang một cách tương ứng.
multichannel: thuộc kiểu dữ liệu Bool, và có thể bỏ qua.
Tham số này nhận giá trị mặc định là None. Nếu nhận giá trị là True,
mỗi kênh sẽ được lọc riêng biệt (các kênh không bị trộn lẫn với
nhau).
anti_aliasing: thuộc kiểu dữ liệu bool, và có thể bỏ qua.
Tham số này cho phép lựa chọn có áp dụng bộ lọc Gaussian để làm
mịn ảnh trước khi giảm tỷ lệ hay không. Kỹ thuật này cho phép giảm
hiện tượng răng cưa của ảnh sau resize.
morphology.binary_dilation(image, selem, out): Hàm thực hiện
phép toán giãn nở với ảnh nhị phân
morphology.binary_erosion(image, selem, out): Hàm thực hiện
phép toán xói mòn với ảnh nhị phân
image: ảnh ngõ vào (ảnh nhị phân).
selem: mảng, tùy chọn. Vùng lân cận được biểu thị dưới dạng
mảng 2-D gồm 1 và 0. Nếu None, sử dụng phần tử cấu trúc hình chữ
thập.
out: Mảng để lưu trữ kết quả của hình thái học. Nếu được
khai báo là None,hoặc không khai báo, một mảng mới sẽ được cấp
phát để lưu kết quả trả về.

219
220
PHỤ LỤC 4: MÔ TẢ CÁC HÀM DÙNG TRONG CHƯƠNG 3

Chi tiết về các hàm chính sử dụng trong chương 3 bao gồm:

random_noise(image, ode='gaussian',  seed=None, clip=True, 
**kwargs)

Chức năng: dữ liệu trả về là một ảnh có dấu chấm động trên phạm vi
[0, 1].

image: Ảnh đầu vào kiểu ndarray. Dữ liệu ảnh đầu vào sẽ
được chuyển thành float.
mode: thuộc kiểu dữ liệu string, và có thể bỏ qua không khai
báo. Tham số này xác định các loại nhiễu và có thể nhận một trong
các giá trị sau:

● 'gaussian': Nhiễu phân bố Gaussian


● ‘localvar’: Nhiễu phân bố Gaussian, xác định phương sai
cục bộ tại mỗi điểm của ảnh
● ‘poisson’: Nhiễu phân phối Poisson được tạo ra từ dữ
liệu.
● 'salt': thay thế các pixel ngẫu nhiên bằng 1.
● 'pepper': Thay thế các pixel ngẫu nhiên bằng 0 hoặc -1
● ‘s&p’: thay thế các pixel ngẫu nhiên bằng 1 hoặc
low_val, trong đó low_val là 0 hoặc -1.
● ‘speckle’ Nhiễu đa số sử dụng out = image + n * image,
trong đó: n là nhiễu Gaussian với giá trị trung bình &
phương sai xác định.

seed: thuộc kiểu dữ liệu int, và có thể bỏ qua không khai báo.

221
Nếu được cung cấp, điều này sẽ đặt hạt giống ngẫu nhiên trước khi
tạo nhiễu, để có các so sánh giả ngẫu nhiên hợp lệ.

clip: thuộc kiểu dữ liệu bool, và có thể bỏ qua không khai


báo. Nếu nhận giá trị là True (mặc định), ngõ ra sẽ được cắt bớt sau
khi thêm nhiễu cho các chế độ "speckle", "poisson" và "gaussian".
Điều này là cần thiết để duy trì phạm vi dữ liệu ảnh thích hợp. Nếu
False, phần cắt không được thêm và ngõ ra có thể vượt ra ngoài
phạm vi [-1, 1].

mean: thuộc kiểu dữ liệu float, và có thể bỏ qua không khai


báo. Trung bình của phân phối ngẫu nhiên. Được sử dụng trong
‘gaussian’ và ‘speckle’. Mặc định: 0.

var: thuộc kiểu dữ liệu float, và có thể bỏ qua không khai báo.
Tham số này điều khiển phương sai của phân phối ngẫu nhiên được
sử dụng trong ‘gaussian’ và ‘speckle’. Lưu ý: phương sai = (độ lệch
chuẩn) ** 2. Giá trị mặc định: 0,01.

amount: thuộc kiểu dữ liệu float, và có thể bỏ qua không khai


báo. Tỷ lệ điểm ảnh cần thay thế bằng nhiễu trên phạm vi [0, 1].
Được sử dụng trong các kiểu nhiễu "salt", "pepper" và "s&p". Giá trị
mặc định của tham số: 0,05.

salt_vs_pepper: thuộc kiểu dữ liệu float, và có thể bỏ qua


không khai báo. Tỷ lệ giữa nhiễu của muối và tiêu cho 's&p' và thuộc
phạm vi [0, 1]. Giá trị cao hơn thể hiện nhiều muối hơn. Mặc định:
0,5 (số lượng hai loại nhiễu là bằng nhau)

denoise_tv_chambolle(image, weight=0.1, eps=0.0002, n_iter_max

222
= 200, multichannel = False):

Chức năng: Lọc nhiễu của ảnh bằng phương pháp giảm nhiễu tổng
phương sai trên ảnh.

image: Ảnh đầu vào kiểu ndarray với các điểm ảnh là int,
units hay float. Dữ liệu đầu vào để khử nhiễu, ảnh có thể thuộc bất kỳ
kiểu số nào.

eps: thuộc kiểu dữ liệu float, và có thể bỏ qua không khai báo.
Tham số thể hiện điều kiện dừng dựa vào sự chênh lệch tương đối
giữa hai lần lặp liên tiếp. Thuật toán dừng khi:‖ En −1− En ‖<esp E 0 .

n_iter_max: thuộc kiểu dữ liệu int, và có thể bỏ qua không


khai báo. Số lần tối đa lặp lại được sử dụng khử nhiễu

weight: thuộc kiểu dữ liệu float, và có thể bỏ qua không khai


báo. Tham số này điều khiển khả năng giảm nhiễu. Giá trị này càng
lớn, lượng nhiễu khử nhiễu càng nhiều.

multichannel: thuộc kiểu dữ liệu bool, và có thể bỏ qua


không khai báo. Áp dụng khử nhiễu tổng phương sai riêng cho từng
kênh. Tùy chọn này là “True” đối với ảnh màu, ngược lại, hiện tượng
khử nhiễu cũng được áp dụng cho chiều của kênh.

Kết quả trả về: ảnh được khử nhiễu

denoise_bilateral(image, win_size=None, sigma_color=None, sigm
a_spatial=1, bins=10000, mode= 'constant', cval=0, multichannel =
False)

Chức năng: Lọc nhiễu của ảnh bằng bộ lọc bilateral.

223
image: Ảnh đầu vào kiểu ndarray với các điểm ảnh là int,
units hay float. Dữ liệu đầu vào để khử nhiễu, ảnh có thể thuộc bất kỳ
kiểu số nào.

win_size: thuộc kiểu dữ liệu int. Kích thước cửa sổ để lọc.


Nếu win_size không được xác định, nó được tính là bằng công thức
max (5, 2 * ceil (3 * sigma_spatial) + 1).

sigma_color: thuộc kiểu dữ liệu float. Độ lệch tiêu chuẩn


trong miền không gian màu. Giá trị này càng lớn dẫn đến tính trung
bình của các điểm ảnh có sự khác biệt về bức xạ lớn hơn.

sigma_spatial: thuộc kiểu dữ liệu float. Độ lệch tiêu chuẩn


cho khoảng cách phạm vi. Giá trị càng lớn dẫn đến tính trung bình
của các pixel có sự khác biệt về không gian lớn hơn.

mode: Tham số này nhận một trong các giá trị {‘constant’,
‘edge’, ‘symmetric’, ‘reflect’, ‘wrap’}. Tham số này lực chọn cách xử
lý các giá trị ngoài viền ảnh.

cval: thuộc kiểu dữ liệu float, và có thể bỏ qua. Tham số này


nhận giá trị mặc định là 0.0. Khi mode là 'constant', tham số này là
hằng số được sử dụng trong các giá trị bên ngoài biên của các dữ liệu
ảnh

skimage.segmentation.slic (image, n_segments = 100,


compactness =10.0, max_iter=10, sigma=0, spacing
=None, multichannel =True, convert2lab
=None, enforce_connectivity= True, min_size_factor=
0.5, max_size_factor=3, slic_zero=False, start_label=None, mask

224
= None)

Chức năng: Gán nhãn cho các khối 2D hoặc 3D được phân đoạn.

image: Ảnh đầu vào kiểu ndarray với các điểm ảnh là int,
units hay float. Dữ liệu đầu vào để khử nhiễu, ảnh có thể thuộc bất kỳ
kiểu số nào.

n_segments: thuộc kiểu dữ liệu int, số lượng nhãn (gần đúng)


trong ảnh đầu ra được phân đoạn.

compactness: thuộc kiểu dữ liệu float. Cân bằng màu sắc


tiệm cận và không gian tiệm cận. Giá trị cao hơn mang lại nhiều
trọng lượng hơn cho khoảng cách không gian, làm cho hình dạng
superpixel vuông vắn.

max_iter: thuộc kiểu dữ liệu int, số lượng lặp tối đa của k-


means

sigma: thuộc kiểu dữ liệu float. Chiều rộng của hạt nhân làm
mịn Gaussian để xử lý trước cho từng kích thước của ảnh. Sigma
giống nhau được áp dụng cho mỗi thứ nguyên trong trường hợp giá
trị vô hướng. None có nghĩa là không làm mịn. Lưu ý, sigma đó sẽ tự
động được chia tỷ lệ nếu nó là vô hướng và khoảng cách voxel thủ
công được cung cấp.

Spacing: Khoảng cách voxel dọc theo mỗi kích thước ảnh.
Theo mặc định, lát cắt giả định khoảng cách đồng nhất (cùng độ phân
giải voxel dọc theo z, y và x). Tham số này kiểm soát trọng số của
các khoảng cách dọc theo z, y và x trong quá trình phân cụm k-mean.

Multichannel: thuộc kiểu dữ liệu bool, ảnh có được hiểu là

225
nhiều kênh hay một chiều không gian khác hay không.

convert2lab: thuộc kiểu dữ liệu bool, đầu vào có được


chuyển đổi sang không gian màu Lab trước khi phân đoạn hay không.
Ảnh đầu vào phải là RGB trước khi chuyển sang Lab Tùy chọn này
mặc định là True khi đa kênh = True và image.shape [-1] == 3.

enforce_connectivity: thuộc kiểu dữ liệu bool, Các phân


đoạn đã tạo có được kết nối hay không

min_size_factor: thuộc kiểu dữ liệu float, tỷ lệ giữa kích


thước phân đoạn tối thiểu sẽ bị xóa đối với kích thước phân đoạn.

max_size_factor: thuộc kiểu dữ liệu bool, tỷ lệ kích thước


phân đoạn được kết nối tối đa. Thông thường tham số này nên để là
3.

start_label: thuộc kiểu dữ liệu int, chỉ mục của label bắt đầu.
Nên là 0 hoặc 1.

mask: mảng 2 chiều, Nếu được cung cấp, superpixel chỉ được
tính khi mask là True và các điểm hạt giống được phân phối đồng
nhất trên mặt nạ bằng cách sử dụng chiến lược phân cụm K-mean.

label2rgb(label, image, colors, alpha=0.3, bg_label =-1, bg_color=


(0,0,0), image_alpha=1,kind= 'overlay')

Chức năng: Trả về một ảnh RGB là kết quả của việc trộn ảnh màu
gốc với một ảnh chứa thông tin nhãn của từng điểm ảnh theo một tỷ
lệ trộn màu nhất định.

label: Mảng số nguyên của các nhãn có cùng hình dạng với

226
‘image’.

image: Ảnh được sử dụng làm lớp phủ cho nhãn. Nếu đầu vào
là ảnh RGB, nó sẽ được chuyển đổi thành ảnh xám.

colors: Danh sách các màu được sử dụng để hiển thị các nhãn.
Nếu số lượng nhãn vượt quá số màu, thì các màu sẽ lặp lại theo chu
kỳ.

alpha: Độ mờ của nhãn được tô màu. Bỏ qua nếu ảnh là


‘None’.

bg_label: Tham số này dùng để xác định nhãn nào được định
nghĩa là khung nền. Nếu tham số ‘bg_label’ và ‘bg_color’ được xác
định lần lượt là ‘None’; và tham số ‘kind’ được xác định là ‘overlay’
thì nền không được sơn bởi bất kỳ màu nào.

bg_color: Tham số này dùng để xác định màu sắc của khung
nền.

image_alpha: Độ mờ của ảnh.

kind: Tham số này xác định cách thức tô màu cho từng nhãn.
Nếu khai báo là ‘overlay ', các màu sẽ xoay vòng theo chu kỳ và phủ
các nhãn màu lên ảnh gốc. Nếu khai báo là ‘avg’, hàm sẽ thay thế
mỗi phân đoạn được dán nhãn bằng màu trung bình của nó.

find_coutours(image, level, full_connected, positive_orientation,


mask)

Chức năng: Tìm các đường bao một mảng 2D cho một giá trị level
nhất định

227
image: Ảnh đầu vào kiểu ndarray với các điểm ảnh là int,
units hay float. Dữ liệu đầu vào để khử nhiễu, ảnh có thể thuộc bất kỳ
kiểu số nào.

level: thuộc kiểu dữ liệu float, và có thể bỏ qua không khai


báo. Tham số này dùng để tìm các đường bao trong mảng. Theo mặc
định, mức được đặt thành (tối đa (ảnh) + tối thiểu (ảnh)) / 2. Nếu đặt
level lớn hơn mức cho phép thì sẽ không có đường viền.

fully_connected: nhận các giá trị {‘low’,’high’}. Tham số


này cho biết liệu các phần tử mảng dưới giá trị mức đã cho có được
coi là được kết nối đầy đủ hay không (và do đó các phần tử trên giá
trị sẽ chỉ được kết nối mặt) hay ngược lại

positive_orientation nhận các giá trị {‘low’,’high’}. Tham số


này cho biết liệu các đường bao đầu ra sẽ tạo ra các đa giác hướng
dương xung quanh các đảo của các phần tử có giá trị thấp hay giá trị
cao. Nếu "low" thì các đường bao sẽ xoay ngược chiều kim đồng hồ
xung quanh các phần tử bên dưới giá trị iso, có nghĩa là các phần tử
có giá trị thấp luôn ở bên trái của đường bao.

mask: bool, True là khi ta muốn vẽ các đường viền. Lưu ý


rằng các giá trị NaN luôn bị loại trừ khỏi vùng được xem xét (mặt nạ
được đặt thành False bất cứ nơi nào mảng là NaN).

228

You might also like