天天看點

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

在前幾篇文章中我們看到了怎樣檢測圖檔上的物體,例如人臉,那麼把實作人臉識别的時候是不是可以把圖檔中的人臉截取出來再交給識别人臉的模型呢?下面的流程是可行的,但因為人臉的範圍不夠準确,截取出來的人臉并不在圖檔的正中心,對于識别人臉的模型來說,資料品質不夠好就會導緻識别的效果打折。

這一篇文章會介紹如何使用機器學習檢測臉部關鍵點 (眼睛鼻子嘴巴的位置),檢測圖檔上的人臉以後,再檢測臉部關鍵點,然後基于臉部關鍵點來調整人臉範圍,再根據調整後的人臉範圍截取人臉并交給後面的模型,即可提升資料品質改善識别效果。

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

臉部關鍵點檢測模型其實就是普通的 CNN 模型,在第八篇文章中已經介紹過🤒,第八篇文章中,輸入是圖檔,輸出是分類 (例如動物的分類,或者驗證碼中的字母分類)。而這一篇文章輸入同樣是圖檔,輸出則是各個臉部關鍵點的坐标:

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

我們會讓模型輸出五個關鍵點 (左眼中心,右眼中心,鼻尖,嘴巴左邊角,嘴巴右邊角) 的 x 坐标與 y 坐标,合計一共 10 個輸出。

模型輸出的坐标值範圍會落在 -1 ~ 1 之間,這是把圖檔左上角視為 -1,-1,右下角視為 1,1 以後正規化的坐标值。不使用絕對值的原因是機器學習模型并不适合處理較大的值,并且使用相對坐标可以讓處理不同大小圖檔的邏輯更加簡單。你可能會問為什麼不像前一篇介紹的 YOLO 一樣,讓坐标值範圍落在 0 ~ 1 之間,這是因為下面會使用仿射變換來增加人臉樣本,而仿射變換要求相對坐标在 -1 ~ 1 之間,讓坐标值範圍落在 -1 ~ 1 之間可以省掉轉換的步驟。

準備資料集是機器學習中最頭疼的部分,一般來說我們需要上百度搜尋人臉的圖檔,然後一張一張的截取,再手動标記各個器官的位置,但這樣太苦累了😭。這篇還是像之前的文章一樣,從網上找一個現成的資料集來訓練,偷個懶🤗。

使用的資料集:

https://www.kaggle.com/drgilermo/face-images-with-marked-landmark-points

下載下傳回來以後可以看到以下的檔案:

<code>face_images.npz</code> 是使用 zip 壓縮後的 numpy 資料轉儲檔案,把檔案名改為 <code>face_images.zip</code> 以後再解壓縮即可得到 <code>face_images.npy</code> 檔案。

之後再執行 python 指令行,輸入以下代碼加載資料内容:

可以看到資料包含了 7049 張 96x96 的黑白人臉圖檔。

再輸入以下代碼儲存第一章圖檔:

這就是提取出來的圖檔:

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

對應以下的坐标,坐标的值可以在 <code>facial_keypoints.csv</code> 中找到:

各個坐标對應 csv 中的字段如下:

左眼中心點的 x 坐标: left_eye_center_x

左眼中心點的 y 坐标: left_eye_center_y

右眼中心點的 x 坐标: right_eye_center_x

右眼中心點的 y 坐标: right_eye_center_y

鼻尖的 x 坐标: nose_tip_x

鼻尖的 y 坐标: nose_tip_y

嘴巴左邊角的 x 坐标: mouth_left_corner_x

嘴巴左邊角的 y 坐标: mouth_left_corner_y

嘴巴右邊角的 x 坐标: mouth_right_corner_x

嘴巴右邊角的 y 坐标: mouth_right_corner_y

csv 中還有更多的坐标但我們隻使用這些🤒。

接下來定義一個在圖檔上标記關鍵點的函數:

再使用這個函數标記圖檔即可得到:

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

