Bootstrap

Python-OpenCV视频帧间差分、高斯混合建模、背景差分提取前景目标标示轮廓、KCF目标跟踪、Meanshift算法跟踪

本人只是想很简单的入门了解opencv,目前相关原理和知识了解的不多,可能存在有些地方写的不对,仅供参考。

1.帧间差分

帧间差分法是一种通过对视频图像序列的连续两帧图像做差分运算获取运动目标轮廓的方法。当监控场景中出现异常目标运动时,相邻两帧图像之间会出现较为明显的差别,两帧相减,求得图像对应位置像素值差的绝对值,判断其是否大于某一阈值,进而分析视频或图像序列的物体运动特性

原理: 当视频中存在移动物体的时候,相邻帧之间在灰度上会有差别,求取两帧图像灰度差的绝对值,则静止的物体在差值图像上表现出来全是0,而移动物体特别是移动物体的轮廓处由于存在灰度变化为非0。

实现: 相邻帧间差分法直接对相邻的两帧图像做差分运算,并取差分运算的绝对值构成移动物体。
更多可以了解 运动目标检测(2)—帧间差分法

class demo1():

    def run(self, Videopath='./video.avi'):
        frames = self.Video_to_image(Videopath)
        self.absdiff_(frames)

    def Video_to_image(self, Videopath):
        capture = cv2.VideoCapture(Videopath)
        # 得到整个视频的帧数
        framesNum = capture.get(cv2.CAP_PROP_FRAME_COUNT)
        print("frames=", framesNum)
        frames = []

        for i in range(int(framesNum) - 1):
            ret, frame = capture.read()
            frames.append(frame)
        return frames

    def absdiff_(self, frames):
        c_frames = []
        for i in range(len(frames) - 2):
            frame_front = frames[i]
            frame_later = frames[i + 1]
            # 帧间做差
            d_frame = cv2.absdiff(frame_front, frame_later)
            c_frames.append(d_frame)
            cv2.imshow('d_frame', d_frame)
            cv2.waitKey()

        return c_frames

在这里插入图片描述

2.高斯混合建模、背景差分提取前景目标,将轮廓信息标示

混合高斯模型:在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应。然后在测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。
更多可以了解 [Python - opencv(十一)背景建模]

class demo2():
    def __init__(self, Videopath='./counting_test.avi'):
        self.capture = cv2.VideoCapture(Videopath)

    def Gaussian(self, drawContours=False, drawRectangle=True):
        cap = self.capture
        # 创建形态学操作时需要使用的核
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
        # 创建混合高斯模型
        fgbg = cv2.createBackgroundSubtractorMOG2()
        # 将行人在视频中实时标记出
        while (True):
            ret, frame = cap.read()
            fgmask = fgbg.apply(frame)
            # 形态学开运算去噪点
            fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
            
            # 寻找视频中的轮廓
            im, contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            
            if drawContours: #背景差分提取前景目标,将轮廓信息标示
                n = len(contours)
                for i in range(n):
                    temp = np.zeros(frame.shape, np.uint8)
                    temp = cv2.drawContours(temp, contours, i, (255, 255, 255), 2)
                    cv2.imshow('frame', frame)
                    cv2.imshow("contours", temp)
                cv2.waitKey()

            if drawRectangle:
                for c in contours:
                    # 计算各轮廓的周长
                    perimeter = cv2.arcLength(c, True)
                    if perimeter > 188:
                        # 找到一个直矩形(不会旋转)
                        x, y, w, h = cv2.boundingRect(c)
                        # 画出这个矩形
                        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

                cv2.imshow('frame', frame)
                cv2.imshow('fgmask', fgmask)
                k = cv2.waitKey()
                if k == 27:
                    break

        cap.release()
        cv2.destroyAllWindows()

在这里插入图片描述
在这里插入图片描述

3.实现KCF目标跟踪

什么是目标跟踪?

简单理解就是在一个视频的连续帧中定位目标,称之为跟踪。

KCF原理:目标跟踪系列–KCF算法

代码建议参考 python调用opencv库中的KCF等跟踪算法(有中文注释),我这边只是根据它的大体思路简化了一下,存在不足。

总共有七种不同的跟踪器:BOOSTING,MIL,KCF,TLD,MEDIANFLOW,GOTURN和MOSSE。

想简单了解更多关于目标跟踪的定义可以参考 Object Tracking using OpenCV (C++/Python)(使用OpenCV进行目标跟踪),其实它的代码才是元祖。

