本项目展示了实现一个基于卡尔曼滤波器的实时跟踪系统的过程,能够在视频中识别和追踪多个目标。系统通过读取视频帧并应用目标检测算法来识别物体,然后使用卡尔曼滤波器对这些目标进行动态跟踪,以便实时更新它们的位置和轨迹。最终,该系统能够在连续的视频流中高效且准确地监控和分析目标物体的移动,提供了一个强大的工具用于各种计算机视觉应用。
2.1 项目介绍
随着计算机视觉和人工智能技术的飞速发展,实时目标跟踪已经成为许多应用领域中的重要技术。目标跟踪技术广泛应用于安全监控、智能交通、无人驾驶、运动分析等领域。卡尔曼滤波器作为一种经典的动态系统估计方法,因其在处理噪声和不确定性方面的优势,被广泛用于实时跟踪系统中。本项目旨在开发一个基于卡尔曼滤波器的实时跟踪系统,该系统能够高效地从视频中识别和追踪多个目标,为用户提供准确的目标位置和运动轨迹信息。这一技术不仅提高了目标跟踪的精度和稳定性,也为进一步的智能分析和决策提供了坚实的基础。
2.1.1 市场需求
安全监控:在公共安全领域,对视频监控系统的需求持续增长,尤其是在城市安全、交通管理、机场和大型活动的安保中。高效的目标跟踪系统能够实时监控并分析可疑活动,增强安全防护能力。
- 智能交通:随着智能交通系统的发展,对车辆和行人的实时跟踪需求不断增加。基于卡尔曼滤波器的跟踪系统可以优化交通流量管理,提升交通安全,减少事故发生率。
- 无人驾驶:无人驾驶技术依赖于高精度的目标检测和跟踪系统,以确保车辆能够准确识别并跟踪周围的物体,确保安全行驶。
- 运动分析:在体育和娱乐行业,实时跟踪运动员的动作和表现可以为教练和运动员提供宝贵的数据支持,优化训练效果,提高比赛表现。
- 无人机和机器人:无人机和机器人在执行任务时需要精确的目标跟踪能力,以实现自动导航和任务执行。在这些领域,基于卡尔曼滤波器的跟踪系统可以提升系统的智能水平和操作效率。
本实时跟踪系统不仅能够满足这些市场需求,还能在不断发展的计算机视觉领域中,为更多应用场景提供可靠的技术支持。
2.1.2 功能介绍
本项目功能全面、灵活且高效,能够满足各种应用场景中的目标检测和跟踪需求,为用户提供强大的视觉分析能力。
1. 实时目标检测
- 系统能够从视频流中实时检测目标对象,通过预训练的深度学习模型识别并标记出视频中的目标。
- 支持对静态和动态场景中的目标进行检测,确保在不同环境条件下的准确性。
2. 目标跟踪
- 使用卡尔曼滤波器对检测到的目标进行跟踪。该滤波器通过动态模型和测量更新,预测目标的运动轨迹,处理目标的噪声和不确定性。
- 能够处理多目标跟踪,识别并跟踪多个目标,实时更新每个目标的位置。
3. 视频流处理
- 支持从视频文件或摄像头获取实时视频流,能够处理不同分辨率和格式的视频数据。
- 提供高效的视频帧读取和处理能力,确保目标跟踪的实时性和流畅性。
4. 可视化展示
- 实时显示目标检测和跟踪结果,包括目标的边界框、标签和运动轨迹。
- 支持多种可视化方式,如使用Bokeh图形库展示目标跟踪结果或通过HTML5视频播放器展示视频内容。
5. 数据存储与管理
- 能够保存处理后的目标跟踪结果和视频帧,用于后续分析和回顾。
- 提供简单的数据管理功能,支持对跟踪结果进行分类和归档。
6. 用户交互
- 通过图形用户界面(GUI)或命令行接口(CLI)提供操作选项,允许用户自定义跟踪参数和设置。
- 支持实时更新和调节跟踪参数,如卡尔曼滤波器的过程噪声和观测噪声,以优化跟踪性能。
2.2 准备工作
在本项目的准备工作中,首先使用 Python 的 os 模块遍历指定目录下的所有文件,并打印出每个文件的完整路径,同时导入了 numpy 和 pandas 库以便进行线性代数和数据处理操作。然后,使用 OpenCV 和 Matplotlib 打开一个视频文件,并在 Jupyter NoteBook 环境中逐帧显示视频,通过设置手动延迟来控制视频播放的帧率。
2.2.1 实现YOLOv目标检测模型
下面的代码实现了一个基于YOLOv3的目标检测模型,并包含了从预训练权重文件中加载模型权重的功能。该模型使用Keras库构建,包含了卷积、批量归一化、Leaky ReLU激活函数、上采样等层。类WeightReader用于读取和解析权重文件,并将权重加载到YOLOv3模型的相应层中。
# 定义卷积块
def _conv_block(inp, convs, skip=True):
x = inp
count = 0
for conv in convs:
if count == (len(convs) - 2) and skip:
skip_connection = x
count += 1
if conv['stride'] > 1: x = ZeroPadding2D(((1,0),(1,0)))(x) # 特殊的填充,因为darknet偏向左边和顶部
x = Conv2D(conv['filter'],
conv['kernel'],
strides=conv['stride'],
padding='valid' if conv['stride'] > 1 else 'same', # 特殊的填充,因为darknet偏向左边和顶部
name='conv_' + str(conv['layer_idx']),
use_bias=False if conv['bnorm'] else True)(x)
if conv['bnorm']: x = BatchNormalization(epsilon=0.001, name='bnorm_' + str(conv['layer_idx']))(x)
if conv['leaky']: x = LeakyReLU(alpha=0.1, name='leaky_' + str(conv['layer_idx']))(x)
return add([skip_connection, x]) if skip else x
# 创建YOLOv3模型
def make_yolov3_model():
input_image = Input(shape=(None, None, 3))
# 层 0 => 4
x = _conv_block(input_image, [{'filter': 32, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 0},
{'filter': 64, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 1},
{'filter': 32, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 2},
{'filter': 64, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 3}])
# 层 5 => 8
x = _conv_block(x, [{'filter': 128, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 5},
{'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 6},
{'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 7}])
# 层 9 => 11
x = _conv_block(x, [{'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 9},
{'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 10}])
# 层 12 => 15
x = _conv_block(x, [{'filter': 256, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 12},
{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 13},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 14}])
# 层 16 => 36
for i in range(7):
x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 16+i*3},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 17+i*3}])
skip_36 = x
# 层 37 => 40
x = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 37},
{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 38},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 39}])
# 层 41 => 61
for i in range(7):
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 41+i*3},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 42+i*3}])
skip_61 = x
# 层 62 => 65
x = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 62},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 63},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 64}])
# 层 66 => 74
for i in range(3):
x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 66+i*3},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 67+i*3}])
# 层 75 => 79
x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 75},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 76},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 77},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 78},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 79}], skip=False)
# 层 80 => 82
yolo_82 = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 80},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 81}], skip=False)
# 层 83 => 86
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 84}], skip=False)
x = UpSampling2D(2)(x)
x = concatenate([x, skip_61])
# 层 87 => 91
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 87},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 88},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 89}], skip=False)
# 层 92 => 95
x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 92}])
x = UpSampling2D(2)(x)
x = concatenate([x, skip_36])
# 层 96 => 100
yolo_100 = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 96},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 97},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 98}], skip=False)
# 构建模型
model = Model(input_image, [yolo_82, yolo_100])
return model
class WeightReader:
def __init__(self, weight_file):
self.offset = 0
self.weight_file = weight_file
def _read_bytes(self, size):
with open(self.weight_file, 'rb') as f:
f.seek(self.offset)
bytes = f.read(size)
self.offset += size
return bytes
def _read_int(self):
return struct.unpack('i', self._read_bytes(4))[0]
def _read_float(self):
return struct.unpack('f', self._read_bytes(4))[0]
def read_weights(self, model):
weights = []
for layer in model.layers:
if 'conv' in layer.name:
kernel_size = layer.get_weights()[0].shape
weights.append(self._read_bytes(np.prod(kernel_size) * 4))
if 'bnorm' in layer.name:
weights.append(self._read_bytes(4 * 4))
return weights
def load_weights(self, model):
weights = self.read_weights(model)
layer_names = [layer.name for layer in model.layers if 'conv' in layer.name]
for layer_name, weight in zip(layer_names, weights):
layer = model.get_layer(layer_name)
kernel_size = layer.get_weights()[0].shape
layer.set_weights([np.frombuffer(weight, dtype=np.float32).reshape(kernel_size)])
对上面代码的具体说明如下所示:
- 卷积块定义:函数_conv_block定义了卷积层、批量归一化层、激活函数和跳跃连接。根据是否需要跳跃连接,跳跃连接的层会被添加到卷积结果上。
- 构建YOLOv3模型:函数make_yolov3_model的功能是逐层创建了YOLOv3网络的结构,利用 _conv_block 函数创建每个卷积块。跳跃连接和上采样操作用于将特征图在不同尺度上进行融合。
- 权重读取和加载:类WeightReader用于从权重文件中读取权重并加载到模型中。read_weights 方法用于读取权重数据,load_weights 方法将这些权重应用到相应的模型层。
这种结构化的方式可以清晰地展示YOLOv3的网络层次和如何从权重文件中加载权重。
2.2.2 目标检测工具函数
接下来介绍实现目标检测功能的关键函数和工具,围绕目标检测的具体实现细节,讲解如何解码网络输出、如何应用非极大值抑制、如何可视化检测结果等。
(1)定义一个 label_map的字典,用于映射目标检测中的不同类别到特定的颜色。每个类别(如“person”、“bicycle”等)都与一种颜色相关联,这可以在可视化目标检测结果时用于为每个类别分配不同的颜色,以便于区分和识别不同的目标。
label_map = {
"person": "blue",
"bicycle": "yellow",
"car": "red",
"truck": "green",
"motorbike": "white",
"aeroplane": "white",
"bus": "white",
"train": "white",
"boat": "white"
}
(2)定义类BoundBox,用于表示目标检测框。该类包含目标框的坐标(xmin, ymin, xmax, ymax)、置信度(objness)、类别概率(classes)等属性,提供了如下所示的两个方法,这些方法确保了在需要时计算并返回目标的类别和分数。
- get_label 用于获取目标的类别标签(通过计算类别概率的最大值索引)
- get_score 用于获取目标的置信度分数(通过类别概率和标签计算得出)。
class BoundBox:
def __init__(self, xmin, ymin, xmax, ymax, objness = None, classes = None):
self.xmin = xmin
self.ymin = ymin
self.xmax = xmax
self.ymax = ymax
self.objness = objness
self.classes = classes
self.label = -1
self.score = -1
def get_label(self):
if self.label == -1:
self.label = np.argmax(self.classes)
return self.label
def get_score(self):
if self.score == -1:
self.score = self.classes[self.get_label()]
return self.score
(3)定义函数decode_netout,用于将 YOLO 网络的输出解码为目标检测框。此函数将网络输出重塑为适当的形状,并通过应用 sigmoid 激活函数和阈值过滤,计算每个检测框的位置、大小和类别概率,最终生成并返回包含目标检测框的列表。
def decode_netout(netout, anchors, obj_thresh, net_h, net_w):
grid_h, grid_w = netout.shape[:2]
nb_box = 3
netout = netout.reshape((grid_h, grid_w, nb_box, -1))
nb_class = netout.shape[-1] - 5
boxes = []
netout[..., :2] = _sigmoid(netout[..., :2])
netout[..., 4:] = _sigmoid(netout[..., 4:])
netout[..., 5:] = netout[..., 4][..., np.newaxis] * netout[..., 5:]
netout[..., 5:] *= netout[..., 5:] > obj_thresh
for i in range(grid_h*grid_w):
row = i / grid_w
col = i % grid_w
for b in range(nb_box):
objectness = netout[int(row)][int(col)][b][4]
if(objectness.all() <= obj_thresh): continue
x, y, w, h = netout[int(row)][int(col)][b][:4]
x = (col + x) / grid_w
y = (row + y) / grid_h
w = anchors[2 * b + 0] * np.exp(w) / net_w
h = anchors[2 * b + 1] * np.exp(h) / net_h
classes = netout[int(row)][col][b][5:]
box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, objectness, classes)
boxes.append(box)
return boxes
(4)定义correct_yolo_boxes函数,功能是将 YOLO 检测框从网络输出的尺度转换到原始图像的尺度。通过调整检测框的坐标值,代码将它们从网络输入尺寸(net_h, net_w)映射到实际图像的尺寸(image_h, image_w),确保检测框在原始图像上的位置和大小是正确的。
def correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w):
new_w, new_h = net_w, net_h
for i in range(len(boxes)):
x_offset, x_scale = (net_w - new_w)/2./net_w, float(new_w)/net_w
y_offset, y_scale = (net_h - new_h)/2./net_h, float(new_h)/net_h
boxes[i].xmin = int((boxes[i].xmin - x_offset) / x_scale * image_w)
boxes[i].xmax = int((boxes[i].xmax - x_offset) / x_scale * image_w)
boxes[i].ymin = int((boxes[i].ymin - y_offset) / y_scale * image_h)
boxes[i].ymax = int((boxes[i].ymax - y_offset) / y_scale * image_h)
(5)定义_interval_overlap函数,功能是计算两个区间interval_a和 interval_b的重叠长度。如果两个区间重叠,它返回重叠部分的长度;如果没有重叠,返回 0。
def _interval_overlap(interval_a, interval_b):
x1, x2 = interval_a
x3, x4 = interval_b
if x3 < x1:
if x4 < x1:
return 0
else:
return min(x2,x4) - x1
else:
if x2 < x3:
return 0
else:
return min(x2,x4) - x3
(6)定义函数bbox_iou,功能是计算两个边界框(box1 和 box2)之间的交并比(IoU,Intersection over Union)。通过计算边界框的交集面积和并集面积,然后返回两者的比值,以衡量这两个边界框的重叠程度。
def bbox_iou(box1, box2):
intersect_w = _interval_overlap([box1.xmin, box1.xmax], [box2.xmin, box2.xmax])
intersect_h = _interval_overlap([box1.ymin, box1.ymax], [box2.ymin, box2.ymax])
intersect = intersect_w * intersect_h
w1, h1 = box1.xmax-box1.xmin, box1.ymax-box1.ymin
w2, h2 = box2.xmax-box2.xmin, box2.ymax-box2.ymin
union = w1*h1 + w2*h2 - intersect
return float(intersect) / union
(7)函数do_nms的功能是实现非极大值抑制(NMS,Non-Maximum Suppression),用于减少目标检测中的冗余检测框。函数do_nms通过遍历每个类别的检测框,按照类别的置信度进行排序,并对重叠度(IoU)超过阈值的检测框进行抑制,即将它们的置信度设为零,从而只保留最优的检测框。
def do_nms(boxes, nms_thresh):
if len(boxes) > 0:
nb_class = len(boxes[0].classes)
else:
return
for c in range(nb_class):
sorted_indices = np.argsort([-box.classes[c] for box in boxes])
for i in range(len(sorted_indices)):
index_i = sorted_indices[i]
if boxes[index_i].classes[c] == 0: continue
for j in range(i+1, len(sorted_indices)):
index_j = sorted_indices[j]
if bbox_iou(boxes[index_i], boxes[index_j]) >= nms_thresh:
boxes[index_j].classes[c] = 0
(8)定义函数load_image_pixels,功能是加载图像文件并预处理图像数据以适应神经网络的输入。具体来说,此函数首先加载图像,将其调整为指定的目标大小,然后将其转换为数组形式并进行归一化处理(将像素值缩放到0到1之间),最后增加一个批量维度。函数load_image_pixels返回预处理后的图像数组以及原始图像的宽度和高度。
def load_image_pixels(filename, shape):
image = load_img(filename)
width, height = image.size
image = load_img(filename, target_size=shape)
image = img_to_array(image)
image = image.astype('float32')
image /= 255.0
image = expand_dims(image, 0)
return image, width, height
(9)函数image_preprocess功能是对输入图像进行预处理,以适应特定尺寸的神经网络输入。此函数的具体操作包括调整图像的尺寸,同时保持其纵横比,将调整后的图像放置在一个填充的背景上(填充区域值为128),并归一化像素值(将像素值缩放到0到1之间)。最后,增加一个批量维度。执行函数image_preprocess后,返回预处理后的图像数组以及原始图像的宽度和高度。
def image_preprocess(image, target_size):
ih, iw = target_size
h, w, _ = image.shape
scale = min(iw/w, ih/h)
nw, nh = int(scale * w), int(scale * h)
image_resized = cv2.resize(image, (nw, nh))
image_paded = np.full(shape=[ih, iw, 3], fill_value=128.0)
dw, dh = (iw - nw) // 2, (ih-nh) // 2
image_paded[dh:nh+dh, dw:nw+dw, :] = image_resized
image_paded = image_paded / 255.
image_expanded = expand_dims(image_paded, 0)
return image_expanded, w, h
(10)定义函数get_boxes,功能是从给定的边界框集合中筛选出符合阈值条件的目标检测结果。具体而言,函数get_boxes会遍历每个边界框,根据其分类得分与指定阈值进行比较,筛选出得分超过阈值的类别。对于每个满足条件的类别,函数将相应的边界框、类别标签和得分(百分比形式)分别添加到结果列表中,最后返回筛选出的边界框、标签和得分列表。
def get_boxes(boxes, labels, thresh):
v_boxes, v_labels, v_scores = list(), list(), list()
for box in boxes:
for i in range(len(labels)):
if box.classes[i] > thresh:
v_boxes.append(box)
v_labels.append(labels[i])
v_scores.append(box.classes[i]*100)
return v_boxes, v_labels, v_scores
(11)定义函数draw_boxes,功能是将目标检测结果可视化。此函数接收一个图像文件名、已筛选的边界框列表、标签列表和得分列表,先在图像上绘制出边界框,然后在每个边界框旁边标注对应的标签和得分。绘制的边界框和标签的颜色根据预定义的 label_map 中的颜色进行设置,最终将标注后的图像显示出来。
def draw_boxes(filename, v_boxes, v_labels, v_scores):
data = pyplot.imread(filename)
pyplot.imshow(data)
ax = pyplot.gca()
for i in range(len(v_boxes)):
box = v_boxes[i]
y1, x1, y2, x2 = box.ymin, box.xmin, box.ymax, box.xmax
width, height = x2 - x1, y2 - y1
rect = Rectangle((x1, y1), width, height, fill=False, color=label_map[v_labels[i]])
ax.add_patch(rect)
label = "%s (%.3f)" % (v_labels[i], v_scores[i])
pyplot.text(x1, y1, label, color=label_map[v_labels[i]])
pyplot.show()
(12)定义函数draw_bbox,功能是将目标检测结果绘制到图像上。此函数接收一个图像、边界框列表、类别标签、是否显示标签和置信度、文本和矩形颜色,以及是否跟踪的标志。函数draw_bbox首先生成每个类别的颜色,并根据需要对矩形和文本进行着色。然后,在图像上绘制边界框,并根据设置选项在边界框上标注类别标签和置信度分数。最终,执行后返回标注后的图像。
def draw_bbox(image, bboxes, CLASSES, show_label=True, show_confidence = True, Text_colors=(255,255,0), rectangle_colors='', tracking=False):
NUM_CLASS = CLASSES
num_classes = len(NUM_CLASS)
image_h, image_w, _ = image.shape
hsv_tuples = [(1.0 * x / num_classes, 1., 1.) for x in range(num_classes)]
colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), colors))
random.seed(0)
random.shuffle(colors)
random.seed(None)
for i, bbox in enumerate(bboxes):
coor = np.array(bbox[:4], dtype=np.int32)
score = bbox[4]
class_ind = int(bbox[5])
bbox_color = rectangle_colors if rectangle_colors != '' else colors[class_ind]
bbox_thick = int(0.6 * (image_h + image_w) / 1000)
if bbox_thick < 1: bbox_thick = 1
fontScale = 0.75 * bbox_thick
(x1, y1), (x2, y2) = (coor[0], coor[1]), (coor[2], coor[3])
cv2.rectangle(image, (x1, y1), (x2, y2), bbox_color, bbox_thick*2)
if show_label:
score_str = " {:.2f}".format(score) if show_confidence else ""
if tracking: score_str = " "+str(score)
label = "{}".format(NUM_CLASS[class_ind]) + score_str
(text_width, text_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_COMPLEX_SMALL,
fontScale, thickness=bbox_thick)
cv2.rectangle(image, (x1, y1), (x1 + text_width, y1 - text_height - baseline), bbox_color, thickness=cv2.FILLED)
cv2.putText(image, label, (x1, y1-4), cv2.FONT_HERSHEY_COMPLEX_SMALL,
fontScale, Text_colors, bbox_thick, lineType=cv2.LINE_AA)
return image
总之,上述代码实现了目标检测输出处理与可视化操作的实现过程,包括将解码网络输出为实际图像坐标和类别得分,调整边界框坐标,计算和应用非最大抑制以去除重复框,以及在图像上绘制检测结果和标签等功能。通过这些功能,能够有效地从检测模型的输出中提取和显示目标信息,帮助进行目标检测结果的分析和展示。
2.2.3 对象分类索引
定义字典 NUM_CLASS,将每个整数索引映射到一个具体的类别标签,如 'person'、'car'、'dog' 等,这些标签表示目标检测模型可以识别的不同物体类别。字典 NUM_CLASS的键是类别的索引值,值是对应的类别名称,用于在目标检测和跟踪系统中对识别到的对象进行分类和标注。
NUM_CLASS = {
0: 'person',
1: 'bicycle',
2: 'car',
3: 'motorbike',
4: 'aeroplane',
5: 'bus',
6: 'train',
7: 'truck',
8: 'boat',
9: 'traffic-light',
10: 'fire-hydrant',
11: 'stop-sign',
12: 'parking-meter',
13: 'bench',
14: 'bird',
15: 'cat',
16: 'dog',
17: 'horse',
18: 'sheep',
19: 'cow',
20: 'elephant',
21: 'bear',
22: 'zebra',
23: 'giraffe',
24: 'backpack',
25: 'umbrella',
26: 'handbag',
27: 'tie',
28: 'suitcase',
29: 'frisbee',
30: 'skis',
31: 'snowboard',
32: 'sports-ball',
33: 'kite',
34: 'baseball-bat',
35: 'baseball-glove',
36: 'skateboard',
37: 'surfboard',
38: 'tennis-racket',
39: 'bottle',
40: 'wine-glass',
41: 'cup',
42: 'fork',
43: 'knife',
44: 'spoon',
45: 'bowl',
46: 'banana',
47: 'apple',
48: 'sandwich',
49: 'orange',
50: 'broccoli',
51: 'carrot',
52: 'hot-dog',
53: 'pizza',
54: 'donut',
55: 'cake',
56: 'chair',
57: 'sofa',
58: 'pottedplant',
59: 'bed',
60: 'diningtable',
61: 'toilet',
62: 'tvmonitor',
63: 'laptop',
64: 'mouse',
65: 'remote',
66: 'keyboard',
67: 'cell-phone',
68: 'microwave',
69: 'oven',
70: 'toaster',
71: 'sink',
72: 'refrigerator',
73: 'book',
74: 'clock',
75: 'vase',
76: 'scissors',
77: 'teddy-bear',
78: 'hair-drier',
79: 'toothbrush'
}
执行后输出下面的内容,这表示成功下载了文件 yolov3.weights,文件大小为 236.52 MB,下载速度为 109 MB/s,整个下载过程花费了大约 2.2 秒。下载文件被保存为 yolov3.weights,文件总大小为 248,007,048 字节(237 MB)。
--2024-09-10 03:12:42-- https://pjreddie.com/media/files/yolov3.weights
Resolving pjreddie.com (pjreddie.com)... 128.208.4.108
Connecting to pjreddie.com (pjreddie.com)|128.208.4.108|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 248007048 (237M) [application/octet-stream]
Saving to: ‘yolov3.weights’
yolov3.weights 100%[===================>] 236.52M 109MB/s in 2.2s
--2024-09-10 03:12:44 (109 MB/s) - ‘yolov3.weights’ saved [248007048/248007048]
2.2.4 保存权重模型
下面代码首先定义了一个YOLOv3模型,然后使用预训练的权重文件yolov3.weights加载模型权重,最后将带有权重的模型保存为model.h5文件,以便后续使用。
# 定义模型
model = make_yolov3_model()
# 加载模型权重,已经在一个单独的数据集中加载了预训练权重
weight_reader = WeightReader('yolov3.weights')
# 将模型权重设置到模型中
weight_reader.load_weights(model)
# 将模型保存到文件
model.save('model.h5')
执行后会输出:
loading weights of convolution #0
loading weights of convolution #1
loading weights of convolution #2
loading weights of convolution #3
no convolution #4
loading weights of convolution #5
loading weights of convolution #6
loading weights of convolution #7
###省略部分结果
no convolution #98
loading weights of convolution #99
loading weights of convolution #100
loading weights of convolution #101
loading weights of convolution #102
loading weights of convolution #103
loading weights of convolution #104
loading weights of convolution #105
上面的输出表明模型权重正在加载过程中,具体指示了哪些卷积层成功加载了权重,以及哪些卷积层没有权重需要加载。