基于Python3和OpenCV的含噪天空无预训练目标检测(非分类)与追踪问询
固定朝天摄像头无训练任意目标检测跟踪实现方案
核心思路
针对动态云层背景下的未知小目标检测需求,跳过目标分类相关的模型训练,通过「局部对比度候选筛选+分块动态背景过滤+多目标跟踪去误检」的链路实现,既可以抓取极小、静止的异常目标,也能过滤云层大区域运动带来的噪声。
实现步骤
- 空域预处理
- 输入帧转灰度后做高斯模糊降噪,避免随机噪点引发误检
- 做11x11窗口的局部对比度计算,筛选出和邻域灰度差超过3倍局部标准差的像素,作为候选前景像素,可捕捉到极远的小目标
- 动态背景过滤
- 将图像划分为32x32的区块,每个区块单独统计过去20帧的灰度分布、运动向量均值
- 若区块连续3帧的运动向量和相邻区块运动向量一致性超过80%,标记为「动态云层背景区块」,直接过滤该区块内的所有候选像素
- 剩余候选像素做连通域分析,过滤面积大于画面1%(大概率是云边缘)和小于3像素(随机噪声)的连通域,得到候选目标框
- 多目标跟踪
- 采用卡尔曼滤波+IoU匹配的多目标跟踪逻辑:每帧的候选框和上一帧的跟踪队列做IoU匹配,阈值设为0.3,匹配成功则更新卡尔曼滤波参数
- 新候选框连续3帧匹配成功才加入正式跟踪队列,连续5帧未匹配到的目标从队列中移除
- 目标被云层短暂遮挡时,用卡尔曼滤波预测的位置补全跟踪框,避免跟踪中断
核心代码示例
import cv2 import numpy as np from collections import deque # 初始化参数 FRAME_WINDOW = 20 BLOCK_SIZE = 32 CONTRAST_THRESH = 3 IOU_THRESH = 0.3 MAX_MISS_FRAME = 5 MIN_DETECT_FRAME = 3 # 跟踪队列存储:{id: {"kalman": 卡尔曼实例, "last_box": 上一帧框, "miss_cnt": 丢失帧数, "detect_cnt": 检测到帧数}} trackers = {} next_id = 0 # 历史帧队列,用于分块统计 frame_history = deque(maxlen=FRAME_WINDOW) def calc_iou(box1, box2): x1, y1, w1, h1 = box1 x2, y2, w2, h2 = box2 inter_x1 = max(x1, x2) inter_y1 = max(y1, y2) inter_x2 = min(x1+w1, x2+w2) inter_y2 = min(y1+h1, y2+h2) inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1) union_area = w1*h1 + w2*h2 - inter_area return inter_area / union_area if union_area >0 else 0 cap = cv2.VideoCapture(0) # 替换为你的视频路径 while True: ret, frame = cap.read() if not ret: break h, w = frame.shape[:2] gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5,5), 0) # 1. 局部对比度候选筛选 local_mean = cv2.blur(blur, (11,11)) local_mean_sq = cv2.blur(blur.astype(np.float32)**2, (11,11)) local_std = np.sqrt(local_mean_sq - local_mean.astype(np.float32)**2) candidate_mask = np.abs(blur.astype(np.float32) - local_mean) > CONTRAST_THRESH * local_std candidate_mask = candidate_mask.astype(np.uint8) * 255 # 2. 分块动态背景过滤 frame_history.append(blur) cloud_mask = np.zeros_like(candidate_mask) if len(frame_history) == FRAME_WINDOW: # 遍历所有块 for i in range(0, h, BLOCK_SIZE): for j in range(0, w, BLOCK_SIZE): block_seq = [f[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE] for f in frame_history] # 计算块的时域方差,方差小且运动一致的判定为云 block_var = np.var(block_seq) if block_var < 10: # 静态背景块 cloud_mask[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE] = 255 else: # 计算块的帧间运动向量,和邻域块对比一致性 flow = cv2.calcOpticalFlowFarneback(block_seq[-2], block_seq[-1], None, 0.5, 3, 15, 3, 5, 1.2, 0) mean_flow = np.mean(flow, axis=(0,1)) # 简单判定:运动向量幅值小于0.5的大区域块判定为云 if np.linalg.norm(mean_flow) < 0.5: cloud_mask[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE] = 255 # 过滤云区域的候选像素 candidate_mask[cloud_mask == 255] = 0 # 连通域分析 contours, _ = cv2.findContours(candidate_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidate_boxes = [] for cnt in contours: area = cv2.contourArea(cnt) if 3 < area < 0.01 * w * h: x,y,wb,hb = cv2.boundingRect(cnt) candidate_boxes.append((x,y,wb,hb)) # 3. 多目标跟踪匹配 matched = set() # 现有跟踪器匹配 for tid in trackers: kalman = trackers[tid]["kalman"] pred = kalman.predict() pred_box = (int(pred[0]), int(pred[1]), trackers[tid]["last_box"][2], trackers[tid]["last_box"][3]) max_iou = 0 match_box = None for box in candidate_boxes: iou = calc_iou(pred_box, box) if iou > max_iou and iou > IOU_THRESH: max_iou = iou match_box = box if match_box: # 更新卡尔曼 kalman.correct(np.array([[match_box[0] + match_box[2]/2], [match_box[1] + match_box[3]/2]], dtype=np.float32)) trackers[tid]["last_box"] = match_box trackers[tid]["miss_cnt"] = 0 trackers[tid]["detect_cnt"] += 1 matched.add(match_box) else: trackers[tid]["miss_cnt"] += 1 # 清理丢失的跟踪器 to_del = [tid for tid in trackers if trackers[tid]["miss_cnt"] > MAX_MISS_FRAME] for tid in to_del: del trackers[tid] # 新增跟踪器 for box in candidate_boxes: if box not in matched: kalman = cv2.KalmanFilter(4,2) kalman.measurementMatrix = np.array([[1,0,0,0],[0,1,0,0]], np.float32) kalman.transitionMatrix = np.array([[1,0,1,0],[0,1,0,1],[0,0,1,0],[0,0,0,1]], np.float32) kalman.processNoiseCov = np.eye(4, dtype=np.float32) * 0.03 kalman.statePost = np.array([[box[0]+box[2]/2], [box[1]+box[3]/2], [0], [0]], dtype=np.float32) trackers[next_id] = { "kalman": kalman, "last_box": box, "miss_cnt": 0, "detect_cnt": 1 } next_id += 1 # 绘制有效跟踪框(连续检测到MIN_DETECT_FRAME的才输出) for tid in trackers: if trackers[tid]["detect_cnt"] >= MIN_DETECT_FRAME: x,y,wb,hb = trackers[tid]["last_box"] cv2.rectangle(frame, (x,y), (x+wb, y+hb), (0,255,0), 2) cv2.imshow("result", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
调优说明
- 若小目标漏检可适当调低
CONTRAST_THRESH参数,若云噪点过多可适当调高该参数 - 摄像头分辨率高于1080P可适当调大
BLOCK_SIZE参数,减少运算量 - 目标移动速度快可适当调高
IOU_THRESH,目标移动慢可适当调低该参数
内容的提问来源于stack exchange,提问作者a_coder
相关产品推荐
相关产品推荐

