??SiamRPN 是一种用于视觉目标跟踪的算法。它结合了 Siamese 网络(孪生网络)和 Region Proposal Network(区域提议网络)的概念。这种算法的主要目的是在视频序列中准确地跟踪单个目标。下面是它的一些关键特点:
孪生网络(Siamese Network):SiamRPN 使用孪生网络来提取视频帧中的特征。孪生网络由两个相同的子网络组成,这两个子网络共享相同的权重,并可以有效地比较两个不同图像的特征。
区域提议网络(Region Proposal Network, RPN):这是一种用于目标检测的网络,可以在图像中生成目标候选区域。SiamRPN 将 RPN 集成到孪生网络中,以便于在连续的视频帧中定位目标。
跟踪和定位:该算法首先在初始帧中标识目标,然后在随后的帧中跟踪这个目标。它通过比较初始帧的目标特征和后续帧中的特征来实现这一点。
鲁棒性和准确性:SiamRPN 能够在多种情况下有效地跟踪目标,即使在目标形状变化、遮挡或光照变化的情况下也能保持较高的跟踪准确性。
??SiamRPN相对于SiamFC,训练数据更加丰富,实践证明,更多的训练数据可以帮助获得更好的性能;增加RPN网络结构,使生成的位置更加准确,目标框能和目标更加贴合。
??论文中的网络结构如下:
??实际转换出来的网络结构图(转成onnx用netron打开),如下图所示,首先经过两个backbone网络提取特征,相比于SiamFC,每个backbone输出两个分支,然后在head中进行两两组合,进行卷积操作,最终输出两个结果,分别是目标置信度和位置回归,具体操作见3。
??训练部分代码说明。
??和SiamFC算法相同SiamRPN网络也有两个输入的,z:初始化的图片;x:当前帧图片。在训练使用COT-10k数据集,下边是部分数据。
COT-10k
└── COT-10k/train
├── COT-10k/train/GOT-10k_Train_000001
├── COT-10k/train/GOT-10k_Train_000002
├── COT-10k/train/GOT-10k_Train_000003
├── COT-10k/train/GOT-10k_Train_000004
├── COT-10k/train/GOT-10k_Train_000005
├── COT-10k/train/GOT-10k_Train_000006
├── COT-10k/train/GOT-10k_Train_000007
├── COT-10k/train/GOT-10k_Train_000008
├── COT-10k/train/GOT-10k_Train_000009
├── COT-10k/train/GOT-10k_Train_000010
└── COT-10k/train/list.txt
??训练过程中主要的迭代流程如下:
def __getitem__(self, index):
index = random.choice(range(len(self.sub_class_dir)))
if self.name == 'GOT-10k':
if index == 4418 or index == 8627 or index == 8629 or index == 9057 or index == 9058 or index == 7787 or index == 5911:
index += 3
self._pick_img_pairs(index)
self.open()
self._tranform()
regression_target, conf_target = self._target()
self.count += 1
return self.ret['train_z_transforms'], self.ret['train_x_transforms'], regression_target, conf_target.astype(
np.int64)
??在 SiamRPN算法中,用于加载并预处理一对图像(模板和检测图像),并生成对应的回归目标和置信度目标:
index = random.choice(range(len(self.sub_class_dir)))
:随机选择一个索引,该索引指向 self.sub_class_dir
中的一个子类别。_pick_img_pairs
方法来选择一对图像(模板和检测图像)。self.open()
:用于加载和预处理模板图像和检测图像。self._transform()
:应用预定义的变换来处理图像数据,例如缩放、裁剪、规范化等。regression_target, conf_target = self._target()
:生成回归目标(例如边界框坐标)和置信度目标(例如目标存在的置信度)。??确保每次迭代都能从数据集中获取一对适当处理的图像及其对应的训练目标。
def _pick_img_pairs(self, index_of_subclass):
assert index_of_subclass < len(self.sub_class_dir), 'index_of_subclass should less than total classes'
video_name = self.sub_class_dir[index_of_subclass][0]
video_num = len(video_name)
video_gt = self.sub_class_dir[index_of_subclass][1]
status = True
while status:
if self.max_inter >= video_num - 1:
self.max_inter = video_num // 2
template_index = np.clip(random.choice(range(0, max(1, video_num - self.max_inter))), 0, video_num - 1)
detection_index = np.clip(random.choice(range(1, max(2, self.max_inter))) + template_index, 0,
video_num - 1)
template_img_path, detection_img_path = video_name[template_index], video_name[detection_index]
template_gt = video_gt[template_index]
detection_gt = video_gt[detection_index]
if template_gt[2] * template_gt[3] * detection_gt[2] * detection_gt[3] != 0:
status = False
else:
# print('Warning : Encounter object missing, reinitializing ...')
print('index_of_subclass:', index_of_subclass, '\n',
'template_index:', template_index, '\n',
'template_gt:', template_gt, '\n',
'detection_index:', detection_index, '\n',
'detection_gt:', detection_gt, '\n')
# load infomation of template and detection
self.ret['template_img_path'] = template_img_path
self.ret['detection_img_path'] = detection_img_path
self.ret['template_target_x1y1wh'] = template_gt
self.ret['detection_target_x1y1wh'] = detection_gt
t1, t2 = self.ret['template_target_x1y1wh'].copy(), self.ret['detection_target_x1y1wh'].copy()
self.ret['template_target_xywh'] = np.array([t1[0] + t1[2] // 2, t1[1] + t1[3] // 2, t1[2], t1[3]], np.float32)
self.ret['detection_target_xywh'] = np.array([t2[0] + t2[2] // 2, t2[1] + t2[3] // 2, t2[2], t2[3]], np.float32)
self.ret['anchors'] = self.anchors
??_pick_img_pairs
函数是 SiamRPN中用于从给定的视频序列中选择一对图像:一张用作模板(template)图像,另一张用作检测(detection)图像。该函数的目的是从视频序列中随机选择两帧图像,并确保这两帧图像都包含目标:
检查子类别索引:
assert
确保提供的 index_of_subclass
小于 self.sub_class_dir
的长度,以避免索引越界。获取视频名称和目标框真值:
video_name
存储了视频序列中每帧图像的路径。video_num
是视频中的总帧数。video_gt
存储了每帧图像的真值(ground truth,通常是目标的边界框)。选择模板和检测图像的索引:
self.max_inter
是模板和检测图像之间允许的最大帧数间隔。template_index
和 detection_index
是通过随机选择并使用 np.clip
确保索引在有效范围内的两帧图像的索引。检查并确保目标的存在:
template_gt
和 detection_gt
),确保它们包含有效的目标信息(即宽度和高度不为零)。提取模板和检测图像路径及目标信息:
转换目标坐标格式:
[x1, y1, width, height]
格式转换为 [center_x, center_y, width, height]
格式。这种格式更适合后续的处理和模型训练。更新返回信息:
self.ret
字典,包含模板和检测图像的路径、目标坐标、锚点等信息。??这个函数的关键作用是为SiamRPN提供一对用于训练的图像,这一步骤至关重要,因为它涉及到从一个图像(模板)到另一个图像(检测)的目标跟踪。通过这种方式,网络可以学习如何在不同帧之间保持对目标的跟踪。
def open(self):
'''template'''
# template_img = cv2.imread(self.ret['template_img_path']) if you use cv2.imread you can not open .JPEG format
template_img = Image.open(self.ret['template_img_path'])
template_img = np.array(template_img)
detection_img = Image.open(self.ret['detection_img_path'])
detection_img = np.array(detection_img)
if np.random.rand(1) < config.gray_ratio:
template_img = cv2.cvtColor(template_img, cv2.COLOR_RGB2GRAY)
template_img = cv2.cvtColor(template_img, cv2.COLOR_GRAY2RGB)
detection_img = cv2.cvtColor(detection_img, cv2.COLOR_RGB2GRAY)
detection_img = cv2.cvtColor(detection_img, cv2.COLOR_GRAY2RGB)
img_mean = np.mean(template_img, axis=(0, 1))
# img_mean = tuple(map(int, template_img.mean(axis=(0, 1))))
exemplar_img, scale_z, s_z, w_x, h_x = self.get_exemplar_image(template_img,
self.ret['template_target_xywh'],
config.exemplar_size,
config.context_amount, img_mean)
# size_x = config.exemplar_size
# x1, y1 = int((size_x + 1) / 2 - w_x / 2), int((size_x + 1) / 2 - h_x / 2)
# x2, y2 = int((size_x + 1) / 2 + w_x / 2), int((size_x + 1) / 2 + h_x / 2)
# frame = cv2.rectangle(exemplar_img, (x1,y1), (x2,y2), (0, 255, 0), 1)
# cv2.imwrite('exemplar_img.png',frame)
# cv2.waitKey(0)
self.ret['exemplar_img'] = exemplar_img
'''detection'''
# detection_img = cv2.imread(self.ret['detection_img_path'])
d = self.ret['detection_target_xywh']
cx, cy, w, h = d # float type
wc_z = w + 0.5 * (w + h)
hc_z = h + 0.5 * (w + h)
s_z = np.sqrt(wc_z * hc_z)
s_x = s_z / (config.instance_size // 2)
img_mean_d = tuple(map(int, detection_img.mean(axis=(0, 1))))
a_x_ = np.random.choice(range(-12, 12))
a_x = a_x_ * s_x
b_y_ = np.random.choice(range(-12, 12))
b_y = b_y_ * s_x
instance_img, a_x, b_y, w_x, h_x, scale_x = self.get_instance_image(detection_img, d,
config.exemplar_size, # 127
config.instance_size, # 255
config.context_amount, # 0.5
a_x, b_y,
img_mean_d)
# size_x = config.instance_size
#
# x1, y1 = int((size_x + 1) / 2 - w_x / 2), int((size_x + 1) / 2 - h_x / 2)
#
# x2, y2 = int((size_x + 1) / 2 + w_x / 2), int((size_x + 1) / 2 + h_x / 2)
# frame_d = cv2.rectangle(instance_img, (int(x1+(a_x*scale_x)),int(y1+(b_y*scale_x))), (int(x2+(a_x*scale_x)),int(y2+(b_y*scale_x))), (0, 255, 0), 1)
# cv2.imwrite('detection_img_ori.png',frame_d)
# w = x2 - x1
# h = y2 - y1
# cx = x1 + w / 2
# cy = y1 + h / 2
# print('[a_x_, b_y_, w, h]', [int(a_x_), int(b_y_), w, h])
self.ret['instance_img'] = instance_img
# self.ret['cx, cy, w, h'] = [int(a_x_*0.16), int(b_y_*0.16), w, h]
self.ret['cx, cy, w, h'] = [int(a_x_), int(b_y_), w, h]
??用于加载和预处理模板图像和检测图像的函数。这个方法执行了多个步骤来准备图像以供网络训练或推断使用:
PIL.Image.open
加载图像,并将其转换为 NumPy 数组。这种方式比 cv2.imread
更好,因为它支持更多的图像格式,如 .JPEG
。config.gray_ratio
控制)将图像转换为灰度图,然后再转换回 RGB,这可以增强模型对灰度图像的适应性。get_exemplar_image
方法来裁剪和处理模板图像,确保其符合网络输入的尺寸要求。exemplar_img
)和检测图像(instance_img
)以及相关的目标信息存储在 self.ret
字典中。??通过这些步骤,open
方法为目标跟踪任务准备了必要的图像数据。这包括加载图像、进行必要的预处理(如缩放、裁剪、颜色转换等),以及提取和调整目标的位置和尺寸信息。这些处理后的数据是模型训练和评估的关键输入。在实际应用中,这种数据准备是非常重要的,因为它直接影响到模型的性能和对不同场景的适应能力。通过随机化处理(如随机灰度转换和位移),可以进一步提高模型对现实世界中的各种情况的鲁棒性。总之,这个 open
方法的目的是确保输入到 SiamRPN 的图像数据是适当处理和格式化的,从而使模型能够更有效地学习和预测目标在视频帧中的位置。
def get_exemplar_image(self, img, bbox, size_z, context_amount, img_mean=None):
cx, cy, w, h = bbox
wc_z = w + context_amount * (w + h)
hc_z = h + context_amount * (w + h)
s_z = np.sqrt(wc_z * hc_z)
scale_z = size_z / s_z
exemplar_img, scale_x = self.crop_and_pad_old(img, cx, cy, size_z, s_z, img_mean)
w_x = w * scale_x
h_x = h * scale_x
return exemplar_img, scale_z, s_z, w_x, h_x
解析边界框(bbox):
cx, cy, w, h
:这些是目标边界框的中心坐标(cx, cy
)和宽高(w, h
)。wc_z = w + context_amount * (w + h)
和 hc_z = h + context_amount * (w + h)
:这里计算模板图像的上下文尺寸,即原始目标尺寸周围应该包括多少额外的背景。context_amount
是一个比例因子,决定了除了目标本身外,还应该包含多少额外的上下文区域。s_z = np.sqrt(wc_z * hc_z)
:这是模板图像的尺寸,基于上下文加上目标大小来计算。scale_z = size_z / s_z
:计算缩放比例,以便将模板图像缩放到所需的尺寸 size_z
。exemplar_img, scale_x = self.crop_and_pad_old(img, cx, cy, size_z, s_z, img_mean)
:使用 crop_and_pad_old
方法来裁剪和填充图像。这个过程包括根据计算得到的尺寸和上下文将图像裁剪到目标周围,然后将其缩放到所需的尺寸。w_x = w * scale_x
和 h_x = h * scale_x
:根据缩放比例调整目标在裁剪后图像中的宽度和高度。??这个方法的最终目的是生成一个围绕目标的裁剪图像区域,同时保留一定量的上下文信息,并将其缩放到网络所需的特定尺寸。这对于确保目标跟踪算法能够有效地学习目标特征和上下文信息至关重要。
def crop_and_pad_old(self, img, cx, cy, model_sz, original_sz, img_mean=None):
im_h, im_w, _ = img.shape
xmin = cx - (original_sz - 1) / 2
xmax = xmin + original_sz - 1
ymin = cy - (original_sz - 1) / 2
ymax = ymin + original_sz - 1
left = int(self.round_up(max(0., -xmin)))
top = int(self.round_up(max(0., -ymin)))
right = int(self.round_up(max(0., xmax - im_w + 1)))
bottom = int(self.round_up(max(0., ymax - im_h + 1)))
xmin = int(self.round_up(xmin + left))
xmax = int(self.round_up(xmax + left))
ymin = int(self.round_up(ymin + top))
ymax = int(self.round_up(ymax + top))
r, c, k = img.shape
if any([top, bottom, left, right]):
te_im = np.zeros((r + top + bottom, c + left + right, k), np.uint8) # 0 is better than 1 initialization
te_im[top:top + r, left:left + c, :] = img
if top:
te_im[0:top, left:left + c, :] = img_mean
if bottom:
te_im[r + top:, left:left + c, :] = img_mean
if left:
te_im[:, 0:left, :] = img_mean
if right:
te_im[:, c + left:, :] = img_mean
im_patch_original = te_im[int(ymin):int(ymax + 1), int(xmin):int(xmax + 1), :]
else:
im_patch_original = img[int(ymin):int(ymax + 1), int(xmin):int(xmax + 1), :]
if not np.array_equal(model_sz, original_sz):
im_patch = cv2.resize(im_patch_original, (model_sz, model_sz)) # zzp: use cv to get a better speed
else:
im_patch = im_patch_original
scale = model_sz / im_patch_original.shape[0]
return im_patch, scale
计算裁剪区域:
cx, cy
)的裁剪区域。这个区域的大小由 original_sz
参数指定,代表裁剪区域的宽度和高度。边界检查与填充:
left
, top
, right
, bottom
分别计算了在各个方向上需要填充的像素数量。img_mean
可以提供填充区域的颜色值。如果未指定,通常使用黑色(值为0)或者图像的平均颜色。执行裁剪与填充:
te_im
),其大小足以容纳填充后的图像区域。调整图像尺寸:
model_sz
)与裁剪区域的尺寸(original_sz
)不同,则需要调整裁剪区域的尺寸以匹配模型的输入尺寸。这通常通过 cv2.resize
完成。计算缩放比例:
返回处理后的图像和缩放比例:
im_patch
)和缩放比例(scale
)。??通过这些步骤,crop_and_pad_old
方法能够确保无论目标在图像中的位置如何,都可以获得一个恰当尺寸和内容的图像区域,用于后续的目标跟踪任务。这对于确保跟踪算法的稳定性和准确性至关重要。
def crop_and_pad(self, img, cx, cy, gt_w, gt_h, a_x, b_y, model_sz, original_sz, img_mean=None):
# random = np.random.uniform(-0.15, 0.15)
scale_h = 1.0 + np.random.uniform(-0.15, 0.15)
scale_w = 1.0 + np.random.uniform(-0.15, 0.15)
im_h, im_w, _ = img.shape
xmin = (cx - a_x) - ((original_sz - 1) / 2) * scale_w
xmax = (cx - a_x) + ((original_sz - 1) / 2) * scale_w
ymin = (cy - b_y) - ((original_sz - 1) / 2) * scale_h
ymax = (cy - b_y) + ((original_sz - 1) / 2) * scale_h
# print('xmin, xmax, ymin, ymax', xmin, xmax, ymin, ymax)
left = int(self.round_up(max(0., -xmin)))
top = int(self.round_up(max(0., -ymin)))
right = int(self.round_up(max(0., xmax - im_w + 1)))
bottom = int(self.round_up(max(0., ymax - im_h + 1)))
xmin = int(self.round_up(xmin + left))
xmax = int(self.round_up(xmax + left))
ymin = int(self.round_up(ymin + top))
ymax = int(self.round_up(ymax + top))
r, c, k = img.shape
if any([top, bottom, left, right]):
te_im_ = np.zeros((int((r + top + bottom)), int((c + left + right)), k),
np.uint8) # 0 is better than 1 initialization
te_im = np.zeros((int((r + top + bottom)), int((c + left + right)), k),
np.uint8) # 0 is better than 1 initialization
# cv2.imwrite('te_im1.jpg', te_im)
te_im[:, :, :] = img_mean
# cv2.imwrite('te_im2_1.jpg', te_im)
te_im[top:top + r, left:left + c, :] = img
# cv2.imwrite('te_im2.jpg', te_im)
if top:
te_im[0:top, left:left + c, :] = img_mean
if bottom:
te_im[r + top:, left:left + c, :] = img_mean
if left:
te_im[:, 0:left, :] = img_mean
if right:
te_im[:, c + left:, :] = img_mean
im_patch_original = te_im[int(ymin):int(ymax + 1), int(xmin):int(xmax + 1), :]
# cv2.imwrite('te_im3.jpg', im_patch_original)
else:
im_patch_original = img[int(ymin):int((ymax) + 1), int(xmin):int((xmax) + 1), :]
# cv2.imwrite('te_im4.jpg', im_patch_original)
if not np.array_equal(model_sz, original_sz):
h, w, _ = im_patch_original.shape
if h < w:
scale_h_ = 1
scale_w_ = h / w
scale = config.instance_size / h
elif h > w:
scale_h_ = w / h
scale_w_ = 1
scale = config.instance_size / w
elif h == w:
scale_h_ = 1
scale_w_ = 1
scale = config.instance_size / w
gt_w = gt_w * scale_w_
gt_h = gt_h * scale_h_
gt_w = gt_w * scale
gt_h = gt_h * scale
# im_patch = cv2.resize(im_patch_original_, (shape)) # zzp: use cv to get a better speed
# cv2.imwrite('te_im8.jpg', im_patch)
im_patch = cv2.resize(im_patch_original, (model_sz, model_sz)) # zzp: use cv to get a better speed
# cv2.imwrite('te_im9.jpg', im_patch)
else:
im_patch = im_patch_original
# scale = model_sz / im_patch_original.shape[0]
return im_patch, gt_w, gt_h, scale, scale_h_, scale_w_
??用于从原始图像中裁剪并调整大小以生成模型所需的图像。
随机缩放:
scale_h
和 scale_w
通过随机数生成,用于随机调整裁剪区域的高度和宽度。这有助于模型适应不同尺寸的目标。计算裁剪区域:
cx, cy
) 和给定的偏移 (a_x, b_y
) 来计算裁剪区域的坐标 (xmin
, ymin
, xmax
, ymax
)。original_sz
是裁剪区域的期望尺寸。边界检查与填充:
img_mean
用于填充超出原始图像边界的区域。执行裁剪与填充:
调整图像尺寸:
model_sz
) 与裁剪区域的尺寸 (original_sz
) 不同,则需要调整裁剪区域的尺寸以匹配模型的输入尺寸。使用 cv2.resize
完成这一步骤。目标尺寸调整:
gt_w
) 和高度 (gt_h
)。返回处理后的图像和尺寸信息:
im_patch
),调整后的目标宽度和高度 (gt_w
, gt_h
),以及用于裁剪和缩放的比例因子 (scale
, scale_h_
, scale_w_
)。?这个方法通过引入随机缩放和偏移,增加了模型对不同目标尺寸和形状的适应性,这对于目标跟踪算法的性能至关重要。通过精确控制裁剪和填充过程,确保了即使目标靠近图像边缘,也能够得到适当处理的图像区域,这有助于模型更准确地识别和跟踪目标。
def compute_target(self, anchors, box):
# box = [-(box[0]), -(box[1]), box[2], box[3]]
regression_target = self.box_transform(anchors, box)
iou = self.compute_iou(anchors, box).flatten()
# print(np.max(iou))
pos_index = np.where(iou > config.pos_threshold)[0]
neg_index = np.where(iou < config.neg_threshold)[0]
label = np.ones_like(iou) * -1
label[pos_index] = 1
label[neg_index] = 0
'''print(len(neg_index))
for i, neg_ind in enumerate(neg_index):
if i % 40 == 0:
label[neg_ind] = 0'''
# max_index = np.argsort(iou.flatten())[-20:]
return regression_target, label
?用于计算SiamRPN中的回归目标和分类标签的。具体来说,这个方法会基于预定义的锚点框(anchors)和一个给定的边界框(box),计算每个锚点的回归目标和它们是否包含目标的标签。我们逐步解析这个方法:
计算回归目标:
regression_target = self.box_transform(anchors, box)
:这一行代码使用 box_transform
函数计算每个锚点框到真实边界框的回归目标。通常,这包括计算锚点框和真实边界框之间的偏移量和尺寸比例。计算交并比(IoU):
iou = self.compute_iou(anchors, box).flatten()
:计算每个锚点框与真实边界框之间的交并比(IoU)。IoU 是一种衡量两个边界框重叠程度的指标。确定正负样本索引:
pos_index = np.where(iou > config.pos_threshold)[0]
:找出那些与真实边界框的 IoU 超过某个正阈值(config.pos_threshold
)的锚点框,这些锚点框被视为正样本。neg_index = np.where(iou < config.neg_threshold)[0]
:找出那些与真实边界框的 IoU 低于某个负阈值(config.neg_threshold
)的锚点框,这些锚点框被视为负样本。初始化标签数组:
label = np.ones_like(iou) * -1
:初始化一个与 IoU 数组大小相同的标签数组,初始值设置为 -1,表示这些锚点框既不是正样本也不是负样本。标记正负样本:
label[pos_index] = 1
:将正样本的标签设为 1。label[neg_index] = 0
:将负样本的标签设为 0。返回回归目标和标签:
?这种方法是目标跟踪算法中生成训练样本的关键步骤。通过这种方式,算法能够学习如何从大量的候选锚点框中区分出包含目标的锚点框,并准确地调整它们以更好地对齐目标。这对于提高算法的跟踪精度和鲁棒性至关重要。
??相主要时track部分,里边包括init和update,对第一帧跟踪目标进行init
def init(self, frame, bbox):
self.bbox = np.array(
[bbox[0] - 1 + (bbox[2] - 1) / 2, bbox[1] - 1 + (bbox[3] - 1) / 2, bbox[2], bbox[3]]) # cx,cy,w,h
self.pos = np.array(
[bbox[0] - 1 + (bbox[2] - 1) / 2, bbox[1] - 1 + (bbox[3] - 1) / 2]) # center x, center y, zero based
self.target_sz = np.array([bbox[2], bbox[3]]) # width, height
self.origin_target_sz = np.array([bbox[2], bbox[3]]) # w,h
self.img_mean = np.mean(frame, axis=(0, 1))
exemplar_img, scale_z, _ = get_exemplar_image(frame, self.bbox, config.exemplar_size, config.context_amount,
self.img_mean)
exemplar_img = self.transforms(exemplar_img)[None, :, :, :] # 在测试阶段,转换成tensor类型就可以了
self.model.track_init(exemplar_img.cuda())
??init负责设置跟踪器的初始状态,包括确定初始的目标边界框、计算图像的平均颜色,并准备第一帧中的模板(exemplar)图像:
设置初始边界框(self.bbox
):
[x, y, width, height]
转换成以中心点坐标表示的形式 [center_x, center_y, width, height]
。这样做通常是为了方便后续计算。设置目标的位置(self.pos
):
self.bbox
类似,这里也计算了目标的中心点坐标,但只包含 x
和 y
坐标。设置目标尺寸(self.target_sz
和 self.origin_target_sz
):
计算图像均值(self.img_mean
):
获取模板图像(exemplar_img
):
get_exemplar_image
函数根据初始边界框从输入帧中提取出模板图像,并进行相应的缩放和处理。图像变换:
exemplar_img = self.transforms(exemplar_img)[None, :, :, :]
:对模板图像应用预定义的变换,并增加一个新的批处理维度。初始化模型跟踪:
self.model.track_init(exemplar_img.cuda())
:使用处理好的模板图像初始化跟踪模型。??初始化过程对于目标跟踪算法来说至关重要,因为它设置了算法在后续帧中进行跟踪的基础。通过精确地定义初始状态和准备第一帧中的模板图像,跟踪器能够在接下来的帧中有效地定位和跟踪目标。
def update(self, frame):
instance_img_np, _, _, scale_x = get_instance_image(frame, self.bbox, config.exemplar_size,
config.instance_size,
config.context_amount, self.img_mean)
instance_img = self.transforms(instance_img_np)[None, :, :, :]
pred_score, pred_regression = self.model.track(instance_img.cuda()) #
pred_conf = pred_score.reshape(-1, 2, config.anchor_num * config.score_size * config.score_size).permute(0, 2,
1)
pred_offset = pred_regression.reshape(-1, 4, config.anchor_num * config.score_size * config.score_size).permute(
0, 2, 1)
delta = pred_offset[0].cpu().detach().numpy()
box_pred = box_transform_inv(self.anchors, delta) # 通过 anchors 和 offset 来预测box
score_pred = F.softmax(pred_conf, dim=2)[0, :, 1].cpu().detach().numpy() # 计算预测分类得分
def change(r):
return np.maximum(r, 1. / r) # x 和 y 逐位进行比较选择最大值
def sz(w, h):
pad = (w + h) * 0.5
sz2 = (w + pad) * (h + pad)
return np.sqrt(sz2)
def sz_wh(wh):
pad = (wh[0] + wh[1]) * 0.5
sz2 = (wh[0] + pad) * (wh[1] + pad)
return np.sqrt(sz2)
s_c = change(sz(box_pred[:, 2], box_pred[:, 3]) / (sz_wh(self.target_sz * scale_x))) # scale penalty
r_c = change((self.target_sz[0] / self.target_sz[1]) / (box_pred[:, 2] / box_pred[:, 3])) # ratio penalty
penalty = np.exp(-(r_c * s_c - 1.) * config.penalty_k) # 尺度惩罚和比例惩罚
pscore = penalty * score_pred # 对每一个anchors的分类预测×惩罚因子
pscore = pscore * (1 - config.window_influence) + self.window * config.window_influence # 再乘以余弦窗
best_pscore_id = np.argmax(pscore) # 得到最大的得分
target = box_pred[best_pscore_id, :] / scale_x # target(x,y,w,h)是以上一帧的pos为(0,0)
lr = penalty[best_pscore_id] * score_pred[best_pscore_id] * config.lr_box # 预测框的学习率
res_x = np.clip(target[0] + self.pos[0], 0, frame.shape[1]) # w=frame.shape[1]
res_y = np.clip(target[1] + self.pos[1], 0, frame.shape[0]) # h=frame.shape[0]
res_w = np.clip(self.target_sz[0] * (1 - lr) + target[2] * lr, config.min_scale * self.origin_target_sz[0],
config.max_scale * self.origin_target_sz[0])
res_h = np.clip(self.target_sz[1] * (1 - lr) + target[3] * lr, config.min_scale * self.origin_target_sz[1],
config.max_scale * self.origin_target_sz[1])
self.pos = np.array([res_x, res_y]) # 更新之后的坐标
self.target_sz = np.array([res_w, res_h])
bbox = np.array([res_x, res_y, res_w, res_h])
self.bbox = ( # cx, cy, w, h
np.clip(bbox[0], 0, frame.shape[1]).astype(np.float64),
np.clip(bbox[1], 0, frame.shape[0]).astype(np.float64),
np.clip(bbox[2], 10, frame.shape[1]).astype(np.float64),
np.clip(bbox[3], 10, frame.shape[0]).astype(np.float64))
bbox = np.array([ # tr-x,tr-y w,h
self.pos[0] + 1 - (self.target_sz[0] - 1) / 2,
self.pos[1] + 1 - (self.target_sz[1] - 1) / 2,
self.target_sz[0], self.target_sz[1]])
# return self.bbox, score_pred[best_pscore_id]
return bbox
?? update
是SiamRPN的关键组成部分,用于在每一帧中更新目标的位置和尺寸。它执行了一系列操作,从处理当前帧图像到应用模型预测,再到调整跟踪框:
处理当前帧图像:
get_instance_image
函数从当前帧中提取出一个实例(instance)图像,这个图像是围绕前一帧中预测的目标位置裁剪和缩放得到的。应用图像变换:
self.transforms
应用于实例图像。模型预测:
self.model.track
使用处理后的实例图像进行预测,输出预测得分(pred_score
)和回归(pred_regression
)。处理预测结果:
box_transform_inv
)将预测的偏移应用于锚点,得到预测的边界框(box_pred
)。计算尺度和比例惩罚:
s_c
和 r_c
分别计算尺度惩罚和比例惩罚。应用惩罚和余弦窗:
选取最佳预测:
更新目标位置和尺寸:
lr
)更新目标的位置(self.pos
)和尺寸(self.target_sz
)。返回新的边界框:
??整个 update
方法的作用是在每一帧中根据模型预测和前一帧的信息来更新目标的位置和尺寸,从而实现对目标的连续跟踪。通过这种方式,目标跟踪算法可以适应目标在视频中的运动和变化。