cv2实现基于粒子滤波的目标跟踪

目标跟踪过程分为2部分,即目标特征提取和目标跟踪算法。

      其中目标特征提取又包括以下几种:1. 各种色彩空间直方图,利用色彩空间的直方图分布作为目标跟踪的特征,可以减少物体远近距离的影响,因为其颜色分布大致相同。2.轮廓特征,提取目标的轮廓特征,可以加快算法的速度,且可以在目标有小部分影响的情况下同样有效果。3. 纹理特征,如果被跟踪目标是有纹理的,则根据其纹理特征来跟踪效果会有所改善。

     目标跟踪算法目前大概分为以下4种:1. 基于meanshift算法,即利用meanshift算法可以快速找到领域目标最相似的地方,效果还不错,但是其只能找到局部最大值,且不能解决遮挡问题以及不能自适应跟踪目标的形状,方向等。其后面有学者对其做了改进,比如说camshift,就可以自适应物体的大小,方向,具有较好的跟踪效果。2. Kalman滤波的思想,该思想是利用物体的运动模型来,即服从高斯模型,来对目标状态进行预测,然后与观察模型进行比较,根据2者之间的误差来寻找运动目标的状态,但是该算法的精度不高,因为其高斯运动模型在现实生活中很多条件下并得不到满足,并且该算法对杂乱的背景也很敏感。3. 基于粒子滤波的思想,每次通过实验可以重采样粒子的分布,根据该分布对粒子进行扩散,然后通过扩散的结果来观察目标的状态,最后更新目标的状态。该算法最大的特点是跟踪速度快,且能解决一部分遮挡问题,在实际应用过程中越来越多。4.基于目标建模的方法。该方法具有一定的针对性,需要提前知道所需跟踪的目标是什么,比如说车辆,人脸,行人等。由于已经知道了跟踪目标,所以必须对目标进行建模,然后利用该模型来进行跟踪。该方法的局限性是必须提前知道所跟踪的目标是什么,因而其推广性比较差。

 实现过程

  1)该阶段要人工指定跟踪目标,程序计算跟踪目标的特征,比如可以采用目标的颜色特征。具体到Rob Hess的代码,开始时需要人工用鼠标拖动出一个跟踪区域,然后程序自动计算该区域色调(Hue)空间的直方图,即为目标的特征。直方图可以用一个向量来表示,所以目标特征就是一个N*1的向量V。

  • 2)搜索阶段-放狗
    好,我们已经掌握了目标的特征,下面放出很多条狗,去搜索目标对象,这里的狗就是粒子particle。狗有很多种放法。比如,a)均匀的放:即在整个图像平面均匀的撒粒子(uniform distribution);b)在上一帧得到的目标附近按照高斯分布来放,可以理解成,靠近目标的地方多放,远离目标的地方少放。Rob Hess的代码用的是后一种方法。狗放出去后,每条狗怎么搜索目标呢?就是按照初始化阶段得到的目标特征(色调直方图,向量V)。每条狗计算它所处的位置处图像的颜色特征,得到一个色调直方图,向量Vi,计算该直方图与目标直方图的相似性。相似性有多种度量,最简单的一种是计算sum(abs(Vi-V)).每条狗算出相似度后再做一次归一化,使得所有的狗得到的相似度加起来等于1.
  • 3)决策阶段
    我们放出去的一条条聪明的狗向我们发回报告,“一号狗处图像与目标的相似度是0.3”,“二号狗处图像与目标的相似度是0.02”,“三号狗处图像与目标的相似度是0.0003”,“N号狗处图像与目标的相似度是0.013”…那么目标究竟最可能在哪里呢?我们做次加权平均吧。设N号狗的图像像素坐标是(Xn,Yn),它报告的相似度是Wn,于是目标最可能的像素坐标X = sum(Xn*Wn),Y = sum(Yn*Wn).
  • 4)重采样阶段Resampling
    既然我们是在做目标跟踪,一般说来,目标是跑来跑去乱动的。在新的一帧图像里,目标可能在哪里呢?还是让我们放狗搜索吧。但现在应该怎样放狗呢?让我们重温下狗狗们的报告吧。“一号狗处图像与目标的相似度是0.3”,“二号狗处图像与目标的相似度是0.02”,“三号狗处图像与目标的相似度是0.0003”,“N号狗处图像与目标的相似度是0.013”…综合所有狗的报告,一号狗处的相似度最高,三号狗处的相似度最低,于是我们要重新分布警力,正所谓好钢用在刀刃上,我们在相似度最高的狗那里放更多条狗,在相似度最低的狗那里少放狗,甚至把原来那条狗也撤回来。这就是Sampling Importance Resampling,根据重要性重采样(更具重要性重新放狗)。
    (2)->(3)->(4)->(2)如是反复循环,即完成了目标的动态跟踪。

示例代码:

import cv2
import numpy as np


