一、原理
OpenCV内置目标检测
OpenCV内置的目标检测主要是对于特定物体(人脸)或者运动物体进行检测,本例使用OpenCV中的MOG2算法对运动的目标进行检测,检测后使用腐蚀、膨胀进行处理,得到较合理的初步目标检测区域。
OpenCV内置目标跟踪
使用OpenCV内置的MIL跟踪器或者DaSiamRPN跟踪器,使用其对于已经检测到的目标进行跟踪,以达到增强软件检测结果稳定性的目的。
软件设计目标
通过结合OpenCV内置的两种算法,达到较为稳定的目标检测目的。
二、软件设计
软件主要设计如图所示,表征了软件的循环结构
需要强调的是,本例完成匆忙仅采用了简单的平均值滤波器,但是本例非常适合采用卡尔曼滤波器对于检测和跟踪的结果进行预测。
三、代码
将上述软件设计封装在类中
import copy
import cv2
import numpy as np
class ImageProcessorCloseLoop:
def __init__(self, subtractor_type="MOG2", tracker_type="DaSiamRPN"):
self.sub = self.create_subtractor(subtractor_type)
self.tracker_type = tracker_type
self.trackers = list()
def create_subtractor(self, subtractor_type):
if subtractor_type == "MOG2":
return cv2.createBackgroundSubtractorMOG2()
elif subtractor_type == "KNN":
return cv2.createBackgroundSubtractorKNN()
def create_tracker(self, tracker_type):
if tracker_type == "MIL":
return cv2.TrackerMIL_create()
elif tracker_type == "GOTURN":
return cv2.TrackerGOTURN_create()
elif tracker_type == "DaSiamRPN":
return cv2.TrackerDaSiamRPN_create()
def process_one_frame(self, frame):
'''
:param frame: original video frame image (np.ndarray)
:return: bboxes (tuple of bbox)
'''
detect_result = self.simple_detect(frame)
track_result = list()
for tracker in self.trackers:
ok, bbox = tracker.update(frame)
if ok:
track_result.append(bbox)
else:
self.trackers.remove(tracker)
matched_detect, matched_track, only_detect, only_track = self.compare_result(detect_result, track_result)
res_matched = list()
for i in range(len(matched_track)):
bb1 = matched_detect[I]
bb2 = matched_track[I]
bb_res = self.bb_filter2(bb1, bb2)
res_matched.append(bb_res)
res_track = only_track
res_detect = only_detect
for res in res_detect:
tracker = self.create_tracker(tracker_type=self.tracker_type)
# tracker.init(frame, res)
res = res_matched + res_track + res_detect
return res
def simple_detect(self, img: np.ndarray):
mask = self.sub.apply(img)
thresh, bw = cv2.threshold(mask, 120, 255, cv2.THRESH_OTSU)
kernel = np.ones((3, 3), dtype=int)
dilated = cv2.erode(bw, kernel, iterations=1)
inflated = cv2.dilate(dilated, kernel, iterations=1)
# find contour:
cnts, hier = cv2.findContours(inflated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
bboxes = list()
for i in range(len(cnts), 0, -1):
c = cnts[i - 1]
area = cv2.contourArea(c)
if area < 10:
continue
bbox = cv2.boundingRect(c)
x, y, w, h = bbox
diameter = w if w > h else h
if diameter <= 15:
continue
bboxes.append(bbox)
return bboxes
def compare_two(self, bb1, bb2):
'''
compare two bbox
:param bb1: bbox tuple (x, y, w, h)
:param bb2: bbox tuple
:return: True or False
'''
x1, y1, w1, h1 = bb1
x2, y2, w2, h2 = bb2
IOU1 = (min(x1 + w1, x2 + w2) - max(x1, x2)) / (max(x1 + w1, x2 + w2) - min(x1, x2))
IOU2 = (min(y1 + h1, y2 + h2) - max(y1, y2)) / (max(y1 + h1, y2 + h2) - min(x1, x2))
IOU = IOU2 * IOU1
if IOU > 0.9:
return True
else:
return False
def compare_result(self, detect_res, track_res):
'''
compare the detect results by comparing IOU of the bboxes
:param last_res: list of bboxes
:param this_res: list of bboxes
:return: tuple (matched, new, only_last)
'''
matched_detect = list()
matched_track = list()
track = list()
detect = list()
for track_box in track_res:
for detect_box in detect_res:
is_matched = self.compare_two(track_box, detect_box)
if is_matched:
matched_track.append(track_box)
matched_detect.append(detect_box)
detect_res.remove(detect_box)
else:
track.append(track_box)
for detect_box in detect_res:
detect.append(detect_box)
return matched_detect, matched_track, detect, track
def bb_filter2(self, bbox1, bbox2, method="mean"):
'''
:param bbox1:bbox (x, y, w, h)
:param bbox2:bbox
:return: bbox
'''
if method == "mean":
return (bbox1[0] + bbox2[0])/2, (bbox1[1] + bbox2[1])/2, (bbox1[2] + bbox2[2])/2, (bbox1[3] + bbox2[3])/2
实例化类和视频读取循环写在main.py
中
main.py
import cv2
import processor
if __name__=="__main__":
cap = cv2.VideoCapture("/Users/wanglingyu/sources/video1.mp4")
proc = processor.ImageProcessorCloseLoop(tracker_type="MIL")
ret, frame = cap.read()
if not ret:
exit(1)
while True:
ret, frame = cap.read()
if not ret:
break
bboxes = proc.process_one_frame(frame)
for bbox in bboxes:
x, y, w, h = bbox
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 0xff), 1)
cv2.imshow("Video", frame)
key = cv2.waitKey(10)
if key == ord(" "):
k1 = cv2.waitKey()
cap.release()
cv2.destroyAllWindows()
四、效果
能够稳定地对于目标进行检测,且检测框不易丢失。
五、环境配置
本例代码运行于macOS 11.4 Big Sur上,该系统运行于arm平台的M1芯片上,其中OpenCV若需要编译则需要在配置cmake时配置好python2或python3的各个选项。如果使用PyCharm软件则在其内置的软件仓库中直接安装即可使用,兼容性没有问题。