美文网首页人工智能机器视觉
OpenCV 3 & Keras 实现多目标车辆跟踪 笔

OpenCV 3 & Keras 实现多目标车辆跟踪 笔

作者: liangchen815 | 来源:发表于2018-07-26 16:10 被阅读293次

    原文: OpenCV 3 & Keras 实现多目标车辆跟踪

    算法

    目标检测: MOG2

    OpenCV提供了一个称为BackgroundSubtractor的类,在分割前景和背景时很方便。

    在OpenCV3中有三种背景分割器:K-Nearest(KNN)、Mixture of Gaussians(MOG2)、Geometric Multigid(GMG)

    BackgroundSubtractor类是专门用于视频分析的,即BackgroundSubtractor类会对每帧的环境进行“学习”。BackgroundSubtractor类常用来对不同帧进行比较,并存储以前的帧,可按时间推移方法来提高运动分析的结果。

    BackgroundSubtractor类的另一个基本特征是它可以计算阴影。这对于精确读取视频帧绝对是至关重要的;通过检测阴影,可以排除检测图像的阴影区域(采用阈值方式),从而能关注实际特征。

    目标跟踪:KCF

    KCF是一种鉴别式追踪方法,这类方法一般都是在追踪过程中训练一个目标检测器,使用目标检测器去检测下一帧预测位置是否是目标,然后再使用新检测结果去更新训练集进而更新目标检测器。而在训练目标检测器时一般选取目标区域为正样本,目标的周围区域为负样本,当然越靠近目标的区域为正样本的可能性越大。

    物体分类: CNN(卷积神经网络)

    吴恩达deeplearning之CNN—卷积神经网络入门

    环境

    Python 3

    OpenCV + contrib

    Tensorflow

    Keras

    Keras是一个高层神经网络API,Keras由纯Python编写而成并基TensorflowTheano以及CNTK后端。Keras 为支持快速实验而生,能够把你的idea迅速转换为结果.

    目标检测

    我们使用MOG2进行当前帧的目标检测任务。MOG2是一种背景减除算法,在之前的文章中进行了介绍。目标检测的代码如下所示:

    OpenCV函数:cv2.erode(), cv2.dilate()
    形态学操作一般作用于二值化图,来连接相邻的元素或分离成独立的元素。腐蚀和膨胀是针对图片中的白色部分!
    腐蚀:腐蚀的效果是把图片"变瘦",其原理是在原图的小区域内取局部最小值。这样原图中边缘地方就会变成0,达到了瘦身目的。
    膨胀:膨胀与腐蚀相反,取的是局部最大值,效果是把图片"变胖"。


    1690384-2d79afac906a96ec.jpg
    import sys
    import copy
    import argparse
    import cv2
    import numpy as np
    from keras.models import load_model
    
    from utils.entity import Entity
    
    
    camera = cv2.VideoCapture(video) #对视频进行读取操作以及调用摄像头
    res, frame = camera.read() #获取视频帧,返回成功/失败,帧
    y_size = frame.shape[0]  
    x_size = frame.shape[1]
    
    # 导入CNN分类模型
    model = load_model('model//weights.h5')
    
    bs = cv2.createBackgroundSubtractorMOG2(detectShadows=True)    # 定义MOG2,用于动态目标检测,是基于自适应混合高斯背景建模的背景减除法,detectShadows:是否检测影子
    history = 20    # MOG2训练使用的帧数
    frames = 0      # 当前帧数
    counter = 0     # 当前目标id
    
    cv2.namedWindow("detection", cv2.WINDOW_NORMAL) #通过指定的名字,创建一个可以作为图像和进度条的容器窗口。WINDOW_NORMAL设置了这个值,用户便可以改变窗口的大小(没有限制)
    while True:
        res, frame = camera.read()
    
        if not res:
            break
        # 使用前20帧训练MOG2
        fg_mask = bs.apply(frame)
    
        if frames < history:
            frames += 1
            continue
        # 对帧图像进行膨胀与去噪声操作
        th = cv2.threshold(fg_mask.copy(), 244, 255, cv2.THRESH_BINARY)[1] #固定阈值二值化:像素高于阈值时,给像素赋予新值,否则,赋予另外一种颜色.ret, dst = cv2.threshold(src, thresh, maxval, type),当像素值超过了thresh(或者小于thresh,根据type来决定),赋予maxval,因为是二值化图,只有0和255
        th = cv2.erode(th, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=2) #腐蚀图像erode(img, kernel),MORPH_ELLIPSE:椭圆
        dilated = cv2.dilate(th, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 3)), iterations=2) #膨胀图像
        # 获得目标位置
        image, contours, hier = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #查找检测物体的轮廓,cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]]),
        # 参数为二值图,即黑白的,mode为轮廓的检索模式,cv2.RETR_EXTERNAL表示只检测外轮廓,method为轮廓的近似办法, cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
        # contour返回值,返回一个list,list中每个元素都是图像中的一个轮廓
        # hierarchy返回值,每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0] ~hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,则该值为负数。
    

    在提取目标之后,我们先对其进行重置大小,去均值等操作,然后将其送入CNN模型,判断是否为车辆。如果当前目标为车辆,那么就与跟踪列表中的对象进行对比。这里我们对比两者的IOU,即重叠度,只有当前目标与列表中所有的目标的重叠度都很小时,才会将当前目标加入跟踪列表。

    for c in contours:
        x, y, w, h = cv2.boundingRect(c) #用一个最小的矩形,把找到的形状包起来,x,y是矩阵左上点的坐标,w,h是矩阵的宽和高
          if cv2.contourArea(c) > 3000: #在遍历检测框时,我们使用面积3000作为阈值对检测框进行过滤,去掉过小的检测框,以减轻分类模型运行的次数。
            # 提取目标
            img = frame[y: y + h, x: x + w, :]
            rimg = cv2.resize(img, (64, 64), interpolation=cv2.INTER_CUBIC) #图像缩放,cv2.INTER_CUBIC:4x4像素邻域的双三次插值
            image_data = np.array(rimg, dtype='float32') #图片与数组的转化
            image_data /= 255.
            roi = np.expand_dims(image_data, axis=0) #转换为预测所需类型
            # 分类
            flag = model.predict(roi)
    
            if flag[0][0] > 0.5:
                 e = Entity(counter, (x, y, w, h), frame)
                 # 排除重复目标
                 if track_list:
                      count = 0
                      num = len(track_list)
                       for p in track_list: #对比当前目标和跟踪列表中现有目标
                            if overlap((x, y, w, h), p.windows) < iou:#track_list重叠,iou:目标窗口和原来标记窗口的交叠率,小于说明重叠度比较小(不重叠)
                                count += 1
                        if count == num: #说明没有重叠的目标
                            track_list.append(e)
                    else:
                            track_list.append(e)
                    counter += 1 #目标id
    

    在处理完新的物体后,我们对跟踪列表中的对象进行处理。如果对象的中心接近帧边界,那么就将其从列表中移除。如果没有,那么就对该对象进行更新操作,激活其跟踪器。

    if track_list:
        tlist = copy.copy(track_list)
            for e in tlist:
                x, y = e.center #获取跟踪目标的中心
                if 10 < x < x_size - 10 and 10 < y < y_size - 10:
                    e.update(frame) #更新操作,激活其跟踪器。
                else:
                    track_list.remove(e)
    

    目标跟踪

    KCF(kernelized correlation filters),全称核相关滤波,是一种鉴别式追踪方法。这类方法一般都是在追踪过程中训练一个目标检测器,使用目标检测器去检测下一帧预测位置是否是目标,然后再使用新检测结果去更新训练集进而更新目标检测器。而在训练目标检测器时一般选取目标区域为正样本,目标的周围区域为负样本,当然越靠近目标的区域为正样本的可能性越大。

    我们定义了一个实体类,该类为每一个被检测到的物体实例化一个对象。对象实例化时会初始化一个KCF跟踪器。KCF跟踪器接受一个帧与目标的坐标位置。通过update()函数载入最新的帧,KCF跟踪器能够计算目标在当前帧中所处的位置。

    # coding:utf8
    
    import cv2
    import numpy as np
    
    
    class Entity(object):
        def __init__(self, vid, windows, frame):
            self.vid = vid #目标ID
            self.windows = windows  #目标矩阵,x y w h
            self.center = self._set_center(windows) #目标中心
            self.trajectory = [self.center] #轨迹
            self.tracker = self._init_tracker(windows, frame) #跟踪器
    
        def _set_center(self, windows):
            x, y, w, h = windows
            x = (2 * x + w) / 2  #x,y更新了?
            y = (2 * y + h) / 2
            center = np.array([np.float32(x), np.float32(y)], np.float32)
            return center
    
        def _init_tracker(self, windows, frame):
        """
        初始化KCF跟踪器
        """
            x, y, w, h = windows
            tracker = cv2.Tracker_create('KCF')
            tracker.init(frame, (x, y, w, h)) #初始化一个帧与目标的坐标位置
            return tracker
    
        def update(self, frame):
        """
        更新目标位置
        """
            self.tracker.update(frame)
            ok, new_box = self.tracker.update(frame) #  retval, boundingBox =   cv.Tracker.update(  image   )
    
            if ok:
                x, y, w, h = int(new_box[0]), int(new_box[1]), int(new_box[2]), int(new_box[3])
                self.center = self._set_center((x, y, w, h))
                self.windows = (x, y, w, h)
                self.trajectory.append(self.center) #轨迹增加中心点
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 1) #画出矩阵
                cv2.putText(frame, "vehicle", (x, y - 5),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)  #添加文字
                cv2.polylines(frame, [np.int32(self.trajectory)], 0, (0, 0, 255)) #绘制轨迹
    

    分类数据

    我们使用MIT的车辆与行人数据训练分类模型,数据为ppm格式,大小分别为128X128与128X64。为了使用数据统一并且符合CNN模型的输入,我们对其进行了处理操作,统一了大小。

    # coding: utf8
    
    import os
    import cv2
    
    
    def main():
        path1 = 'cars128x128//'
        path2 = 'pedestrians128x64//'
        path3 = 'data//train//cars//'
        path4 = 'data//train//pedestrians//'
    
        for root, dirs, files in os.walk(path1):#目录遍历器
            for f in files:
                n = f.split('.')[0] #文件名
                img = path1 + f
                image = cv2.imread(img) #读取图片
                resized_image = cv2.resize(image, (64, 64), interpolation=cv2.INTER_CUBIC)
                cv2.imwrite(path3 + str(n) + '.jpg', resized_image)
    
        for root, dirs, files in os.walk(path2):
            for f in files:
                n = f.split('.')[0]
                img = path2 + f
                image = cv2.imread(img)
                resized_image = cv2.resize(image, (64, 64), interpolation=cv2.INTER_CUBIC)
                cv2.imwrite(path4 + str(n) + '.jpg', resized_image)
    
    
    if __name__ == '__main__':
        main()
    

    点击下载原始数据与处理好的数据。

    CNN分类模型

    我们使用了3层的CNN网络作为分类模型,每个卷积单元由卷积层、BN层、LeakyRelu与池化层组成。如下图:


    image

    CNN网络使用Keras进行定义,如下:

    # coding: utf8
    
    from keras.models import Sequential
    from keras.regularizers import l2
    from keras.layers import Convolution2D, MaxPooling2D
    from keras.layers import Activation, Dropout, Flatten, Dense
    from keras.layers.normalization import BatchNormalization
    from keras.layers.advanced_activations import LeakyReLU
    
    
    def cnn_net(size):
        model = Sequential()
        model.add(Convolution2D(16, 5, 5, W_regularizer=l2(5e-4), border_mode='same', input_shape=(size, size, 3))) #卷积层,,第一个参数:卷积核的数目(即输出的维度),input_shape = (128,128,3)代表128*128的彩色RGB图像
        model.add(BatchNormalization()) #规范化,该层在每个batch上将前一层的激活值重新规范化,即使得其输出数据的均值接近0,其标准差接近1
        model.add(LeakyReLU(alpha=0.1)) #激活函数,增加神经网络模型的非线性
    
        model.add(MaxPooling2D(pool_size=(2, 2))) #池化层,池化层对输入做降采样,常用的池化做法是对每个滤波器的输出求最大值,平均值,中位数等。
     
        model.add(Convolution2D(32, 5, 5, W_regularizer=l2(5e-4), border_mode='same'))
        model.add(BatchNormalization())
        model.add(LeakyReLU(alpha=0.1))
        model.add(MaxPooling2D(pool_size=(2, 2)))
    
        model.add(Convolution2D(64, 3, 3, W_regularizer=l2(5e-4), border_mode='same'))
        model.add(BatchNormalization())
        model.add(LeakyReLU(alpha=0.1))
        model.add(MaxPooling2D(pool_size=(2, 2)))
    
        model.add(Flatten()) #Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten不影响batch的大小。
        model.add(Dense(128)) #Dense就是常用的全连接层,就是将样本从特征空间映射到标签。全连接层的每一个结点都与上一层的所有结点相连,用来把前边提取到的特征综合起来。由于其全相连的特性,一般全连接层的参数也是最多的。
        model.add(Activation('relu')) #激活层
        model.add(Dropout(0.2)) #Dropout将在训练过程中每次更新参数时按一定概率(rate)随机断开输入神经元,Dropout层用于防止过拟合。
        model.add(Dense(1)) 
        model.add(Activation('sigmoid'))
    
        model.compile(loss='binary_crossentropy', optimizer='Adam', metrics=['accuracy']) #编译模型以供训练
    
        return model
    

    数据增强与模型训练

    Keras提供了数据增强的功能,能够增加数据量并且防止过拟合。使用ImageDataGenerator能够将文件夹中的原始数据转化为生成器,供模型使用。

    import os
    import sys
    import argparse
    import pandas as pd
    from keras.preprocessing.image import ImageDataGenerator
    from keras.callbacks import EarlyStopping
    from keras.utils.visualize_util import plot
    
    def data_process(size, batch_size_train, batch_size_val):
        path = os.path.abspath(os.path.join(os.path.dirname("__file__"), os.path.pardir))
     
        datagen1 = ImageDataGenerator(
            rescale=1. / 255,
            shear_range=0.2,
            zoom_range=0.2,
            rotation_range=90,
            width_shift_range=0.2,
            height_shift_range=0.2,
            horizontal_flip=True) #图片生成器
    
        datagen2 = ImageDataGenerator(rescale=1. / 255)
    
        train_generator = datagen1.flow_from_directory(
            path + '//data//train',
            target_size=(size, size),
            batch_size=batch_size_train,
            class_mode='binary') #以文件夹路径为参数,生成经过数据提升/归一化后的数据
    
        validation_generator = datagen2.flow_from_directory(
            path + '//data//validation',
            target_size=(size, size),
            batch_size=batch_size_val,
            class_mode='binary')
    
        return train_generator, validation_generator
    

    模型训练函数如下所示,我们在训练的过程中引入了EarlyStopping,保证模型在准确度不再上升的时候自动结束训练。

    def train(model, epochs, batch_size_train, batch_size_val, size):
        train_generator, validation_generator = data_process(size, batch_size_train, batch_size_val)
        earlyStopping = EarlyStopping(monitor='val_loss', patience=50, verbose=1, mode='auto') #当监测值不再改善时,该回调函数将中止训练
    
        hist = model.fit_generator(
            train_generator,
            nb_epoch=epochs,
            samples_per_epoch=batch_size_train,
            validation_data=validation_generator,
            nb_val_samples=batch_size_val,
            callbacks=[earlyStopping])
    
        df = pd.DataFrame.from_dict(hist.history) #记录了损失函数和其他指标的数值随epoch变化的情况,如果有验证集的话,也包含了验证集的这些指标变化情况
        df.to_csv('hist.csv', encoding='utf-8', index=False)
        model.save('weights.h5')
    

    结果

    运行下列命令,即可对video文件夹中保存的文件进行处理。

    python track.py --file "car.flv"

    效果如下图所示:


    image

    相关文章

      网友评论

      • tsmotlp:您好,训练数据可以发我一份吗?1642271630@qq.com,谢谢您

      本文标题:OpenCV 3 & Keras 实现多目标车辆跟踪 笔

      本文链接:https://www.haomeiwen.com/subject/ecvpmftx.html