仔細觀察 csv 中的坐标值,你可能會發現裡面的坐标大多都是很接近的,例如左眼中心點的 x 坐标大部分都落在 65 ~ 75 之間。這是因為資料中的人臉圖檔都是經過處理的,占比和位置比較标準。如果我們直接拿這個資料集來訓練,那麼模型隻會輸出學習過的區間的值,這是再拿一張占比和位置不标準的人臉圖檔給模型,模型就會輸出錯誤的坐标。

解決這個問題我們可以随機旋轉移動縮放人臉以增加資料量,在第十篇文章我們學到怎樣用仿射變換來提取圖檔中的某個區域并縮放到固定的大小,仿射變換還可以用來實作旋轉移動和縮放,批量計算時的效率非常高。

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

首先我們需要以下的變量:

弧度,範圍是 -π ~ π,對應 -180°~ 180°

縮放比例,1 代表 100%

橫向移動量:範圍是 -1 ~ 1,把圖檔中心視為 0,左邊視為 -1,右邊視為 1

縱向移動量:範圍是 -1 ~ 1,把圖檔中心視為 0,左邊視為 -1,右邊視為 1

根據這些變量生成仿射變換參數的公式如下:

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

需要注意的是仿射變換參數用于轉換 目标坐标 到 來源坐标,在處理圖檔的時候可以根據目标像素找到來源像素,然後設定來源像素的值到目标像素的值實作各種變形操作。上述的參數隻能用于處理圖檔,如果我們想計算變形以後的圖檔對應的坐标,我們還需要一個轉換 來源坐标 到 目标坐标 的仿射變換參數,計算相反的仿射變換參數的公式如下:

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

翻譯到代碼如下:

變形後的人臉樣本如下,背景添加了随機顔色讓模型更難作弊,具體代碼參考後面的 <code>prepare</code> 函數吧😰:

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

完整代碼的時間到了🥳,結構跟前面的文章一樣,分為 <code>prepare</code>, <code>train</code>, <code>eval</code> 三步。

文章中的 Resnet 模型直接引用了 torchvision 中的實作,結構在第八篇文章已經介紹過,為了支援黑白圖檔修改了 <code>conv1</code> 使得入通道數變為 1。

另外一個細節是部分關鍵點的資料是缺損的,例如隻有左眼和右眼的坐标,但是沒有鼻子和嘴巴的坐标,缺損資料在讀取的時候會變為 <code>nan</code>,是以代碼中計算損失和正确率的時候會排除掉值為 <code>nan</code> 的資料,這樣即使同一資料的部分坐标缺損模型仍然可以學習沒有缺損的坐标。

把代碼儲存到 <code>face_landmark.py</code>,然後按以下的檔案夾結構放代碼和資料集:

dataset

face-images-with-marked-landmark-points

face_images.npy

facial_keypoints.csv

face_landmark.py

再執行以下指令即可開始訓練:

最終訓練結果如下:

坐标的偏差大約是 <code>(1 - 0.976565406219812) * 2</code> 即相對圖檔長寬的 4.68% 左右🤔。

再執行以下指令即可使用訓練好的模型:

以下是部分識别結果😤:

寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測
寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測
寫給程式員的機器學習入門 (十二) - 臉部關鍵點檢測

有一定誤差,但是用來調整臉部範圍是足夠了。此外,訓練出來的模型檢測出來的鼻尖坐标會稍微偏下,這是因為訓練資料中的大部分都是鼻子比較高的白人😡,我們看到新聞裡面說人臉識别模型對于黑人識别率很低也是因為樣本中絕大部分都是白人,資料的偏向會直接影響模型的檢測結果🤒。

這篇比較簡單,下一篇将會介紹人臉識别模型,到時會像這篇最開始給出的圖檔一樣結合三個模型實作。

最後罵一句部落格園的傻逼驗證碼,這種驗證碼隻是拿來惡心使用者,對安全沒啥實質性的幫助🤬。