MeanShift 目标跟踪
MeanShift算法,又稱為均值漂移算法,采用基于顏色特征的核密度估計,尋找局部最優,使得跟蹤過程中對目標旋轉,小范圍遮擋不敏感。
文章目錄
- MeanShift 原理
- MeanShift 跟蹤步驟
- meanShift 函數原型
- 反向投影
- MeanShift 跟蹤器
- 跟蹤管理器
- 車輛監測與跟蹤
- MeanShift 算法的優缺點
MeanShift 原理
MeanShift的本質是一個迭代的過程,在一組數據的密度分布中,使用無參密度估計尋找到局部極值(不需要事先知道樣本數據的概率密度分布函數,完全依靠對樣本點的計算)。
在d維空間中,任選一個點,然后以這個點為圓心,h為半徑做一個高維球,因為有d維,d可能大于2,所以是高維球。落在這個球內的所有點和圓心都會產生一個向量,向量是以圓心為起點落在球內的點位終點。然后把這些向量都相加。相加的結果就是下圖中黃色箭頭表示的MeanShift向量:
然后,再以這個MeanShift 向量的終點為圓心,繼續上述過程,又可以得到一個MeanShift 向量:
不斷地重復這樣的過程,可以得到一系列連續的MeanShift 向量,這些向量首尾相連,最終可以收斂到概率密度最大得地方(一個點):
從上述的過程可以看出,MeanShift 算法的過程就是:從起點開始,一步步到達樣本特征點的密度中心。
MeanShift 跟蹤步驟
1.獲取待跟蹤對象
獲取初始目標框(RoI)位置信息(x,y,w,h),截取 RoI圖像區域
# 初始化RoI位置信息 track_window = (c,r,w,h) # 截取圖片RoI roi = img[r:r+h, c:c+w]2.轉換顏色空間
將BGR格式的RoI圖像轉換為HSV格式,對 HSV格式的圖像進行濾波,去除低亮度和低飽和度的部分。
在 HSV 顏色空間中要比在 BGR 空間中更容易表示一個特定顏色。在 OpenCV 的 HSV 格式中,H(色度)的取值范圍是 [0,179], S(飽和度)的取值范圍 [0,255],V(亮度)的取值范圍 [0,255]。
# 轉換到HSV hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) # 設定濾波的閥值 lower = np.array([0.,130.,32.]) upper = np.array([180.,255.,255.]) # 根據閥值構建掩模 mask = cv2.inRange(hsv,lower, upper)3.獲取色調統計直方圖
# 獲取色調直方圖 roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180]) # 直方圖歸一化 cv2.normalize(roi_hist,roi_hist,0,180,cv2.NORM_MINMAX)cv2.calcHist的原型為:
cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]])-
images: 待統計的圖像,必須用方括號括起來,
-
channels:用于計算直方圖的通道,這里使用色度通道
-
mask:濾波掩模
-
histSize:表示這個直方圖分成多少份(即多少個直方柱)
-
ranges:表示直方圖中各個像素的值的范圍
4.在新的一幀中尋找跟蹤對象
# 讀入目標圖片 ret, frame = cap.read() # 轉換到HSV hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 獲取目標圖片的反向投影 dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1) # 定義迭代終止條件 term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 ) # 計算得到迭代次數和目標位置 ret, track_window = cv2.meanShift(dst, track_window, term_crit)meanShift 函數原型
def meanShift(probImage, window, criteria)-
probImage:輸入反向投影直方圖
-
window:需要移動的矩形(ROI)
-
criteria:對meanshift迭代過程進行控制的初始參量
其中,criteria參數如下:
-
type:判定迭代終止的條件類型:
-
COUNT:按最大迭代次數作為求解結束標志
-
EPS:按達到某個收斂的閾值作為求解結束標志
-
COUNT + EPS:兩個條件達到一個就算結束
-
-
maxCount:具體的最大迭代的次數
-
epsilon:具體epsilon的收斂閾值
反向投影
反向投影圖輸出的是一張概率密度圖,與輸入圖像大小相同,每一個像素值代表了輸入圖像上對應點屬于目標對象的概率,像素點越亮,代表這個點屬于目標物體的概率越大。
跟蹤目標:
跟蹤目標在下一幀中的反向投影:
MeanShift 跟蹤器
import numpy as np import cv2class MeanShiftTracer:def __init__(self, id):# Stop criteria for the iterative search algorithm.self._term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)self._roi_hist = Noneself.predict_count = 0self.frame = Noneself.frame_begin_id = idself.frame_end_id = idself.roi_xywh = Nonedef _log_last_correct(self, frame, frame_id, xywh):x, y, w, h = xywhself.correct_box = (x, y, w, h)self.correct_img = frame[y:y + h, x:x + w]self.correct_id = frame_iddef correct(self, frame, frame_id, xywh):self._log_last_correct(frame,frame_id, xywh)self._refresh_roi(frame, frame_id, xywh)self.predict_count = 0def predict(self, frame, frame_id):hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)dst = cv2.calcBackProject([hsv], [0], self._roi_hist, [0, 180], 1) ret, track_window = cv2.meanShift(dst, self.roi_xywh, self._term_crit)self._refresh_roi(frame, frame_id, track_window)self.predict_count += 1return track_windowdef _refresh_roi(self, frame, frame_id, xywh):x, y, w, h = xywhroi = frame[y:y + h, x:x + w]hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)mask = cv2.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.)))roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180])cv2.normalize(roi_hist, roi_hist, 0, 180, cv2.NORM_MINMAX)self.roi_xywh = (x, y, w, h)self._roi_hist = roi_histself.frame = frameself.frame_end_id = frame_iddef get_roi_info(self):return {'correct_box': self.correct_box,'correct_img': self.correct_img,'correct_id': self.correct_id,'beginId': self.frame_begin_id,'endId': self.frame_end_id}跟蹤管理器
import numpy as np import cv2class TracerManager:def __init__(self, image_shape, trace_tool, trace_margin, max_predict):""":param image_shape: (height,width):param trace_tool: MeanShiftTracer:param trace_margin: (0,0,30,50)(px)(left,top,right,bottom):param max_predict: 3 (times)"""self._tracers = []self._trace_tool = trace_toolself._max_predict = max_predictself._image_shape = image_shapeself.trace_margin = trace_margindef _calc_iou(self, A, B):""":param A: [x1, y1, x2, y2]:param B: [x1, y1, x2, y2]:return: IoU"""IoU = 0iw = min(A[2], B[2]) - max(A[0], B[0])if iw > 0:ih = min(A[3], B[3]) - max(A[1], B[1])if ih > 0:A_area = (A[2] - A[0]) * (A[3] - A[1])B_area = (B[2] - B[0]) * (B[3] - B[1])uAB = float(A_area + B_area - iw * ih)IoU = iw * ih / uABreturn IoUdef box_in_margin(self, box):in_bottom = (self._image_shape[0] - (box[1] + box[3])) < self.trace_margin[3]in_right = (self._image_shape[1] - (box[0] + box[2])) < self.trace_margin[2]return in_bottom or in_rightdef _get_box_tracer_iou(self, A, B):a = (A[0], A[1], A[0] + A[2], A[1] + A[3])b = (B[0], B[1], B[0] + B[2], B[1] + B[3])return self._calc_iou(a, b)def _check_over_trace(self):remove_tracer = []trace_info = []for t in self._tracers:if t.predict_count > self._max_predict:remove_tracer.append(t)if t.frame_end_id != t.frame_begin_id:trace_info.append(t.get_roi_info())for t in remove_tracer:self._tracers.remove(t)return trace_infodef _get_tracer(self, box):tracer = NonemaxIoU = 0for t in self._tracers:iou = self._get_box_tracer_iou(box, t.roi_xywh)if iou > maxIoU:tracer = tmaxIoU = ioureturn tracerdef update_tracer(self, frame, frame_id, boxes):trace_info = self._check_over_trace()for box in boxes:if self.box_in_margin(box):continuetracer = self._get_tracer(box)if tracer is not None:tracer.correct(frame, frame_id, box)else:tracer = self._trace_tool(frame_id)tracer.correct(frame, frame_id, box)self._tracers.append(tracer)return trace_infodef trace(self, frame, frame_id):track_windows = []for t in self._tracers:window = t.predict(frame, frame_id)track_windows.append(window)return track_windows車輛監測與跟蹤
檢測與跟蹤以1:1的比例交替進行。
import cv2 import numpy as np import os.path import Tracerclass car_detector:def __init__(self, cascade_file):if not os.path.isfile(cascade_file):raise RuntimeError("%s: not found" % cascade_file)self._cascade = cv2.CascadeClassifier(cascade_file)def _detect_cars(self, image):gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)gray = cv2.equalizeHist(gray)cars = self._cascade.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=15, minSize=(60, 60))return carsdef _show_trace_object(self, infos):for info in infos:title = "%d - %d from frame: %d" % (info['beginId'], info['endId'], info['correct_id'])cv2.imshow(title, info['correct_img']) cv2.waitKey(1)def _get_area_invalid_mark(self, img_shape, margin):area = np.zeros(img_shape,np.uint8)h, w = img_shape[:2]disable_bg_color = (0, 0, 80)disable_fg_color = (0, 0, 255)cv2.rectangle(area, (0, h-margin[3]), (w, h), disable_bg_color, -1)cv2.putText(area, "Invalid Region", (w-220, h-20), cv2.FONT_HERSHEY_SIMPLEX, 1, disable_fg_color, 2)return areadef _show_trace_state(self, image, id, tracer, state, boxes, mark):image = cv2.addWeighted(mark, 0.5, image, 1, 0)title = 'frame : %s [%s]' % (state, id)colors = {'detect': (0, 255, 0), 'trace': (255, 255, 0), 'invalid': (150, 150, 150), 'title_bg': (0, 0, 0)}for (x, y, w, h) in boxes:if tracer.box_in_margin((x, y, w, h)):cv2.rectangle(image, (x, y), (x + w, y + h),colors['invalid'], 2)else:cv2.rectangle(image, (x, y), (x + w, y + h), colors[state], 2)cv2.rectangle(image, (10, 20), (250, 50), colors['title_bg'], -1)cv2.putText(image, title, (30, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[state],2)cv2.imshow("result", image)cv2.waitKey(1)def trace_detect_video(self, video_path, trace_rate = 1):cap = cv2.VideoCapture(video_path)w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) start_frame = 0invalid_margin = (0, 0, 0, 100)mark = self._get_area_invalid_mark((h, w, 3), invalid_margin)tracer = Tracer.TracerManager((h, w), Tracer.MeanShiftTracer, invalid_margin, trace_rate + 5)warm = Falsewhile True:ret, image = cap.read()start_frame += 1if not ret: returnresult = image.copy()if not warm or start_frame % (trace_rate + 1) == 0:warm = Truecars = self._detect_cars(image)self._show_trace_state(result, start_frame, tracer, 'detect', cars, mark)trace_obj = tracer.update_tracer(image, start_frame, cars)self._show_trace_object(trace_obj)else:cars = tracer.trace(image, start_frame)self._show_trace_state(result, start_frame, tracer, 'trace', cars, mark)if __name__ == "__main__":car_cascade_lbp_21 = './train/cascade_lbp_21/cascade.xml'video_path = "./test.mp4"detect = car_detector(car_cascade_lbp_21)detect.trace_detect_video(video_path)MeanShift 算法的優缺點
優點:
-
算法計算量不大,在目標區域已知的情況下完全可以做到實時跟蹤;
-
采用核函數直方圖模型,對邊緣遮擋、目標旋轉、變形和背景運動不敏感。
缺點:
-
跟蹤過程中由于窗口寬度大小保持不變,框出的區域不會隨著目標的擴大(或縮小)而擴大(或縮小);
-
當目標速度較快時,跟蹤效果不好;
-
直方圖特征在目標顏色特征描述方面略顯匱乏,缺少空間信息;
總結
以上是生活随笔為你收集整理的MeanShift 目标跟踪的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NMS 非极大值抑制
- 下一篇: 图像滤波