OpenCV-Python系列之Camshift演算法
上一個教程中我們介紹了一個視訊跟蹤的演算法,但是通過實驗我們發現,在視訊或者是攝像頭當中,如果被追蹤的物體迎面過來,由於****效果,物體會放大,之前設定好的視窗區域大小會不合適。
OpenCV實現了一個Camshift演算法,首先使用meanshift演算法找到目標,然後調整視窗大小,而且還會計算目標物件的的最佳外接圓的角度,並調整視窗。並使用調整後的視窗對物體繼續追蹤。
使用方法與meanShift演算法一樣,不過返回的是一個帶有旋轉角度的矩形。
Camshift原理
camshift利用目標的顏色直方圖模型將影象轉換為顏色概率分佈圖,初始化一個搜尋窗的大小和位置,並根據上一幀得到的結果自適應調整搜尋視窗的位置和大小,從而定位出當前影象中目標的中心位置。
分為三個部分:
1、色彩投影圖(反向投影):
(1).RGB顏色空間對光照亮度變化較為敏感,為了減少此變化對跟蹤效果的影響,首先將影象從RGB空間轉換到HSV空間。
(2).然後對其中的H分量作直方圖,在直方圖中代表了不同H分量值出現的概率或者畫素個數,就是說可以查找出H分量大小為h的概率或者畫素個數,即得到了顏色概率查詢表。
(3).將影象中每個畫素的值用其顏色出現的概率對替換,就得到了顏色概率分佈圖。這個過程就叫反向投影,顏色概率分佈圖是一個灰度影象。
2、meanshift
meanshift演算法是一種密度函式梯度估計的非引數方法,通過迭代尋優找到概率分佈的極值來定位目標。
演算法過程為:
(1).在顏色概率分佈圖中選取搜尋窗W
(2).計算零階距:
計算一階距:
計算搜尋窗的質心:
(3).調整搜尋窗大小,寬度為:
長度為1.2s;
(4).移動搜尋窗的中心到質心,如果移動距離大於預設的固定閾值,則重複2)3)4),直到搜尋窗的中心與質心間的移動距離小於預設的固定閾值,或者迴圈運算的次數達到某一最大值,停止計算。
3--camshift
將meanshift演算法擴充套件到連續影象序列,就是camshift演算法。它將視訊的所有幀做meanshift運算,並將上一幀的結果,即搜尋窗的大小和中心,作為下一幀meanshift演算法搜尋窗的初始值。如此迭代下去,就可以實現對目標的跟蹤。
演算法過程為:
(1).初始化搜尋窗
(2).計算搜尋窗的顏色概率分佈(反向投影)
(3).執行meanshift演算法,獲得搜尋窗新的大小和位置。
(4).在下一幀視訊影象中用(3)中的值重新初始化搜尋窗的大小和位置,再跳轉到(2)繼續進行。
camshift能有效解決目標變形和遮擋的問題,對系統資源要求不高,時間複雜度低,在簡單背景下能夠取得良好的跟蹤效果。但當背景較為複雜,或者有許多與目標顏色相似畫素干擾的情況下,會導致跟蹤失敗。因為它單純的考慮顏色直方圖,忽略了目標的空間分佈特性,所以這種情況下需加入對跟蹤目標的預測演算法。
OpenCV中的camshift演算法
Camshift演算法的使用在OpenCV中類似於上一個教程的Meanshift演算法的使用,我們來看程式碼(將給出詳細的程式碼解釋):
def camshift(): cap = cv2.VideoCapture(0) # ret判斷是否讀到圖片 # frame讀取到的當前幀的矩陣 # 返回的是元組型別,所以也可以加括號 ret, frame = cap.read() # print(type(ret), ret) # print(type(frame), frame) # 設定跟蹤框引數 r, h, c, w = 0, 100, 0, 100 # simply hardcoded the values track_window = (c, r, w, h) # 從當前幀中框出一個小框 roi = frame[r:r + h, c:c + w] # RGB轉為HSV更好處理 hsv_roi = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # inRange函式設定亮度閾值 # 去除低亮度的畫素點的影響 # eg. mask = cv2.inRange(hsv, lower_red, upper_red) # 將低於和高於閾值的值設為0 mask = cv2.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.))) # 然後得到框中影象的直方圖 # cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) # mask 即上文的閾值設定 # histSize表示這個直方圖分成多少份(即多少個直方柱) # range是表示直方圖能表示畫素值的範圍 # 返回直方圖 roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180]) # 歸一化函式cv2.normalize(src[, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]]]) # 返回dst型別 # 歸一化就是要把需要處理的資料經過處理後(通過某種演算法)限制在你需要的一定範圍內 # src - 輸入陣列 # dst - 與src大小相同的輸出陣列 # alpha - 範圍值, 以便在範圍歸一化的情況下歸一化到較低範圍邊界 # beta - 範圍歸一化時的上限範圍; 它不用於標準規範化 # normType - 規範化型別 這裡的NORM_MINMAX是陣列的數值被平移或縮放到一個指定的範圍,線性歸一化。 # dtype - 當為負數時,輸出陣列與src的型別相同;否則,它具有與src相同的通道數;深度=CV_MAT_DEPTH(dtype) # mask - 可選的操作掩碼。 cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX) # 設定迭代的終止標準,最多十次迭代 term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1) while (1): ret, frame = cap.read() if ret == True: hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 反向投影函式(特徵提取函式) # 反向投影是一種記錄給定影象中的畫素點如何適應直方圖模型畫素分佈的方式 # 反向投影就是首先計算某一特徵的直方圖模型,然後使用模型去尋找影象中存在的特徵 # cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) # images:待處理的影象,影象格式為uint8或float32 # channels:對應影象需要統計的通道,若是灰度圖則為0,彩色影象B、G、R對應0、1、2 # mask:掩膜影象。如果統計整幅影象就設定為None,否則這裡傳入設計的掩膜影象。 # histSize表示這個直方圖分成多少份(即多少個直方柱) # ranges:畫素量化範圍,通常為0 - 255。 dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1) # RotatedRect CamShift(InputArray probImage, Rect&window, TermCriteria criteria)。 # probImage為輸入影象直方圖的反向投影圖, # window為要跟蹤目標的初始位置矩形框, # criteria為演算法結束條件。 # 函式返回一個有方向角度的矩陣。 # ret, track_window = cv2.CamShift(dst, track_window, term_crit) # Draw it on image pts = cv2.boxPoints(ret) # 型別轉換int0()用於索引的整數(same as C ssize_t; normally either int32 or int64) pts = np.int0(pts) # 非填充多邊形:polylines() # cv2.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]) # img – 要畫的圖片 # pts – 多邊形的頂點 # isClosed – 是否閉合線段 # color – 顏色 img2 = cv2.polylines(frame, [pts], True, 255, 2) cv2.imshow('img2', img2) # 停止追蹤按鈕 k = cv2.waitKey(60) & 0xff if k == 27: break else: cv2.imwrite(chr(k) + ".jpg", img2) else: break cv2.destroyAllWindows() cap.release()
大致看一下結果:
天道酬勤 循序漸進 技壓群雄