Cách áp dụng thị giác máy tính để xây dựng bộ lọc chó dựa trên cảm xúc trong Python 3
Thị giác máy tính là một lĩnh vực con của khoa học máy tính nhằm mục đích rút ra hiểu biết cấp cao hơn từ hình ảnh và video. Trường này bao gồm các việc như phát hiện đối tượng, khôi phục hình ảnh (hoàn thành ma trận) và stream quang học. Thị giác máy tính hỗ trợ các công nghệ như nguyên mẫu ô tô tự lái, cửa hàng tạp hóa ít nhân viên, bộ lọc Snapchat vui nhộn và trình xác thực khuôn mặt trên thiết bị di động của bạn.Trong hướng dẫn này, bạn sẽ khám phá tầm nhìn máy tính khi bạn sử dụng các mô hình được đào tạo trước để xây dựng bộ lọc Snapchat-esque dog. Đối với những người không quen với Snapchat, bộ lọc này sẽ phát hiện khuôn mặt của bạn và sau đó chồng mặt nạ chó lên đó. Sau đó, bạn sẽ đào tạo một bộ phân loại cảm xúc khuôn mặt để bộ lọc có thể chọn mặt nạ cho chó dựa trên cảm xúc, chẳng hạn như một chú chó corgi khi vui vẻ hoặc một chú pug khi buồn. Trên đường đi, bạn cũng sẽ khám phá các khái niệm liên quan trong cả hình vuông nhỏ nhất thông thường và thị giác máy tính, điều này sẽ giúp bạn tiếp cận với các nguyên tắc cơ bản của học máy.
Khi bạn làm qua hướng dẫn này, bạn sẽ sử dụng OpenCV
, một thư viện thị giác máy tính, numpy
cho các tiện ích đại số tuyến tính và matplotlib
để vẽ đồ thị. Bạn cũng sẽ áp dụng các khái niệm sau khi xây dựng ứng dụng máy tính nhìn ra:
- Bình phương nhỏ nhất thông thường như một kỹ thuật hồi quy và phân loại.
- Những điều cơ bản về mạng nơ ron gradient ngẫu nhiên.
Mặc dù không cần thiết để hoàn thành hướng dẫn này, nhưng bạn sẽ thấy dễ dàng hơn khi hiểu một số giải thích chi tiết hơn nếu bạn quen thuộc với các khái niệm toán học này:
- Các khái niệm đại số tuyến tính cơ bản: đại lượng vô hướng, vectơ và ma trận.
- Giải tích cơ bản: cách lấy đạo hàm.
Bạn có thể tìm thấy mã hoàn chỉnh cho hướng dẫn này tại https://github.com/do-community/emotion-based-dog-filter .
Bắt đầu nào.
Yêu cầu
Để hoàn thành hướng dẫn này, bạn cần những thứ sau:
- Môi trường phát triển local cho Python 3 với ít nhất 1GB RAM. Bạn có thể làm theo Cách cài đặt và cài đặt môi trường lập trình local cho Python 3 để cấu hình mọi thứ bạn cần.
- Một webcam hoạt động để phát hiện hình ảnh theo thời gian thực.
Bước 1 - Tạo dự án và cài đặt phụ thuộc
Hãy tạo không gian làm việc cho dự án này và cài đặt các phụ thuộc mà ta cần. Ta sẽ gọi không gian làm việc của bạn là DogFilter
:
- mkdir ~/DogFilter
Điều hướng đến folder DogFilter
:
- cd ~/DogFilter
Sau đó, tạo một môi trường ảo Python mới cho dự án:
- python3 -m venv dogfilter
Kích hoạt môi trường của bạn.
- source dogfilter/bin/activate
Dấu nhắc thay đổi, cho biết môi trường đang hoạt động. Bây giờ hãy cài đặt PyTorch , một khuôn khổ học sâu cho Python mà ta sẽ sử dụng trong hướng dẫn này. Quá trình cài đặt phụ thuộc vào hệ điều hành bạn đang sử dụng.
Trên macOS, cài đặt Pytorch bằng lệnh sau:
- python -m pip install torch==0.4.1 torchvision==0.2.1
Trên Linux, sử dụng các lệnh sau:
- pip install http://download.pytorch.org/whl/cpu/torch-0.4.1-cp35-cp35m-linux_x86_64.whl
- pip install torchvision
Và đối với Windows, hãy cài đặt Pytorch bằng các lệnh sau:
- pip install http://download.pytorch.org/whl/cpu/torch-0.4.1-cp35-cp35m-win_amd64.whl
- pip install torchvision
Bây giờ hãy cài đặt các file binary đóng gói sẵn cho OpenCV
và numpy
, lần lượt là các thư viện thị giác máy tính và đại số tuyến tính. Cái trước cung cấp các tiện ích như phép quay hình ảnh, và cái sau cung cấp các tiện ích đại số tuyến tính như đảo ngược ma trận.
- python -m pip install opencv-python==3.4.3.18 numpy==1.14.5
Cuối cùng, tạo một folder cho nội dung của ta , folder này sẽ chứa các hình ảnh mà ta sẽ sử dụng trong hướng dẫn này:
- mkdir assets
Với các phần phụ thuộc được cài đặt, hãy xây dựng version đầu tiên của bộ lọc của ta : máy dò khuôn mặt.
Bước 2 - Xây dựng máy dò tìm khuôn mặt
Mục tiêu đầu tiên của ta là phát hiện tất cả các khuôn mặt trong một hình ảnh. Ta sẽ tạo một tập lệnh chấp nhận một hình ảnh duy nhất và xuất ra một hình ảnh có chú thích với các khuôn mặt được viền bằng các hộp.
May mắn là thay vì viết logic nhận diện khuôn mặt của riêng mình, ta có thể sử dụng các mô hình được đào tạo trước . Ta sẽ cài đặt một mô hình và sau đó tải các thông số được đào tạo trước. OpenCV làm cho việc này trở nên dễ dàng bằng cách cung cấp cả hai.
OpenCV cung cấp các tham số mô hình trong mã nguồn của nó. nhưng ta cần đường dẫn tuyệt đối đến OpenCV được cài đặt local để sử dụng các tham số này. Kể từ đó đường dẫn tuyệt đối có thể thay đổi, ta sẽ tải về bản sao của chính ta thay vào đó và đặt nó trong assets
folder :
- wget -O assets/haarcascade_frontalface_default.xml https://github.com/opencv/opencv/raw/master/data/haarcascades/haarcascade_frontalface_default.xml
Tùy chọn -O
chỉ định đích là assets/haarcascade_frontalface_default.xml
. Đối số thứ hai là URL nguồn.
Ta sẽ phát hiện tất cả các khuôn mặt trong hình ảnh sau đây từ Pexels (CC0, liên kết đến hình ảnh root ).
Đầu tiên, tải hình ảnh xuống. Lệnh sau lưu ảnh tải về như children.png
trong assets
folder :
- wget -O assets/children.png /image_static/mg_sid_64/6b5f/5f53/6b5f5f535a535b/6b5f5f535a535b.png
Để kiểm tra xem thuật toán phát hiện có hoạt động hay không, ta sẽ chạy nó trên một hình ảnh riêng lẻ và lưu hình ảnh có chú thích kết quả vào đĩa. Tạo một folder outputs
cho các kết quả được chú thích này.
- mkdir outputs
Bây giờ, hãy tạo một tập lệnh Python cho bộ phát hiện khuôn mặt. Tạo file step_1_face_detect
bằng nano
hoặc editor yêu thích của bạn:
- nano step_2_face_detect.py
Thêm mã sau vào file . Mã này nhập OpenCV, chứa các tiện ích hình ảnh và bộ phân loại khuôn mặt. Phần còn lại của mã là bản soạn sẵn chương trình Python điển hình.
"""Test for face detection""" import cv2 def main(): pass if __name__ == '__main__': main()
Bây giờ thay thế pass
trong main
chức năng với mã này mà khởi tạo một phân loại mặt sử dụng các thông số OpenCV bạn đã tải về để bạn assets
folder :
def main(): # initialize front face classifier cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")
Tiếp theo, thêm dòng này để tải hình ảnh children.png
.
frame = cv2.imread('assets/children.png')
Sau đó, thêm mã này để chuyển đổi hình ảnh sang màu đen và trắng, vì bộ phân loại đã được đào tạo về hình ảnh đen trắng. Để thực hiện điều này, ta chuyển đổi sang thang độ xám và sau đó tùy chỉnh biểu đồ:
# Convert to black-and-white gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blackwhite = cv2.equalizeHist(gray)
Sau đó, sử dụng chức năng detectMultiScale
của OpenCV để phát hiện tất cả các khuôn mặt trong ảnh.
rects = cascade.detectMultiScale( blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE)
scaleFactor
chỉ định mức độ giảm hình ảnh dọc theo mỗi kích thước.-
minNeighbors
biểu thị có bao nhiêu hình chữ nhật lân cận mà một hình chữ nhật ứng viên cần được giữ lại. -
minSize
là kích thước đối tượng được phát hiện tối thiểu cho phép. Các đối tượng nhỏ hơn này sẽ bị loại bỏ.
Kiểu trả về là danh sách các bộ giá trị , trong đó mỗi bộ giá trị có bốn số biểu thị x tối thiểu, y tối thiểu, chiều rộng và chiều cao của hình chữ nhật theo thứ tự đó.
Lặp lại tất cả các đối tượng được phát hiện và vẽ chúng trên hình ảnh có màu xanh lá cây bằng cách sử dụng cv2.rectangle
:
for x, y, w, h in rects: cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
- Đối số thứ hai và thứ ba là các góc đối lập của hình chữ nhật.
- Đối số thứ tư là màu sắc để sử dụng.
(0, 255, 0)
tương ứng với màu xanh lục cho không gian màu RGB của ta . - Đối số cuối cùng biểu thị chiều rộng của dòng của ta .
Cuối cùng, ghi hình ảnh với các hộp giới hạn vào một file mới tại outputs/children_detected.png
:
cv2.imwrite('outputs/children_detected.png', frame)
Tập lệnh đã hoàn thành của bạn sẽ trông như thế này:
"""Tests face detection for a static image.""" import cv2 def main(): # initialize front face classifier cascade = cv2.CascadeClassifier( "assets/haarcascade_frontalface_default.xml") frame = cv2.imread('assets/children.png') # Convert to black-and-white gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blackwhite = cv2.equalizeHist(gray) rects = cascade.detectMultiScale( blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) for x, y, w, h in rects: cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imwrite('outputs/children_detected.png', frame) if __name__ == '__main__': main()
Lưu file và thoát khỏi editor . Sau đó chạy script:
- python step_2_face_detect.py
Mở outputs/children_detected.png
. Bạn sẽ thấy hình ảnh sau cho thấy các khuôn mặt được viền bằng các hộp:
Đến đây, bạn có một máy dò khuôn mặt đang hoạt động. Nó chấp nhận một hình ảnh làm đầu vào và vẽ các hộp bao quanh tất cả các mặt trong hình ảnh, xuất ra hình ảnh có chú thích. Bây giờ, hãy áp dụng cùng một phát hiện này cho nguồn cấp dữ liệu camera trực tiếp.
Bước 3 - Liên kết nguồn cấp máy ảnh
Mục tiêu tiếp theo là liên kết camera của máy tính với bộ dò tìm khuôn mặt. Thay vì phát hiện khuôn mặt trong một hình ảnh tĩnh, bạn sẽ phát hiện tất cả các khuôn mặt từ máy ảnh của máy tính. Bạn sẽ thu thập thông tin đầu vào của máy ảnh, phát hiện và ghi chú thích tất cả các khuôn mặt, sau đó hiển thị lại hình ảnh được chú thích cho user . Bạn sẽ tiếp tục từ tập lệnh trong Bước 2, vì vậy hãy bắt đầu bằng cách sao chép tập lệnh đó:
- cp step_2_face_detect.py step_3_camera_face_detect.py
Sau đó, mở tập lệnh mới trong editor :
- nano step_3_camera_face_detect.py
Bạn sẽ cập nhật chức năng main
bằng cách sử dụng một số phần tử từ tập lệnh thử nghiệm này từ tài liệu OpenCV chính thức. Bắt đầu bằng cách khởi tạo đối tượng VideoCapture
được cài đặt để chụp nguồn cấp dữ liệu trực tiếp từ máy ảnh của máy tính của bạn. Đặt mã này ở đầu hàm main
, trước mã khác trong hàm:
def main(): cap = cv2.VideoCapture(0) ...
Bắt đầu từ frame
xác định dòng, hãy thụt lề tất cả mã hiện có của bạn, đặt tất cả mã trong một vòng lặp while
.
while True: frame = cv2.imread('assets/children.png') ... for x, y, w, h in rects: cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imwrite('outputs/children_detected.png', frame)
Thay thế dòng định frame
vào lúc bắt đầu của while
vòng lặp. Thay vì đọc từ một hình ảnh trên đĩa, bây giờ bạn đang đọc từ máy ảnh:
while True: # frame = cv2.imread('assets/children.png') # DELETE ME # Capture frame-by-frame ret, frame = cap.read()
Thay thế dòng cv2.imwrite(...)
ở phần cuối của while
vòng lặp. Thay vì ghi hình ảnh vào đĩa, bạn sẽ hiển thị hình ảnh được chú thích trở lại màn hình của user :
cv2.imwrite('outputs/children_detected.png', frame) # DELETE ME # Display the resulting frame cv2.imshow('frame', frame)
Ngoài ra, hãy thêm một số mã để theo dõi nhập liệu bằng bàn phím để bạn có thể dừng chương trình. Kiểm tra xem user có nhấn vào ký tự q
hay không và nếu có, hãy thoát khỏi ứng dụng. Ngay sau cv2.imshow(...)
thêm phần sau:
... cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break ...
Dòng cv2.waitkey(1)
tạm dừng chương trình trong 1 mili giây để hình ảnh đã chụp có thể hiển thị lại cho user .
Cuối cùng, giải phóng chụp và đóng tất cả các cửa sổ. Đặt bên ngoài này của while
vòng lặp để chấm dứt main
chức năng.
... while True: ... cap.release() cv2.destroyAllWindows()
Tập lệnh của bạn sẽ giống như sau:
"""Test for face detection on video camera. Move your face around and a green box will identify your face. With the test frame in focus, hit `q` to exit. Note that typing `q` into your terminal will do nothing. """ import cv2 def main(): cap = cv2.VideoCapture(0) # initialize front face classifier cascade = cv2.CascadeClassifier( "assets/haarcascade_frontalface_default.xml") while True: # Capture frame-by-frame ret, frame = cap.read() # Convert to black-and-white gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blackwhite = cv2.equalizeHist(gray) # Detect faces rects = cascade.detectMultiScale( blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) # Add all bounding boxes to the image for x, y, w, h in rects: cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) # Display the resulting frame cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break # When everything done, release the capture cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main()
Lưu file và thoát khỏi editor .
Bây giờ hãy chạy tập lệnh thử nghiệm.
- python step_3_camera_face_detect.py
Thao tác này sẽ kích hoạt máy ảnh của bạn và mở ra một cửa sổ hiển thị nguồn cấp dữ liệu máy ảnh của bạn. Khuôn mặt của bạn sẽ được đóng hộp bởi một hình vuông màu xanh lá cây trong thời gian thực:
Lưu ý : Nếu bạn thấy rằng bạn phải giữ yên để mọi thứ hoạt động, thì có thể ánh sáng trong phòng không đủ. Hãy thử chuyển đến một căn phòng có ánh sáng rực rỡ, nơi bạn và bối cảnh của bạn có giới hạn cao. Ngoài ra, tránh đèn sáng gần đầu của bạn. Ví dụ, nếu bạn quay lưng lại với ánh nắng mặt trời, quá trình này có thể không hoạt động tốt.
Mục tiêu tiếp theo của ta là lấy các khuôn mặt được phát hiện và chồng mặt nạ chó lên mỗi khuôn mặt.
Bước 4 - Xây dựng Bộ lọc Chó
Trước khi ta tự tạo bộ lọc, hãy khám phá cách hình ảnh được biểu thị bằng số. Điều này sẽ cung cấp cho bạn nền cần thiết để sửa đổi hình ảnh và cuối cùng là áp dụng bộ lọc chó.
Hãy xem một ví dụ. Ta có thể dựng một hình ảnh đen trắng bằng cách sử dụng các con số, trong đó 0
tương ứng với đen và 1
tương ứng với trắng.
Tập trung vào đường phân cách giữa số 1 và số 0. Bạn thấy hình dạng gì?
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
Hình ảnh là một viên kim cương. Nếu lưu ma trận giá trị này dưới dạng hình ảnh. Điều này cho ta hình ảnh sau:
Ta có thể sử dụng bất kỳ giá trị nào từ 0 đến 1, chẳng hạn như 0,1, 0,26 hoặc 0,74391. Các số gần 0 đậm hơn và các số gần 1 nhạt hơn. Điều này cho phép ta thể hiện màu trắng, đen và bất kỳ bóng nào của màu xám. Đây là một tin tuyệt vời đối với ta vì giờ đây ta có thể tạo bất kỳ hình ảnh thang độ xám nào sử dụng 0, 1 và bất kỳ giá trị nào ở giữa. Hãy xem xét những điều sau đây, chẳng hạn. bạn có thể nói đó là gì? , mỗi số tương ứng với màu của pixel.
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 0 0 .4 .4 .4 .4 0 0 1 1 1 0 .4 .4 .5 .4 .4 .4 .4 .4 0 1 1 0 .4 .5 .5 .5 .4 .4 .4 .4 0 1 0 .4 .4 .4 .5 .4 .4 .4 .4 .4 .4 0 0 .4 .4 .4 .4 0 0 .4 .4 .4 .4 0 0 0 .4 .4 0 1 .7 0 .4 .4 0 0 0 1 0 0 0 .7 .7 0 0 0 1 0 1 0 1 1 1 0 0 .7 .7 .4 0 1 1 0 .7 1 1 1 .7 .7 .7 .7 0 1 1 1 0 0 .7 .7 .7 .7 0 0 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Được hiển thị lại dưới dạng hình ảnh, bây giờ bạn có thể biết rằng đây thực chất là Quả bóng Poké:
Đến đây bạn đã thấy hình ảnh đen trắng và thang độ xám được thể hiện bằng số như thế nào. Để giới thiệu màu sắc, ta cần một cách để mã hóa thêm thông tin. Hình ảnh có chiều cao và chiều rộng được biểu thị bằng hxw
.
Trong biểu diễn thang độ xám hiện tại, mỗi pixel là một giá trị từ 0 đến 1. Ta có thể nói một cách tương đương hình ảnh của ta có kích thước hxwx 1
. Nói cách khác, mọi vị trí (x, y)
trong hình ảnh của ta chỉ có một giá trị.
Đối với biểu diễn màu, ta biểu thị màu của mỗi pixel bằng cách sử dụng ba giá trị từ 0 đến 1. Một số tương ứng với “độ đỏ”, một số tương ứng với “độ xanh” và số cuối cùng là “độ xanh lam. ” Ta gọi đây là không gian màu RGB . Điều này nghĩa là với mọi vị trí (x, y)
trong hình ảnh của ta , ta có ba giá trị (r, g, b)
. Kết quả là, hình ảnh của ta bây giờ là hxwx 3
:
Ở đây, mỗi số nằm trong repository ảng từ 0 đến 255 thay vì 0 đến 1, nhưng ý tưởng là giống nhau. Các kết hợp số khác nhau tương ứng với các màu khác nhau, chẳng hạn như màu tím đậm (102, 0, 204)
hoặc màu cam sáng (255, 153, 51)
. Những điểm cần rút ra như sau:
- Mỗi hình ảnh sẽ được biểu diễn dưới dạng một hộp số có ba kích thước: chiều cao, chiều rộng và kênh màu. Thao tác trực tiếp với ô số này tương đương với thao tác trên ảnh.
- Ta cũng có thể làm phẳng hộp này để chỉ trở thành một danh sách các số. Bằng cách này, hình ảnh của ta trở thành một vector . Sau đó, ta sẽ đề cập đến hình ảnh dưới dạng vectơ.
Đến đây bạn đã hiểu cách hình ảnh được biểu thị bằng số, bạn đã được trang bị đầy đủ để bắt đầu áp dụng mặt nạ chó lên khuôn mặt. Để áp dụng mặt nạ chó, bạn sẽ thay thế các giá trị trong hình ảnh con bằng các pixel mặt nạ chó không phải màu trắng. Để bắt đầu, bạn sẽ làm việc với một hình ảnh duy nhất. Download hình cắt khuôn mặt này từ hình ảnh bạn đã sử dụng ở Bước 2.
- wget -O assets/child.png /image_static/mg_sid_64/6652/525e/6652525e575e5e/6652525e575e5e.png
Ngoài ra, hãy download mặt nạ chó sau đây. Mặt nạ chó được sử dụng trong hướng dẫn này là bản vẽ của chính tôi, hiện đã được phát hành cho domain công cộng theo Giấy phép CC0.
Download cái này với wget
:
- wget -O assets/dog.png /image_static/mg_sid_64/4175/7579/41757579707978/41757579707978.png
Tạo một file mới có tên step_4_dog_mask_simple.py
sẽ chứa mã cho tập lệnh áp dụng mặt nạ chó cho các khuôn mặt:
- nano step_4_dog_mask_simple.py
Thêm bảng soạn sau cho tập lệnh Python và nhập thư viện OpenCV và thư viện numpy
:
"""Test for adding dog mask""" import cv2 import numpy as np def main(): pass if __name__ == '__main__': main()
Thay thế pass
trong hàm main
bằng hai dòng này để tải hình ảnh root và mặt nạ chó vào bộ nhớ.
... def main(): face = cv2.imread('assets/child.png') mask = cv2.imread('assets/dog.png')
Tiếp theo, lắp mặt nạ chó cho trẻ. Logic phức tạp hơn những gì ta đã làm trước đây, vì vậy ta sẽ tạo một hàm mới có tên apply_mask
để module hóa mã của ta . Ngay sau hai dòng tải hình ảnh, thêm dòng này gọi hàm apply_mask
:
... face_with_mask = apply_mask(face, mask)
Tạo một hàm mới có tên apply_mask
và đặt nó bên trên hàm main
:
... def apply_mask(face: np.array, mask: np.array) -> np.array: """Add the mask to the provided face, and return the face with mask.""" pass def main(): ...
Đến đây, file của bạn sẽ giống như sau:
"""Test for adding dog mask""" import cv2 import numpy as np def apply_mask(face: np.array, mask: np.array) -> np.array: """Add the mask to the provided face, and return the face with mask.""" pass def main(): face = cv2.imread('assets/child.png') mask = cv2.imread('assets/dog.png') face_with_mask = apply_mask(face, mask) if __name__ == '__main__': main()
Hãy xây dựng hàm apply_mask
. Mục tiêu của ta là đắp mặt nạ lên mặt đứa trẻ. Tuy nhiên, ta cần duy trì tỷ lệ khung hình cho mặt nạ chó của bạn . Để làm như vậy, ta cần tính toán rõ ràng kích thước cuối cùng của mặt nạ cho chó của bạn . Bên trong hàm apply_mask
, hãy thay thế pass
bằng hai dòng sau để extract chiều cao và chiều rộng của cả hai hình ảnh:
... mask_h, mask_w, _ = mask.shape face_h, face_w, _ = face.shape
Tiếp theo, xác định thứ nguyên nào cần được “thu nhỏ nhiều hơn”. Nói một cách chính xác, ta cần thắt chặt hơn hai ràng buộc. Thêm dòng này vào hàm apply_mask
:
... # Resize the mask to fit on face factor = min(face_h / mask_h, face_w / mask_w)
Sau đó, tính toán hình dạng mới bằng cách thêm mã này vào hàm:
... new_mask_w = int(factor * mask_w) new_mask_h = int(factor * mask_h) new_mask_shape = (new_mask_w, new_mask_h)
Ở đây ta chuyển các số thành số nguyên, vì hàm resize
cần các kích thước tích phân.
Bây giờ thêm mã này để thay đổi kích thước mặt nạ chó thành hình dạng mới:
... # Add mask to face - ensure mask is centered resized_mask = cv2.resize(mask, new_mask_shape)
Cuối cùng, ghi hình ảnh vào đĩa để bạn có thể kiểm tra lại xem mặt nạ con chó đã thay đổi kích thước của bạn có đúng không sau khi bạn chạy tập lệnh:
cv2.imwrite('outputs/resized_dog.png', resized_mask)
Tập lệnh đã hoàn thành sẽ trông như thế này:
"""Test for adding dog mask""" import cv2 import numpy as np def apply_mask(face: np.array, mask: np.array) -> np.array: """Add the mask to the provided face, and return the face with mask.""" mask_h, mask_w, _ = mask.shape face_h, face_w, _ = face.shape # Resize the mask to fit on face factor = min(face_h / mask_h, face_w / mask_w) new_mask_w = int(factor * mask_w) new_mask_h = int(factor * mask_h) new_mask_shape = (new_mask_w, new_mask_h) # Add mask to face - ensure mask is centered resized_mask = cv2.resize(mask, new_mask_shape) cv2.imwrite('outputs/resized_dog.png', resized_mask) def main(): face = cv2.imread('assets/child.png') mask = cv2.imread('assets/dog.png') face_with_mask = apply_mask(face, mask) if __name__ == '__main__': main()
Lưu file và thoát khỏi editor . Chạy tập lệnh mới:
- python step_4_dog_mask_simple.py
Mở hình ảnh tại outputs/resized_dog.png
để kiểm tra kỹ mặt nạ đã được thay đổi kích thước chính xác chưa. Nó sẽ trùng với mặt nạ chó được hiển thị trước đó trong phần này.
Bây giờ thêm mặt nạ con chó cho đứa trẻ. Mở lại file step_4_dog_mask_simple.py
và quay lại hàm apply_mask
:
- nano step_4_dog_mask_simple.py
Đầu tiên, xóa dòng mã ghi mặt nạ đã thay đổi kích thước khỏi hàm apply_mask
vì bạn không cần nó nữa:
cv2.imwrite('outputs/resized_dog.png', resized_mask) # delete this line ...
Thay vào đó, hãy áp dụng kiến thức của bạn về biểu diễn hình ảnh từ đầu phần này để sửa đổi hình ảnh. Bắt đầu bằng cách tạo một bản sao của hình ảnh con. Thêm dòng này vào hàm apply_mask
:
... face_with_mask = face.copy()
Tiếp theo, tìm tất cả các vị trí mà mặt nạ chó không có màu trắng hoặc gần màu trắng. Để thực hiện việc này, hãy kiểm tra xem giá trị pixel có nhỏ hơn 250 trên tất cả các kênh màu hay không, vì ta mong đợi một pixel gần trắng gần [255, 255, 255]
. Thêm mã này:
... non_white_pixels = (resized_mask < 250).all(axis=2)
Đến đây, hình ảnh con chó lớn nhất bằng hình ảnh đứa trẻ. Ta muốn căn giữa hình ảnh con chó trên khuôn mặt, vì vậy hãy tính toán độ lệch cần thiết để căn giữa hình ảnh con chó bằng cách thêm mã này vào apply_mask
:
... off_h = int((face_h - new_mask_h) / 2) off_w = int((face_w - new_mask_w) / 2)
Sao chép tất cả các pixel không phải màu trắng từ hình ảnh con chó vào hình ảnh con. Vì hình ảnh con có thể lớn hơn hình ảnh con chó, ta cần lấy một tập hợp con của hình ảnh con:
face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \ resized_mask[non_white_pixels]
Sau đó trả về kết quả:
return face_with_mask
Trong hàm main
, hãy thêm mã này để ghi kết quả của hàm apply_mask
vào hình ảnh kết quả để bạn có thể kiểm tra lại kết quả theo cách thủ công:
... face_with_mask = apply_mask(face, mask) cv2.imwrite('outputs/child_with_dog_mask.png', face_with_mask)
Tập lệnh đã hoàn thành của bạn sẽ giống như sau:
"""Test for adding dog mask""" import cv2 import numpy as np def apply_mask(face: np.array, mask: np.array) -> np.array: """Add the mask to the provided face, and return the face with mask.""" mask_h, mask_w, _ = mask.shape face_h, face_w, _ = face.shape # Resize the mask to fit on face factor = min(face_h / mask_h, face_w / mask_w) new_mask_w = int(factor * mask_w) new_mask_h = int(factor * mask_h) new_mask_shape = (new_mask_w, new_mask_h) resized_mask = cv2.resize(mask, new_mask_shape) # Add mask to face - ensure mask is centered face_with_mask = face.copy() non_white_pixels = (resized_mask < 250).all(axis=2) off_h = int((face_h - new_mask_h) / 2) off_w = int((face_w - new_mask_w) / 2) face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \ resized_mask[non_white_pixels] return face_with_mask def main(): face = cv2.imread('assets/child.png') mask = cv2.imread('assets/dog.png') face_with_mask = apply_mask(face, mask) cv2.imwrite('outputs/child_with_dog_mask.png', face_with_mask) if __name__ == '__main__': main()
Lưu tập lệnh và chạy nó:
- python step_4_dog_mask_simple.py
Bạn sẽ có hình ảnh sau của một đứa trẻ với mặt nạ con chó trong outputs/child_with_dog_mask.png
:
Đến đây bạn có một tiện ích áp dụng mặt nạ chó cho khuôn mặt. Bây giờ, hãy sử dụng những gì bạn đã tạo để thêm mặt nạ cho chó trong thời gian thực.
Ta sẽ tiếp tục từ nơi ta đã dừng lại ở Bước 3. Sao chép step_3_camera_face_detect.py
sang step_4_dog_mask.py
.
- cp step_3_camera_face_detect.py step_4_dog_mask.py
Mở tập lệnh mới của bạn.
- nano step_4_dog_mask.py
Đầu tiên, nhập thư viện NumPy ở đầu tập lệnh:
import numpy as np ...
Sau đó, thêm hàm apply_mask
từ tác phẩm trước của bạn vào file mới này bên trên hàm main
:
def apply_mask(face: np.array, mask: np.array) -> np.array: """Add the mask to the provided face, and return the face with mask.""" mask_h, mask_w, _ = mask.shape face_h, face_w, _ = face.shape # Resize the mask to fit on face factor = min(face_h / mask_h, face_w / mask_w) new_mask_w = int(factor * mask_w) new_mask_h = int(factor * mask_h) new_mask_shape = (new_mask_w, new_mask_h) resized_mask = cv2.resize(mask, new_mask_shape) # Add mask to face - ensure mask is centered face_with_mask = face.copy() non_white_pixels = (resized_mask < 250).all(axis=2) off_h = int((face_h - new_mask_h) / 2) off_w = int((face_w - new_mask_w) / 2) face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \ resized_mask[non_white_pixels] return face_with_mask ...
Thứ hai, định vị dòng này trong hàm main
:
cap = cv2.VideoCapture(0)
Thêm mã này sau dòng đó để tải mặt nạ chó:
cap = cv2.VideoCapture(0) # load mask mask = cv2.imread('assets/dog.png') ...
Tiếp theo, trong while
vòng lặp, xác định vị trí dòng này:
ret, frame = cap.read()
Thêm dòng này sau nó để extract chiều cao và chiều rộng của hình ảnh:
ret, frame = cap.read() frame_h, frame_w, _ = frame.shape ...
Tiếp theo, xóa dòng trong main
vẽ các hộp giới hạn. Bạn sẽ tìm thấy dòng này trong vòng lặp for
lặp qua các khuôn mặt được phát hiện:
for x, y, w, h in rects: ... cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) # DELETE ME ...
Vào vị trí của nó, hãy thêm mã này để cắt khung. Vì mục đích thẩm mỹ, ta cắt một khu vực lớn hơn một chút so với khuôn mặt.
for x, y, w, h in rects: # crop a frame slightly larger than the face y0, y1 = int(y - 0.25*h), int(y + 0.75*h) x0, x1 = x, x + w
Kiểm tra trong trường hợp khuôn mặt được phát hiện quá gần với cạnh.
# give up if the cropped frame would be out-of-bounds if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h: continue
Cuối cùng, chèn khuôn mặt có mặt nạ vào ảnh.
# apply mask frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)
Xác minh tập lệnh của bạn trông giống như sau:
"""Real-time dog filter Move your face around and a dog filter will be applied to your face if it is not out-of-bounds. With the test frame in focus, hit `q` to exit. Note that typing `q` into your terminal will do nothing. """ import numpy as np import cv2 def apply_mask(face: np.array, mask: np.array) -> np.array: """Add the mask to the provided face, and return the face with mask.""" mask_h, mask_w, _ = mask.shape face_h, face_w, _ = face.shape # Resize the mask to fit on face factor = min(face_h / mask_h, face_w / mask_w) new_mask_w = int(factor * mask_w) new_mask_h = int(factor * mask_h) new_mask_shape = (new_mask_w, new_mask_h) resized_mask = cv2.resize(mask, new_mask_shape) # Add mask to face - ensure mask is centered face_with_mask = face.copy() non_white_pixels = (resized_mask < 250).all(axis=2) off_h = int((face_h - new_mask_h) / 2) off_w = int((face_w - new_mask_w) / 2) face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \ resized_mask[non_white_pixels] return face_with_mask def main(): cap = cv2.VideoCapture(0) # load mask mask = cv2.imread('assets/dog.png') # initialize front face classifier cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml") while(True): # Capture frame-by-frame ret, frame = cap.read() frame_h, frame_w, _ = frame.shape # Convert to black-and-white gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blackwhite = cv2.equalizeHist(gray) # Detect faces rects = cascade.detectMultiScale( blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) # Add mask to faces for x, y, w, h in rects: # crop a frame slightly larger than the face y0, y1 = int(y - 0.25*h), int(y + 0.75*h) x0, x1 = x, x + w # give up if the cropped frame would be out-of-bounds if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h: continue # apply mask frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask) # Display the resulting frame cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break # When everything done, release the capture cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main()
Lưu file và thoát khỏi editor . Sau đó chạy script.
- python step_4_dog_mask.py
Đến đây bạn có bộ lọc chó thời gian thực đang chạy. Tập lệnh cũng sẽ hoạt động với nhiều khuôn mặt trong ảnh, vì vậy bạn có thể tập hợp bạn bè của bạn để thực hiện một số thao tác tự động hóa chó.
Điều này kết thúc mục tiêu chính đầu tiên của ta trong hướng dẫn này, đó là tạo bộ lọc chú chó Snapchat-esque. Bây giờ ta hãy sử dụng biểu cảm khuôn mặt để xác định mặt nạ chó được áp dụng cho khuôn mặt.
Bước 5 - Xây dựng Bộ phân loại cảm xúc khuôn mặt cơ bản sử dụng Hình vuông nhỏ nhất
Trong phần này, bạn sẽ tạo một bộ phân loại cảm xúc để áp dụng các mặt nạ khác nhau dựa trên cảm xúc hiển thị. Nếu bạn cười, bộ lọc sẽ áp dụng một mặt nạ corgi. Nếu bạn cau mày, nó sẽ áp dụng một mặt nạ pug. Trên đường đi, bạn sẽ khám phá khuôn khổ bình phương nhỏ nhất , là nền tảng cơ bản để hiểu và thảo luận về các khái niệm học máy.
Để hiểu cách xử lý dữ liệu của ta và đưa ra dự đoán, trước tiên, ta sẽ khám phá ngắn gọn các mô hình học máy.
Ta cần đặt hai câu hỏi cho mỗi mô hình mà ta xem xét. Hiện tại, hai câu hỏi này sẽ đủ để phân biệt giữa các mô hình:
- Đầu vào: Mô hình được đưa ra thông tin gì?
- Đầu ra: Mô hình đang cố gắng dự đoán điều gì?
Ở cấp độ cao, mục tiêu là phát triển một mô hình phân loại cảm xúc. Mô hình là:
- Đầu vào: hình ảnh khuôn mặt đã cho.
- Đầu ra: dự đoán cảm xúc tương ứng.
model: face -> emotion
Cách tiếp cận ta sẽ sử dụng là bình phương nhỏ nhất ; ta lấy một tập hợp các điểm và ta tìm thấy một dòng phù hợp nhất. Dòng phù hợp nhất, được hiển thị trong hình ảnh sau đây, là mô hình của ta .
Xem xét đầu vào và kết quả cho đường dây của ta :
- Đầu vào: tọa độ
x
cho. - Đầu ra: dự đoán tọa độ $ y $ tương ứng.
least squares line: x -> y
Đầu vào x
của ta phải đại diện cho khuôn mặt và kết quả y
của ta phải đại diện cho cảm xúc, để ta sử dụng bình phương nhỏ nhất để phân loại cảm xúc:
-
x -> face
: Thay vì sử dụng một số chox
, ta sẽ sử dụng một vector giá trị chox
. Như vậy,x
có thể biểu diễn ảnh của các khuôn mặt. Bài viết Bình phương nhỏ nhất thông thường giải thích tại sao bạn có thể sử dụng vectơ có giá trị chox
. -
y -> emotion
: Mỗi cảm xúc sẽ tương ứng với một con số. Ví dụ: “tức giận” là 0, “buồn” là 1 và “vui” là 2. Theo cách này,y
có thể đại diện cho cảm xúc. Tuy nhiên, dòng của ta không bị giới hạn để xuất ra các giá trịy
0, 1 và 2. Nó có vô số giá trị y có thể có – nó có thể là 1,2, 3,5 hoặc 10003,42. Làm thế nào để ta dịch các giá trịy
thành các số nguyên tương ứng với các lớp? Xem bài viết Mã hóa một nóng để biết thêm chi tiết và giải thích.
Được trang bị kiến thức nền tảng này, bạn sẽ xây dựng một bộ phân loại bình phương nhỏ nhất đơn giản bằng cách sử dụng hình ảnh được vector hóa và các nhãn được mã hóa một lần. Bạn sẽ thực hiện điều này trong ba bước:
- Xử lý trước dữ liệu: Như đã giải thích ở đầu phần này, các mẫu của ta là các vectơ trong đó mỗi vectơ mã hóa hình ảnh của một khuôn mặt. Các nhãn của ta là các số nguyên tương ứng với một cảm xúc và ta sẽ áp dụng mã hóa một lần cho các nhãn này.
- Xác định và huấn luyện mô hình: Sử dụng giải pháp bình phương nhỏ nhất dạng đóng,
w^*
. - Chạy dự đoán bằng cách sử dụng mô hình: Lấy argmax của
Xw^*
để có được cảm xúc dự đoán.
Bắt đầu nào.
Đầu tiên, hãy cài đặt một folder để chứa dữ liệu:
- mkdir data
Sau đó, download dữ liệu do Pierre-Luc Carrier và Aaron Courville tuyển chọn, từ cuộc thi Phân loại cảm xúc trên khuôn mặt năm 2013 trên Kaggle .
- wget -O data/fer2013.tar https://bitbucket.org/alvinwan/adversarial-examples-in-computer-vision-building-then-fooling/raw/babfe4651f89a398c4b3fdbdd6d7a697c5104cff/fer2013.tar
Điều hướng đến folder data
và extract dữ liệu.
- cd data
- tar -xzf fer2013.tar
Bây giờ ta sẽ tạo một tập lệnh để chạy mô hình bình phương nhỏ nhất. Điều hướng đến folder root của dự án của bạn:
- cd ~/DogFilter
Tạo một file mới cho tập lệnh:
- nano step_5_ls_simple.py
Thêm bảng soạn sẵn Python và nhập các gói bạn cần :
"""Train emotion classifier using least squares.""" import numpy as np def main(): pass if __name__ == '__main__': main()
Tiếp theo, tải dữ liệu vào bộ nhớ. Thay thế pass
ở của bạn main
chức năng với đoạn mã sau:
# load data with np.load('data/fer2013_train.npz') as data: X_train, Y_train = data['X'], data['Y'] with np.load('data/fer2013_test.npz') as data: X_test, Y_test = data['X'], data['Y']
Bây giờ mã hóa một nóng các nhãn. Để làm điều này, hãy xây dựng ma trận nhận dạng với numpy
và sau đó lập index vào ma trận này bằng cách sử dụng danh sách các nhãn của ta :
# one-hot labels I = np.eye(6) Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]
Ở đây, ta sử dụng thực tế là hàng thứ i
trong ma trận nhận dạng đều bằng 0, ngoại trừ mục nhập thứ i
. Do đó, hàng thứ i là mã hóa một nóng cho nhãn của lớp i
. Ngoài ra, ta sử dụng lập index nâng cao của numpy
, trong đó [a, b, c, d][[1, 3]] = [b, d]
.
Tính toán (X^TX)^{-1}
sẽ mất quá nhiều thời gian trên phần cứng hàng hóa, vì X^TX
là ma trận 2304x2304
với hơn bốn triệu giá trị, vì vậy ta sẽ giảm thời gian này bằng cách chỉ chọn 100 tính năng đầu tiên. Thêm mã này:
... # select first 100 dimensions A_train, A_test = X_train[:, :100], X_test[:, :100]
Tiếp theo, thêm mã này để đánh giá giải pháp bình phương nhỏ nhất dạng đóng:
... # train model w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train))
Sau đó, xác định một hàm đánh giá cho các tập hợp đào tạo và xác nhận. Đặt cái này trước chức năng main
của bạn:
def evaluate(A, Y, w): Yhat = np.argmax(A.dot(w), axis=1) return np.sum(Yhat == Y) / Y.shape[0]
Để ước tính nhãn, ta lấy sản phẩm bên trong với mỗi mẫu và lấy chỉ số của giá trị lớn nhất bằng cách sử dụng np.argmax
. Sau đó, ta tính toán số lượng phân loại chính xác trung bình. Con số cuối cùng này là độ chính xác của bạn.
Cuối cùng, thêm mã này vào cuối hàm main
để tính toán độ chính xác của quá trình đào tạo và xác nhận bằng cách sử dụng hàm evaluate
mà bạn vừa viết:
# evaluate model ols_train_accuracy = evaluate(A_train, Y_train, w) print('(ols) Train Accuracy:', ols_train_accuracy) ols_test_accuracy = evaluate(A_test, Y_test, w) print('(ols) Test Accuracy:', ols_test_accuracy)
Kiểm tra kỹ xem tập lệnh của bạn có trùng với những điều sau:
"""Train emotion classifier using least squares.""" import numpy as np def evaluate(A, Y, w): Yhat = np.argmax(A.dot(w), axis=1) return np.sum(Yhat == Y) / Y.shape[0] def main(): # load data with np.load('data/fer2013_train.npz') as data: X_train, Y_train = data['X'], data['Y'] with np.load('data/fer2013_test.npz') as data: X_test, Y_test = data['X'], data['Y'] # one-hot labels I = np.eye(6) Y_oh_train, Y_oh_test = I[Y_train], I[Y_test] # select first 100 dimensions A_train, A_test = X_train[:, :100], X_test[:, :100] # train model w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train)) # evaluate model ols_train_accuracy = evaluate(A_train, Y_train, w) print('(ols) Train Accuracy:', ols_train_accuracy) ols_test_accuracy = evaluate(A_test, Y_test, w) print('(ols) Test Accuracy:', ols_test_accuracy) if __name__ == '__main__': main()
Lưu file của bạn, thoát khỏi editor và chạy tập lệnh Python.
- python step_5_ls_simple.py
Bạn sẽ thấy kết quả sau:
Output(ols) Train Accuracy: 0.4748918316507146 (ols) Test Accuracy: 0.45280545359202934
Mô hình của ta cho độ chính xác của chuyến tàu là 47,5%. Ta lặp lại điều này trên bộ xác thực để có được độ chính xác 45,3%. Đối với bài toán phân loại ba chiều, 45,3% cao hơn phỏng đoán một cách hợp lý, là 33 \%. Đây là bộ phân loại ban đầu của ta để phát hiện cảm xúc và trong bước tiếp theo, bạn sẽ xây dựng mô hình bình phương nhỏ nhất này để cải thiện độ chính xác. Độ chính xác càng cao, bộ lọc chó dựa trên cảm xúc của bạn càng có thể tìm thấy bộ lọc chó thích hợp cho từng cảm xúc được phát hiện.
Bước 6 - Cải thiện độ chính xác bằng cách làm sạch các đầu vào
Ta có thể sử dụng một mô hình biểu cảm hơn để tăng độ chính xác. Để thực hiện được điều này, ta đã làm sạch các đầu vào của bạn .
Hình ảnh ban đầu cho ta biết rằng vị trí ( 0, 0
) là màu đỏ, ( 1, 0
) là màu nâu, v.v. Một hình ảnh được làm lông có thể cho ta biết rằng có một con chó ở phía trên bên trái của hình ảnh, một người ở giữa, v.v. Lông vũ rất mạnh, nhưng định nghĩa chính xác của nó nằm ngoài phạm vi của hướng dẫn này.
Ta sẽ sử dụng một phép gần đúng cho kernel của hàm cơ sở xuyên tâm (RBF), sử dụng ma trận Gaussian ngẫu nhiên . Ta sẽ không đi vào chi tiết trong hướng dẫn này. Thay vào đó, ta sẽ coi đây là một hộp đen tính toán các tính năng bậc cao hơn cho ta .
Ta sẽ tiếp tục nơi ta đã dừng lại ở bước trước. Sao chép tập lệnh trước để bạn có một điểm khởi đầu tốt:
- cp step_5_ls_simple.py step_6_ls_simple.py
Mở file mới trong editor :
- nano step_6_ls_simple.py
Ta sẽ bắt đầu bằng cách tạo ma trận ngẫu nhiên tạo lông. , ta sẽ chỉ sử dụng 100 tính năng trong không gian tính năng mới của bạn .
Tìm dòng sau, xác định A_train
và A_test
:
# select first 100 dimensions A_train, A_test = X_train[:, :100], X_test[:, :100]
Ngay trên định nghĩa này cho A_train
và A_test
, hãy thêm một ma trận tính năng ngẫu nhiên:
d = 100 W = np.random.normal(size=(X_train.shape[1], d)) # select first 100 dimensions A_train, A_test = X_train[:, :100], X_test[:, :100] ...
Sau đó, thay thế các định nghĩa cho A_train
và A_test
. Ta xác định lại các ma trận của ta , được gọi là ma trận thiết kế , bằng cách sử dụng phép tính ngẫu nhiên này.
A_train, A_test = X_train.dot(W), X_test.dot(W)
Lưu file của bạn và chạy tập lệnh.
- python step_6_ls_simple.py
Bạn sẽ thấy kết quả sau:
Output(ols) Train Accuracy: 0.584174642717 (ols) Test Accuracy: 0.584425799685
Tính năng tạo lông này hiện mang lại độ chính xác của chuyến tàu là 58,4% và độ chính xác khi xác nhận là 58,4%, cải thiện 13,1% về kết quả xác nhận. Ta đã cắt ma trận X thành 100 x 100
, nhưng lựa chọn 100 là sai. Ta cũng có thể cắt ma trận X
thành 1000 x 1000
hoặc 50 x 50
. Nói số chiều của x
là dxd
. Ta có thể kiểm tra nhiều giá trị hơn của d
bằng cách cắt xén lại X thành dxd
và tính toán lại một mô hình mới.
Thử thêm các giá trị của d
, ta thấy độ chính xác của thử nghiệm được cải thiện thêm 4,3% lên 61,7%. Trong hình sau, ta coi hiệu suất của bộ phân loại mới của ta khi ta thay đổi d
. Theo trực giác, khi d
tăng, độ chính xác cũng sẽ tăng lên, khi ta sử dụng ngày càng nhiều dữ liệu root của bạn . Tuy nhiên, thay vì vẽ một bức tranh màu hồng, biểu đồ thể hiện một xu hướng tiêu cực:
Khi ta lưu giữ nhiều dữ liệu của bạn hơn, khoảng cách giữa độ chính xác của quá trình đào tạo và xác nhận cũng tăng lên. Đây là bằng chứng rõ ràng về việc trang bị quá mức , trong đó mô hình của ta đang học các đại diện không còn có thể khái quát cho tất cả dữ liệu. Để chống lại việc trang bị quá nhiều, ta sẽ chính thức hóa mô hình của bạn bằng cách phạt các mô hình phức tạp.
Ta sửa đổi hàm mục tiêu bình phương nhỏ nhất thông thường của ta bằng một thuật ngữ chính quy, tạo cho ta một mục tiêu mới. Hàm mục tiêu mới của ta được gọi là hồi quy sườn núi và có dạng như sau:
min_w |Aw- y|^2 + lambda |w|^2
Trong phương trình này, lambda
là một siêu tham số có thể điều chỉnh được. Cắm lambda = 0
vào phương trình và hồi quy sườn núi trở thành bình phương nhỏ nhất. Cắm lambda = infinity
vào phương trình và bạn sẽ thấy giá trị w
tốt nhất bây giờ phải bằng 0, vì bất kỳ w
khác 0 đều phải chịu tổn thất vô hạn. Hóa ra, mục tiêu này cũng mang lại một giải pháp dạng đóng:
w^* = (A^TA + lambda I)^{-1}A^Ty
Vẫn sử dụng các mẫu đã được chải lông, hãy đào tạo lại và đánh giá lại mô hình .
Mở lại step_6_ls_simple.py
trong editor :
- nano step_6_ls_simple.py
Lần này, tăng số chiều của không gian đặc trưng mới cho d=1000
. Thay đổi giá trị của d
từ 100
thành 1000
như trong khối mã sau:
... d = 1000 W = np.random.normal(size=(X_train.shape[1], d)) ...
Sau đó, áp dụng hồi quy sườn núi bằng cách sử dụng chính quy lambda = 10^{10}
. Thay thế dòng xác định w
bằng hai dòng sau:
... # train model I = np.eye(A_train.shape[1]) w = np.linalg.inv(A_train.T.dot(A_train) + 1e10 * I).dot(A_train.T.dot(Y_oh_train))
Sau đó xác định vị trí khối này:
... ols_train_accuracy = evaluate(A_train, Y_train, w) print('(ols) Train Accuracy:', ols_train_accuracy) ols_test_accuracy = evaluate(A_test, Y_test, w) print('(ols) Test Accuracy:', ols_test_accuracy)
Thay thế nó bằng những thứ sau:
... print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w)) print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w))
Tập lệnh đã hoàn thành sẽ trông như thế này:
"""Train emotion classifier using least squares.""" import numpy as np def evaluate(A, Y, w): Yhat = np.argmax(A.dot(w), axis=1) return np.sum(Yhat == Y) / Y.shape[0] def main(): # load data with np.load('data/fer2013_train.npz') as data: X_train, Y_train = data['X'], data['Y'] with np.load('data/fer2013_test.npz') as data: X_test, Y_test = data['X'], data['Y'] # one-hot labels I = np.eye(6) Y_oh_train, Y_oh_test = I[Y_train], I[Y_test] d = 1000 W = np.random.normal(size=(X_train.shape[1], d)) # select first 100 dimensions A_train, A_test = X_train.dot(W), X_test.dot(W) # train model I = np.eye(A_train.shape[1]) w = np.linalg.inv(A_train.T.dot(A_train) + 1e10 * I).dot(A_train.T.dot(Y_oh_train)) # evaluate model print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w)) print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w)) if __name__ == '__main__': main()
Lưu file , thoát khỏi editor và chạy tập lệnh:
- python step_6_ls_simple.py
Bạn sẽ thấy kết quả sau:
Output(ridge) Train Accuracy: 0.651173462698 (ridge) Test Accuracy: 0.622181436812
Có sự cải thiện thêm 0,4% về độ chính xác xác thực lên 62,2%, khi độ chính xác của tàu giảm xuống 65,1%. đánh giá lại trên một số d
khác nhau, ta thấy khoảng cách nhỏ hơn giữa độ chính xác đào tạo và xác nhận cho hồi quy sườn núi. Nói cách khác, hồi quy sườn núi ít bị thừa hơn.
Hiệu suất cơ bản cho bình phương nhỏ nhất, với những cải tiến bổ sung này, hoạt động khá tốt. Thời gian đào tạo và suy luận, tất cả cùng nhau, chỉ mất không quá 20 giây để có kết quả tốt nhất. Trong phần tiếp theo, bạn sẽ khám phá các mô hình phức tạp hơn nữa.
Bước 7 - Xây dựng Bộ phân loại cảm xúc khuôn mặt bằng cách sử dụng mạng nơ-ron phù hợp trong PyTorch
Trong phần này, bạn sẽ xây dựng bộ phân loại cảm xúc thứ hai bằng cách sử dụng mạng nơ-ron thay vì bình phương nhỏ nhất. , mục tiêu của ta là tạo ra một mô hình chấp nhận khuôn mặt làm đầu vào và kết quả là cảm xúc. Cuối cùng, bộ phân loại này sau đó sẽ xác định loại mặt nạ chó nào sẽ áp dụng.
Để có hình dung và giới thiệu ngắn gọn về mạng thần kinh, hãy xem bài viết Tìm hiểu về mạng thần kinh . Ở đây, ta sẽ sử dụng một thư viện học sâu có tên là PyTorch . Có một số thư viện học sâu đang được sử dụng rộng rãi và mỗi thư viện đều có những ưu và nhược điểm khác nhau. PyTorch là một nơi đặc biệt tốt để bắt đầu. Để áp dụng bộ phân loại mạng nơ-ron này, ta lại thực hiện ba bước như đã làm với bộ phân loại bình phương nhỏ nhất:
- Xử lý trước dữ liệu: Áp dụng mã hóa một lần và sau đó áp dụng các rút gọn PyTorch.
- Chỉ định và đào tạo mô hình: Cài đặt mạng nơ-ron bằng các lớp PyTorch. Xác định siêu tham số tối ưu hóa và chạy giảm độ dốc ngẫu nhiên.
- Chạy dự đoán bằng mô hình: Đánh giá mạng nơ-ron.
Tạo một file mới, có tên step_7_fer_simple.py
- nano step_7_fer_simple.py
Nhập các tiện ích cần thiết và tạo một lớp Python sẽ chứa dữ liệu . Để xử lý dữ liệu ở đây, bạn sẽ tạo tập dữ liệu huấn luyện và kiểm tra. Để thực hiện những điều này, hãy triển khai giao diện Dataset
của PyTorch, cho phép bạn tải và sử dụng đường ống dữ liệu tích hợp của PyTorch cho tập dữ liệu nhận dạng cảm xúc khuôn mặt:
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import numpy as np import torch import cv2 import argparse class Fer2013Dataset(Dataset): """Face Emotion Recognition dataset. Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier and Aaron Courville in 2013. Each sample is 1 x 1 x 48 x 48, and each label is a scalar. """ pass
Xóa trình giữ chỗ pass
trong lớp Fer2013Dataset
. Thay vào đó, hãy thêm một hàm sẽ khởi tạo người giữ dữ liệu của ta :
def __init__(self, path: str): """ Args: path: Path to `.np` file containing sample nxd and label nx1 """ with np.load(path) as data: self._samples = data['X'] self._labels = data['Y'] self._samples = self._samples.reshape((-1, 1, 48, 48)) self.X = Variable(torch.from_numpy(self._samples)).float() self.Y = Variable(torch.from_numpy(self._labels)).float() ...
Chức năng này bắt đầu bằng cách tải các mẫu và nhãn. Sau đó, nó bao bọc dữ liệu trong cấu trúc dữ liệu PyTorch.
Ngay sau hàm __init__
, hãy thêm một hàm __len__
, vì điều này là cần thiết để triển khai giao diện Dataset
PyTorch mong đợi:
... def __len__(self): return len(self._labels)
Cuối cùng, thêm một phương thức __getitem__
, phương thức này sẽ trả về một từ điển chứa mẫu và nhãn:
def __getitem__(self, idx): return {'image': self._samples[idx], 'label': self._labels[idx]}
Kiểm tra kỹ xem file của bạn trông giống như sau:
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import numpy as np import torch import cv2 import argparse class Fer2013Dataset(Dataset): """Face Emotion Recognition dataset. Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier and Aaron Courville in 2013. Each sample is 1 x 1 x 48 x 48, and each label is a scalar. """ def __init__(self, path: str): """ Args: path: Path to `.np` file containing sample nxd and label nx1 """ with np.load(path) as data: self._samples = data['X'] self._labels = data['Y'] self._samples = self._samples.reshape((-1, 1, 48, 48)) self.X = Variable(torch.from_numpy(self._samples)).float() self.Y = Variable(torch.from_numpy(self._labels)).float() def __len__(self): return len(self._labels) def __getitem__(self, idx): return {'image': self._samples[idx], 'label': self._labels[idx]}
Tiếp theo, tải tập dữ liệu Fer2013Dataset
. Thêm mã sau vào cuối file của bạn sau lớp Fer2013Dataset
:
trainset = Fer2013Dataset('data/fer2013_train.npz') trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True) testset = Fer2013Dataset('data/fer2013_test.npz') testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)
Mã này khởi tạo tập dữ liệu bằng cách sử dụng lớp Fer2013Dataset
mà bạn đã tạo. Sau đó, đối với tập hợp xác thực và đào tạo, nó bao bọc tập dữ liệu trong DataLoader
. Điều này chuyển tập dữ liệu thành một tập dữ liệu có thể lặp lại để sử dụng sau này.
Để kiểm tra sự tỉnh táo, hãy xác minh các tiện ích tập dữ liệu đang hoạt động. Tạo bộ tải tập dữ liệu mẫu bằng DataLoader
và in phần tử đầu tiên của bộ tải đó. Thêm phần sau vào cuối file của bạn:
if __name__ == '__main__': loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False) print(next(iter(loader)))
Xác minh tập lệnh đã hoàn thành của bạn trông giống như sau:
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import numpy as np import torch import cv2 import argparse class Fer2013Dataset(Dataset): """Face Emotion Recognition dataset. Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier and Aaron Courville in 2013. Each sample is 1 x 1 x 48 x 48, and each label is a scalar. """ def __init__(self, path: str): """ Args: path: Path to `.np` file containing sample nxd and label nx1 """ with np.load(path) as data: self._samples = data['X'] self._labels = data['Y'] self._samples = self._samples.reshape((-1, 1, 48, 48)) self.X = Variable(torch.from_numpy(self._samples)).float() self.Y = Variable(torch.from_numpy(self._labels)).float() def __len__(self): return len(self._labels) def __getitem__(self, idx): return {'image': self._samples[idx], 'label': self._labels[idx]} trainset = Fer2013Dataset('data/fer2013_train.npz') trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True) testset = Fer2013Dataset('data/fer2013_test.npz') testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False) if __name__ == '__main__': loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False) print(next(iter(loader)))
Thoát khỏi editor và chạy tập lệnh.
- python step_7_fer_simple.py
Điều này tạo ra cặp tenxơ sau . Đường ống dữ liệu của ta xuất ra hai mẫu và hai nhãn. Điều này cho thấy rằng đường dẫn dữ liệu của ta đã hoàn tất và sẵn sàng hoạt động:
Output{'image': (0 ,0 ,.,.) = 24 32 36 ... 173 172 173 25 34 29 ... 173 172 173 26 29 25 ... 172 172 174 ... ⋱ ... 159 185 157 ... 157 156 153 136 157 187 ... 152 152 150 145 130 161 ... 142 143 142 ⋮ (1 ,0 ,.,.) = 20 17 19 ... 187 176 162 22 17 17 ... 195 180 171 17 17 18 ... 203 193 175 ... ⋱ ... 1 1 1 ... 106 115 119 2 2 1 ... 103 111 119 2 2 2 ... 99 107 118 [torch.LongTensor of size 2x1x48x48] , 'label': 1 1 [torch.LongTensor of size 2] }
Đến đây bạn đã xác minh đường ống dữ liệu hoạt động, hãy quay lại step_7_fer_simple.py
để thêm mạng thần kinh và trình tối ưu hóa. Mở step_7_fer_simple.py
.
- nano step_7_fer_simple.py
Đầu tiên, hãy xóa ba dòng cuối cùng bạn đã thêm trong lần lặp trước:
# Delete all three lines if __name__ == '__main__': loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False) print(next(iter(loader)))
Thay vào đó, hãy xác định một mạng nơ-ron PyTorch bao gồm ba lớp tích tụ, tiếp theo là ba lớp được kết nối đầy đủ. Thêm cái này vào cuối tập lệnh hiện có của bạn:
class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 6, 3) self.conv3 = nn.Conv2d(6, 16, 3) self.fc1 = nn.Linear(16 * 4 * 4, 120) self.fc2 = nn.Linear(120, 48) self.fc3 = nn.Linear(48, 3) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) x = x.view(-1, 16 * 4 * 4) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x
Bây giờ hãy khởi tạo mạng nơ-ron, xác định một hàm mất mát và xác định các siêu tham số tối ưu hóa bằng cách thêm đoạn mã sau vào cuối tập lệnh:
net = Net().float() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
Ta sẽ tập luyện trong hai kỷ nguyên . Hiện tại, ta định nghĩa kỷ nguyên là một quá trình lặp lại của quá trình đào tạo trong đó mọi mẫu đào tạo đã được sử dụng chính xác một lần.
Đầu tiên, extract image
và label
từ bộ tải tập dữ liệu và sau đó bọc mỗi thứ trong một Variable
PyTorch. Thứ hai, chạy chuyển tiếp phía trước và sau đó nhân rộng ngược thông qua mạng lưới tổn thất và thần kinh. Thêm mã sau vào cuối tập lệnh của bạn để làm điều đó:
for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs = Variable(data['image'].float()) labels = Variable(data['label'].long()) optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.data[0] if i % 100 == 0: print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))
Tập lệnh của bạn bây giờ sẽ giống như sau:
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import numpy as np import torch import cv2 import argparse class Fer2013Dataset(Dataset): """Face Emotion Recognition dataset. Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier and Aaron Courville in 2013. Each sample is 1 x 1 x 48 x 48, and each label is a scalar. """ def __init__(self, path: str): """ Args: path: Path to `.np` file containing sample nxd and label nx1 """ with np.load(path) as data: self._samples = data['X'] self._labels = data['Y'] self._samples = self._samples.reshape((-1, 1, 48, 48)) self.X = Variable(torch.from_numpy(self._samples)).float() self.Y = Variable(torch.from_numpy(self._labels)).float() def __len__(self): return len(self._labels) def __getitem__(self, idx): return {'image': self._samples[idx], 'label': self._labels[idx]} trainset = Fer2013Dataset('data/fer2013_train.npz') trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True) testset = Fer2013Dataset('data/fer2013_test.npz') testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False) class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 6, 3) self.conv3 = nn.Conv2d(6, 16, 3) self.fc1 = nn.Linear(16 * 4 * 4, 120) self.fc2 = nn.Linear(120, 48) self.fc3 = nn.Linear(48, 3) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) x = x.view(-1, 16 * 4 * 4) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x net = Net().float() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs = Variable(data['image'].float()) labels = Variable(data['label'].long()) optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.data[0] if i % 100 == 0: print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))
Lưu file và thoát khỏi editor khi bạn đã xác minh mã của bạn . Sau đó, chạy chương trình đào tạo bằng chứng về khái niệm này:
- python step_7_fer_simple.py
Bạn sẽ thấy kết quả tương tự như sau khi mạng thần kinh đào tạo:
Output[0, 0] loss: 1.094 [0, 100] loss: 1.049 [0, 200] loss: 1.009 [0, 300] loss: 0.963 [0, 400] loss: 0.935 [1, 0] loss: 0.760 [1, 100] loss: 0.768 [1, 200] loss: 0.775 [1, 300] loss: 0.776 [1, 400] loss: 0.767
Sau đó, bạn có thể tăng cường tập lệnh này bằng cách sử dụng một số tiện ích PyTorch khác để lưu và tải các mô hình, đào tạo kết quả và xác nhận độ chính xác, tinh chỉnh lịch trình tốc độ học tập, v.v. Sau khi đào tạo trong 20 kỷ với tốc độ học là 0,01 và động lượng là 0.9, mạng nơ-ron của ta đạt độ chính xác đoàn tàu 87,9% và độ chính xác xác nhận 75,5%, cải thiện thêm 6,8% so với phương pháp tiếp cận bình phương nhỏ nhất thành công nhất cho đến nay là 66,6%. Ta sẽ đưa những chuông và còi bổ sung này vào một tập lệnh mới.
Tạo một file mới để chứa bộ phát hiện cảm xúc khuôn mặt cuối cùng mà nguồn cấp dữ liệu camera trực tiếp của bạn sẽ sử dụng. Tập lệnh này chứa mã ở trên cùng với giao diện dòng lệnh và version dễ nhập của mã của ta sẽ được sử dụng sau này. Ngoài ra, nó còn chứa các siêu tham số được điều chỉnh trước, cho một mô hình có độ chính xác cao hơn.
- nano step_7_fer.py
Bắt đầu với các lần nhập sau. Điều này trùng với file trước đó của ta nhưng cũng bao gồm OpenCV dưới dạng import cv2.
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import numpy as np import torch import cv2 import argparse
Ngay bên dưới những lần nhập này, hãy sử dụng lại mã của bạn từ step_7_fer_simple.py
để xác định mạng thần kinh:
class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 6, 3) self.conv3 = nn.Conv2d(6, 16, 3) self.fc1 = nn.Linear(16 * 4 * 4, 120) self.fc2 = nn.Linear(120, 48) self.fc3 = nn.Linear(48, 3) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) x = x.view(-1, 16 * 4 * 4) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x
, hãy sử dụng lại mã cho tập dữ liệu Nhận dạng cảm xúc khuôn mặt từ step_7_fer_simple.py
và thêm nó vào file này:
class Fer2013Dataset(Dataset): """Face Emotion Recognition dataset. Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier and Aaron Courville in 2013. Each sample is 1 x 1 x 48 x 48, and each label is a scalar. """ def __init__(self, path: str): """ Args: path: Path to `.np` file containing sample nxd and label nx1 """ with np.load(path) as data: self._samples = data['X'] self._labels = data['Y'] self._samples = self._samples.reshape((-1, 1, 48, 48)) self.X = Variable(torch.from_numpy(self._samples)).float() self.Y = Variable(torch.from_numpy(self._labels)).float() def __len__(self): return len(self._labels) def __getitem__(self, idx): return {'image': self._samples[idx], 'label': self._labels[idx]}
Tiếp theo, xác định một vài tiện ích để đánh giá hiệu suất của mạng nơ-ron. Trước tiên, hãy thêm một hàm evaluate
để so sánh cảm xúc dự đoán của mạng nơ-ron với cảm xúc thực sự cho một hình ảnh:
def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float: """Evaluate neural network outputs against non-one-hotted labels.""" Y = labels.data.numpy() Yhat = np.argmax(outputs.data.numpy(), axis=1) denom = Y.shape[0] if normalized else 1 return float(np.sum(Yhat == Y) / denom)
Sau đó, thêm một hàm được gọi là batch_evaluate
áp dụng hàm đầu tiên cho tất cả các hình ảnh:
def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float: """Evaluate neural network in batches, if dataset is too large.""" score = 0.0 n = dataset.X.shape[0] for i in range(0, n, batch_size): x = dataset.X[i: i + batch_size] y = dataset.Y[i: i + batch_size] score += evaluate(net(x), y, False) return score / n
Bây giờ, hãy xác định một hàm có tên get_image_to_emotion_predictor
nhận vào một hình ảnh và xuất ra một cảm xúc được dự đoán, sử dụng một mô hình được đào tạo trước:
def get_image_to_emotion_predictor(model_path='assets/model_best.pth'): """Returns predictor, from image to emotion index.""" net = Net().float() pretrained_model = torch.load(model_path) net.load_state_dict(pretrained_model['state_dict']) def predictor(image: np.array): """Translates images into emotion indices.""" if image.shape[2] > 1: image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48)) X = Variable(torch.from_numpy(frame)).float() return np.argmax(net(X).data.numpy(), axis=1)[0] return predictor
Cuối cùng, thêm đoạn mã sau để xác định chức năng main
để tận dụng các tiện ích khác:
def main(): trainset = Fer2013Dataset('data/fer2013_train.npz') testset = Fer2013Dataset('data/fer2013_test.npz') net = Net().float() pretrained_model = torch.load("assets/model_best.pth") net.load_state_dict(pretrained_model['state_dict']) train_acc = batch_evaluate(net, trainset, batch_size=500) print('Training accuracy: %.3f' % train_acc) test_acc = batch_evaluate(net, testset, batch_size=500) print('Validation accuracy: %.3f' % test_acc) if __name__ == '__main__': main()
Điều này tải một mạng thần kinh được đào tạo trước và đánh giá hiệu suất của nó trên tập dữ liệu Nhận dạng cảm xúc khuôn mặt được cung cấp. Cụ thể, tập lệnh đưa ra độ chính xác trên các hình ảnh mà ta sử dụng để đào tạo, cũng như một tập hợp hình ảnh riêng biệt mà ta dành cho mục đích thử nghiệm.
Kiểm tra kỹ xem file của bạn có trùng với file sau đây không:
from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import numpy as np import torch import cv2 import argparse class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 6, 3) self.conv3 = nn.Conv2d(6, 16, 3) self.fc1 = nn.Linear(16 * 4 * 4, 120) self.fc2 = nn.Linear(120, 48) self.fc3 = nn.Linear(48, 3) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) x = x.view(-1, 16 * 4 * 4) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x class Fer2013Dataset(Dataset): """Face Emotion Recognition dataset. Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier and Aaron Courville in 2013. Each sample is 1 x 1 x 48 x 48, and each label is a scalar. """ def __init__(self, path: str): """ Args: path: Path to `.np` file containing sample nxd and label nx1 """ with np.load(path) as data: self._samples = data['X'] self._labels = data['Y'] self._samples = self._samples.reshape((-1, 1, 48, 48)) self.X = Variable(torch.from_numpy(self._samples)).float() self.Y = Variable(torch.from_numpy(self._labels)).float() def __len__(self): return len(self._labels) def __getitem__(self, idx): return {'image': self._samples[idx], 'label': self._labels[idx]} def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float: """Evaluate neural network outputs against non-one-hotted labels.""" Y = labels.data.numpy() Yhat = np.argmax(outputs.data.numpy(), axis=1) denom = Y.shape[0] if normalized else 1 return float(np.sum(Yhat == Y) / denom) def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float: """Evaluate neural network in batches, if dataset is too large.""" score = 0.0 n = dataset.X.shape[0] for i in range(0, n, batch_size): x = dataset.X[i: i + batch_size] y = dataset.Y[i: i + batch_size] score += evaluate(net(x), y, False) return score / n def get_image_to_emotion_predictor(model_path='assets/model_best.pth'): """Returns predictor, from image to emotion index.""" net = Net().float() pretrained_model = torch.load(model_path) net.load_state_dict(pretrained_model['state_dict']) def predictor(image: np.array): """Translates images into emotion indices.""" if image.shape[2] > 1: image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48)) X = Variable(torch.from_numpy(frame)).float() return np.argmax(net(X).data.numpy(), axis=1)[0] return predictor def main(): trainset = Fer2013Dataset('data/fer2013_train.npz') testset = Fer2013Dataset('data/fer2013_test.npz') net = Net().float() pretrained_model = torch.load("assets/model_best.pth") net.load_state_dict(pretrained_model['state_dict']) train_acc = batch_evaluate(net, trainset, batch_size=500) print('Training accuracy: %.3f' % train_acc) test_acc = batch_evaluate(net, testset, batch_size=500) print('Validation accuracy: %.3f' % test_acc) if __name__ == '__main__': main(
Lưu file và thoát khỏi editor .
Như trước đây, với các máy dò khuôn mặt, tải pre-đào tạo các thông số mô hình và lưu chúng vào của bạn assets
folder với lệnh sau đây:
- wget -O assets/model_best.pth https://github.com/alvinwan/emotion-based-dog-filter/raw/master/src/assets/model_best.pth
Chạy tập lệnh để sử dụng và đánh giá mô hình được đào tạo trước:
- python step_7_fer.py
Điều này sẽ xuất ra như sau:
OutputTraining accuracy: 0.879 Validation accuracy: 0.755
Đến đây, bạn đã xây dựng một bộ phân loại cảm xúc khuôn mặt khá chính xác. Về bản chất, mô hình của ta có thể phân biệt chính xác giữa các khuôn mặt vui, buồn và ngạc nhiên tám trong số mười lần. Đây là một mô hình khá tốt, vì vậy bây giờ bạn có thể chuyển sang sử dụng công cụ phân loại cảm xúc khuôn mặt này để xác định loại mặt nạ chó nào sẽ áp dụng cho khuôn mặt.
Bước 8 - Hoàn thiện bộ lọc chó dựa trên cảm xúc
Trước khi tích hợp bộ phân loại cảm xúc khuôn mặt hoàn toàn mới của ta , ta cần mặt nạ động vật để chọn. Ta sẽ sử dụng mặt nạ chó đốm và mặt nạ chó cừu:
Thực hiện các lệnh này để tải về cả hai mặt nạ để bạn assets
folder :
- wget -O assets/dalmation.png /image_static/mg_sid_64/3400/000c/3400000c050d02/3400000c050d02.png # dalmation
- wget -O assets/sheepdog.png /image_static/mg_sid_64/7044/4448/70444448414945/70444448414945.png # sheepdog
Bây giờ ta hãy sử dụng các mặt nạ trong bộ lọc của ta . Bắt đầu bằng cách sao chép file step_4_dog_mask.py
:
- cp step_4_dog_mask.py step_8_dog_emotion_mask.py
Mở tập lệnh Python mới.
- nano step_8_dog_emotion_mask.py
Chèn một dòng mới ở đầu tập lệnh để nhập công cụ dự đoán cảm xúc:
from step_7_fer import get_image_to_emotion_predictor ...
Sau đó, trong hàm main()
, định vị dòng này:
mask = cv2.imread('assets/dog.png')
Thay thế nó bằng phần sau để tải các mặt nạ mới và tổng hợp tất cả các mặt nạ thành một bộ:
mask0 = cv2.imread('assets/dog.png') mask1 = cv2.imread('assets/dalmation.png') mask2 = cv2.imread('assets/sheepdog.png') masks = (mask0, mask1, mask2)
Thêm dấu ngắt dòng, sau đó thêm mã này để tạo công cụ dự đoán cảm xúc.
# get emotion predictor predictor = get_image_to_emotion_predictor()
Chức năng main
của bạn bây giờ sẽ trùng với những điều sau:
def main(): cap = cv2.VideoCapture(0) # load mask mask0 = cv2.imread('assets/dog.png') mask1 = cv2.imread('assets/dalmation.png') mask2 = cv2.imread('assets/sheepdog.png') masks = (mask0, mask1, mask2) # get emotion predictor predictor = get_image_to_emotion_predictor() # initialize front face classifier ...
Tiếp theo, xác định vị trí các dòng sau:
# apply mask frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)
Chèn dòng sau vào bên dưới dòng # apply mask
để chọn mặt nạ thích hợp bằng cách sử dụng công cụ dự đoán:
# apply mask mask = masks[predictor(frame[y:y+h, x: x+w])] frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)
Tệp đã hoàn thành sẽ giống như sau:
"""Test for face detection""" from step_7_fer import get_image_to_emotion_predictor import numpy as np import cv2 def apply_mask(face: np.array, mask: np.array) -> np.array: """Add the mask to the provided face, and return the face with mask.""" mask_h, mask_w, _ = mask.shape face_h, face_w, _ = face.shape # Resize the mask to fit on face factor = min(face_h / mask_h, face_w / mask_w) new_mask_w = int(factor * mask_w) new_mask_h = int(factor * mask_h) new_mask_shape = (new_mask_w, new_mask_h) resized_mask = cv2.resize(mask, new_mask_shape) # Add mask to face - ensure mask is centered face_with_mask = face.copy() non_white_pixels = (resized_mask < 250).all(axis=2) off_h = int((face_h - new_mask_h) / 2) off_w = int((face_w - new_mask_w) / 2) face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \ resized_mask[non_white_pixels] return face_with_mask def main(): cap = cv2.VideoCapture(0) # load mask mask0 = cv2.imread('assets/dog.png') mask1 = cv2.imread('assets/dalmation.png') mask2 = cv2.imread('assets/sheepdog.png') masks = (mask0, mask1, mask2) # get emotion predictor predictor = get_image_to_emotion_predictor() # initialize front face classifier cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml") while True: # Capture frame-by-frame ret, frame = cap.read() frame_h, frame_w, _ = frame.shape # Convert to black-and-white gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blackwhite = cv2.equalizeHist(gray) rects = cascade.detectMultiScale( blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE) for x, y, w, h in rects: # crop a frame slightly larger than the face y0, y1 = int(y - 0.25*h), int(y + 0.75*h) x0, x1 = x, x + w # give up if the cropped frame would be out-of-bounds if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h: continue # apply mask mask = masks[predictor(frame[y:y+h, x: x+w])] frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask) # Display the resulting frame cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main()
Lưu và thoát khỏi editor . Bây giờ chạy tập lệnh:
- python step_8_dog_emotion_mask.py
Bây giờ hãy thử nó ra! Mỉm cười sẽ đăng ký là "hạnh phúc" và hiển thị con chó ban đầu. Khuôn mặt trung tính hoặc cau có sẽ được coi là "buồn" và mang lại sự hạ thấp. Khuôn mặt "ngạc nhiên", với cái hàm cụp xuống đẹp đẽ sẽ mang lại lợi ích cho chó chăn cừu.
Điều này kết thúc bộ lọc chó dựa trên cảm xúc của ta và xâm nhập vào thị giác máy tính.
Kết luận
Trong hướng dẫn này, bạn đã xây dựng một máy dò khuôn mặt và bộ lọc chó bằng cách sử dụng thị giác máy tính và sử dụng các mô hình học máy để áp dụng mặt nạ dựa trên cảm xúc được phát hiện.
Học máy được áp dụng rộng rãi. Tuy nhiên, người thực hành phải cân nhắc các tác động đạo đức của mỗi ứng dụng khi áp dụng học máy. Ứng dụng bạn xây dựng trong hướng dẫn này là một bài tập thú vị, nhưng hãy nhớ rằng bạn dựa vào OpenCV và tập dữ liệu hiện có để nhận dạng khuôn mặt, thay vì cung cấp dữ liệu của bạn để đào tạo các mô hình. Dữ liệu và mô hình được sử dụng có tác động đáng kể đến cách thức hoạt động của chương trình.
Ví dụ, hãy tưởng tượng một công cụ tìm kiếm việc làm nơi các người mẫu được đào tạo với dữ liệu về các ứng viên. chẳng hạn như chủng tộc, giới tính, tuổi tác, văn hóa, ngôn ngữ đầu tiên hoặc các yếu tố khác. Và có lẽ các nhà phát triển đã đào tạo một mô hình thực thi sự thưa thớt, kết quả là giảm không gian đối tượng thành một không gian con, nơi giới tính giải thích hầu hết các phương sai. Kết quả là, mô hình ảnh hưởng đến tìm kiếm việc làm của ứng viên và thậm chí cả quy trình lựa chọn công ty chủ yếu dựa trên giới tính. Bây giờ hãy xem xét các tình huống phức tạp hơn trong đó mô hình khó diễn giải hơn và bạn không biết một tính năng cụ thể tương ứng với cái gì. Bạn có thể tìm hiểu thêm về vấn đề này trong Bình đẳng Cơ hội trong Học máy của Giáo sư Moritz Hardt tại UC Berkeley.
Có thể có rất nhiều mức độ không chắc chắn trong học máy. Để hiểu được sự ngẫu nhiên và phức tạp này, bạn sẽ phải phát triển cả trực giác toán học và kỹ năng tư duy xác suất. Là một người thực hành, việc đào sâu vào cơ sở lý thuyết của máy học là tùy thuộc vào bạn.
Các tin liên quan
Cách phát hiện và trích xuất khuôn mặt từ một image bằng OpenCV và Python2019-03-27
Cách tạo bộ phân loại học máy bằng Python với Scikit-learning
2019-03-24
Cách thiết lập sổ tay Jupyter cho Python 3
2019-03-21
Cách viết chương trình Python 3 đầu tiên của bạn
2019-02-22
Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên Ubuntu 18.04
2019-02-22
Cách thiết lập notebook Jupyter với Python 3 trên Ubuntu 18.04
2018-11-28
Cách thực hiện chuyển kiểu neural với Python 3 và PyTorch
2018-09-13
Cách thực hiện chuyển kiểu neural với Python 3 và PyTorch
2018-09-13
Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên Windows 10
2018-09-11
Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên Windows 10
2018-09-11