本文受 PyImageSearch 的讀者 Ezekiel 的啟發,他上個星期在郵件中諮詢道:
Adrian 你好,
我仔細地瀏覽了您之前關於深度學習目標檢測 的文章及其跟進的實時深度學習目標檢測 。感謝你做的這一切,我在自己的樣例專案中使用了你的原始碼,但是我有兩個問題:
1. 我該如何過濾/忽略那些我不感興趣的類?
2. 我如何才能向自己的目標檢測器中增加新類別?有這個可能嗎?
如果你能就這兩個問題寫一篇文章,我將不勝感激。
Ezekiel 並不是受此問題困擾的唯一讀者。事實上,如果你仔細瀏覽了我最近關於深度目標檢測兩篇文章的評論,你會發現最常見的問題可以被表述為:
我該如何修改你的原始碼來包含我自己的類別?
由於這是一個如此常見的問題,並且是關於神經網路/深度學習目標檢測器實際工作的一個誤解,所以我決定在今天的部落格中重溫深度學習目標檢測的話題。
具體地,你將在這篇文章中學到以下內容:
影像分類和目標檢測的區別
深度學習目標檢測器的組成:包含不同目標檢測架構的區別和基本模型之間的區別
如何使用預訓練模型進行深度學習目標檢測
如何從一個深度學習模型中過濾或者忽略一些預測類別
向深度神經網路增加類別或從中刪除類別時常見的誤區和誤解
為了更多地瞭解深度學習目標檢測,並解釋清楚我們對基於深度學習的目標檢測的一些誤區和誤解,請繼續閱讀本文。
想要檢視本文相關的原始碼?請檢視原文的下載連結:https://www.pyimagesearch.com/2018/05/14/a-gentle-guide-to-deep-learning-object-detection/#
深度學習目標檢測的一般指南
今天的部落格是對基於深度學習的目標檢測的簡單介紹。我儘可能對深度學習目標檢測器的組成做一個概述,包括使用預訓練的目標檢測器執行任務的原始碼。
你可以使用這份指南來幫助學習深度學習目標檢測,但是也要意識到,目標檢測是高度細節化的工作,我不可能在一篇文章中包含關於深度學習目標檢測的所有細節。
這篇文章將從討論影像分類和目標檢測之間的本質區別開始,其中包括判斷一個影像分類網路是否可以用於目標檢測,以及在什麼情況下可以這樣使用等話題。
當我們理解了什麼是目標檢測時,隨後會概述一個深度學習目標檢測器的核心模組。它一般包括目標檢測架構和基本模型,不熟悉目標檢測的讀者可能會誤解這兩個部分。
在這裡,我們將使用 OpenCV 來實現實時深度學習目標檢測。我也會展示如何在不修改網路架構或者重新訓練的情況下忽略或者過濾一些不感興趣的目標類別。最後,我們透過討論如何從深度學習目標檢測器中增加或者刪除類別來總結本文。
影像分類和目標檢測的區別
圖 1: 影像分類(左)和目標檢測(右)的區別是比較直觀和簡單的。在影像分類中,整幅影像被分類為單一的標籤。而在目標檢測中,我們的神經網路還要找出影像中目標的位置(有可能是多個)。
在進行標準的影像分類時,我們將一張給定的影像輸入到神經網路,然後得到一個最可能的標籤,而且也許會同時得到相關的機率。
這個類別標籤用來表徵整個影像的內容,或者至少是影像最主要的可見內容。例如,上面的圖 1 中,給定輸入影像(左),我們的 CNN 給它的標籤是「比格犬」。所以我們可以認為影像分類具有以下特點:
一張影像輸入
一個類別標籤輸出
無論是透過深度學習還是其他計算機視覺技術的目標檢測,都是基於影像分類構建的,只不過需要精確定位每個物件在影像中出現的位置。在進行目標檢測的時候,給定一張輸入影像,我們期望得到:
一個邊界框列表,或者一幅影像中每個物件的(x,y)座標
與每個邊界框關聯的類別標籤
與每個邊界框和類別標籤關聯的機率或者置信度得分
圖 1(右)展示了一個深度學習目標檢測的例子。請注意,人物和狗都被用邊界框找出了位置,同時類標籤也被預測到了。
所以,目標檢測允許我們:
向網路輸入一張影像
得到多個邊界框以及類別標籤
深度學習影像分類可以被用於目標檢測嗎?
圖 2:非端到端深度學習的目標檢測器使用一個滑動視窗(左)+影像金字塔(右)相結合的方法來分類。
所以現在你理解了影像分類和目標檢測的根本區別:
在進行影像分類時,我們輸入一張影像,得到一個輸出類別
然而在進行目標檢測時,我們輸入一張影像,得到多個邊界框以及類別標籤的輸出
這自然引發這麼一個問題:
我們可以拿一個已訓練的分類網路,將其用於目標檢測嗎?
這個答案有些棘手,因為這在技術上是可以的,但是理由並不太明顯。解決方案涉及:
1. 應用基於計算機視覺的標準目標檢測方法(非深度學習方法),例如滑動視窗和影像金字塔等方法通常被用在 HOG+基於線性 SVM 的目標檢測器。
2. 採用預訓練的網路,並將其作為深度學習目標檢測架構的基本網路(例如 Faster R-CNN, SSD, YOLO)。
方法 #1: 傳統的目標檢測技術路線
第一個方法不是純端到端的深度學習目標檢測器。相反,我們使用:
1. 固定尺寸的滑動視窗,它從左到右,自上而下滑動,來定位不同位置的物件。
2. 影像金字塔,用來檢測不同尺度的物件
3. 一個預訓練(分類)的 CNN 來分類
在滑動窗和對應影像金字塔每一次停留的時候,我們會提取 ROI(感興趣區域),將其輸入到 CNN 中,得到對 RIO 的分類。
如果標籤 L 的分類機率比某個閾值 T 高,我們就將這個 ROI 的邊界框標記為該標籤(L)。對滑動窗和影像金字塔的每次停留都重複這個過程,我們就得到了目標檢測器的輸出。最終,我們對邊界框應用非極大值抑制(NMS),得到最終輸出的檢測結果:
圖 3:應用 NMS 會抑制重疊的和置信度不高的邊界框。這個方法在一些特定的用例中是有效的,但是它通常比較慢和繁瑣,也容易出錯。
然而,這個方法也是值得學習的,因為它可以將任意影像分類網路轉換為一個目標檢測器,而不需要顯式地訓練一個端到端的深度學習目標檢測器。這個方法可以節省大量的時間和精力,且效率的高低具體取決於你的用例。
方法 #2:目標檢測架構的基本網路
第二個深度學習目標檢測的方法允許我們將一個預訓練的分類網路作為深度學習目標檢測架構(例如 Faster R-CNN、SSD 或者 YOLO)的基本網路。
這個方法的好處是:你可以建立一個基於深度學習的複雜端到端目標檢測器。
而其不足之處是:它需要一些關於深度學習目標檢測器如何工作的知識,我們將在後面的部分中討論這個問題。
深度學習目標檢測器的模組
圖 4: VGG16 基本網路是 SSD 深度學習目標檢測框架的一個特徵抽取模組。
深度學習目標檢測器有很多模組,子模組以及更小的子模組,但是我們今天要重點關注的是深度學習入門讀者所困惑的兩個:
1. 目標檢測框架(不包括 Faster R-CNN, SSD, YOLO)
2. 適合目標檢測框架的基本網路
你可能已經比較熟悉基本網路(只是你之前並沒聽到它被稱作基本網路而已)。基本網路就是你常用的分類 CNN 架構,包括:
VGGNet
ResNet
MobileNet
DenseNet
通常這些網路在大資料集上進行預訓練來進行分類,例如 ImageNet,它們可以學習到很多具有鑑別能力的濾波器。
目標檢測框架由很多組成部分和子模組構成。例如,Faster R-CNN 框架包括:
候選區域網路(RPN)
一組錨點
ROI 池化模組
最終基於區域的卷積神經網路
在使用 SSD(單步檢測器,single shot detectors)時,具有以下的組成部分:
多框(MultiBox)
先驗(Priors)
固定先驗(Fixed priors)
請記住,基本網路只是整個深度學習目標檢測框架的眾多元件之一,上文圖 4 描述了 SSD 框架內部的 VGG-16 網路。通常,我們需要在基本網路上進行「網路手術」。這種修改:
讓它變成全卷積的形式,並接受任意輸入維度。
剪除了基本網路中更深層的卷積和池化層,將它們以一系列新層(SSD)、新模組(Faster R-CNN),或者這兩者的一些組合代替。
這裡的「網路手術」是一種口語化的說法,它的意思是移除基本網路中的一些原始卷積層,將它們用新層替代。網路手術也是講究策略的,我們移除一些不需要的部分,然後用一組新的部分來替代它們。
然後,當我們開始訓練我們的框架進行目標檢測時,(1)新層、模組和(2)基本網路的權重都被修改了。
再強調一次,綜述關於不同深度學習目標檢測框架是如何工作的(包括基本網路所起的作用)並不屬於本文的探討範圍。
如果你對深度學習目標檢測的完整綜述(包括理論和實現)感興趣,請參考機器之心曾經發過的文章:從 RCNN 到 SSD,這應該是最全的一份目標檢測演算法盤點 。
我是如何計算一個深度學習目標檢測器的準確度的?
在評價目標檢測器的效能時我們使用了一個叫做均值平均精度(mAP)的指標,它是以我們資料集中所有類別的交併比(IoU)為基礎的。
交併比(IoU)
圖 5: 在這個交併比的視覺化例子中,標註邊界框(綠色)可以與預測的邊界框(紅色)進行對比。IoU 與 mAP 一起被用來評價一個深度學習目標檢測器的精度。計算 IoU 的簡單方程如圖 5(右)所示。
你通常會發現 IoU 和 mAP 被用於評價 HOG+線性 SVM 檢測器、Haar cascades 以及基於深度學習的方法的效能;但是請記住,實際用於生成預測邊界框的演算法並不是那麼重要。
任何一個以預測邊界框作(以及可選擇的標籤)為輸出的演算法都可以用 IoU 來評價。更一般的地,為了使用 IoU 來評價任意一個目標檢測器,我們需要:
1. 真實的邊界框(也就是測試集中表明我們的目標在影像的哪個位置的人工標籤)
2. 模型預測到的邊界框
3. 如果你想一起計算召回率和精度,那麼還需要真實類別標籤和預測類別標籤
在圖 5(左)中,我展示了真實邊界框(綠色)與預測邊界框(紅色)相比的視覺化例子。IoU 的計算可以用圖 5 右邊的方程表示。
仔細檢查這個方程你會發現,IoU 就是一個比值。在分子項中,我們計算了真實邊界框和預測邊界框重疊的區域。分母是一個並集,或者更簡單地說,是由預測邊界框和真實邊界框所包括的區域。兩者相除就得到了最終弄的得分:交併比。
平均精度均值(MAP)
圖 6:為了計算目標檢測器的 mAP@0.5,我們執行了以下計算。對於所有被標記為「正檢測」(positive detection)、具備至少 0.5 的交併比(IoU)的物件,我們對所有 N 個類別計算 IoU (>0.5) 均值,然後對 N 個均值再求平均。這個指標就是 mAP@0.5。
為了在我們的資料集中評估目標檢測器,我們需要同時基於以下兩者的 IoU 來計算 mAP:
1. 基於每個類別(也就是說每個類別的平均 IoU);
2. 資料集中所有類別(也就是說所有類別平均 IoU 的均值,所以這個術語就是平均精度均值)。
為了計算每個類別的平均精度,我們在所有的資料點上計算某個類別的 IoU。一旦我們計算出了一個類別在每個資料點的 IoU,我們對它們求一次平均(第一次平均)。
為了計算 mAP,我們對所有的 N 個類別計算平均 IoU,然後對這 N 個平均值取平均值(均值的平均)。
通常我們使用 mAP@0.5,表示測試集中要被標記為「正檢測」的目標必須具備的條件,真值不小於 0.5 的 IoU(並可以被正確地標記)。這裡的 0.5 是可以調整的,但是在所有的目標檢測資料集和挑戰中,0.5 是一個相對標準的數值。
再次強調,這只是一個關於目標檢測評價指標的快速指南,所以我將整個過程簡化了一些。
使用 OpenCV 進行基於深度學習的目標檢測
我們已經在本文以及之前的部落格中討論了深度學習和目標檢測。出於完整性考慮,我們將在本文中概述實際的程式碼。
我們的例子包含以 MobileNet 作為基礎模型的單次檢測器(SSD)。該模型由 GitHub 使用者 chuanqi305(https://github.com/chuanqi305/MobileNet-SSD)在 COCO 資料集上訓練得到。更多細節請瀏覽我之前的文章(https://www.pyimagesearch.com/2017/09/11/object-detection-with-deep-learning-and-opencv/),這篇文章介紹了該模型以及相關的背景資訊。
讓我們回到 Ezekiel 在本文開始提出的第一個問題上。
1. 我該如何過濾/忽略那些我不感興趣的類?
我會在下面的示例程式碼中回答這個問題,但是首先你需要準備一下系統:
你需要在 Python 虛擬環境中安裝版本不低於 3.3 的 OpenCV(如果你在使用 python 虛擬環境的話)。OpenCV 3.3+ 包含執行以下程式碼所需的 DNN 模組。確保使用連結中的 OpenCV 安裝教程之一(https://www.pyimagesearch.com/opencv-tutorials-resources-guides/),要額外注意你下載和安裝的 OpenCV 版本。
你還應該安裝我的 imutils 包(https://github.com/jrosebr1/imutils)。為了在 Python 虛擬環境中安裝/更新 imutils,簡單地使用以下命令即可: pip install --upgrade imutils。
系統準備好之後,建立一個新檔案,命名為 filter_object_detection.py。下面讓我們開始:
在第 2 到 8 行中,我們匯入了所需的包和模組,尤其是 imultils 和 OpenCV。我們會使用我的 VideoStream 類處理從攝像頭獲取的幀。
我們已經具備了所需的工具,接著我們來解析命令列引數:
我們的指令碼在執行時需要兩個命令列引數:
--prototxt : The path to the Caffe prototxt file which defines the model definition.
--model : Our CNN model weights file path.
--prototxt:Caffe prototxt 檔案的路徑,它定義了模型的定義。
--model:CNN 模型權重的檔案路徑。
你還可以有選擇性地指定--confidence,這是過濾弱檢測的閾值。
我們的模型可以預測 21 個物件類別:
CLASSES 列表包含該網路訓練時的所有類別標籤(也就是 COCO 中的標籤)。
對 CLASSES 列表的一個常見誤解是你可以:
1. 向列表增加一個新的類別標籤;
2. 或者從列表移除一個類別標籤。
……以及以為網路可以自動「瞭解」你想要完成的任務。
不是這樣的。
你不能簡單地修改文字標籤列表,讓網路自動修改自己,在非訓練所用資料上學習、增加或者移除模式。這並不是神經網路的執行方式。
也就是說,有一個快速的技巧,你可以使用它來過濾或者忽略你不感興趣的預測。
解決方案就是:
1. 定義一個 IGNORE 標籤集合(即網路是在這個類別標籤列表上進行訓練的,但你現在想忽略這些標籤)。
2. 對一個輸入影像/影片幀進行預測。
3. 忽略類別標籤存在於 IGNORE 集合中的所有預測結果。
在 Python 中實現時,IGNORE 集合是這樣的:
這裡我們忽略所有具有類別標籤「person」的預測物件(用於過濾的 if 語句會在後續內容中介紹)。
你可以很容易地增加額外的元素(CLASS 列表中的類別標籤)來忽略該集合。
接下來,我們將生成隨機的類別/框顏色,載入模型,然後啟動影片流:
第 27 行中名為 COLORS 的隨機陣列為 21 個類別中的每一個隨機生成顏色。這些顏色會在後邊用於顯示。
第 31 行中使用 cv2.dnn.readNetFromCaffe 函式載入我們的 Caffe 模型,我們所需的兩個命令列引數作為引數被傳遞。
然後我們將 VideoStream 物件例項化為 vs,並開始 fps 計數(36-38 行)。2 秒鐘的 sleep 讓我們的攝像頭有足夠的預熱時間。
現在我們已經準備好在來自攝像頭的影片幀中進行迴圈,並將它們傳送到我們的 CNN 目標檢測器中:
在第 44 行,我們抓取 1 幀,然後重新調整它的大小並保留用於顯示的長寬比(第 45 行)。
我們從中提取高度和寬度,稍後會用到(第 48 行)。
第 48 行和 49 行從這一幀中生成 blob。要了解更多 blob,以及如何使用 cv2.dnn.blobFromImage 函式構建 blob,請在以前的博文中檢視所有細節(https://www.pyimagesearch.com/2017/11/06/deep-learning-opencvs-blobfromimage-works/)。
下一步,我們將 blob 傳送到神經網路中來檢測目標(54-55 行)。
迴圈檢測:
從第 58 行開始檢測迴圈。
對於每一次檢測,我們都提取 confidence(#61 行),然後將它與置信度閾值進行比較(#65 行)。當 confidence 超過最小值(預設的 0.5 可以透過命令列引數進行修改),我們可以認為這次檢測是一次積極有效的檢測,可以繼續進行處理。
首先,我們從 detections 中提取類別標籤索引(#68)。
然後,回到 Ezekiel 的第一個問題,我們可以忽略 INGNORE 集合中的類別(#72—73)。如果這個類別是要被忽略的,我們只需返回到頂部的檢測迴圈(不會顯示這個類別的標籤或邊界框)。這符合我們的「quick hack」解決方案。
否則,我們檢測到的目標就在白名單中,我們需要在該幀中顯示對應的類別標籤和矩形框:
在這段程式碼中,我們提取出了邊界框的座標(#77-78),然後畫出這幀的標籤和矩形框(#81-87)。
每個類別的標籤和矩形框都是同樣的顏色,也就是說,同一類別的物件都會具有相同的顏色(即影片中所有的「boats」都具有相同顏色的標籤和邊界框)。
最後,仍然在這個 while 迴圈中,我們將結果顯示在螢幕上:
我們顯示出這一幀,並且捕捉按鍵(#90-91)。
如果 q 鍵被按下,則我們透過跳出迴圈來結束程式(#94-95)。
否則,我們會繼續更新 fps 計數(#98),並且繼續抓取並分析影片幀。
在後面幾行中,當迴圈中斷後,我們會顯示時間+fps(幀每秒)指標然後清空。
執行你的深度學習目標檢測器
為了執行今天的指令碼,你需要滾動到下面的「下載」部分來抓取檔案。
當你提取到檔案之後,開啟一個終端,切換到已下載程式碼+模型的路徑。並在這裡執行以下命令:
圖 6: 使用同一個模型的實時深度學習目標檢測演示,在右邊的影片中我在程式中忽略了某些目標類別。
在上邊的動圖中,你在左邊可以看到「person」類別被檢測到了。這是由於 IGNORE 是空的。在右邊的動圖中,你可以看到我沒有被檢測到,這是由於我把「person」增加到了 IGNORE 集合了。
儘管我們的深度學習目標檢測器仍然從技術上檢測到了「person」類別,但是我們的後期處理程式碼將它過濾出來了。
也許你在執行這個深度學習目標檢測器的時候會遇到錯誤?
排除故障的第一步是檢查你是否連線了攝像頭。如果這個是正常的,也許你會在你的終端中看到以下錯誤資訊:
如果你看到這個資訊,那說明你沒有向程式傳遞「命令列引數」。如果他們不熟悉 Python、argparse 以及命令列引數的話(https://www.pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/)。
這是 PyImageSearch 讀者最常遇見的問題。檢視一下這個連結,看看你是否存在這個問題。帶有註釋的完整影片在這裡:https://youtu.be/5cwFBUQb6_w
如何向深度學習目標檢測器新增或者刪除類別?
圖7:深度學習目標檢測的微調過程和遷移學習。
正如我在這份指南中前期所提到的,你不能在 CLASS 列表中簡單地增加或者刪除類別,基礎的網路並沒有改變。
你所能做的,最好就是修改一個能夠列出所有類別標籤的文字檔案。
此外,如果你想顯式地在神經網路中增加或者刪除類別,你需要做的工作有:
1. 從零開始訓練
2. 進行微調
從零開始訓練通常會比較耗時間,是一個代價昂貴的操作,所以我們儘可能避免,但是在一些情況下是無法避免的。另一個選擇就是進行微調。微調是一種遷移學習的形式,它是這樣的過程:
1. 刪除負責分類/打標籤的全連線層
2. 並用全新的、隨機初始化的全連線層替代
我們也可以選擇性地修改網路中的其它層(包括在訓練過程中凍結一些層的權重,以及解凍它們)。
準確來說,如何訓練你自己的定製的深度學習目標檢測器(包括微調和從零開始訓練)是比較高階的話題,並不屬於本文的討論範圍,但是閱讀下面的部分可以有助於你開始這項工作。
我可以在哪裡學到更多關於深度學習目標檢測的內容?
圖 8: 汽車前後視角的實時深度學習目標檢測
正如我們在這篇博文中討論過的,目標檢測並不是像影像分類一樣簡單,其細節和複雜度超出了本文的範圍(我已經囉嗦了好多遍了)。
本教程肯定不是我在深度學習目標檢測方方面的最後一篇文章(毫無疑問,我會在未來寫更多關於深度學習目標檢測的文章),但是如果你對學習以下內容感興趣:
1. 為目標檢測準備你的資料集。
2. 在你的資料集上精調並訓練你自己的定製化目標檢測器,包括 Faster R-CNN 和 SSD。
3. 瞭解我的最好的實踐做法、技術和過程,並使用它們來訓練自己的深度學習目標檢測器。
... 然後,你可能會想看一下我的新書(https://www.pyimagesearch.com/deep-learning-computer-vision-python-book/)。在《Deep Learning for Computer Vision with Python》一書中,我會一步一步地指導你構建自己的深度學習目標檢測器。
總結
這篇部落格簡單介紹了深度學習目標檢測所涉及的一些難點。我們以概述影像分類和目標檢測之間的本質區別作為開始,包括如何將一個影像分類的神經網路用於目標檢測。
然後我們概述了深度學習目標檢測器的核心組成:
1. 檢測框架
2. 基本模型
基本模型通常是一個預訓練的(分類)網路,為了學習到一系列具有辨識能力的濾波器,一般是在大型影像資料集(例如 ImageNet)上進行訓練的。
我們也可以從零開始訓練基本網路,但是,對於目標檢測器而言,為了達到較合理的準確率。這通常需要更長的訓練時間。
在絕大多數情況下,你應該以一個預訓練的基本模型作為開始,而不是嘗試著從零開始訓練。
當我們對深度學習目標檢測器有了充分的理解之後,我們就可以在 OpenCV 中實現能夠實時執行的目標檢測器。
我還概述瞭如何過濾或者忽略那些不感興趣的類別標籤。
最後,我們瞭解到:實際地向深度學習目標檢測器增加一個類別標籤,或者從深度學習目標檢測器中刪除一個類別標籤並不是像從硬編碼的標籤列表張增加或者刪除標籤一樣簡單。
神經網路本身並不在乎你是否修改了類別標籤,相反,你需要:
1. 透過刪除全連線目標預測層並進行調整來修改網路結構
2. 或者從零開始訓練目標檢測網路框架
對於更多的深度學習目標檢測專案,你需要從一個基於目標檢測任務(例如 COCO)的預訓練深度學習目標檢測器開始。然後基於預訓練模型進行微調,以得到你自己的檢測器。
訓練一個端到端的定製深度學習目標檢測器並不屬於本文的範疇,所以,如果你對探索如何訓練自己的目標檢測器感興趣,請參考我的書籍《deep learning for computer vision with python》。
在這本書中,我描述了一些深度學習目標檢測的例子,包括為以下任務訓練你自己的深度學習目標檢測器:
1. 檢測交通標誌,例如 Stop 標誌,人行橫道標誌等等。
2. 汽車的前後視角
原文連結:
https://www.pyimagesearch.com/2018/05/14/a-gentle-guide-to-deep-learning-object-detection/