使用Python+OpenCV進行影象處理(三)| 視覺入門
譯者 | 磐石
編輯 | 安可
檢測是計算機視覺任務中的主要任務之一,而且應用很廣泛。檢測技術可以幫助人類檢測那些容易被肉眼忽略的錯誤;也可以”幫助“自動駕駛汽車感知空間資訊。無疑自動化的檢測技術的廣泛應用將為我們帶來效率與安全。
本篇是這個系列的第三篇。整個系列目錄如下:
-
從特徵檢測到人臉檢測。
-
輪廓檢測
之前已經介紹了幾種顏色模型以及如何在影象上繪製圖形。還介紹了常用的影象處理技術,如:模糊、梯度、腐蝕、擴張等。本篇將把這些技術應用到影象特徵檢測和人臉檢測中。
本篇會用到本系列前兩篇中介紹的影象處理技術。
邊緣檢測 (Edge Detection)
邊緣檢測本質上是檢測影象中變化劇烈或者不連續的畫素點。將這些畫素點連線線段即為邊。實際上,在上一篇文章中我們已經介紹了一種基礎的邊緣檢測技術:使用Sobel運算元和拉普拉斯運算元進行梯度濾波。通過計算影象畫素值在給定方向上的導數,梯度濾波器即可以描繪出影象的邊緣從而實現邊緣檢測。
Canny檢測演算法是另外一種影象邊緣檢測技術。而且是目前最流行的邊緣檢測技術之一,分為以下四個步驟實現: 降噪、判斷梯度及梯度方向、非最大值抑制和滯後閾值化處理。
首先通過高斯模糊技術實現降噪。然後,使用sobel運算元得到影象梯度。接著使用得到的梯度,檢測每一個畫素點與其中周圍的畫素點,確認這個畫素點是不是這些區域性畫素點中的區域性最大值。如果不是區域性最大值,則將這個點的畫素值置為零(完全缺失,黑色)。這個過程即為 非極大值抑制 。