def demo3():
    # 初始化视频捕获设备
    # gVideoDevice = cv2.VideoCapture("./video.avi")
    gVideoDevice = cv2.VideoCapture(0)
    if not gVideoDevice.isOpened():
        print('open video failed')
        return
    else:
        print('open video succeeded')

    # 选择 框选帧
    print("按 enter 选择当前帧,否则继续下一帧")
    while True:
        gCapStatus, gFrame = gVideoDevice.read()
        cv2.imshow("pick frame", gFrame)
        k = cv2.waitKey()
        if k == 13:
            break

    # 框选感兴趣区域
    cv2.destroyWindow("pick frame")
    gROI = cv2.selectROI("ROI frame", gFrame, False)
    if (not gROI):
        print("空框选,退出")
        quit()

    # 初始化追踪器
    gTracker = cv2.TrackerKCF_create()
    gTracker.init(gFrame, gROI)

    # 循环帧读取,开始跟踪
    while True:
        gCapStatus, gFrame = gVideoDevice.read()
        if (gCapStatus):
            # 展示跟踪图片
            status, coord = gTracker.update(gFrame)
            if status:
                message = {"coord": [((int(coord[0]), int(coord[1])),
                                      (int(coord[0] + coord[2]), int(coord[1] + coord[3])))]}
                p1 = (int(coord[0]), int(coord[1]))
                p2 = (int(coord[0] + coord[2]), int(coord[1] + coord[3]))
                cv2.rectangle(gFrame, p1, p2, (255, 0, 0), 2, 1)
                message['msg'] = "is tracking"
            else:
                message['msg'] = "KCF error,需要重新使用调用跟踪器"
            cv2.imshow('tracked image', gFrame)
            print(message)
            key = cv2.waitKey(1)
            if key == 27:
                break
        else:
            print("捕获帧失败")
            quit()

在这里插入图片描述

我这边还修改了 python调用opencv库中的KCF等跟踪算法 的一些小地方,你也可以自己补充使用不同的跟踪器。

class MessageItem(object):
    # 用于封装信息的类,包含图片和其他信息
    def __init__(self, frame, message):
        self._frame = frame
        self._message = message

    def getFrame(self):
        # 图片信息
        return self._frame

    def getMessage(self):
        # 文字信息,json格式
        return self._message


