選自Medium,作者:Vladimir Goncharov,機器之心編譯。
作者 Vladimir Goncharov 平常主要關注與研究兩個主題:PHP 和 Server Administration(伺服器管理)。在過去的半年中,作者利用空閒時間探索 PHP 與 OpenCV 的結合,並藉此呼叫與訓練優秀的機器學習模型。本文從實踐的角度介紹瞭如何使用 PHP 與 OpenCV 構建人臉檢測、人臉識別、超解析度與目標檢測等系統,因此 PHP 的各位擁躉們,可以盡情使用 OpenCV 探索計算機視覺了。
就像許多開發人員一樣,我也經常使用別人的工作成果(Medium 上的文章、GitHub 上的程式碼等),因此也很樂意與社群分享我的成果。寫文章不僅是對社群的一種回報,還可以讓你找到志趣相投的人,在一個狹小的領域內得到專業人員的指教,並進一步加深你對研究領域的理解。
事實上,本篇文章正是有關這些時刻之一。在本篇文章中,除了那些我看電視節目和玩遊戲的時間,我敘述了在過去六個月的幾乎所有空閒時間裡所做的探索。
現今,「機器學習」發展迅速,並有大量相關的文章,包括那些 Medium 上的部落格,同時幾乎每位開發人員都開始在工作任務和本地專案中使用機器學習,但是從何處開始以及使用什麼方法總是令人困惑的。大多針對初學者的文章提供了一堆文獻,在閱讀中發現這些文章脫離生活,或提供一些「價」的課程等。
通常在新發表的文章中描述瞭解決特定問題的新方法,你可以在 GitHub 上找到文章中方法的實現。由於更普遍使用的程式語言是:C / C ++、Python 2/3、Lua 和 Matlab,以及框架:Caffe、TensorFlow、Torch。因此在程式語言和框架上的大量細分選擇使得找到你所需要的,並整合到專案中的過程變得更加複雜。
OpenCV 中新增的一個 DNN 模組以某種方式減少了這些混亂,它使得你可以直接使用一個在基本框架中訓練過的模型。我會向你展示如何在 PHP 中使用這個模組。
DNN 模組:github.com/opencv/open…
Jeremy Howard(免費的實踐課程「machine learning for coders」的建立者)認為現如今在學習機器學習和實際應用之間存在一個很大的界限。
Howard 認為開始學習機器學習一年的程式設計經驗就足夠了。我完全同意他的觀點,並且我希望我的文章可以幫助那些對機器學習不熟悉,以及還不清楚是否願意從事機器學習的 PHP 開發人員降低 OpenCV 的使用門檻,同時我會盡力闡述我花了大量時間得到的觀點,所以你們甚至都不需要很長的時間就可以瞭解它。
我曾考慮使用 SWIG 寫一個 php-opencv 模組,並花費了大量時間在上面,但是並沒有取得任何成果。一切都因為我不懂 C / C++ 並且沒有為 PHP 7 編寫過擴充套件檔案而變得複雜。不幸的是,網上大多數材料都是基於 PHP 5 而寫的 PHP 擴充套件,因此我不得不一點點收集資訊並自己解決問題。
然後,我在 GitHub 上找到了 php-opencv 庫,它是一個用於呼叫 OpenCV 方法的 PHP 7 模組。我花了幾個晚上來編譯、安裝和執行示例。我開始嘗試這個模組的不同功能,但這個庫還缺少一些方法,因此我就自己新增了它們並建立了一個 pull request,且該庫的作者接受了它們。之後,我新增了更多的功能。
php-opencv:github.com/hihozhou/ph…
這是影象載入的方法:
$image = cv\imread(“images/faces.jpg”);
複製程式碼
相比之下,在 python 下影象載入是這樣的:
image = cv2.imread(“images/faces.jpg”)
複製程式碼
當在 PHP(以及在 C++中)中讀取一張影象時,資訊就儲存在 Mat 物件(矩陣)中。在 PHP 中,類似的是一個多維陣列,但又與多維陣列有所不同,該物件可以進行多種快速操作,例如,所有元素同時除以一個數。在 Python 中,當載入影象時,會返回「NumPy」物件。
小心原有的預設操作!它會發生這樣的情況,imread(在 php、c ++ 和 python 中)不是以 RGB 格式載入影象,而是 BGR 格式。因此,在 OpenCV 的示例中,你經常可以看到轉換 BGR 到 RGB 的過程,反之亦然。
人臉檢測
我第一次嘗試的是這個功能。為此,在 OpenCV 中有一個「CascadeClassifier」類,它可以載入 xml 格式的預訓練模型。在找到人臉之前,該類建議將影象轉換為黑白格式。
$src = imread(“images/faces.jpg”);
$gray = cvtColor($src, COLOR_BGR2GRAY);
$faceClassifier = new CascadeClassifier();
$faceClassifier->load(‘models/lbpcascades/lbpcascade_frontalface.xml’);
$faceClassifier->detectMultiScale($gray, $faces);
複製程式碼
完整測示例程式碼:github.com/php-opencv/…
結果:
從這個示例中可以看出,即使在殭屍妝容的照片上也可以找到一張人臉。特徵點不會干擾人臉的定位。
人臉識別
對於人臉識別,OpenCV 擁有「LBPHFaceRecognizer」類和「train / predict」方法。
如果我們想要知道照片中是誰,首先我們需要使用 train 方法訓練模型,它需要兩個引數:對於這些影象的一個人臉影象的陣列和一個數值標籤的陣列。然後你可以在測試影象(人臉)上呼叫 predict 方法並獲得相匹配的數值標籤。
$faceRecognizer = LBPHFaceRecognizer :: create ();
$faceRecognizer-> train ($myFaces, $myLabels = [1,1,1,1]); // 4 my faces
$faceRecognizer-> update ($angelinaFaces, $angelinaLabels = [2,2,2,2]); // 4 faces of Angelina
$label = $faceRecognizer-> predict ($faceImage, $confidence);
// get label (1 or 2) and confidence
複製程式碼
完整的示例程式碼:github.com/php-opencv/…
資料集:
結果:
當我開始呼叫 LBPHFaceRecognizer 類時,它無法儲存/載入/更新訓練好的模型。事實上,我的第一個 pull request 新增了這些方法:寫入/讀取/更新。
人臉標記/特徵點
當我開始熟悉 OpenCV 時,我經常看到一些人的照片,這些照片上的點標記著眼睛、鼻子、嘴脣等。我想自己重複這個實驗,但在 OpenCV 的 Python 版本中並沒有實現。我花了一個晚上為 PHP 新增了 FacematkLBF 支援並返回一個物件。一切都是簡單易行的,我們載入預訓練的模型,輸入關於人臉的一個陣列,然後得到關於每個人的特徵點的一個陣列。
$facemark = FacemarkLBF::create();
$facemark->loadModel(‘models/opencv-facemark-lbf/lbfmodel.yaml’);
$facemark->fit($image, $faces, $landmarks);
複製程式碼
完整的示例程式碼:github.com/php-opencv/…
結果:
從這個示例中可以看出,殭屍妝容使得找到人臉上的特徵點變得更難。特徵點也會干擾人臉的定位。光照也有影響,在這個例項中,嘴裡的異物(草莓、香菸等)可能不會有干擾。
在我第一次拉拽請求之後,我受到了啟發同時開始瞭解 opencv 可以做些什麼,偶然發現了一篇文章《Deep Learning,now in OpenCV》(OpenCV 中的深度學習)。我立刻決定在 php-opencv 中使用預訓練模型,這些模型在網際網路上有很多。儘管後來我花了很多時間學習如何使用多維矩陣並在不使用 OpenCV 的情況下使用 Caffe / Torch / TensorFlow 模型,但事實證明載入 Caffe 模型並不困難。
Deep Learning,now in OpenCV:github.com/opencv/open…
使用 DNN 模型進行人臉檢測
因此,OpenCV 允許你使用 readNetFromCaffe 函式在 Caffe 中載入預訓練模型。它需要兩個引數:指向 .prototxt 和 .caffemodel 檔案的路徑。prototxt 檔案中有模型的描述,而在 caffemodel 中有模型訓練期間計算的權重。
以下是一個 prototxt 檔案開頭的示例:
input:“data”
input_shape {
dim: 1
dim: 3
dim: 300
dim: 300
}
複製程式碼
這段檔案描述了輸入一個 1x3x300x300 的 4 維矩陣。在對模型的描述中,通常會說明以這種格式輸入的意義是什麼,但在大多數情況下,這意味著將輸入尺寸為 300x300 的 RGB 影象(3 通道)。
通過使用 imread 函式載入一張 300x300 的 RGB 影象,我們得到一個 300x300x3 的矩陣。
OpenCV 中有一個 blobFromImage 函式能將 300x300x3 的矩陣轉換為 1x3x300x300 的格式。
之後,我們可以僅通過使用 setInput 方法將 blob 應用於網路輸入並呼叫 forward 方法,其可以返回最終的結果給我們。
$src = imread(“images/faces.jpg”);
$net = \CV\DNN\readNetFromCaffe(‘models/ssd/res10_300x300_ssd_deploy.prototxt’, ‘models/ssd/res10_300x300_ssd_iter_140000.caffemodel’);
$blob = \CV\DNN\blobFromImage($src, $scalefactor = 1.0, $size = new Size(300, 300), $mean = new Scalar(104, 177, 123), $swapRB = true, $crop = false);
$net->setInput($blob,“”);
$result = $net->forward();
複製程式碼
在這個例項中,結果是一個 1×1×200×7 的矩陣,即每張影象有 7 個元素的 200 個陣列。在一張有 4 張臉的照片中,網路尋找到 200 個候選物件。其中每一個物件的形式為 [,, $confidence, $startX, $startY, $endX, $endY]。元素 $confidence 代表「置信度」,即預測概率有多好,比如 0.75 是好的。之後的元素代表人臉矩形框的座標。在這個示例中,只有 3 張人臉以超過 50% 的置信度被找到,而剩下的 197 個候選物件的置信度小於 15%。
完整的示例程式碼:github.com/php-opencv/…
結果:
從這個示例中可以看出,神經網路「在額頭上「使用時並不總是產生良好的結果。沒有找到第四張臉,但是如果將第四張照片單獨拿出來並匯入神經網路,人臉就會被找到。
使用神經網路提升影象質量
很久之前,我聽說過 waifu2x 庫,它可以消除噪聲並增加圖示/照片的大小。該庫使用 lua 編寫,在底層使用幾種 Torch 中訓練好的模型(為了增加圖示大小,消除照片噪聲等)。該庫的作者將這些模型匯出為 Caffe 並幫助我在 OpenCV 中使用它們。因此,一個示例就是在 PHP 中編寫的用於增加圖示的解析度。
- waifu2x 庫:github.com/nagadomi/wa…
- 示例的完整程式碼:github.com/php-opencv/…
影象分類
在 ImageNet 上訓練的 MobileNet 神經網路可以分類影象。總的來說,它可以區分 1000 個類別,這對我來說還不夠。
示例的完整程式碼:github.com/php-opencv/…
Tensorflow 目標檢測 API
在 COCO 資料集上使用 Tensorflow 訓練的 MobileNet SSD(Single Shot MultiBox Detector)網路不僅可以對影象進行分類,還可以返回目標區域,儘管只能檢測 182 個類別。
示例的完整程式碼:github.com/php-opencv/…
語法高亮和程式碼補全
我還新增了 phpdoc.php 檔案到版本庫中並作為示例。多虧了它,Phpstorm 突出了函式的語法、類和它們的方法,並且還可以用於程式碼補全。這個檔案不需要包含在你的程式碼中(否則會出現錯誤),將其放到你的專案中就足夠了。就個人而言,它使得我的程式設計更輕鬆。這個檔案描述了 OpenCV 中的大多數函式,但不是所有,因此歡迎傳送拉拽請求。
phpdoc.php:github.com/php-opencv/…
安裝
「dnn」模組僅在 OpenCV 3.4 中出現(對於之前的版本它是在 contrib 中)。
Ubuntu 18.04 最新的 OpenCV 版本是 3.2。從原始碼搭建 OpenCV 大約需要 半個小時,所以我在 Ubuntu 18.04 下編譯了這個包(也適用於 17.10 版本,大小 25 MB),同時為 PHP 7.2(Ubuntu 18.04)和 PHP 7.1(Ubuntu 17.10)(大小 100 KB)編譯了 php-opencv 包。註冊 ppa:php-opencv,但還沒上傳完,同時沒有發現比在 GitHub 上傳包更好的。我還建立了一個在 pecl 中申請一個賬戶的請求,但幾個月都沒得到回覆。
在 GitHub 上傳包:github.com/php-opencv/…
因此現在在 Ubuntu 18.04 下的安裝看起來是這樣的:
apt update && apt install -y wget && \
wget https://raw.githubusercontent.com/php-opencv/php-opencv-packages/master/opencv_3.4_amd64.deb && dpkg -i opencv_3.4_amd64.deb && rm opencv_3.4_amd64.deb && \
wget https://raw.githubusercontent.com/php-opencv/php-opencv-packages/master/php-opencv_7.2-3.4_amd64.deb && dpkg -i php-opencv_7.2–3.4_amd64.deb && rm php-opencv_7.2–3.4_amd64.deb && \
echo“extension=opencv.so”> /etc/php/7.2/cli/conf.d/opencv.ini
複製程式碼
安裝這個選項大約需要 1 分鐘,所有安裝選項在 Ubuntu 上進行:github.com/php-opencv/…
我同時編譯了 168 MB 的 docker 映像。
使用示例
下載:
git clone https://github.com/php-opencv/php-opencv-examples.git && cd php-opencv-examples
複製程式碼
執行:
php detect_face_by_dnn_ssd.php
複製程式碼