2024 Datawhale AI夏令营 第五期 Task1:视频处理方法与物体检测模型

虢叔 2024-09-03 13:01:08 阅读 69

#Datawhale AI夏令营 #CV赛道 #目标检测 #视频处理方法 #物体检测模型

CSDN社区的各位朋友们好久不见了,本次AI夏令营笔记将专注于违规问题智能检测的解决方案,希望能给你一些启示和参考,以作为官方讲解的有益补充

在这里插入图片描述

由于datawhale更改了笔记发布政策,本次笔记分享将分为三个部分发布,我将逐步介绍违规问题智能检测任务的实现过程。在本次分享中,我将对赛题进行简单解读,并着重介绍视频文件的处理方法以及物体检测模型,而第二次和第三次分享我将着重介绍YOLO检测框架的原理、调参方法以及上分经验与技巧。

研究背景与意义

研究意义

本次赛题求选手研究开发高效可靠的计算机视觉算法,提升违规行为检测识别的准确度,降低对大量人工的依赖,提升检测效果和效率,从而推动城市治理向更高效、更智能、更文明的方向发展,为居民创造一个安全、和谐、可持续的居住环境。

在这里插入图片描述

初赛任务则是根据给定的城管视频监控数据集,进行城市违规行为的检测。选手需要能够从视频中分析并标记出违规行为,提供违规行为发生的时间和位置信息。违规行为包括垃圾桶满溢、机动车违停、非机动车违停等。

数据格式

视频数据为mp4格式,标注文件为json格式,每个视频对应一个json文件。

json文件的内容是每帧检测到的违规行为,包括以下字段:

frame_id 违规行为出现的帧编号
event_id 违规行为ID
category 违规行为类别
bbox 检测到的违规行为矩形框的坐标,[xmin,ymin,xmax,ymax]形式

评估指标

【初赛】

使用F1score、MOTA指标来评估模型预测结果。

img

对每个json文件得到两个指标的加权求和,最终得分为所有文件得分取均值。

注1:若真实目标框与预测框IOU大于0.5,则判定目标正确识别。若MOTA指标为负,则该类别精度得分为0。

注2:若该视频中没有某个类别的目标,则此类别计算均值时,忽略该视频。

【复赛】

复赛需同时评估模型的准确度与效率。

模型准确率评估指标与初赛一致,使用F1score、MOTA进行评估。

模型效率使用FPS(每秒钟能够处理的帧数)等进行评估。

违法标准

这个其实是在数据标注阶段的时候起作用;

就我所知,与专家系统不同,真正进行模型训练时,深度学习模型构建的系统并不太好将规则反映在模型参数中;

这也是进一步研究的一个重要方向;

【机动车违停】

在这里插入图片描述

机动车在设有禁止停车标志、标线的路段停车,或在非机动车道、人行横道、施工地段等禁止停车的地方停车。具体包含以下:

1、无论有无禁停标志,机动车道禁止车辆停放;

2、距路口、桥梁50米以内禁止车辆停放;

3、距公交车站、消防栓、消防队、医院30米以内禁止使用上述设施以外的车辆停放;

4、禁止车辆双排停放、靠左侧停放、横向停放、逆向停放;

5、人行道仅允许在已设置的停车泊位内停车,禁止在停车泊位外停车;

6、在设有禁停标志、标线的路段,人行横道、施工路段,不得停车。

【非机动车违停】

在这里插入图片描述

非机动车(如自行车、‌电动车等)‌未按照规定停放在指定的非机动车停车泊位或停车线内,‌而是在非机动车禁停区域或未划定的区域(消防通道、盲道、非机动车停车区线外、机动车停车区等)随意停放。

【垃圾满溢】

在这里插入图片描述

生活垃圾收集容器内垃圾超过三分之二以上即为满溢。垃圾桶无法封闭、脏污且周边有纸屑、污渍、塑料、生活垃圾及杂物堆放。

【占道经营】

在这里插入图片描述

经营者占用城市道路、桥梁、城市广场等公共场所进行盈利性买卖商品或服务的行为。

baseline代码详解

1.安装依赖