如果這個點被確認為區域性最大值,則進行下一步即第四個步驟。第四步是決定之前檢測出的邊是否為真正邊緣的最後一個決策階段。這一決策階段被稱為 滯後閾值化 ,它需要兩個閾值(“較小閾值”、“較大閾值”)來進行決策。
給定兩個不同的閾值,我們可以得到三個閾值化區間。因此,如果這個點的畫素值大於兩個閾值中的“較大閾值”則被判定為邊緣點。相對地,如果其小於所設定的兩個閾值引數中的“較小閾值”則被認定為非邊緣點,即會被丟棄。另外,如果這個點的畫素值位於兩個引數閾值之間則是跟據其是否與”確認邊緣點“之間有連線來決定是否丟棄,遵循有連線則不丟棄的原則。
img = cv2.imread('images/giraffe.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Canny detection without blurring edges = cv2.Canny(image=img, threshold1=127, threshold2=127) plt.figure(figsize = (20, 20)) plt.subplot(1, 2, 1); plt.imshow(img) plt.axis('off') plt.subplot(1, 2, 2); plt.imshow(edges) plt.axis('off')

上方僅使用了一個閾值中值作判斷,也沒有進行影象模糊處理,邊緣檢測結果不是很理想。接下來讓我們嘗試不同的引數閾值設定:
# Set the lower and upper threshold med_val = np.median(img) lower = int(max(0, .7*med_val)) upper = int(min(255, 1.3*med_val))
為了更直觀的比較模糊化對影象邊緣檢測的影響,將使用兩種不同尺寸的卷積核(5x5)與(9x9)。設定兩種閾值引數,一種在上述閾值設定的基礎上將“較大閾值”增加100。也就意味著我們會得到四種不同的組合結果圖。如下:
# Blurring with ksize = 5 img_k5 = cv2.blur(img, ksize = (5, 5)) # Canny detection with different thresholds edges_k5 = cv2.Canny(img_k5, threshold1 = lower, threshold2 = upper) edges_k5_2 = cv2.Canny(img_k5, lower, upper+100) # Blurring with ksize = 9 img_k9 = cv2.blur(img, ksize = (9, 9)) # Canny detection with different thresholds edges_k9 = cv2.Canny(img_k9, lower, upper) edges_k9_2 = cv2.Canny(img_k9, lower, upper+100) # Plot the images images = [edges_k5, edges_k5_2, edges_k9, edges_k9_2] plt.figure(figsize = (20, 15)) for i in range(4): plt.subplot(2, 2, i+1) plt.imshow(images[i]) plt.axis('off') plt.show()

正如上圖所示,模糊化可以幫助減少噪聲。我們在使用卷積核尺寸為(9x9)時得到了更好的結果。而且,在使用更大的“較大閾值”時得到了更好的 邊緣檢測 結果。
角點檢測(Corner Detection)
角點檢測是另一種廣泛應用於目標檢測、運動檢測、視訊目標追蹤等領域的檢測演算法。影象處理中的角是什麼?應該如何定義?在這裡,我們把角看作是邊相交的連線點。那我們怎麼才能找到他們呢? 你可能會想到一個最基礎的方式是先找到所有的邊,然後找到它們相交的點。但實際上,還有另一種更高效的方法確認角點提高效率的方法,即 Ha rris角點檢測 和 Shi&Tomasi角點檢測 。接下來讓我們來詳細瞭解這兩種演算法。
這兩種演算法的工作原理如下。首先,檢測出各個方向上畫素強度值有很大變化的點。然後構造一個矩陣,從中提取特徵值。通過這些特徵值進行評分從而決定它是否是一個角。數學表示式如下所示。

現在讓我們看看它們的程式碼實現。首先,需要把圖片轉換為灰度圖。Harris角點檢測可以通過OpenCV中的cv2.cornerHarris()函式實現。
img = cv2.imread('images/desk.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # Apply Harris corner detection dst = cv2.cornerHarris(img_gray, blockSize = 2, ksize = 3, k = .04)
引數blocksize是指定領域視窗設定的大小,k是Harris檢測的自由引數對應上方公式中的k值。輸出結構為得分R,我們將使用R得分檢測角點。
# Spot the detected corners img_2 = img.copy() img_2[dst>0.01*dst.max()]=[255,0,0] # Plot the image plt.figure(figsize = (20, 20)) plt.subplot(1, 2, 1); plt.imshow(img) plt.axis('off') plt.subplot(1, 2, 2); plt.imshow(img_2) plt.axis('off')

下方是Shi-Tomasi角點檢測的程式碼實現。使用函式cv2.goodFeaturesToTrack()實現。通過maxCorners引數指定最大角點個數。相應地,通過minDistance指定角點間的最小距離和角點評定的最小質量級別。得到檢測到的角點後,使用圓圈標記這些角點,如下所示:
# Apply Shi-Tomasi corner detection corners = cv2.goodFeaturesToTrack(img_gray, maxCorners = 50, qualityLevel = 0.01, minDistance = 10) corners = np.int0(corners) # Spot the detected corners img_2 = img.copy() for i in corners: x,y = i.ravel() cv2.circle(img_2, center = (x, y), radius = 5, color = 255, thickness = -1) # Plot the image plt.figure(figsize = (20, 20)) plt.subplot(1, 2, 1); plt.imshow(img) plt.axis('off') plt.subplot(1, 2, 2); plt.imshow(img_2) plt.axis('off')

人臉檢測
人臉檢測是一種識別影象中是否存在人臉以及人臉的位置的技術。人臉檢測不同於 人臉識別 ,人臉識別是通過一個人的臉來識別這個人。 所以人臉檢測並不能告訴我們這個人臉是屬於誰。
人臉檢測本質上是一項分類任務,訓練其分類物體是否存在來從而實現檢測。 基於Haar特徵的級聯分類器 是OpenCV中常用的人臉檢測模型之一。它已經在數千副影象上進行過預訓練。理解該演算法的四個關鍵點分別是: Haar特徵提取、積分影象、Adaboost和級聯分類器。

類haar特徵(Haar-like features) 是用於目標檢測的數字影象特徵,示例如上圖。Haar特徵這個名字來源於其與Harr小波的直觀相似性,且Haar小波最初是由Alfred Haar提出的。在檢測過程中,通過滑動視窗和濾波器上的卷積操作來確認這些特徵是不是我們所需要的特徵。如下方所示:

那麼,我們具體如何來確定給定區域是否含有需要的特徵呢? 如上方圖片中所示。使用一個特定卷積核(上半區域是暗的,下半區域是亮的)得到每個區域畫素值的平均值,並減去兩者之間的差距。如果結果高於閾值(比如0.5),則可得出結果,其就是我們正在檢測的特徵。對每個核心重複這個過程,同時在影象上滑動視窗。
雖然這個計算過程並不複雜,但如果在正個影象重複這個過程計算量還是很大的。這也是 積分影象 要解決的主要問題。積分影象是一種影象表示方式,它是為了提高特徵估計的速度與效率而衍生出來的。
如下圖所示,左邊是原始影象的畫素值,右邊是積分影象的畫素值。從左上角開始計算給定矩形區域下畫素的累加值。在積分影象上,將虛線框畫素值的累加和填充在右邊框的右下角處。

使用上方這個“預計算表”,我們可以通過子矩形(上圖中紅色、橙色、藍色和紫色框)的值方便地得到某個區域的畫素值總和。
所以積分影象可以幫助我們在一定程度上解決計算量過大的問題。但還不夠,還存在著計算量優化的空間。當檢測視窗位於沒有目標或人臉的空白背景時,執行檢測則會耗費不必要的計算量。這時就可以通過使用Adaboost和 級聯分類器 ,從而實現計算量進一步優化。

上圖展示了級聯分類器逐步構造的各個階段,並對類haar特徵進行排序。基本特徵會在早期階段被識別出來,後期只識別有希望成為目標特徵的複雜特徵。在每一個階段,Adaboost模型都將由整合弱分類器進行訓練。如果子部件或子視窗在前一階段被分類為“不像人臉的區域”,則將被拒絕進入下一步。通過上述操作,只須考慮上一階段篩選出來的特徵,從而實現更高的速度。
我們的英雄在哪?
接下來讓我們使用上述級聯分類器實現漫威英雄面部檢測--驚奇隊長面部檢測。

我們只須使用影象中的一部分即頭部部分。首先,獲取驚奇隊長臉部周圍感興趣區域;然後把影象轉換成灰度圖。之所以只使用一個通道,是因為我們只對特徵的畫素值強度變化感興趣。
cap_mavl = cv2.imread('images/captin_marvel.jpg') # Find the region of interest roi = cap_mavl[50:350, 200:550] roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) plt.imshow(roi, cmap = 'gray')

通過下方程式碼使用Haar級聯分類器。
# Load Cascade filter face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
接下來,我們將建立一個函式來檢測人臉並在目標區域周圍繪製一個矩形。為了檢測人臉,我們可以使用上面載入的分類器face_cascade的. detectmulitscale()方法。它返回指定區域的四個點所以我們在那個位置畫一個矩形。scaleFactor是一個引數,表示在每個影象尺度上影象大小減少了多少,minNeighbors表示每個候選矩形應該訓練多少個鄰居。現在我們把這個函式應用到影象上,看看結果。
# Create the face detecting function def detect_face(img): img_2 = img.copy() face_rects = face_cascade.detectMultiScale(img_copy, scaleFactor = 1.1, minNeighbors = 3) for (x, y, w, h) in face_rects: cv2.rectangle(img_2, (x, y), (x+w, y+h), (255, 255, 255), 3) return img_2 # Detect the face roi_detected = detect_face(roi) plt.imshow(roi_detected, cmap = 'gray') plt.axis('off')

正如看到的那樣,haar級聯分類器取得了不錯的人臉檢測效果。接下來,讓我們嘗試檢測含有多張人臉的圖片。
# Load the image file and convert the color mode avengers = cv2.imread('images/avengers.jpg') avengers = cv2.cvtColor(avengers, cv2.COLOR_BGR2GRAY) # Detect the face and plot the result detected_avengers = detect_face(avengers) display(detected_avengers, cmap = 'gray')

很明顯檢測結果不完全準確。出現了錯誤捕捉“非人臉”目標以及丟失了部分“真實人臉”。有趣的是,它成功地探測到了蜘蛛俠,卻把美國隊長和黑寡婦的手誤當成了眼睛。通常在人臉影象凸顯出更加清晰的五官時,可以得到更好的人臉檢測結果。
嘗試檢測自己的臉
接下來介紹使用網路攝像頭檢測人臉的實現方法。類似上方的實現方式。程式碼如下方所示。可以通過ESC按鍵終止退出檢測。
import cv2 import numpy as np # Step 1. Define detect function face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml') def detect_face(img): img_copy = img.copy() face_rects = face_cascade.detectMultiScale(img_copy) for (x, y, w, h) in face_rects: cv2.rectangle(img_copy, (x, y), (x+w, y+h), (255, 255, 255), 3) return img_copy # Step 2. Call the cam cap = cv2.VideoCapture(0) while True: ret, frame = cap.read(0) frame = detect_face(frame) cv2.imshow('Video Face Detection', frame) c = cv2.waitKey(1) if c == 27: break cap.release() cv2.destroyAllWindows()
總結
本篇介紹了傳統的邊緣檢測、角點檢測以及人臉檢測方法。下篇將介紹輪廓檢測技術等。敬請期待。
長按掃描下方小程式碼,互動提問
你也許還想 看 :
● 使用Python+OpenCV進行影象處理(二)| 視覺入門
● 使用Python+opencv進行影象處理(一) | 視覺入門
● Github標星3K+,熱榜第三,一網打盡資料科學速查表
歡迎掃碼關注:
覺得贊你就點 在看 ,多謝大佬