视觉检测是追踪系统最关键的一环。对于蚊子这种小而快的目标,传统的HSV颜色阈值完全失效,必须切换到运动检测范式。
为什么不能用HSV颜色阈值
气球场景HSV颜色阈值有效,因为气球大、颜色鲜艳。但蚊子场景完全不同:
| 维度 | 气球 | 蚊子 |
|---|---|---|
| 大小 | 占画面几十~几百像素 | 几像素到十几像素 |
| 颜色 | 单一鲜艳 | 暗色杂色,HSV无效 |
| 速度 | 慢可预测 | 快(1-2 m/s)、轨迹zigzag |
| 检测方法 | HSV+轮廓/霍夫圆 | 帧差/MOG2 + 轮廓面积过滤 |
两种主流运动检测算法
A. MOG2背景减除(推荐主用)
维护一个背景模型(混合高斯),每帧与当前帧对比。自适应光照变化,抗噪声,阴影可检测(灰色标记)。
OpenCV接口:cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True)
参数调优:
| 参数 | 默认 | 调节方向 |
|---|---|---|
| history | 500 | 越大越鲁棒但响应慢 |
| varThreshold | 16 | 越大越严格(蚊子小目标建议25-50) |
| detectShadows | True | 关掉可减少噪声,但易误判阴影为前景 |
B. 帧差法(轻量备用)
直接计算连续帧灰度差,计算量小,但只对快速运动有效。改进版本是三帧差分法,可以减少”空洞”现象。
检测流水线
读取帧
→ 灰度化 + GaussianBlur降噪
→ MOG2.apply() 得到前景掩码
→ morphologyEx 开运算去噪点
→ findContours 找轮廓
→ 按面积过滤(蚊子5~50像素²,去除过大过小)
→ 取最大/最近运动blob
→ moments计算质心(cx, cy)
→ 返回质心坐标
完整代码:MOG2背景减除检测
"""
motion_mog2.py — OpenCV MOG2 背景减除检测运动物体
来源:改编自 myronabotanel/motion-tracking-opencv 与百度云开发者社区教程
依赖:pip install opencv-python numpy
运行:python motion_mog2.py (按q退出)
说明:摄像头0号,输出前景掩码+检测框+质心
"""
import cv2
import numpy as np
# === 配置 ===
CAM_INDEX = 0
RES_W, RES_H = 320, 240 # 低分辨率提帧率
HISTORY = 500
VAR_THRESHOLD = 40 # 蚊子小目标建议25-50
DETECT_SHADOWS = False # 关闭阴影检测减噪
MIN_AREA = 5 # 蚊子最小面积(像素²)
MAX_AREA = 500 # 排除人等大物体
MORPH_KERNEL = 3 # 形态学核大小
# MOG2初始化
fgbg = cv2.createBackgroundSubtractorMOG2(
history=HISTORY,
varThreshold=VAR_THRESHOLD,
detectShadows=DETECT_SHADOWS
)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (MORPH_KERNEL, MORPH_KERNEL))
cap = cv2.VideoCapture(CAM_INDEX)
cap.set(3, RES_W)
cap.set(4, RES_H)
# 预热背景模型(采集几十帧让MOG2学背景)
print("预热背景模型(请勿在前方活动),3秒...")
for _ in range(60):
ret, _ = cap.read()
if not ret:
break
def detect_largest_motion(frame):
"""返回最大运动blob的质心(cx, cy)与面积,无则返回None"""
fgmask = fgbg.apply(frame)
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
contours, _ = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return None, fgmask
# 按面积过滤
valid = [c for c in contours if MIN_AREA <= cv2.contourArea(c) <= MAX_AREA]
if not valid:
return None, fgmask
largest = max(valid, key=cv2.contourArea)
M = cv2.moments(largest)
if M["m00"] == 0:
return None, fgmask
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
return (cx, cy, cv2.contourArea(largest)), fgmask
if __name__ == "__main__":
try:
while True:
ret, frame = cap.read()
if not ret:
break
result, fgmask = detect_largest_motion(frame)
if result:
cx, cy, area = result
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
cv2.putText(frame, f"area={area}", (cx + 10, cy),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
# 画画面中心十字
cv2.drawMarker(frame, (RES_W // 2, RES_H // 2), (255, 0, 0),
cv2.MARKER_CROSS, 10, 1)
cv2.imshow("frame", frame)
cv2.imshow("fgmask", fgmask)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
cap.release()
cv2.destroyAllWindows()
蚊子检测的额外建议
- 降分辨率提帧率:320×240甚至160×120,可上60fps以上,这对于追快速运动至关重要。
- 环境光照稳定:蚊子背景复杂时,可在视野中放浅色背景板。
- 缩小ROI:先用低分辨率全帧检测,锁定后只处理ROI区域,大幅提帧率。
- 多帧确认:连续3-5帧检测到才触发跟踪,避免单帧噪点误触发。
- 有效距离:C270在1米外蚊子已近亚像素,实际有效范围0.5~1.5米。