【OpenCV 入门到精通 10】视频分析与光流跟踪:背景减除与运动检测
标签 :
OpenCV背景减除光流MeanshiftCamshift运动检测难度 :⭐⭐⭐ | 阅读时长 :约 65 分钟 | 系列 :第 10/12 集

写在前面
监控画面怎么检测移动的人?无人机怎么跟踪目标?本集讲解 背景减除 、Meanshift/Camshift 跟踪 和 光流法 ,对应官方 第6章 视频分析。
本集学习目标
- 使用 MOG2/KNN 进行背景减除
- 掌握 Meanshift 和 Camshift 目标跟踪
- 理解稠密光流和稀疏光流
- 完成运动检测实战
文章目录
- [【OpenCV 入门到精通 10】视频分析与光流跟踪:背景减除与运动检测](#【OpenCV 入门到精通 10】视频分析与光流跟踪:背景减除与运动检测)
10.1 背景减除
从视频中分离运动前景:
python
import cv2
cap = cv2.VideoCapture("traffic.mp4")
# MOG2 背景减除器
fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True)
while True:
ret, frame = cap.read()
if not ret: break
fgmask = fgbg.apply(frame)
# 去阴影(MOG2 阴影值为 127)
_, fgmask = cv2.threshold(fgmask, 200, 255, cv2.THRESH_BINARY)
cv2.imshow("Frame", frame)
cv2.imshow("FG Mask", fgmask)
if cv2.waitKey(30) & 0xFF == ord('q'): break
cap.release()
cv2.destroyAllWindows()
| 算法 | 特点 |
|---|---|
| MOG2 | 最常用,支持阴影检测 |
| KNN | 对噪声更鲁棒 |
| GMG | 需较长初始化 |
10.2 Meanshift 跟踪
在指定区域内找颜色直方图最匹配的位置:
python
import cv2
cap = cv2.VideoCapture("slow_traffic.mp4")
ret, frame = cap.read()
# 手动选择 ROI
r, h, c, w = cv2.selectROI("Frame", frame, False)
track_window = (c, r, w, h)
roi = frame[r:r+h, c:c+w]
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
roi_hist = cv2.calcHist([hsv_roi], [0], None, [180], [0, 180])
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
while True:
ret, frame = cap.read()
if not ret: break
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
ret, track_window = cv2.meanShift(dst, track_window, term_crit)
x, y, w, h = track_window
cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2)
cv2.imshow("Meanshift", frame)
if cv2.waitKey(30) & 0xFF == ord('q'): break
10.3 Camshift 跟踪
Meanshift 的改进版,窗口大小和角度可自适应:
python
ret, track_window = cv2.CamShift(dst, track_window, term_crit)
pts = cv2.boxPoints(ret)
pts = np.int32(pts)
cv2.polylines(frame, [pts], True, (0, 255, 0), 2)
| 对比 | Meanshift | Camshift |
|---|---|---|
| 窗口 | 固定大小 | 自适应大小+角度 |
| 适用 | 大小不变的目标 | 远近变化的目标 |
10.4 光流法
跟踪像素在两帧间的运动。
稀疏光流 Lucas-Kanade
python
import cv2
import numpy as np
cap = cv2.VideoCapture("slow_traffic.mp4")
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# Shi-Tomasi 角点
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
lk_params = dict(winSize=(15,15), maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
mask = np.zeros_like(old_frame)
while True:
ret, frame = cap.read()
if not ret: break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
good_new = p1[st == 1]
good_old = p0[st == 1]
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel()
c, d = old.ravel()
mask = cv2.line(mask, (int(a),int(b)), (int(c),int(d)), (0,255,0), 2)
frame = cv2.circle(frame, (int(a),int(b)), 5, (0,0,255), -1)
img = cv2.add(frame, mask)
cv2.imshow("Optical Flow", img)
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
if cv2.waitKey(30) & 0xFF == ord('q'): break
稠密光流 Farneback
python
flow = cv2.calcOpticalFlowFarneback(prev_gray, gray, None,
pyr_scale=0.5, levels=3, winsize=15, iterations=3,
poly_n=5, poly_sigma=1.2, flags=0)
# 可视化
magnitude, angle = cv2.cartToPolar(flow[...,0], flow[...,1])
hsv = np.zeros((h,w,3), dtype=np.uint8)
hsv[...,0] = angle * 180 / np.pi / 2
hsv[...,2] = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX)
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
| 方法 | 类型 | 特点 |
|---|---|---|
| calcOpticalFlowPyrLK | 稀疏 | 跟踪特征点,快 |
| calcOpticalFlowFarneback | 稠密 | 每像素运动,慢 |
10.5 综合实战:运动检测
python
import cv2
cap = cv2.VideoCapture(0)
fgbg = cv2.createBackgroundSubtractorMOG2(detectShadows=False)
while True:
ret, frame = cap.read()
if not ret: break
fgmask = fgbg.apply(frame)
# 形态学去噪
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
# 找轮廓
contours, _ = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) < 500: continue
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2)
cv2.imshow("Motion Detection", frame)
if cv2.waitKey(1) & 0xFF == ord('q'): break
cap.release()
cv2.destroyAllWindows()
10.6 踩坑指南
| 坑 | 解决 |
|---|---|
| 背景减除前景太多 | 调高 varThreshold;等待背景学习 |
| Meanshift 跟丢 | 目标颜色与背景太接近 |
| 光流点消失 | 特征点被遮挡,重新检测 |
| 摄像头背景减除慢 | 前几秒是背景学习期 |
10.7 本集小结
| 技术 | API | 场景 |
|---|---|---|
| 背景减除 | createBackgroundSubtractorMOG2 |
运动检测 |
| Meanshift | meanShift |
颜色跟踪 |
| Camshift | CamShift |
自适应跟踪 |
| 稀疏光流 | calcOpticalFlowPyrLK |
特征点跟踪 |
| 稠密光流 | calcOpticalFlowFarneback |
运动场可视化 |
| ← 第09集 | 第11集:机器学习 → |
|---|
参考:6_1 背景分离 | 6_2 Meanshift | 6_3 光流