本次实验主要包含opencv-python、pandas 、matplotlib、ultralytics这四个依赖,其中OpenCV是一个功能强大的计算机视觉库,它提供了大量的图像和视频处理算法,包括但不限于对象检测、图像识别、图像处理等;而Ultralytics 是一个专注于计算机视觉领域的库,它提供了 YOLOv8 模型的一个最新版本,在速度和准确性上都有显著提升,非常适合实时应用。

<code>!/opt/miniconda/bin/pip install opencv-python pandas matplotlib ultralytics

# 首先,导入库

import os, sys

import cv2, glob, json

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

2.数据下载与读取

(1)数据下载

!apt install zip unzip -y

!apt install unar -y

!wget "https://comp-public-prod.obs.cn-east-3.myhuaweicloud.com/dataset/2024/%E8%AE%AD%E7%BB%83%E9%9B%86%28%E6%9C%89%E6%A0%87%E6%B3%A8%E7%AC%AC%E4%B8%80%E6%89%B9%29.zip?AccessKeyId=583AINLNMLDRFK7CC1YM&Expires=1739168844&Signature=9iONBSJORCS8UNr2m/VZnc7yYno%3D" -O 训练集\(有标注第一批\).zip

!unar -q 训练集\(有标注第一批\).zip

!wget "https://comp-public-prod.obs.cn-east-3.myhuaweicloud.com/dataset/2024/%E6%B5%8B%E8%AF%95%E9%9B%86.zip?AccessKeyId=583AINLNMLDRFK7CC1YM&Expires=1739168909&Signature=CRsB54VqOtrzIdUHC3ay0l2ZGNw%3D" -O 测试集.zip

!unar -q 测试集.zip

(2)数据读取的基本思路

在这里,有必要说明一下数据读取的基本思路:

官方给出的数据具有这样的结构,有多个批次,每个批次包含一段mp4格式的视频文件和一个json格式的标注文件;

而我们读取数据的基本思路就是,首先查看一个批次里面的数据,具体包括mp4格式的视频文件(也可以看做多个连续帧的图形集合(这个理解对不对,有没有数字媒体专业的大佬指点一下))和标注文件,查看标注是否与官方叙述格式一致,并尝试可视化前几个帧的图片和标注;

然后,我们对多个批次运用同样的处理方式进行处理;

最后,我们通过整合将文件整合为YOLO v8或其他框架可以识别的文件形式;这样,我们就可以认为是完成了数据读取的基本过程。

(3)尝试读取一个批次的数据

让我们尝试读取第45个批次的视频数据和标注数据

先读取标注文件

train_anno = json.load(open('训练集(有标注第一批)/标注/45.json', encoding='utf-8'))code>

train_anno[0], len(train_anno)

pd.read_json('训练集(有标注第一批)/标注/45.json')

再读取视频文件

video_path = '训练集(有标注第一批)/视频/45.mp4'

cap = cv2.VideoCapture(video_path)

while True:

# 读取下一帧

ret, frame = cap.read()

if not ret:

break

break

frame.shape

int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

cv2是我们之前导入的一个用于处理视频的第三方包,cv2.VideoCapture() 方法返回两个值:一个布尔值表示是否成功读取帧,和一个 ndarray 对象,该对象是读取的帧本身。

最后我们尝试对第45批次的视频和标注进行简单的可视化

bbox = [746, 494, 988, 786]

pt1 = (bbox[0], bbox[1])

pt2 = (bbox[2], bbox[3])

color = (0, 255, 0)

thickness = 2 # 线条粗细

cv2.rectangle(frame, pt1, pt2, color, thickness)

frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

plt.imshow(frame)

坐标是[xmin,ymin,xmax,ymax]这个形式

3.数据处理

视频格式文件处理其他比较麻烦,我们把这些步骤和数据读取区分开,单独作为一个步骤

(1)生成配置文件

说实话YOLO这个框架我是真不熟,但是我想最起码的就是输入输出,模型架构,超参数这些了,输入的话呢,YOLO-V8似乎需要一个满足特定格式要求的yaml文件来说明视频文件和标注文件的路径。

if not os.path.exists('yolo-dataset/'):

os.mkdir('yolo-dataset/')

if not os.path.exists('yolo-dataset/train'):

