日萌社
人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)
4.4. 目标估计模型-卡尔曼滤波
学习目标
- 了解卡尔曼滤波器中的状态变量和观测输入
- 了解目标框的状态更新向量
- 了解预测边界框的估计
在这里我们主要完成卡尔曼滤波器进行跟踪的相关内容的实现。
- 初始化:卡尔曼滤波器的状态变量和观测输入
- 更新状态变量
- 根据状态变量预测目标的边界框
-
初始化:
状态量x的设定是一个七维向量:
分别表示目标中心位置的x,y坐标,面积s和当前目标框的纵横比,最后三个则是横向,纵向,面积的变化速率,其中速度部分初始化为0,其他根据观测进行输入。
初始化卡尔曼滤波器参数,7个状态变量和4个观测输入,运动形式和转换矩阵的确定都是基于匀速运动模型,状态转移矩阵F根据运动学公式确定:
量测矩阵H是4*7的矩阵,将观测值与状态变量相对应:
以及相应的协方差参数的设定,根据经验值进行设定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | def __init__(self, bbox): # 定义等速模型 # 内部使用KalmanFilter,7个状态变量和4个观测输入 self.kf = KalmanFilter(dim_x=7, dim_z=4) # F是状态变换模型,为7*7的方阵 self.kf.F = np.array( [[1, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 1]]) # H是量测矩阵,是4*7的矩阵 self.kf.H = np.array( [[1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]]) # R是测量噪声的协方差,即真实值与测量值差的协方差 self.kf.R[2:, 2:] *= 10. # P是先验估计的协方差 self.kf.P[4:, 4:] *= 1000. # give high uncertainty to the unobservable initial velocities self.kf.P *= 10. # Q是过程激励噪声的协方差 self.kf.Q[-1, -1] *= 0.01 self.kf.Q[4:, 4:] *= 0.01 # 状态估计 self.kf.x[:4] = convert_bbox_to_z(bbox) # 参数的更新 self.time_since_update = 0 self.id = KalmanBoxTracker.count KalmanBoxTracker.count += 1 self.history = [] self.hits = 0 self.hit_streak = 0 self.age = 0 |
-
更新状态变量
使用观测到的目标框更新状态变量
1 2 3 4 5 6 7 8 9 10 | def update(self, bbox): # 重置 self.time_since_update = 0 # 清空history self.history = [] # hits计数加1 self.hits += 1 self.hit_streak += 1 # 根据观测结果修改内部状态x self.kf.update(convert_bbox_to_z(bbox)) |
-
进行目标框的预测
推进状态变量并返回预测的边界框结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def predict(self): # 推进状态变量 if (self.kf.x[6] + self.kf.x[2]) <= 0: self.kf.x[6] *= 0.0 # 进行预测 self.kf.predict() # 卡尔曼滤波的次数 self.age += 1 # 若过程中未更新过,将hit_streak置为0 if self.time_since_update > 0: self.hit_streak = 0 self.time_since_update += 1 # 将预测结果追加到history中 self.history.append(convert_x_to_bbox(self.kf.x)) return self.history[-1] |
整个代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | class KalmanBoxTracker(object): count = 0 def __init__(self, bbox): """ 初始化边界框和跟踪器 :param bbox: """ # 定义等速模型 # 内部使用KalmanFilter,7个状态变量和4个观测输入 self.kf = KalmanFilter(dim_x=7, dim_z=4) self.kf.F = np.array( [[1, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 1]]) self.kf.H = np.array( [[1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]]) self.kf.R[2:, 2:] *= 10. self.kf.P[4:, 4:] *= 1000. # give high uncertainty to the unobservable initial velocities self.kf.P *= 10. self.kf.Q[-1, -1] *= 0.01 self.kf.Q[4:, 4:] *= 0.01 self.kf.x[:4] = convert_bbox_to_z(bbox) self.time_since_update = 0 self.id = KalmanBoxTracker.count KalmanBoxTracker.count += 1 self.history = [] self.hits = 0 self.hit_streak = 0 self.age = 0 def update(self, bbox): """ 使用观察到的目标框更新状态向量。filterpy.kalman.KalmanFilter.update 会根据观测修改内部状态估计self.kf.x。 重置self.time_since_update,清空self.history。 :param bbox:目标框 :return: """ self.time_since_update = 0 self.history = [] self.hits += 1 self.hit_streak += 1 self.kf.update(convert_bbox_to_z(bbox)) def predict(self): """ 推进状态向量并返回预测的边界框估计。 将预测结果追加到self.history。由于 get_state 直接访问 self.kf.x,所以self.history没有用到 :return: """ if (self.kf.x[6] + self.kf.x[2]) <= 0: self.kf.x[6] *= 0.0 self.kf.predict() self.age += 1 if self.time_since_update > 0: self.hit_streak = 0 self.time_since_update += 1 self.history.append(convert_x_to_bbox(self.kf.x)) return self.history[-1] def get_state(self): """ 返回当前边界框估计值 :return: """ return convert_x_to_bbox(self.kf.x) |
总结
-
了解初始化,卡尔曼滤波器的状态变量和观测输入
-
更新状态变量update()
-
根据状态变量预测目标的边界框predict()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | from filterpy.kalman import KalmanFilter import numpy as np def convert_bbox_to_z(bbox): """ 将[x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r]。 其中x、y是框的中心坐标点,s 是面积尺度,r 是宽高比w/h :param bbox: [x1,y1,x2,y2] 分别是左上角坐标和右下角坐标 即 [左上角的x坐标,左上角的y坐标,右下角的x坐标,右下角的y坐标] :return: [ x, y, s, r ] 4行1列,其中x、y是box中心位置的坐标,s是面积,r是纵横比w/h """ w = bbox[2] - bbox[0] #右下角的x坐标 - 左上角的x坐标 = 检测框的宽 h = bbox[3] - bbox[1] #右下角的y坐标 - 左上角的y坐标 = 检测框的高 x = bbox[0] + w / 2. #左上角的x坐标 + 宽/2 = 检测框中心位置的x坐标 y = bbox[1] + h / 2. #左上角的y坐标 + 高/2 = 检测框中心位置的y坐标 s = w * h #检测框的宽 * 高 = 检测框面积 r = w / float(h) #检测框的宽w / 高h = 宽高比 #因为卡尔曼滤波器的输入格式要求为4行1列,因此该[x, y, s, r]的形状要转换为4行1列再输入到卡尔曼滤波器 return np.array([x, y, s, r]).reshape((4, 1)) """ 将候选框从中心面积的形式[x,y,s,r] 转换为 坐标的形式[x1,y1,x2,y2] """ def convert_x_to_bbox(x, score=None): """ 将[cx,cy,s,r]的目标框表示转为[x_min,y_min,x_max,y_max]的形式 :param x:[ x, y, s, r ],其中x,y是box中心位置的坐标,s是面积,r是纵横比w/h :param score: 置信度 :return:[x1,y1,x2,y2],左上角坐标和右下角坐标 """ """ x[2]:s是面积,原公式s的来源为s = w * h,即检测框的宽 * 高 = 检测框面积。 x[3]:r是纵横比w/h,原公式r的来源为r = w / float(h),即检测框的宽w / 高h = 宽高比。 x[2] * x[3]:s*r 即(w * h) * (w / float(h)) = w^2 sqrt(x[2] * x[3]):sqrt(w^2) = w """ w = np.sqrt(x[2] * x[3]) #sqrt(w^2) = w h = x[2] / w #w * h / w = h if score is None: return np.array([x[0] - w / 2., #检测框中心位置的x坐标 - 宽 / 2 = 左上角的x坐标 x[1] - h / 2., #检测框中心位置的y坐标 - 高 / 2 = 左上角的y坐标 x[0] + w / 2., #检测框中心位置的x坐标 + 宽 / 2 = 右下角的x坐标 x[1] + h / 2.] #检测框中心位置的y坐标 + 高 / 2 = 右下角的y坐标 ).reshape((1, 4)) else: return np.array([x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2., score]).reshape((1, 5)) """ 卡尔曼滤波器进行跟踪的相关内容的实现 目标估计模型: 1.根据上一帧的目标框结果来预测当前帧的目标框状态,预测边界框(目标框)的模型定义为一个等速运动/匀速运动模型。 2.每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象), KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数, 并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。 3.yoloV3、卡尔曼滤波器预测/更新流程步骤 1.第一步: yoloV3目标检测阶段: --> 1.检测到目标则创建检测目标链/跟踪目标链,反之检测不到目标则重新循环目标检测。 --> 2.检测目标链/跟踪目标链不为空则进入卡尔曼滤波器predict预测阶段,反之为空则重新循环目标检测。 2.第二步: 卡尔曼滤波器predict预测阶段: 连续多次预测而不进行一次更新操作,那么代表了每次预测之后所进行的“预测目标和检测目标之间的”相似度匹配都不成功, 所以才会出现连续多次的“预测然后相似度匹配失败的”情况,导致不会进入一次更新阶段。 如果一次预测然后相似度匹配成功的话,那么然后就会进入更新阶段。 --> 1.目标位置预测 1.kf.predict():目标位置预测 2.目标框预测总次数:age+=1。 3.if time_since_update > 0: hit_streak = 0 time_since_update += 1 1.连续预测的次数,每执行predict一次即进行time_since_update+=1。 2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。 3.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话, 就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作, 即连续预测的过程中没有执行过一次update。 4.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下, 当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0, 然后才会进行time_since_update+=1; 当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0, 然后继续进行time_since_update+=1。 --> 2.预测的目标和检测的目标之间的相似度匹配成功则进入update更新阶段,反之匹配失败则删除跟踪目标。 3.第三步: 卡尔曼滤波器update更新阶段: 如果一次预测然后“预测目标和检测目标之间的”相似度匹配成功的话,那么然后就会进入更新阶段。 kf.update([x,y,s,r]):使用的是通过yoloV3得到的“并且和预测框相匹配的”检测框来更新预测框。 --> 1.目标位置信息更新到检测目标链/跟踪目标链 1.目标框更新总次数:hits+=1。 2.history = [] time_since_update = 0 hit_streak += 1 1.history列表用于在预测阶段保存单个目标框连续预测的多个结果,一旦执行update就会清空history列表。 2.连续更新的次数,每执行update一次即进行hit_streak+=1。 3.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。 4.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话, 就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作, 即连续预测的过程中没有执行过一次update。 5.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下, 当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0, 然后才会进行time_since_update+=1; 当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0, 然后继续进行time_since_update+=1。 --> 2.目标位置修正。 1.kf.update([x,y,s,r]): 使用观测到的目标框bbox更新状态变量x(状态更新向量x)。 使用的是通过yoloV3得到的“并且和预测框相匹配的”检测框来更新卡尔曼滤波器得到的预测框。 1.初始化、预测、更新 1.__init__(bbox): 初始化卡尔曼滤波器的状态更新向量x(状态变量x)、观测输入[x,y,s,r](通过[x1,y1,x2,y2]转化而来)、状态转移矩阵F、 量测矩阵H(观测矩阵H)、测量噪声的协方差矩阵R、先验估计的协方差矩阵P、过程激励噪声的协方差矩阵Q。 2.update(bbox):根据观测输入来对状态更新向量x(状态变量x)进行更新 3.predict():根据状态更新向量x(状态变量x)更新的结果来预测目标的边界框 2.状态变量、状态转移矩阵F、量测矩阵H(观测矩阵H)、测量噪声的协方差矩阵R、先验估计的协方差矩阵P、过程激励噪声的协方差矩阵Q 1.状态更新向量x(状态变量x) 状态更新向量x(状态变量x)的设定是一个7维向量:x=[u,v,s,r,u^,v^,s^]T。 u、v分别表示目标框的中心点位置的x、y坐标,s表示目标框的面积,r表示目标框的纵横比/宽高比。 u^、v^、s^分别表示横向u(x方向)、纵向v(y方向)、面积s的运动变化速率。 u、v、s、r初始化:根据第一帧的观测结果进行初始化。 u^、v^、s^初始化:当第一帧开始的时候初始化为0,到后面帧时会根据预测的结果来进行变化。 2.状态转移矩阵F 定义的是一个7*7的方阵(其对角线上的值都是1)。。 运动形式和转换矩阵的确定都是基于匀速运动模型,状态转移矩阵F根据运动学公式确定,跟踪的目标假设为一个匀速运动的目标。 通过7*7的状态转移矩阵F 乘以 7*1的状态更新向量x(状态变量x)即可得到一个更新后的7*1的状态更新向量x, 其中更新后的u、v、s即为当前帧结果。 3.量测矩阵H(观测矩阵H) 量测矩阵H(观测矩阵H),定义的是一个4*7的矩阵。 通过4*7的量测矩阵H(观测矩阵H) 乘以 7*1的状态更新向量x(状态变量x) 即可得到一个 4*1的[u,v,s,r]的估计值。 4.测量噪声的协方差矩阵R、先验估计的协方差矩阵P、过程激励噪声的协方差矩阵Q 1.测量噪声的协方差矩阵R:diag([1,1,10,10]T) 2.先验估计的协方差矩阵P:diag([10,10,10,10,1e4,1e4,1e4]T)。1e4:1x10的4次方。 3.过程激励噪声的协方差矩阵Q:diag([1,1,1,1,0.01,0.01,1e-4]T)。1e-4:1x10的-4次方。 4.1e数字的含义 1e4:1x10的4次方 1e-4:1x10的-4次方 5.diag表示对角矩阵,写作为diag(a1,a2,...,an)的对角矩阵实际表示为主对角线上的值依次为a1,a2,...,an, 而主对角线之外的元素皆为0的矩阵。 对角矩阵(diagonal matrix)是一个主对角线之外的元素皆为0的矩阵,常写为diag(a1,a2,...,an) 。 对角矩阵可以认为是矩阵中最简单的一种,值得一提的是:对角线上的元素可以为 0 或其他值,对角线上元素相等的对角矩阵称为数量矩阵; 对角线上元素全为1的对角矩阵称为单位矩阵。对角矩阵的运算包括和、差运算、数乘运算、同阶对角阵的乘积运算,且结果仍为对角阵。 """ #目标估计模型-卡尔曼滤波 class KalmanBoxTracker(object): """ 每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象), KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数, 并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。 """ count = 0 #类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象 """ __init__(bbox) 使用目标框bbox为卡尔曼滤波的状态进行初始化。初始化时传入bbox,即根据观测到的检测框的结果来进行初始化。 每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象), KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数, 并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。 1.kf = KalmanFilter(dim_x=7, dim_z=4) 定义一个卡尔曼滤波器,利用这个卡尔曼滤波器对目标的状态进行估计。 dim_x=7定义是一个7维的状态更新向量x(状态变量x):x=[u,v,s,r,u^,v^,s^]T。 dim_z=4定义是一个4维的观测输入,即中心面积的形式[x,y,s,r],即[检测框中心位置的x坐标,y坐标,面积,宽高比]。 2.kf.F = np.array(7*7的方阵) 状态转移矩阵F,定义的是一个7*7的方阵其(对角线上的值都是1)。 通过7*7的状态转移矩阵F 乘以 7*1的状态更新向量x(状态变量x)即可得到一个更新后的7*1的状态更新向量x, 其中更新后的u、v、s即为当前帧结果。 通过状态转移矩阵对当前的观测结果进行估计获得预测的结果,然后用当前的预测的结果来作为下一次估计预测的基础。 3.kf.H = np.array(4*7的矩阵) 量测矩阵H(观测矩阵H),定义的是一个4*7的矩阵。 通过4*7的量测矩阵H(观测矩阵H) 乘以 7*1的状态更新向量x(状态变量x) 即可得到一个 4*1的[u,v,s,r]的估计值。 4.相应的协方差参数的设定,根据经验值进行设定。 1.R是测量噪声的协方差矩阵,即真实值与测量值差的协方差。 R=diag([1,1,10,10]T) kf.R[2:, 2:] *= 10. 2.P是先验估计的协方差矩阵 diag([10,10,10,10,1e4,1e4,1e4]T)。1e4:1x10的4次方。 kf.P[4:, 4:] *= 1000. # 设置了一个较大的值,给无法观测的初始速度带来很大的不确定性 kf.P *= 10. 3.Q是过程激励噪声的协方差矩阵 diag([1,1,1,1,0.01,0.01,1e-4]T)。1e-4:1x10的-4次方。 kf.Q[-1, -1] *= 0.01 kf.Q[4:, 4:] *= 0.01 5.kf.x[:4] = convert_bbox_to_z(bbox) convert_bbox_to_z负责将[x1,y1,x2,y2]形式的检测框bbox转为中心面积的形式[x,y,s,r]。 状态更新向量x(状态变量x)设定是一个七维向量:x=[u,v,s,r,u^,v^,s^]T。 x[:4]即表示 u、v、s、r初始化为第一帧bbox观测到的结果[x,y,s,r]。 6.单个目标框对应的单个卡尔曼滤波器中的统计参数的更新 每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象), KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数, 并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。 1.卡尔曼滤波器的个数 有多少个目标框就有多少个卡尔曼滤波器,每个目标框都会有一个卡尔曼滤波器,即每个目标框都会有一个KalmanBoxTracker实例对象。 count = 0:类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象。 id = KalmanBoxTracker.count:卡尔曼滤波器的个数,即目标框的个数。 KalmanBoxTracker.count += 1:每增加一个目标框,即增加一个KalmanBoxTracker实例对象(卡尔曼滤波器),那么类属性count+=1。 2.统计一个目标框对应的卡尔曼滤波器中各参数统计的次数 1.age = 0: 该目标框进行预测的总次数。每执行predict一次,便age+=1。 2.hits = 0: 该目标框进行更新的总次数。每执行update一次,便hits+=1。 3.time_since_update = 0 1.连续预测的次数,每执行predict一次即进行time_since_update+=1。 2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。 3.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话, 就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作, 即连续预测的过程中没有执行过一次update。 4.hit_streak = 0 1.连续更新的次数,每执行update一次即进行hit_streak+=1。 2.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下, 当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0, 然后才会进行time_since_update+=1; 当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0, 然后继续进行time_since_update+=1。 7.history = []: 保存单个目标框连续预测的多个结果到history列表中,一旦执行update就会清空history列表。 将预测的候选框从中心面积的形式[x,y,s,r]转换为坐标的形式[x1,y1,x2,y2] 的bbox 再保存到 history列表中。 """ def __init__(self, bbox): # 定义等速模型 # 内部使用KalmanFilter,7个状态变量和4个观测输入 self.kf = KalmanFilter(dim_x=7, dim_z=4) # F是状态变换模型,为7*7的方阵 self.kf.F = np.array([[1, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 1]]) # H是量测矩阵,是4*7的矩阵 self.kf.H = np.array([[1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]]) # R是测量噪声的协方差,即真实值与测量值差的协方差 self.kf.R[2:, 2:] *= 10. # P是先验估计的协方差 self.kf.P[4:, 4:] *= 1000. # 给无法观测的初始速度带来很大的不确定性 self.kf.P *= 10. # Q是过程激励噪声的协方差 self.kf.Q[-1, -1] *= 0.01 self.kf.Q[4:, 4:] *= 0.01 # 状态估计 self.kf.x[:4] = convert_bbox_to_z(bbox) # 参数的更新 self.time_since_update = 0 self.id = KalmanBoxTracker.count KalmanBoxTracker.count += 1 self.history = [] self.hits = 0 self.hit_streak = 0 self.age = 0 """ update(bbox):使用观测到的目标框bbox更新状态更新向量x(状态变量x) 1.time_since_update = 0 1.连续预测的次数,每执行predict一次即进行time_since_update+=1。 2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。 2.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话, 就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作, 即连续预测的过程中没有执行过一次update。 2.history = [] 清空history列表。 history列表保存的是单个目标框连续预测的多个结果([x,y,s,r]转换后的[x1,y1,x2,y2]),一旦执行update就会清空history列表。 3.hits += 1: 该目标框进行更新的总次数。每执行update一次,便hits+=1。 4.hit_streak += 1 1.连续更新的次数,每执行update一次即进行hit_streak+=1。 2.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下, 当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0, 然后才会进行time_since_update+=1; 当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0, 然后继续进行time_since_update+=1。 5.kf.update(convert_bbox_to_z(bbox)) convert_bbox_to_z负责将[x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r],那么传入的为kf.update([x,y,s,r])。 然后根据观测结果修改内部状态x(状态更新向量x)。 使用的是通过yoloV3得到的“并且和预测框相匹配的”检测框来更新卡尔曼滤波器得到的预测框。 """ #更新状态变量,使用观测到的目标框bbox更新状态变量 def update(self, bbox): """ 使用观察到的目标框更新状态向量。filterpy.kalman.KalmanFilter.update 会根据观测修改内部状态估计self.kf.x。 重置self.time_since_update,清空self.history。 :param bbox:目标框 :return: """ # 重置 self.time_since_update = 0 # 清空history self.history = [] # hits计数加1 self.hits += 1 self.hit_streak += 1 # 根据观测结果修改内部状态x。 #convert_bbox_to_z负责将[x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r],那么update传入的为(x,y,s,r) self.kf.update(convert_bbox_to_z(bbox)) """ predict:进行目标框的预测并返回预测的边界框结果 1.if(kf.x[6] + kf.x[2]) <= 0: self.kf.x[6] *= 0.0 状态更新向量x(状态变量x)为[u,v,s,r,u^,v^,s^]T,那么x[6]为s^,x[2]为s。 如果x[6]+x[2]<= 0,那么x[6] *= 0.0,即把s^置为0.0。 2.kf.predict() 进行目标框的预测。 3.age += 1 该目标框进行预测的总次数。每执行predict一次,便age+=1。 4.if time_since_update > 0: hit_streak = 0 time_since_update += 1 1.连续预测的次数,每执行predict一次即进行time_since_update+=1。 2.在连续预测(连续执行predict)的过程中,一旦执行update的话,time_since_update就会被重置为0。 3.在连续预测(连续执行predict)的过程中,只要连续预测的次数time_since_update大于0的话, 就会把hit_streak(连续更新的次数)重置为0,表示连续预测的过程中没有出现过一次更新状态更新向量x(状态变量x)的操作, 即连续预测的过程中没有执行过一次update。 4.在连续更新(连续执行update)的过程中,一旦开始连续执行predict两次或以上的情况下, 当连续第一次执行predict时,因为time_since_update仍然为0,并不会把hit_streak重置为0, 然后才会进行time_since_update+=1; 当连续第二次执行predict时,因为time_since_update已经为1,那么便会把hit_streak重置为0, 然后继续进行time_since_update+=1。 5.history.append(convert_x_to_bbox(kf.x)) convert_x_to_bbox(kf.x):将目标框所预测的结果从中心面积的形式[x,y,s,r] 转换为 坐标的形式[x1,y1,x2,y2] 的bbox。 history列表保存的是单个目标框连续预测的多个结果([x,y,s,r]转换后的[x1,y1,x2,y2]),一旦执行update就会清空history列表。 6.predict 返回值:history[-1] 把目标框当前该次的预测的结果([x,y,s,r]转换后的[x1,y1,x2,y2])进行返回输出。 """ #进行目标框的预测,推进状态变量并返回预测的边界框结果 def predict(self): """ 推进状态向量并返回预测的边界框估计。 将预测结果追加到self.history。由于 get_state 直接访问 self.kf.x,所以self.history没有用到 :return: """ # 推进状态变量 if (self.kf.x[6] + self.kf.x[2]) <= 0: self.kf.x[6] *= 0.0 # 进行预测 self.kf.predict() # 卡尔曼滤波的次数 self.age += 1 # 若过程中未更新过,将hit_streak置为0 if self.time_since_update > 0: self.hit_streak = 0 self.time_since_update += 1 # 将预测结果追加到history中 self.history.append(convert_x_to_bbox(self.kf.x)) return self.history[-1] """ get_state(): 获取当前目标框预测的结果([x,y,s,r]转换后的[x1,y1,x2,y2])。 return convert_x_to_bbox(kf.x):将候选框从中心面积的形式[x,y,s,r] 转换为 坐标的形式[x1,y1,x2,y2] 的bbox并进行返回输出。 直接访问 kf.x并进行返回,所以history没有用到。 """ def get_state(self): """ 返回当前边界框估计值。 由于 get_state 直接访问 self.kf.x,所以self.history没有用到。 :return: """ #将候选框从中心面积的形式[x,y,s,r] 转换为 坐标的形式[x1,y1,x2,y2] 的bbox return convert_x_to_bbox(self.kf.x) |