class Tracker(object):
    '''
    追踪者模块,用于追踪指定目标
    '''

    def __init__(self, tracker_type="BOOSTING", draw_coord=True):
        '''
        初始化追踪器种类
        '''
        # 获得opencv版本
        (major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')
        self.tracker_types = ['BOOSTING', 'MIL', 'KCF', 'TLD', 'MEDIANFLOW', 'GOTURN']
        self.tracker_type = tracker_type
        self.isWorking = False
        self.draw_coord = draw_coord
        # 构造追踪器
        if int(major_ver) < 3:
            self.tracker = cv2.Tracker_create(tracker_type)
        else:
            if tracker_type == 'BOOSTING':
                self.tracker = cv2.TrackerBoosting_create()
            if tracker_type == 'MIL':
                self.tracker = cv2.TrackerMIL_create()
            if tracker_type == 'KCF':
                self.tracker = cv2.TrackerKCF_create()
            if tracker_type == 'TLD':
                self.tracker = cv2.TrackerTLD_create()
            if tracker_type == 'MEDIANFLOW':
                self.tracker = cv2.TrackerMedianFlow_create()
            if tracker_type == 'GOTURN':
                self.tracker = cv2.TrackerGOTURN_create()

    def initWorking(self, frame, box):
        '''
        追踪器工作初始化
        frame:初始化追踪画面
        box:追踪的区域
        '''
        if not self.tracker:
            raise Exception("追踪器未初始化")
        status = self.tracker.init(frame, box)
        if not status:
            raise Exception("追踪器工作初始化失败")
        self.coord = box
        self.isWorking = True

    def track(self, frame):
        '''
        开启追踪
        '''
        message = None
        if self.isWorking:
            status, self.coord = self.tracker.update(frame)
            if status:
                message = {"coord": [((int(self.coord[0]), int(self.coord[1])),
                                      (int(self.coord[0] + self.coord[2]), int(self.coord[1] + self.coord[3])))]}
                if self.draw_coord:
                    p1 = (int(self.coord[0]), int(self.coord[1]))
                    p2 = (int(self.coord[0] + self.coord[2]), int(self.coord[1] + self.coord[3]))
                    cv2.rectangle(frame, p1, p2, (255, 0, 0), 2, 1)
                    message['msg'] = "is tracking"
        return MessageItem(frame, message)


def TrackerDemo():
    # 初始化视频捕获设备
    # gVideoDevice = cv2.VideoCapture("./video.avi")
    gVideoDevice = cv2.VideoCapture(0)
    if not gVideoDevice.isOpened():
        print('open video failed')
        return
    else:
        print('open video succeeded')

    # 选择 框选帧
    print("按 enter 为当前帧,否则继续下一帧")
    while True:
        gCapStatus, gFrame = gVideoDevice.read()
        cv2.imshow("pick frame", gFrame)
        k = cv2.waitKey()
        if k == 13:
            break

    # 框选感兴趣区域region of interest
    cv2.destroyWindow("pick frame")
    gROI = cv2.selectROI("ROI frame", gFrame, False)
    if (not gROI):
        print("空框选,退出")
        quit()

    # 初始化追踪器
    gTracker = Tracker(tracker_type="KCF")
    gTracker.initWorking(gFrame, gROI)

    # 循环帧读取,开始跟踪
    while True:
        gCapStatus, gFrame = gVideoDevice.read()
        if (gCapStatus):
            # 展示跟踪图片
            _item = gTracker.track(gFrame)
            cv2.imshow("track result", _item.getFrame())

            if _item.getMessage():
                # 打印跟踪数据
                print(_item.getMessage())
            else:
                # 丢失,重新用初始ROI初始
                print("丢失,重新使用初始ROI开始")
                gTracker = Tracker(tracker_type="KCF")
                gTracker.initWorking(gFrame, gROI)

            _key = cv2.waitKey(1) & 0xFF
            if (_key == ord('q')) | (_key == 27):
                break
            if (_key == ord('r')):
                # 用户请求用初始ROI
                print("用户请求用初始ROI")
                gTracker = Tracker(tracker_type="KCF")
                gTracker.initWorking(gFrame, gROI)

        else:
            print("捕获帧失败")
            quit()
4.Meanshift算法跟踪视频中对象

后续我还简单了解到跟踪视频中对象的Meanshift和Camshift算法,也是不错的跟踪方法,而且更好理解。

Meanshift(均值漂移)是一种在一组数据的密度分布中寻找局部极值的稳定的方法。Meanshift不仅可以用于图像滤波,视频跟踪,还可以用于图像分割。
通过给出一组多维数据点,其维数是(x,y,r,g,b),均值漂移可以用一个窗口扫描空间来找到数据密度最大的区域,可以理解为数据分布最集中的区域。

Meanshift背后的直觉很简单,假设你有点的集合。(它可以是像素分布,例如直方图反投影)。你会得到一个小窗口(可能是一个圆形),并且必须将该窗口移到最大像素密度(或最大点数)的区域。

更多可以了解 滤波与模糊操作python opencv入门 Meanshift 和 Camshift 算法(40)

def demo4():
    cap = cv2.VideoCapture('../实验10 视频处理/video.avi')
    # 视频的第一帧
    ret, frame = cap.read()
    # 设置窗口的初始位置
    x, y, w, h = 510, 286, 50, 50
    track_window = (x, y, w, h)
    # 设置初始ROI来追踪
    roi = frame[y:y + h, x:x + w]
    hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.)))
    roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180])
    cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
    # 设置终止条件,可以是10次迭代,也可以至少移动1 pt
    term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
    while (1):
        ret, frame = cap.read()
        if ret == True:
            dst = cv2.pyrMeanShiftFiltering(frame, 10, 50)  # 均值迁移
            # cv2.namedWindow("meanshift_demo", cv2.WINDOW_NORMAL)
            cv2.imshow("meanshift_demo", dst)

            hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
            dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
            # 应用meanshift来获取新位置
            ret, track_window = cv2.meanShift(dst, track_window, term_crit)
            # 在图像上绘制
            x, y, w, h = track_window
            print("window: ", track_window)
            img2 = cv2.rectangle(frame, (x, y), (x + w, y + h), 255, 2)
            cv2.imshow('img2', img2)
            k = cv2.waitKey(30) & 0xff
            if k == 27:
                break
        else:
            break

均值迁移 pyrMeanShiftFiltering

在这里插入图片描述

Meanshift跟踪对象

在这里插入图片描述

;