os.mkdir('yolo-dataset/train')

if not os.path.exists('yolo-dataset/val'):

os.mkdir('yolo-dataset/val')

dir_path = os.path.abspath('./') + '/'

# 需要按照你的修改path

with open('yolo-dataset/yolo.yaml', 'w', encoding='utf-8') as up:code>

up.write(f'''

path: { dir_path}/yolo-dataset/

train: train/

val: val/

names:

0: 非机动车违停

1: 机动车违停

2: 垃圾桶满溢

3: 违法经营

''')

看完代码了好像也不是,这个yaml格式的文件就像windows系统里面创建了一个文件夹,然后把接下来我们需要的文件按YOLO-V8要求的格式给填进去。

(2)批量读入全部文件

train_annos = glob.glob('训练集(有标注第一批)/标注/*.json')

train_videos = glob.glob('训练集(有标注第一批)/视频/*.mp4')

train_annos.sort(); train_videos.sort();

category_labels = ["非机动车违停", "机动车违停", "垃圾桶满溢", "违法经营"]

glob.glob 是 Python 中 glob 模块提供的一个函数,它的作用是文件名模式匹配,即搜索文件系统中与指定模式(模式字符串)匹配的所有路径名。

这里就是把所有文件提取出来,然后按文件名(字母顺序)排序。这在windows系统操作者看来是理所应当的事情,但在python中,你仍然需要一些步骤去完成它们以便应对更复杂的情况。

(3)批量读入全部数据

读入训练集

for anno_path, video_path in zip(train_annos[:5], train_videos[:5]):

print(video_path)

anno_df = pd.read_json(anno_path)

cap = cv2.VideoCapture(video_path)

frame_idx = 0

while True:

ret, frame = cap.read()

if not ret:

break

img_height, img_width = frame.shape[:2]

frame_anno = anno_df[anno_df['frame_id'] == frame_idx]

cv2.imwrite('./yolo-dataset/train/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.jpg', frame)

if len(frame_anno) != 0:

with open('./yolo-dataset/train/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.txt', 'w') as up:

for category, bbox in zip(frame_anno['category'].values, frame_anno['bbox'].values):

category_idx = category_labels.index(category)

x_min, y_min, x_max, y_max = bbox

x_center = (x_min + x_max) / 2 / img_width

y_center = (y_min + y_max) / 2 / img_height

width = (x_max - x_min) / img_width

height = (y_max - y_min) / img_height

if x_center > 1:

print(bbox)

up.write(f'{ category_idx} { x_center} { y_center} { width} { height}\n')

frame_idx += 1

别忘了在测试集上也要进行相应的操作

for anno_path, video_path in zip(train_annos[-3:], train_videos[-3:]):

print(video_path)

anno_df = pd.read_json(anno_path)

cap = cv2.VideoCapture(video_path)

frame_idx = 0

while True:

ret, frame = cap.read()

if not ret:

break

img_height, img_width = frame.shape[:2]

frame_anno = anno_df[anno_df['frame_id'] == frame_idx]

cv2.imwrite('./yolo-dataset/val/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.jpg', frame)

if len(frame_anno) != 0:

with open('./yolo-dataset/val/' + anno_path.split('/')[-1][:-5] + '_' + str(frame_idx) + '.txt', 'w') as up:

for category, bbox in zip(frame_anno['category'].values, frame_anno['bbox'].values):

category_idx = category_labels.index(category)

x_min, y_min, x_max, y_max = bbox

x_center = (x_min + x_max) / 2 / img_width

y_center = (y_min + y_max) / 2 / img_height

width = (x_max - x_min) / img_width

height = (y_max - y_min) / img_height

up.write(f'{ category_idx} { x_center} { y_center} { width} { height}\n')

frame_idx += 1

喂喂喂,这真的好吗,不知道你发现没有,我发现这里读数据的时候baseline代码没有读取全部数据,要是你不好好学习直接跑baseline的话,你的分数肯定不会很高啊,原来datawhale官方为了让大家认真学习真的用心良苦啊!

不过不要紧,你还可以看小虢的解说来发现这一点,然后修改代码跑一个不错的分数,截止到笔记发表的时候,排行榜有将近100人没有发现这一点,看了小虢的解说,你的名次可以前进100名!(手动狗头)

