传统运动物体检测方法的Python实现
文章目录
一、目标跟踪算法综述
视觉目标(单目标)跟踪任务就是在给定某视频序列初始帧的目标大小与位置的情况下,预测后续帧中该目标的大小与位置
1. 传统方法:特征提取+滤波类搜索算法
(1) 帧差法、光流法、背景减除法
(2) 相关滤波法
2. 深度学习方法: 目标检测和相似度匹配
(1) tracking-by-detection方式:
主要针对目标检测算法和滤波类算法(多目标跟踪),yolo系列、SSD系列、anchor-free系列、two-stage系列等等,滤波类和上述传统方式相似。
(2) 基于Siamese Networks(生成式,主要针对单目标):
主要通过Siamese网络进行相似度匹配,主要操作为:首先手动选择初始图像中的目标,使用Siamese网络进行特征提取,然后以此特征为标准,遍历后面帧图像的每个位置,对每个位置进行特征提取,然后做比较,确定位置。
二、Python实现
0.引入库
代码如下:
import cv2
import numpy as np
1. 帧差法
(1)二帧法
当前帧与前一帧差分,核心代码如下:
# 当前帧
tempFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 差分帧
disFrame = cv2.absdiff(tempFrame, last_frame)
# 中值滤波去噪
median = cv2.medianBlur(disFrame, 3)
# 二值化
ret, threshold_frame = cv2.threshold(median, 20, 255, cv2.THRESH_BINARY)
# 高斯模糊
gauss_image = cv2.GaussianBlur(threshold_frame, (3, 3), 0)
# 更新,便于下一次差分
last_frame = tempFrame
(2)三帧法
对于连续的三帧,12相减,23相减,然后将两次差分的结果做与运算作为最终结果,相比于二帧法可以消除微小抖动的影响。核心代码如下:
# 当前帧
temp_frame = frame_gray
# 12差分
dis_frame1 = cv2.absdiff(frame2, frame1)
_, thresh1 = cv2.threshold(dis_frame1, 40, 255, cv2.THRESH_BINARY) # 二值,大于40的为255,小于0
# 23差分
dis_frame2 = cv2.absdiff(temp_frame, frame2)
_, thresh2 = cv2.threshold(dis_frame2, 40, 255, cv2.THRESH_BINARY) # 二值,大于40的为255,小于0
# 与运算
dis_frame = cv2.bitwise_and(thresh1, thresh2) # 二值化图像
# 更新,便于下一次差分
frame1, frame2 = frame2, temp_frame
2. 背景减除法
采用OpenCV的createBackgroundSubtractorMOG2实现。
这个也是以高斯混合模型为基础的背景 / 前景分割算法。它是以2004年和2006年Z.Zivkovic的两篇文章为基础的。这个算法的一个特点是它为每
一个像素选择一个合适数目的高斯分布。这样就会对由于亮度等发生变化引起的场景变化产生更好的适应。
cv2.createBackgroundSubtractorMOG2(history, varThreshold, detectShadows)
该方法可以选择是否检测阴影。如果detectShadows = True(默认值),它就会检测并将影子标记出来,但是这样做会降低处理速度。影子会被标记为灰色。
fgbg = cv2.createBackgroundSubtractorMOG2()
fgmask = fgbg.apply(frame)
3. 光流法
所谓光流就是瞬时速率,在时间间隔很小(比如视频的连续前后两帧之间)时,也等同于目标点的位移
(1)实现流程
(1)首先获取视频或者摄像头的第一帧图像 用goodFeaturesToTrack函数获取初始化的角点
(2)然后开始无限循环获取视频图像帧 将新图像和上一帧图像放入calcOpticalFlowPyrLK函数当中,从而获取新图像的光流。
使用光流法的前提假设:
(1)相邻帧之间的亮度恒定;
(2)相邻视频帧的取帧时间连续,或者,相邻帧之间物体的运动比较“微小”;
(3)保持空间一致性;即,同一子图像的像素点具有相同的运动
(2)Python代码
# 从第一帧中选择利于跟踪的特征
p0 = cv2.goodFeaturesToTrack(frame0_gray, mask=None, **feature_params) # **param 关键字参数
mask = np.zeros_like(frame0)
tempFrame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
p1, st, err = cv2.calcOpticalFlowPyrLK(frame0_gray, tempFrame_gray, p0, None, **lk_params)
# 选取好的跟踪点
good2track_temp = p1[st == 1]
good2track_0 = p0[st == 1]
# 更新上一帧的图像和追踪点
old_gray = tempFrame_gray.copy()
p0 = good2track_0.reshape(-1, 1, 2)
三、完整代码
运行环境:Python3.8、OpenCV-Python4.5.5.62
# _*_ coding: utf-8 _*_
# @time : 2022/1/12 16:29
# @name : traditionalMethod.py
# @author : 霜晨月~
import cv2
import numpy as np
# 1.1、帧差分法-->二帧法
def dis_2frame():
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
last_frame = np.zeros((height, width), dtype=np.uint8) # 前1帧
num = 0
if not cap.isOpened():
print('Error opening video or file!')
while cap.isOpened(): # 视频打开成功
ret, frame = cap.read()
if ret:
# 当前帧
tempFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 差分帧
disFrame = cv2.absdiff(tempFrame, last_frame)
# 中值滤波去噪
median = cv2.medianBlur(disFrame, 3)
# 二值化
ret, threshold_frame = cv2.threshold(median, 20, 255, cv2.THRESH_BINARY)
# 高斯模糊
gauss_image = cv2.GaussianBlur(threshold_frame, (3, 3), 0)
cv2.imshow('dis_medianBlur_threshold_gaussianBlur', gauss_image)
# 更新,便于下一次差分
last_frame = tempFrame
num += 1
if cv2.waitKey(20) & 0xFF == 27:
cv2.destroyAllWindows()
break
cap.release()
# 1.2、帧差分法-->三帧法:12相减,23相减,与运算
def dis_3frame():
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
frame1 = np.zeros((height, width), dtype=np.uint8) # 前2帧 1
frame2 = frame1 # 前1帧 2
num = 0
if not cap.isOpened():
print('Error opening video or file!')
while cap.isOpened(): # 视频打开成功
ret, frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
if ret:
# 当前帧
temp_frame = frame_gray
# 12差分
dis_frame1 = cv2.absdiff(frame2, frame1)
_, thresh1 = cv2.threshold(dis_frame1, 40, 255, cv2.THRESH_BINARY) # 二值,大于40的为255,小于0
# 23差分
dis_frame2 = cv2.absdiff(temp_frame, frame2)
_, thresh2 = cv2.threshold(dis_frame2, 40, 255, cv2.THRESH_BINARY) # 二值,大于40的为255,小于0
# 与运算
dis_frame = cv2.bitwise_and(thresh1, thresh2) # 二值化图像
# 形态学去噪
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
erode = cv2.erode(dis_frame, kernel) # 腐蚀
# dilate = cv2.dilate(erode, kernel) # 膨胀
# dilate = cv2.dilate(dilate, kernel) # 膨胀
img, contours, hei = cv2.findContours(erode.copy(), mode=cv2.RETR_EXTERNAL,
method=cv2.CHAIN_APPROX_SIMPLE) # 寻找轮廓
for contour in contours:
if 100 < cv2.contourArea(contour) < 40000:
x, y, w, h = cv2.boundingRect(contour) # 找方框
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255))
cv2.namedWindow("binary", cv2.WINDOW_NORMAL)
cv2.namedWindow("dilate", cv2.WINDOW_NORMAL)
cv2.namedWindow("frame", cv2.WINDOW_NORMAL)
cv2.imshow("binary", dis_frame)
cv2.imshow("dilate", erode)
cv2.imshow("frame", frame)
# 更新,便于下一次差分
frame1, frame2 = frame2, temp_frame
num += 1
if cv2.waitKey(20) & 0xFF == 27:
cv2.destroyAllWindows()
break
cap.release()
# 2、背景减除法
def createBackground():
cap = cv2.VideoCapture(0)
# kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
# fgbg = cv2.bgsegm.createBackgroundSubtractorGMG()
fgbg = cv2.createBackgroundSubtractorMOG2()
while cap.isOpened():
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
# fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
cv2.imshow('fgmask', fgmask)
if cv2.waitKey(20) & 0xFF == 27:
cv2.destroyAllWindows()
break
cap.release()
# 3、光流法
def lightFlow():
cap = cv2.VideoCapture(0)
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7) # ShiTomasi 角点检测参数
ret, frame0 = cap.read()
w, h = cap.get(3), cap.get(4)
FPS = cap.get(5)
print('size:', w, h)
print('FPS:', FPS)
frame0_gray = cv2.cvtColor(frame0, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(frame0_gray, mask=None, **feature_params) # **param 关键字参数
mask = np.zeros_like(frame0) # 创建一个蒙版用来画轨迹,i.e.和每帧图像大小相同的全0张量
lk_params = dict(winSize=(15, 15), maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) # lucas kanade光流法参数
color = np.random.randint(0, 255, (100, 3)) # 创建随机颜色
while cap.isOpened():
ret, frame = cap.read()
tempFrame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
p1, st, err = cv2.calcOpticalFlowPyrLK(frame0_gray, tempFrame_gray, p0, None, **lk_params)
# 选取好的跟踪点
good2track_temp = p1[st == 1]
good2track_0 = p0[st == 1]
# 画出轨迹
for i, (new, old) in enumerate(zip(good2track_temp, good2track_0)):
a, b = new.ravel()
c, d = old.ravel()
mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2) # 添加了该帧光流的轨迹图
frame = cv2.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)
img = cv2.add(frame, mask) # 将该图和轨迹图合并
cv2.imshow('frame', img)
if cv2.waitKey(20) & 0xFF == 27:
cv2.destroyAllWindows()
break
# 更新上一帧的图像和追踪点
old_gray = tempFrame_gray.copy()
p0 = good2track_0.reshape(-1, 1, 2)
cap.release()
if __name__ == '__main__':
dis_2frame()
# dis_3frame()
# createBackground()
# lightFlow()