一、基本原理核心思想用目标的颜色特征来定位目标——建立颜色模板然后在后续帧中找到颜色最相似的区域。颜色阈值法不关心目动不动只关注目标的颜色。颜色阈值法不在RGB空间中工作而是在HSV空间中。H色相:决定是什么颜色。S饱和度决定颜色有多纯。V明度决定颜色有多亮。二、关键步骤1.建模版从第一帧的目标区域统计颜色直方图这个直方图就是模板。颜色直方图对图像中颜色分布的统计把每一种颜色的像素数量画成柱状图。2.反投影在后续帧的每个像素上查这个像素的颜色在模板颜色直方图上的匹配度最后生成概率图。3.定位用算法在该旅途中找到最密集的区域就是目标位置。三、关键代码实现1.类结构一初始化class ColorThresholdTracker(BaseTracker): def __init__(self, tracker_namecolor): super().__init__(tracker_name) self.roi_hist None # ROI的颜色直方图目标的颜色模板 self.track_window None # CamShift跟踪窗口 self.prev_bbox None # 上一帧的跟踪框 self.target_area 0 # 目标面积 self.target_aspect 1.0 # 目标宽高比 self.hist_update_count 0 # 直方图更新计数器 self.lost_count 0 # 跟丢计数 # 形态学核椭圆形状5×5 self.kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 运动速度用于运动预测 self.velocity np.array([0.0, 0.0]) # 初始框用于窗口退化恢复 self.init_bbox None # 目标模板灰度图用于模板匹配后备 self.target_template None集成状态变量与多种跟踪策略来强化颜色阈值的跟踪效果。2.初始化方法def init(self, frame, bbox): x, y, w, h [int(v) for v in bbox] self.init_bbox (x, y, w, h) self.prev_bbox (x, y, w, h) self.target_area w * h self.target_aspect w / (h 1e-6) # 中心区域采样 # 切除边框15%只取中心区域学颜色 margin_x, margin_y int(w * 0.15), int(h * 0.15) y1 max(0, y margin_y) y2 min(frame.shape[0], y h - margin_y) x1 max(0, x margin_x) x2 min(frame.shape[1], x w - margin_x) hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) roi hsv[y1:y2, x1:x2] # 如果裁剪后为空用整个框 if roi.size 0: roi hsv[y:yh, x:xw] # 排除低饱和度/低亮度像素 mask cv2.inRange(roi, (0, 30, 30), (180, 255, 255)) # 自适应bins h_bins min(60, max(30, w // 5)) s_bins min(64, max(32, h // 5)) self.roi_hist cv2.calcHist([roi], [0, 1], mask, [h_bins, s_bins], [0, 180, 0, 256]) cv2.normalize(self.roi_hist, self.roi_hist, 0, 255, cv2.NORM_MINMAX) self.track_window (x, y, w, h) # 保存灰度模板模板匹配后备用 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) self.target_template gray[y:yh, x:xw] self.trajectory [bbox] self.frame_count 1初始化时做了四件关键事情第一是中心区域采样切除目标框四周 15% 的边缘区域只取中心 70% 的区域来建立颜色模型第二是排除低饱和度/低亮度像素排除它们让颜色模板更纯净第三是自适应bins颜色直方图的分辨率bins 数量根据目标大小自适应大目标像素多对应bins多否则反之第四是保存灰度模板当颜色跟踪和轮廓跟踪都失败时用传统的模板匹配来找目标。3.三层次跟踪策略颜色阈值法设计了 3 层递进的跟踪策略。第一层是CamShift跟踪如果失败则到第二层颜色概率 轮廓筛选如果失败并且跟丢两帧则到模板匹配。第一层CamShift 跟踪def _search_by_camshift(self, frame, margin_scale): hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # ① 反投影计算每个像素属于目标颜色的概率 back_proj cv2.calcBackProject([hsv], [0, 1], self.roi_hist, [0, 180, 0, 256], 1) back_proj cv2.GaussianBlur(back_proj, (5, 5), 0) # 平滑概率图 # ② 运动预测根据历史运动速度预测当前帧的目标位置 pred_window self._predict_window() x, y, w, h pred_window # ③ 扩大搜索区域 margin int(max_dim * margin_scale) # margin_scale 依次为 1.0, 2.0, 4.0 search_window (x-margin, y-margin, w2*margin, h2*margin) # ④ CamShift 算法在概率图上寻找最密集的区域 term_crit (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 15, 1) ret, new_window cv2.CamShift(back_proj, search_window, term_crit) x_new, y_new, w_new, h_new new_window w_new max(15, int(w_new)) # 防止窗口退化到太小 h_new max(15, int(h_new)) # ⑤ 窗口退化恢复 self._recover_window() return (x_new, y_new, w_new, h_new)CamShift 的工作原理1.反投影用目标的颜色直方图扫描整张图像每个像素得到一个颜色匹配度值生成一张概率图2.MeanShift在概率图上从一个起始窗口开始计算窗口内所有像素的质心然后把窗口中心移到质心位置重复直到收敛。3.CamShift MeanShift 窗口自适应每次迭代不仅移动窗口还会根据窗口内像素分布调整窗口的大小和方向第二层颜色概率 轮廓筛选def _search_by_contour(self, frame): hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) prob cv2.calcBackProject([hsv], [0, 1], self.roi_hist, [0, 180, 0, 256], 1) prob cv2.GaussianBlur(prob, (7, 7), 0) # 7×7 高斯滤波更大的平滑核 # 自适应阈值取概率最大值的25%作为阈值 max_prob prob.max() if max_prob 10: return None # 完全没有匹配的颜色 _, thresh cv2.threshold(prob, max(10, max_prob * 0.25), 255, cv2.THRESH_BINARY) # 形态学处理 thresh cv2.morphologyEx(thresh, cv2.MORPH_OPEN, self.kernel, iterations1) thresh cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, self.kernel, iterations2) # 找轮廓 → 筛选 → 评分与帧差法和背景差减法类似 ...当 CamShift 失败时这个方法采用了与帧差法类似的思路——__找轮廓 筛选 评分__但输入不是帧差或运动检测的结果而#x662F;__#x989C;色概率#x56FE;__#x7684;二值化结果。第三层模板匹配def _search_by_template(self, frame): if self.target_template is None: return None gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) th, tw self.target_template.shape # 模板大小初始化时保存的 # 动态扩大搜索范围跟丢越久搜索范围越大 max_dim max(th, tw) margin int(max_dim * (1.0 self.lost_count * 0.5)) # lost_count0: margin1.0倍 # lost_count4: margin3.0倍 search_roi gray[y1:y2, x1:x2] res cv2.matchTemplate(search_roi, self.target_template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) if max_val 0.4: # 匹配度超过0.4才接受 tx, ty max_loc return (x1 tx, y1 ty, tw, th) return None模板匹配是计算机视觉最经典的匹配方法——把第一帧保存的目标图片在后续帧中滑动计算每个位置的相似度找到最像的地方。4.主更新方法def update(self, frame): # 第一步多尺度 CamShift从小范围开始尝试 margins [1.0, 2.0, 4.0] if self.lost_count 3: margins [2.0, 4.0, 8.0] # 丢失后扩大搜索范围 cand None for margin in margins: cand self._search_by_camshift(frame, margin) if cand is not None: break # 找到就停止 # 第二步CamShift 失败 → 轮廓搜索 if cand is None: cand self._search_by_contour(frame) # 第三步都失败且丢失超过2帧 → 模板匹配 if cand is None and self.lost_count 2: cand self._search_by_template(frame) # 都失败了保持上一帧位置 if cand is None: cand self.prev_bbox # 有效性检查 prev_center self._center(self.prev_bbox) new_center self._center(cand) dist np.linalg.norm(new_center - prev_center) max_dim max(self.prev_bbox[2], self.prev_bbox[3]) new_area cand[2] * cand[3] area_ratio new_area / (self.target_area 1e-6) if dist max_dim * 5 and 0.03 area_ratio 12: #有效更新 self.prev_bbox cand self.lost_count 0 # 更新运动速度指数滑动平均 self.velocity 0.6 * self.velocity 0.4 * (new_center - prev_center) # 每5帧更新一次颜色直方图适应光照变化 if self.hist_update_count % 5 0: ... # 从当前帧目标区域重新计算直方图 self.roi_hist 0.8 * self.roi_hist 0.2 * new_hist # 融合更新 else: #候选框不合理视为丢失 self.lost_count 1 self.hist_update_count 1 self.frame_count 1 self.trajectory.append(self.prev_bbox) return self.prev_bbox每 5 帧用当前跟踪结果重新计算直方图然后与旧直方图按 0.8:0.2 融合这使颜色模板能适应缓慢的光照变化又不会因为单帧错误而剧烈变化。四、局限性1.颜色是弱的特征目标可能与其他物体颜色相似2.光照剧烈变化虽然 HSV 做了分离但极端光照变化比如强光直射仍会导致颜色偏移3.目标旋转或形变目标背后和正面的颜色分布可能不同导致匹配失败4.初始化依赖必须从第一帧学颜色如果第一帧目标被部分遮挡颜色模型不完整。