# 权重计算函数
def likelihood(x, y, func, image, w=30, h=30):
    x1 = np.int32(max(0, x - w / 2))
    y1 = np.int32(max(0, y - h / 2))
    x2 = np.int32(min(image.shape[1], x + w / 2))
    y2 = np.int32(min(image.shape[0], y + h / 2))
    # 截取图片区域
    region = image[y1: y2, x1: x2]
    # 统计符合色值的像素点个数
    count = region[func(region)].size
    return (float(count) / image.size) if count > 0 else 0.0001


# 粒子(particle)的初始化函数
def init_particle(func, image):
    mask = image.copy()
    mask[func(mask) == False] = 0
    # 查找物体轮廓
    _, contours, _= cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    if len(contours) <=0:
        return None
    # 面积最大轮廓
    max_contour = max(contours, key=cv2.contourArea)
    # 获取外部矩形边界(返回值:返回四个值,分别是x, y, w, h;x, y是矩阵左上方的坐标,w,h是矩阵的宽和高
    max_rect = np.array(cv2.boundingRect(max_contour))
    max_rect = max_rect[: 2] + max_rect[2:] / 2
    weight = likelihood(max_rect[0], max_rect[1], func, image)
    particles = np.ndarray((500, 3), dtype=np.float32)
    particles[:] = [max_rect[0], max_rect[1], weight]
    return particles


# 根据粒子的权重的后验概率分布重新采样
def resample(particles):
    tmp_particles = particles.copy()
    # 元素相加
    weights = particles[:, 2].cumsum()
    last_weight = weights[weights.shape[0] - 1]
    for i in range(particles.shape[0]):
        weight = np.random.rand() * last_weight
        # 只返回第一次出现的关系表达式为真的索引
        particles[i] = tmp_particles[(weights > weight).argmax()]
        particles[i][2] = 1.0


# 预测
def predict(particles, variance=13.0):
    # np.random.randn:以给定的形状创建一个数组,数组元素符合标准正态分布N(0,1)
    # 13位半径,向外随机扩散
    particles[:, 0] += np.random.randn((particles.shape[0])) * variance
    particles[:, 1] += np.random.randn((particles.shape[0])) * variance


# 权重处理
def weight(particles, func, image):
    for i in range(particles.shape[0]):
        particles[i][2] = likelihood(particles[i][0], particles[i][1], func, image)
        # 权重相加
        sum_weight = particles[:, 2].sum()
        particles[:, 2] *= (particles.shape[0] / sum_weight)


# 测定坐标
def measure(particles):
    x = (particles[:, 0] * particles[:, 2]).sum()
    y = (particles[:, 0] * particles[:, 2]).sum()
    weight = particles[:, 2].sum()
    return x / weight, y / weight


# 粒子(particle)坐标获取
particle_filter_cur_frame = 0
def particle_filter(particles, func, image, max_frame=10):
    global particle_filter_cur_frame

    if image[func(image)].size <= 0:
        if particle_filter_cur_frame >= max_frame:
            return None, -1, -1
    else:
        particle_filter_cur_frame = 0
        if particles is None:
            particles = init_particle(func, image)

    if particles is None:
        return None, -1, -1

    resample(particles)
    predict(particles)
    weight(particles, func, image)

    x, y = measure(particles)
    return particles, x, y


if __name__ == '__main__':
    def is_color(region):
        # 色调范围限制
        return (region >= 11) & (region < 34)

    # 打开摄像头
    cap = cv2.VideoCapture(0)
    particles = None

    while cv2.waitKey(30) < 0:
        # 读取图像
        _, frame = cap.read()
        # 图像转化为HSV格式
        frame_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        frame_h = frame_hsv[:, :, 0]
        _, frame_s = cv2.threshold(frame_hsv[:, :, 1], 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
        _, frame_v = cv2.threshold(frame_hsv[:, :, 2], 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

        # s或v为0的像素点,h赋值为0
        frame_h[(frame_s == 0) | (frame_v == 0)] = 0

        # 获取粒子点,中心坐标
        particles, x, y = particle_filter(particles, is_color, frame_h)

        if particles is not None:
            # 防止出边界
            valid_particles = np.int32(particles[(particles[:, 0] >= 0) & (particles[:, 0] < frame.shape[1])
                                       & (particles[:, 1] >= 0) & (particles[:, 1] < frame.shape[0])])

            # 修改粒子点的颜色
            for i in range(valid_particles.shape[0]):
                frame[valid_particles[i][1], valid_particles[i][0]] = [255, 0, 0]
            p = np.array([x, y], dtype=np.int32)
            # 根据中心点画框
            cv2.rectangle(frame, tuple(p - 15), tuple(p + 15), (0, 0, 255), thickness=2)
        cv2.imshow('green', frame)

    cap.release()
    cv2.destroyAllWindows()

测试结果:

猜你喜欢

转载自blog.csdn.net/github_39611196/article/details/83380650