【图像工程入门】OpenCV检测器和跟踪器(运动物体检测跟踪)

一、原理

OpenCV内置目标检测

OpenCV内置的目标检测主要是对于特定物体(人脸)或者运动物体进行检测,本例使用OpenCV中的MOG2算法对运动的目标进行检测,检测后使用腐蚀、膨胀进行处理,得到较合理的初步目标检测区域。

OpenCV内置目标跟踪

使用OpenCV内置的MIL跟踪器或者DaSiamRPN跟踪器,使用其对于已经检测到的目标进行跟踪,以达到增强软件检测结果稳定性的目的。

软件设计目标

通过结合OpenCV内置的两种算法,达到较为稳定的目标检测目的。

二、软件设计

软件主要设计如图所示,表征了软件的循环结构


循环中每次调用类方法(process_one_frame)

需要强调的是,本例完成匆忙仅采用了简单的平均值滤波器,但是本例非常适合采用卡尔曼滤波器对于检测和跟踪的结果进行预测。

三、代码

将上述软件设计封装在类中

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软件则在其内置的软件仓库中直接安装即可使用,兼容性没有问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356