- 详细资料和代码请加微信:17324069443
一、项目介绍
基于PyTorchVideo的实时动作识别框架:
我们选择了yolov5作为目标检测器,而不是Faster R-CNN,它速度更快、更方便。
我们使用一个跟踪器(deepsort)来为不同帧中所有具有相同ID的对象分配动作标签。
行为识别使用 slowfast算法,根据前后帧的图片,分析这个序列,来判断是做了什么动作
我们在单个RTX 2080Ti GPU上以30个推理批处理大小达到了24.2 FPS的处理速度。
二、算法介绍
2.1 yolov5目标检测
YOLOv5是一种基于深度学习的目标检测算法,它在目标检测和图像识别领域具有很高的性能。YOLOv5建立在先前版本(如YOLOv3和YOLOv4)的基础上,通过引入一系列改进来提高检测准确性和速度。
YOLOv5的核心架构是基于单阶段目标检测器(one-stage detector)。与传统的两阶段方法不同,YOLOv5通过单个神经网络模型直接从图像中预测目标的类别和边界框位置。这使得YOLOv5在速度和准确性之间取得了良好的平衡,适用于许多实时应用场景。
YOLOv5的主要特点和优势包括:
- 轻量化设计
- 多尺度检测
- 自动数据增强
- 简单易用
YOLOv5在许多基准数据集上取得了优秀的检测性能,并且被广泛应用于实际场景中,包括智能监控、自动驾驶、工业检测等领域。其高性能和灵活性使得YOLOv5成为目标检测领域的热门选择之一。
2.2 DeepSort目标跟踪
DeepSort是一种基于深度学习的目标跟踪算法,旨在解决多目标跟踪问题。它结合了目标检测和目标跟踪两个关键任务,能够在视频序列中准确地追踪多个目标并分配唯一的ID。
DeepSort算法的核心思想是将卷积神经网络(CNN)用于目标检测,以便识别视频帧中的目标,并结合深度学习技术来进行目标特征提取和关联。具体来说,DeepSort首先使用一个预训练的目标检测器(如YOLO或Faster R-CNN)来检测视频帧中的目标,并提取目标的特征表示。
接着,DeepSort利用深度学习模型(通常是基于Siamese网络或类似的架构)来计算不同目标之间的相似度,并根据目标之间的特征相似性来进行目标关联。通过在时间序列中跟踪目标的位置,并根据目标的外观和运动特征来更新目标的状态,DeepSort能够有效地处理目标在视频中的出现、消失和遮挡等情况,实现稳健的多目标跟踪。
DeepSort算法在目标跟踪领域取得了显著的成果,特别是在具有复杂背景和目标重叠的场景下表现突出。它在许多跟踪竞赛和实际应用中都取得了优异的性能,成为目标跟踪领域的重要算法之一。深度学习方法的引入使得DeepSort在目标跟踪任务中更加准确、高效,并且具有良好的通用性和可扩展性。
2.3 slowfast动作识别
SlowFast是一种用于视频动作识别的深度学习算法。它是在Facebook AI研究团队提出的基础上发展而来的,旨在解决传统的单帧或连续帧方法在动作识别任务中存在的困难。
SlowFast算法的核心思想是引入两个不同速度的流来处理视频数据:慢速流(Slow)和快速流(Fast)。慢速流用于捕捉视频中的空间细节,它对每个帧进行较高的时间采样,以更好地理解运动的细节。快速流则用于捕捉动作的快速变化,它对每个帧进行较低的时间采样,以更好地捕捉动作的整体动态。
具体实现上,SlowFast网络由两个并行的卷积神经网络流组成:慢速流和快速流。慢速流通常包含较多的卷积层和较大的时间步长,以便对空间细节进行更深入的分析。而快速流则包含较少的卷积层和较小的时间步长,以便对动作的快速变化进行快速响应。
在训练过程中,SlowFast网络通过使用慢速流和快速流来学习视频的空间和时间特征,以及它们之间的关系。通过在大规模视频数据集上进行训练,SlowFast网络能够学习到通用的动作表示,从而在动作识别任务中取得良好的性能。
SlowFast算法在视频动作识别领域表现出色,其能够有效地捕捉视频中的空间和时间信息,并且在处理速度和准确率之间取得了良好的平衡。它在许多视频动作识别的挑战中取得了领先的结果,并被广泛应用于视频分析、智能监控等领域。
三、代码实现
def tensor_to_numpy(tensor):
img = tensor.cpu().numpy().transpose((1, 2, 0))
return img
def ava_inference_transform(clip, boxes,
num_frames = 32, #if using slowfast_r50_detection, change this to 32, 4 for slow
crop_size = 640,
data_mean = [0.45, 0.45, 0.45],
data_std = [0.225, 0.225, 0.225],
slow_fast_alpha = 4, #if using slowfast_r50_detection, change this to 4, None for slow
):
boxes = np.array(boxes)
roi_boxes = boxes.copy()
clip = uniform_temporal_subsample(clip, num_frames)
clip = clip.float()
clip = clip / 255.0
height, width = clip.shape[2], clip.shape[3]
boxes = clip_boxes_to_image(boxes, height, width)
clip, boxes = short_side_scale_with_boxes(clip,size=crop_size,boxes=boxes,)
clip = normalize(clip,
np.array(data_mean, dtype=np.float32),
np.array(data_std, dtype=np.float32),)
boxes = clip_boxes_to_image(boxes, clip.shape[2], clip.shape[3])
if slow_fast_alpha is not None:
fast_pathway = clip
slow_pathway = torch.index_select(clip,1,
torch.linspace(0, clip.shape[1] - 1, clip.shape[1] // slow_fast_alpha).long())
clip = [slow_pathway, fast_pathway]
return clip, torch.from_numpy(boxes), roi_boxes
def plot_one_box(x, img, color=[100,100,100], text_info="None",
velocity=None,thickness=1,fontsize=0.5,fontthickness=1):
# Plots one bounding box on image img
c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
cv2.rectangle(img, c1, c2, color, thickness, lineType=cv2.LINE_AA)
t_size = cv2.getTextSize(text_info, cv2.FONT_HERSHEY_TRIPLEX, fontsize , fontthickness+2)[0]
cv2.rectangle(img, c1, (c1[0] + int(t_size[0]), c1[1] + int(t_size[1]*1.45)), color, -1)
cv2.putText(img, text_info, (c1[0], c1[1]+t_size[1]+2),
cv2.FONT_HERSHEY_TRIPLEX, fontsize, [255,255,255], fontthickness)
return img
def deepsort_update(Tracker,pred,xywh,np_img):
outputs = Tracker.update(xywh, pred[:,4:5],pred[:,5].tolist(),cv2.cvtColor(np_img,cv2.COLOR_BGR2RGB))
return outputs
def save_yolopreds_tovideo(yolo_preds,id_to_ava_labels,color_map,output_video):
for i, (im, pred) in enumerate(zip(yolo_preds.ims, yolo_preds.pred)):
im=cv2.cvtColor(im,cv2.COLOR_BGR2RGB)
if pred.shape[0]:
for j, (*box, cls, trackid, vx, vy) in enumerate(pred):
if int(cls) != 0:
ava_label = ''
elif trackid in id_to_ava_labels.keys():
ava_label = id_to_ava_labels[trackid].split(' ')[0]
else:
ava_label = 'Unknow'
text = '{} {} {}'.format(int(trackid),yolo_preds.names[int(cls)],ava_label)
color = color_map[int(cls)]
im = plot_one_box(box,im,color,text)
output_video.write(im.astype(np.uint8))
def main(config):
model = torch.hub.load('ultralytics/yolov5', 'yolov5l6')
model.conf = config.conf
model.iou = config.iou
model.max_det = 200
if config.classes:
model.classes = config.classes
device = config.device
imsize = config.imsize
video_model = slowfast_r50_detection(True).eval().to(device)
deepsort_tracker = DeepSort("data/models/ckpt.t7")
ava_labelnames,_ = AvaLabeledVideoFramePaths.read_label_map("selfutils/temp.pbtxt")
coco_color_map = [[random.randint(0, 255) for _ in range(3)] for _ in range(80)]
vide_save_path = config.output
video=cv2.VideoCapture(config.input)
width,height = int(video.get(3)),int(video.get(4))
video.release()
outputvideo = cv2.VideoWriter(vide_save_path,cv2.VideoWriter_fourcc(*'mp4v'), 25, (width,height))
print("processing...")
video = pytorchvideo.data.encoded_video.EncodedVideo.from_path(config.input)
a=time.time()
for i in range(0,math.ceil(video.duration),1):
video_clips=video.get_clip(i, i+1-0.04)
video_clips=video_clips['video']
if video_clips is None:
continue
img_num=video_clips.shape[1]
imgs=[]
for j in range(img_num):
imgs.append(tensor_to_numpy(video_clips[:,j,:,:]))
yolo_preds=model(imgs, size=imsize)
yolo_preds.files=[f"img_{i*25+k}.jpg" for k in range(img_num)]
print(i,video_clips.shape,img_num)
deepsort_outputs=[]
for j in range(len(yolo_preds.pred)):
temp=deepsort_update(deepsort_tracker,yolo_preds.pred[j].cpu(),yolo_preds.xywh[j][:,0:4].cpu(),yolo_preds.ims[j])
if len(temp)==0:
temp=np.ones((0,8))
deepsort_outputs.append(temp.astype(np.float32))
yolo_preds.pred=deepsort_outputs
id_to_ava_labels={}
if yolo_preds.pred[img_num//2].shape[0]:
inputs,inp_boxes,_=ava_inference_transform(video_clips,yolo_preds.pred[img_num//2][:,0:4],crop_size=imsize)
inp_boxes = torch.cat([torch.zeros(inp_boxes.shape[0],1), inp_boxes], dim=1)
if isinstance(inputs, list):
inputs = [inp.unsqueeze(0).to(device) for inp in inputs]
else:
inputs = inputs.unsqueeze(0).to(device)
with torch.no_grad():
slowfaster_preds = video_model(inputs, inp_boxes.to(device))
slowfaster_preds = slowfaster_preds.cpu()
for tid,avalabel in zip(yolo_preds.pred[img_num//2][:,5].tolist(),np.argmax(slowfaster_preds,axis=1).tolist()):
id_to_ava_labels[tid]=ava_labelnames[avalabel+1]
save_yolopreds_tovideo(yolo_preds,id_to_ava_labels,coco_color_map,outputvideo)
print("total cost: {:.3f}s, video clips length: {}s".format(time.time()-a,video.duration))
outputvideo.release()
print('saved video to:', vide_save_path)
if __name__=="__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--input', type=str, default="data/1.mov", help='test imgs folder or video or camera')
parser.add_argument('--output', type=str, default="data/output.mp4", help='folder to save result imgs, can not use input folder')
# object detect config
parser.add_argument('--imsize', type=int, default=640, help='inference size (pixels)')
parser.add_argument('--conf', type=float, default=0.4, help='object confidence threshold')
parser.add_argument('--iou', type=float, default=0.4, help='IOU threshold for NMS')
parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
config = parser.parse_args()
print(config)
main(config)