目標檢測簡介
目標檢測是計算機視覺中一個重要的研究方向。人眼可以輕松、準確地識別出圖片中的物體是什么、這個物體在圖片中的哪個位置。
例如,當我們看到下圖左邊的圖片時,我們可以輕松的識別出圖片中的動物是貓和狗,并且知道它們在圖片中所處的位置。但是對于計算機來說,在以數字形式表示的圖片中尋找目標物體,并判斷這個物體是什么,這是一件困難的事情。目標檢測的目的就是使計算機能夠識別圖片中的目標(物體、動物等)是什么、這個目標的位置在哪里。如下右邊的圖片所示,通過檢測算法的幫助,計算機不僅能識別出圖中的動物,還可以標記出其所處的位置。
目標檢測的發展可以分為兩個階段:傳統檢測算法和基于深度學習的檢測算法。
傳統的目標檢測算法是通過將人為設計的目標特征和機器學習的分類器相結合來實現的。
基于深度學習的檢測算法可分為兩類,一類是使用基于候選區域(Region Proposal)的方法先找出圖片中可能存在目標的區域,然后通過卷積神經網絡對該區域進行分類;另一類是直接使用卷積神經網絡預測目標所屬類別的概率和其在圖片中的位置坐標。
由于深度學習的崛起,推動了目標檢測的快速發展和應用,如今目標檢測已經廣泛的應用于我們的日常生活中,如自動駕駛、安保監控、醫療影像、機器視覺等領域。雖然目標檢測在發展過程中取得了很多成果,但同時也面臨很多挑戰,例如目標檢測在實時性、抗干擾性、工業的大規模應用等方面還存在很多阻礙。
相關的工具的介紹
OpenCV
是一個基于 C++ 編寫的輕量級高效的開源許可發行的跨平臺計算機視覺庫,可運行在多種操作系統上:Windows、Mac OS、Linux、Android。由于其具有友好的可讀性和運行的效率,故獲得大量開發者的青睞,同時其還提供 Python、Ruby 等語言的接口方便開發者調用。本教程將使用 Python 語言對 OpenCV 的庫函數進行調用。大家可以訪問 OpenCV 官方網站 獲取更多相關知識。
NumPy
是一個支持處理多維度大型矩陣的 Python 科學計算包。在對圖像進行處理時經常會用到 NumPy,OpenCV 中讀取存儲圖片都是以 NumPy 形式完成的。利用 NumPy 我們可以輕松的以多維數據的形式呈現圖片,并對圖片進行重組、計算、數值分析等操作。想要深入學習可以訪問 NumPy 官方中文文檔。
TensorFlow
是一個開源的機器學習平臺,其廣泛應用于機器學習和深度學習算法實現上。其可運行在多種平臺上,如 PC、移動和分布式平臺。支持多種計算機語言:C++、Python、Java、Go等。由于其靈活、便捷、高性能以及活躍的社區等因素使得 TensorFlow 成為目前最受歡迎的機器學習開源框架之一。本教程將使用 Python 語言對 TensorFlow 進行調用。想要更深入學習該框架可以訪問 TensorFlow 官方網站。
scikit-learn
是基于 Python 的開源機器學習工具,其涵蓋大部分機器學習算法,包括分類、回歸、非監督分類、數據降維、數據預處理等,是一款高效的數據挖掘和分析的工具。其通過 NumPy、SciPy 和 Matplotlib 等實現多種算法并且可在各種環境中使用。關于該工具更詳細的信息可以參考 scikit-learn 官方網站。
scikit-image
是一個基于 Python 的開源圖像處理工具,其將圖片作為 NumPy 數組,并包括分割、幾何變換、過濾等處理方法。
滑動窗口(Sliding Windows)
當我們構建一個傳統的目標檢測方法時,首先需要提供待檢測圖片,然后將滑動窗口(Sliding Windows)和圖像金字塔(Image Pyramid)這兩種方法相結合,從圖片中選擇出一些區域。接下來通過一些算法提取出這些區域的特征信息,然后通過機器學習的分類器對提取的特征分類。目標檢測的輸出結果一般是使用矩形框標記出要檢測的目標,但是使用滑動窗口和圖像金字塔時在同一個目標上會標記出多個矩形框,所以我們需要使用非極大值抑制(Non-maxima suppression)來剔除多余的矩形框,確保每個目標只用一個矩形框標記。
滑動窗口(Sliding Windows)在目標檢測過程中的作用是定位目標(物體、動物等)在圖片中的位置。在計算機視覺中滑動窗口是一個矩形框,它沿著從左向右、從上向下的方向在圖片上滑動以達到提取出圖片中每一個區域的目的。對于矩形框滑過的每一區域,我們使用分類器來判斷該區域中是否存在物體。
滑動窗口的實現比較簡單,下面我們通過代碼來實現這個方法。首先在腳本中我們導入需要用到的模塊,這里我們導入 cv2 模塊用于處理圖片。分別從 matplotlib 導入 pyplot 和從 IPython 中導入 display 模塊用于顯示圖片。然后我們使用 %matplotlib inline 魔法函數讓圖片在頁面中顯示。
import cv2
from matplotlib import pyplot as plt
from IPython import display
%matplotlib inline
然后我們定義一個函數 sliding_window 用于獲取滑動窗口。這個函數有三個參數 image、window 和 step。
第一個參數 image 是輸入函數的圖片,我們將用矩形框在這個圖片上滑動。
第二個參數 window 是一個元組,表示滑動的矩形框的高和寬。
第三個參數 step 表示矩形框間隔多少個像素移動一次,這里我們可以稱之為步長。
下圖中我們用三種顏色表示不同位置的同一矩形框,圖中的 n 就表示矩形框每次移動 n 個像素的距離,step 設置的太小或太大都會對目標檢測造成負面的影響,一般這個值會設置在 4 到 8 之間。
def sliding_window(image, window, step):
for y in range(0, image.shape[0] - window[1], step):
for x in range(0, image.shape[1] - window[0], step):
yield (x, y, image[y:y + window[1], x:x + window[0]])
想要實現矩形框在圖片上滑動,我們需要知道矩形框每次滑動位于圖片中的位置。我們使用兩個 for 循環獲取矩形框的所有坐標位置。第一個 for 循環控制矩形框以 step 的步長在圖片中上下移動,第二個 for 循環控制矩形框以 step 的步長在圖中左右移動。最后通過 yield 生成器返回一個元組,其中元組的第一個元素 x 和第二個元素 y 表示矩形框左上角的坐標,元組的第三個元素 image[y:y + window[1], x:x + window[0]] 就是處在圖片中不同位置的矩形框。
我們構建好了函數后,下面我們可以使用這個函數來實現滑動窗口操作了。首先我們使用以下命令來獲取需要用到的圖片 pets.jpg。
!wget https://labfile.oss.aliyuncs.com/courses/3096/pets.jpg
接下來我們使用 cv2.imread 函數讀取圖片,pets.jpg 是要讀取的圖片名。然后我們定義滑動窗口的寬 window_w 為 400 個像素,滑動窗口的高 window_h 為 400 個像素。我們使用 n 來表示滑動窗口的數量。
image = cv2.imread("pets.jpg")
(window_w, window_h) = (400, 400)
接下來使用一個 for 循環來遍歷每一個滑動窗口,我們需要傳遞三個參數給 sliding_window。
第一個參數 image 是我們讀取的圖片。
第二個參數 (window_w, window_h) 表示滑動窗口的寬和高。
第三個參數 200 表示滑動窗口將每次滑動的步長為 200 個像素(注意這里為了演示方便將滑動窗口和步長的值設置得都很大,在實際使用中不建議將其設置過大或過小)。
然后我們使用一個 if 語句來判斷獲得的滑動窗口和我們設定的滑動窗口大小是否一致,如果滑動窗口截取的區域與設定的 (window_w, window_h) 中任意一個元素不同,則執行 continue 跳過該滑動窗口。
for (x, y, window) in sliding_window(image, (window_w, window_h), 200):
if window.shape[0] != window_w or window.shape[1] != window_h:
continue
clone = image.copy()
cv2.rectangle(clone, (x, y), (x + window_w, y + window_h), (0, 255, 0), 2)
clone = clone[:,:,::-1]
plt.imshow(clone)
plt.pause(0.1)
display.clear_output(wait=True)
在循環內我們繪制出圖片中每一個滑動窗口了,在繪制前使用 copy 函數復制輸入函數的圖片,因為接下來的畫圖操作將會修改源圖片,接著我們使用 cv2.rectangle 在 clone 上繪制出每個滑動窗口。因為在 OpenCV 圖片是以 B,G,R(藍,綠,紅)的通道順序存儲的,而在 Matplotlib 中圖片是以 R,G,B 的通道順序存儲,所以我們使用 clone[:,:,::-1] 切片方法來跳轉圖片通道的順序,然后使用 plt.imshow 在頁面中呈現繪圖后的結果。
因為我們在 for 循環內要繪制多張圖片,所以使用 plt.pause(0.1) 讓每張圖片顯示暫停一段時間,函數的參數 0.1 表示暫停 0.1 秒。最后我們使用 display 的 clear_output(wait=True) 方法清除已經顯示的圖片為下一張圖片顯示做準備。
執行代碼后繪制的滑動窗口的結果如下所示。從圖中我們可以看到滑動窗口按照從左向右、從上向下在圖片上滑動。
下圖是執行代碼后每個滑動窗口內的區域,與上圖對照可以看出下圖中每一小塊圖片對應上圖中每個矩形框內的區域。