上面是开个玩笑,baseline这么写主办方主要是时间和内存方面的考量,毕竟视频处理起来还是非常耗费时间的啦!另外提醒一下,如果你在使用主办方提供算力资源中的最基础版本,大概30批视频就已经够呛了,请朋友们自行留意硬件使用限制与资费情况,合理控制训练集与测试集的规模,合理划分训练集与测试集。

在这里插入图片描述

好了,话归正题,这段代码主要做了这些工作:

1.读取训练数据:通过zip函数同时获取训练注释(标注)路径train_annos和训练视频路径train_videos。

2.读取标注数据:使用pd.read_json读取每个视频对应的注释文件,存储在anno_df变量中。

3.使用cv2.VideoCapture打开视频文件,并逐帧读取。

4.循环读取帧:使用while循环和cap.read()函数读取视频的每一帧,如果读取失败(即ret为False),则跳出循环。

5.保存帧图像:将读取的帧保存为JPEG图像文件,路径为./yolo-dataset/train/,文件名由原注释文件名和帧编号组成。

6.处理标注:如果当前帧存在注释(即frame_anno非空),则执行以下操作:打开一个文本文件用于写入处理后的注释数据;遍历注释中的每一类别和边界框;将边界框的坐标转换为YOLO格式(类别索引、中心点的x和y坐标、宽高);写入转换后的注释数据到文本文件;将原始的边界框坐标(x_min, y_min, x_max, y_max)转换为中心点坐标(x_center, y_center)和宽高(width, height),这些值被归一化到[0, 1]区间。

7.错误检查:如果检测到x_center的值大于1,打印出边界框的坐标值,归一化后的坐标不应该大于1。

8.帧编号递增:在处理完当前帧的注释后,递增frame_idx以准备处理下一帧。

4.训练模型

1.加载模型

<code>!wget http://mirror.coggle.club/yolo/yolov8n-v8.2.0.pt -O yolov8n.pt

2.加载字体,创建文件夹

!mkdir -p ~/.config/Ultralytics/

!wget http://mirror.coggle.club/yolo/Arial.ttf -O ~/.config/Ultralytics/Arial.ttf

3.训练模型

import os

os.environ["CUDA_VISIBLE_DEVICES"] = "0"

import warnings

warnings.filterwarnings('ignore')

from ultralytics import YOLO

model = YOLO("yolov8n.pt")

results = model.train(data="yolo-dataset/yolo.yaml", epochs=2, imgsz=1080, batch=16)code>

调参下次笔记再讲咯~

4.生成官方格式文件

category_labels = ["非机动车违停", "机动车违停", "垃圾桶满溢", "违法经营"]

if not os.path.exists('result/'):

os.mkdir('result')

from ultralytics import YOLO

model = YOLO("runs/detect/train/weights/best.pt")

import glob

for path in glob.glob('测试集/*.mp4'):

submit_json = []

results = model(path, conf=0.05, imgsz=1080, verbose=False)

for idx, result in enumerate(results):

boxes = result.boxes # Boxes object for bounding box outputs

masks = result.masks # Masks object for segmentation masks outputs

keypoints = result.keypoints # Keypoints object for pose outputs

probs = result.probs # Probs object for classification outputs

obb = result.obb # Oriented boxes object for OBB outputs

if len(boxes.cls) == 0:

continue

xywh = boxes.xyxy.data.cpu().numpy().round()

cls = boxes.cls.data.cpu().numpy().round()

conf = boxes.conf.data.cpu().numpy()

for i, (ci, xy, confi) in enumerate(zip(cls, xywh, conf)):

submit_json.append(

{

'frame_id': idx,

'event_id': i+1,

'category': category_labels[int(ci)],

'bbox': list([int(x) for x in xy]),

"confidence": float(confi)

}

)

with open('./result/' + path.split('/')[-1][:-4] + '.json', 'w', encoding='utf-8') as up:code>

json.dump(submit_json, up, indent=4, ensure_ascii=False)

下次笔记,我将带着大伙一起炼丹,争取获得更好的分数和排名!

笔者水平有限,欢迎各位大佬批判指正!

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。