Đặc điểm HOG¶
Lược đồ biểu diễn đặc trưng Gradient là một quy trình trích xuất đặc trưng đơn giản được phát triển trong ngữ cảnh xác định người đi bộ trong hình ảnh.HOG bao gồm các bước sau:
- Tùy ý tiền xử lý hình ảnh. Điều này dẫn đến các tính năng chống lại sự phụ thuộc vào biến thể về ánh sáng.
- Convolve hình ảnh với hai bộ lọc nhạy cảm đến các độ dốc sáng theo chiều ngang và dọc. Chúng thu thập thông tin về viền, đường viền và kết cấu.
- Chia nhỏ hình ảnh thành các ô có kích thước xác định trước và tính toán lược đồ về hướng độ dốc trong mỗi ô.
- Chuẩn hóa lược đồ trong mỗi ô bằng cách so sánh với khối của các ô láng giềng. Điều này tiếp tục làm giảm ảnh hưởng của ánh sáng trên toàn bộ hình ảnh.
- Tạo một vector đặc trưng một chiều từ thông tin trong mỗi ô.
Một trình trích xuất HOG nhanh được tích hợp trong dự án Scikit-Image, và chúng ta có thể thử nghiệm nó khá nhanh chóng và hiển thị hướng đạo hàm trong từng ô:
from skimage import data, color, featureimport skimage.dataimage = color.rgb2gray(data.chelsea())hog_vec, hog_vis = feature.hog(image, visualise=True)fig, ax = plt.subplots(1, 2, figsize=(12, 6), subplot_kw=dict(xticks=[], yticks=[]))ax[0].imshow(image, cmap='gray')ax[0].set_title('input image')ax[1].imshow(hog_vis)ax[1].set_title('visualization of HOG features');
HOG trong Hành động: Một Bộ phát hiện khuôn mặt Đơn giản¶
Sử dụng các đặc trưng HOG này, chúng ta có thể xây dựng một thuật toán phát hiện khuôn mặt đơn giản với bất kỳ bộ phân loại Scikit-Learn nào; ở đây chúng tôi sẽ sử dụng một máy vector hỗ trợ tuyến tính (tham khảo lại về In-Depth: Support Vector Machines nếu bạn cần làm mới nhớ lại điều này).Các bước như sau:
- Lấy một bộ thumbnail hình ảnh khuôn mặt để tạo thành các mẫu huấn luyện “positive”.
- Lấy một bộ thumbnail hình ảnh không phải khuôn mặt để tạo thành các mẫu huấn luyện “negative”.
- Trích xuất các đặc trưng HOG từ các mẫu huấn luyện này.
- Huấn luyện một bộ phân loại SVM tuyến tính trên các mẫu này.
- Đối với một hình ảnh “không xác định”, chạy một cửa sổ trượt qua hình ảnh, sử dụng mô hình để đánh giá xem cửa sổ đó có chứa khuôn mặt hay không.
- Nếu các phát hiện chồng lấn lên nhau, kết hợp chúng thành một cửa sổ duy nhất.
Hãy theo các bước sau và thử nó:
1. Lấy một tập hợp các ví dụ huấn luyện đúng¶
Bắt đầu bằng việc tìm một số mẫu huấn luyện tích cực cho thấy một loạt các khuôn mặt.Chúng ta có một tập dữ liệu dễ dàng để làm việc – tập dữ liệu Labeled Faces in the Wild, có thể được tải xuống bởi Scikit-Learn:
from sklearn.datasets import fetch_lfw_peoplefaces = fetch_lfw_people()positive_patches = faces.imagespositive_patches.shape
(13233, 62, 47)
Câu này đưa cho chúng ta một mẫu gồm 13.000 hình ảnh khuôn mặt để sử dụng cho việc huấn luyện.
2. Thu thập một tập hợp các mẫu huấn luyện âm¶
Tiếp theo, chúng ta cần một tập hình thu nhỏ có kích thước tương tự nhau mà không có khuôn mặt trong đó.Một cách để làm điều này là lấy bất kỳ tập hình ảnh nguồn nào và trích xuất hình thu nhỏ từ chúng ở nhiều tỷ lệ khác nhau.Ở đây, chúng ta có thể sử dụng một số hình ảnh được gửi kèm với Scikit-Image, cùng với PatchExtractor
của Scikit-Learn:
from skimage import data, transformimgs_to_use = ['camera', 'text', 'coins', 'moon', 'page', 'clock', 'immunohistochemistry', 'chelsea', 'coffee', 'hubble_deep_field']images = [color.rgb2gray(getattr(data, name)()) for name in imgs_to_use]
from sklearn.feature_extraction.image import PatchExtractordef extract_patches(img, N, scale=1.0, patch_size=positive_patches[0].shape): extracted_patch_size = tuple((scale * np.array(patch_size)).astype(int)) extractor = PatchExtractor(patch_size=extracted_patch_size, max_patches=N, random_state=0) patches = extractor.transform(img[np.newaxis]) if scale != 1: patches = np.array([transform.resize(patch, patch_size) for patch in patches]) return patchesnegative_patches = np.vstack([extract_patches(im, 1000, scale) for im in images for scale in [0.5, 1.0, 2.0]])negative_patches.shape
(30000, 62, 47)
Hiện tại chúng ta có 30.000 mẫu ảnh phù hợp không chứa khuôn mặt.Hãy xem qua một số trong số chúng để có được một ý tưởng về hình dạng của chúng:
fig, ax = plt.subplots(6, 10)for i, axi in enumerate(ax.flat): axi.imshow(negative_patches[500 * i], cmap='gray') axi.axis('off')
Hi vọng của chúng tôi là những thuộc tính này sẽ đầy đủ đáp ứng các trường hợp “không phải khuôn mặt” mà thuật toán của chúng tôi có thể gặp phải.
3. Kết hợp các bộ và trích xuất đặc trưng HOG¶
Bây giờ chúng ta đã có các mẫu tích cực và mẫu tiêu cực, chúng ta có thể kết hợp chúng và tính toán các đặc trưng HOG.Bước này mất một chút thời gian, vì các đặc trưng HOG liên quan đến một phép tính không đơn giản cho mỗi hình ảnh:
from itertools import chainX_train = np.array([feature.hog(im) for im in chain(positive_patches, negative_patches)])y_train = np.zeros(X_train.shape[0])y_train[:positive_patches.shape[0]] = 1
X_train.shape
(43233, 1215)
Chúng ta còn lại 43.000 mẫu huấn luyện trong 1.215 chiều, và hiện nay dữ liệu của chúng ta đã có dạng mà chúng ta có thể feed vào Scikit-Learn!
4. Training a support vector machine¶
Tiếp theo, chúng ta sử dụng các công cụ mà chúng ta đã khám phá trong chương này để tạo một bộ phân loại của các mảnh minh họa nhỏ.Đối với một nhiệm vụ phân loại nhị phân có số chiều cao như thế này, một máy vector hỗ trợ tuyến tính là một lựa chọn tốt.Chúng ta sẽ sử dụng LinearSVC
trong Scikit-Learn, vì so với SVC
, nó thường có khả năng tỉ lệ tốt hơn cho số lượng mẫu lớn.
Tuy nhiên, trước tiên, chúng ta hãy sử dụng một phân tích Bayesian nai để có một cơ sở sơ bộ nhanh:
from sklearn.naive_bayes import GaussianNBfrom sklearn.cross_validation import cross_val_scorecross_val_score(GaussianNB(), X_train, y_train)
array([ 0.9408785 , 0.8752342 , 0.93976823])
Chúng ta nhận thấy trên dữ liệu đào tạo của chúng ta, ngay cả một thuật toán naive Bayes đơn giản đã mang lại độ chính xác lên đến 90%.Hãy thử máy vector hỗ trợ, với việc tìm kiếm trên mạng lưới một số lựa chọn của tham số C:
from sklearn.svm import LinearSVCfrom sklearn.grid_search import GridSearchCVgrid = GridSearchCV(LinearSVC(), {'C': [1.0, 2.0, 4.0, 8.0]})grid.fit(X_train, y_train)grid.best_score_
0.98667684407744083
grid.best_params_
{'C': 4.0}
Hãy chọn mô hình ước lượng tốt nhất và huấn luyện lại nó trên toàn bộ bộ dữ liệu:
model = grid.best_estimator_model.fit(X_train, y_train)
LinearSVC(C=4.0, class_weight=None, dual=True, fit_intercept=True, intercept_scaling=1, loss='squared_hinge', max_iter=1000, multi_class='ovr', penalty='l2', random_state=None, tol=0.0001, verbose=0)
5. Tìm khuôn mặt trong một hình ảnh mới¶
Bây giờ chúng ta đã có mô hình này, hãy lấy một hình ảnh mới và xem mô hình hoạt động như thế nào.Chúng ta sẽ sử dụng một phần của hình ảnh phi hành gia để đơn giản hóa (xem thảo luận về điều này trong Caveats and Improvements), và chạy một cửa sổ trượt qua và đánh giá từng mảnh:
test_image = skimage.data.astronaut()test_image = skimage.color.rgb2gray(test_image)test_image = skimage.transform.rescale(test_image, 0.5)test_image = test_image[:160, 40:180]plt.imshow(test_image, cmap='gray')plt.axis('off');
Tiếp theo, chúng ta hãy tạo ra một cửa sổ mà lặp qua các ổ hình này và tính toán các đặc trưng HOG cho mỗi ổ hình:
def sliding_window(img, patch_size=positive_patches[0].shape, istep=2, jstep=2, scale=1.0): Ni, Nj = (int(scale * s) for s in patch_size) for i in range(0, img.shape[0] - Ni, istep): for j in range(0, img.shape[1] - Ni, jstep): patch = img[i:i + Ni, j:j + Nj] if scale != 1: patch = transform.resize(patch, patch_size) yield (i, j), patch indices, patches = zip(*sliding_window(test_image))patches_hog = np.array([feature.hog(patch) for patch in patches])patches_hog.shape
(1911, 1215)
Cuối cùng, chúng ta có thể lấy các patch được đặc trưng bằng phương pháp HOG và sử dụng mô hình của chúng ta để đánh giá xem mỗi patch có chứa một khuôn mặt hay không:
labels = model.predict(patches_hog)labels.sum()
33.0
Chúng tôi nhận thấy rằng từ hơn 2.000 bản vá, chúng tôi đã tìm thấy 30 phát hiện.Hãy sử dụng thông tin mà chúng tôi có về các bản vá này để chỉ rõ vị trí của chúng trên hình ảnh kiểm tra, vẽ chúng dưới dạng các hình chữ nhật:
fig, ax = plt.subplots()ax.imshow(test_image, cmap='gray')ax.axis('off')Ni, Nj = positive_patches[0].shapeindices = np.array(indices)for i, j in indices[labels == 1]: ax.add_patch(plt.Rectangle((j, i), Nj, Ni, edgecolor='red', alpha=0.3, lw=2, facecolor='none'))
Tất cả các bản vá đã được phát hiện chồng chất và tìm thấy khuôn mặt trong hình ảnh!Không tệ cho vài dòng code Python.
Lưu ý và Cải tiến¶
Nếu bạn đào sâu vào mã và ví dụ trước đó, bạn sẽ thấy chúng ta vẫn còn một chút công việc để hoàn thiện trình nhận diện khuôn mặt sử dụng trong môi trường thực tế.
Bộ dữ liệu đào tạo của chúng tôi, đặc biệt là cho các đặc trưng âm, không quá đầy đủ¶
Vấn đề trung tâm là có nhiều bề mặt giống khuôn mặt không có trong bộ dữ liệu huấn luyện, vì vậy mô hình hiện tại của chúng tôi rất dễ bị sai sót.Bạn có thể nhìn thấy điều này nếu bạn thử thuật toán trên hình ảnh nhà du hành vũ trụ đầy đủ: mô hình hiện tại dẫn đến nhiều phát hiện sai trong các vùng khác của hình ảnh.
Chúng ta có thể tưởng tượng giải quyết điều này bằng cách thêm vào một loạt ảnh đa dạng hơn vào tập huấn luyện tiêu cực, và điều này có thể tạo ra một số cải thiện.Một cách khác để giải quyết điều này là sử dụng một phương pháp có hướng hơn, như hard negative mining.Trong hard negative mining, chúng ta lấy một tập hình ảnh mới mà bộ phân loại của chúng ta chưa nhìn thấy, tìm tất cả các mảnh biểu diễn cho các dương tính sai, và thêm chúng một cách rõ ràng vào các trường hợp tiêu cực trong tập huấn luyện trước khi huấn luyện lại bộ phân loại.
Ống-condu mảng hiện tại chỉ tìm kiếm ở một tỷ lệ¶
Như đã viết, thuật toán của chúng ta sẽ bỏ qua các khuôn mặt không có kích thước gần 62×47 pixels.Vấn đề này có thể giải quyết một cách dễ dàng bằng cách sử dụng các cửa sổ trượt có nhiều kích thước khác nhau, và thay đổi kích thước từng mảnh bằng cách sử dụng skimage.transform.resize
trước khi đưa vào mô hình.Thực tế, tiện ích sliding_window()
đã được xây dựng sẵn với ý này.
Chúng ta nên kết hợp các bản vá phát hiện trùng lắp
Đối với một chuỗi sản xuất sẵn sàng, chúng tôi muốn tránh việc có 30 phát hiện cùng một khuôn mặt, mà thay vào đó, giảm nhóm phát hiện chồng chéo thành một phát hiện duy nhất một cách nào đó.Điều này có thể được thực hiện thông qua một phương pháp gom cụm không giám sát (MeanShift Clustering là một cựu ứng viên tốt cho việc này), hoặc thông qua một phương pháp tạo quy trình như giảm chéo tối đa, một thuật toán phổ biến trong thị giác máy.
Quy trình cần được tối ưu hóa¶
Khi chúng ta đề cập đến những vấn đề này, thì cũng nên tạo ra một quy trình xử lý hình ảnh đào tạo và dự đoán mượt mà hơn.
Các thành tựu gần đây: Học sâu¶
Tôi muốn nhấn mạnh rằng HOG và các phương pháp trích xuất đặc trưng procedural khác cho ảnh không còn là các kỹ thuật tiên tiến nữa.Thay vào đó, nhiều ống dẫn phát hiện đối tượng hiện đại sử dụng các biến thể của mạng nơ-ron sâu: một cách để hiểu về mạng nơ-ron là nó là một bộ ước lượng xác định các chiến lược trích xuất đặc trưng tối ưu từ dữ liệu, thay vì phụ thuộc vào trực giác của người dùng.Cách tiếp cận với các phương pháp mạng nơ-ron sâu này khá phức tạp (về mặt khái niệm và tính toán!), nếu không nằm trong phạm vi của phần này, mặc dù các công cụ mở như TensorFlow từ Google đã làm cho các phương pháp học sâu trở nên dễ tiếp cận hơn so với trước đây.Khi cuốn sách này được viết, học sâu trong Python vẫn còn khá trẻ, và vì vậy tôi chưa thể chỉ ra bất kỳ tài liệu tham khảo xác định nào.Tuy nhiên, danh sách các tài liệu tham khảo trong phần tiếp theo sẽ là một nơi hữu ích để bắt đầu!