参考教程:
Docshttps://datawhaler.feishu.cn/wiki/CwJ7wkrI2iP56xknQlRc4cxRnpg?from=from_copylink
赛题介绍
赛事链接:MARS大数据服务平台> shttps://www.marsbigdata.com/competition/details?id=3839107548872
赛事任务
随着城市化步伐的加速迈进,城市治理面临着前所未有的挑战与机遇。城市管理的精细化、智能化已成为全球城市追求卓越的关键路径。然而,机动车违停、非机动车违停、占道经营等城市违规行为如同现代都市肌体上的疮疤,不仅侵蚀着城市的美学与秩序,更对公众福祉构成了潜在威胁。传统的人力巡查与被动响应模式,已然无法匹配当今城市治理的需求。
本赛题最终目标是开发一套智能识别系统,能够自动检测和分类城市管理中的违规行为。该系统应利用先进的图像处理和计算机视觉技术,通过对摄像头捕获的视频进行分析,自动准确识别违规行为,并及时向管理部门发出告警,以实现更高效的城市管理。
初赛任务是根据给定的城管视频监控数据集,进行城市违规行为检测。违规行为主要包括垃圾桶满溢、机动车违停、非机动车违停等。
需要从视频中分析并标记出违规行为,提供违规行为发生的时间和位置信息。
总结一下,可以暂时理解为视频异常检测任务。
数据集分析
初步探索
初赛提供城管视频监控数据与对应违规行为标注。违规行为包括垃圾桶满溢、机动车违停、非机动车违停等。
视频数据为mp4格式,标注文件为json格式,每个视频对应一个json文件。
json文件的内容是每帧检测到的违规行为,包括以下字段:
- frame_id:违规行为出现的帧编号
- event_id:违规行为ID
- category:违规行为类别
- bbox:检测到的违规行为矩形框的坐标,[xmin,ymin,xmax,ymax]形式
标注示例如下:
[
{
"frame_id": 20,
"event_id": 1,
"category": "机动车违停",
"bbox": [200, 300, 280, 400]
},
{
"frame_id": 20,
"event_id": 2,
"category": "机动车违停",
"bbox": [600, 500, 720, 560]
},
{
"frame_id": 30,
"event_id": 3,
"category": "垃圾桶满溢",
"bbox": [400, 500, 600, 660]
}
]
违规行为示例如下:
垃圾桶满溢:
机动车违停:
非机动车违停:
Task1的baseline代码中的“数据读取”部分对数据集做了简单的分析,包括展示json标注、显示视频帧数、显示图片大小、检测框可视化等,这里就不赘述。
详细分析
对训练集的视频和标注文件进行分析:
(CV小白,写的代码可能很繁杂,见谅QAQ)
import cv2
import os
import matplotlib.pyplot as plt
import json
video_dir = "训练集(有标注第一批)/视频"
lable_dir = "训练集(有标注第一批)/标注"
frame_cnt_v = []
frame_cnt_l = []
labels = []
# 视频帧数分布
for path, dir_lst, file_lst in os.walk(video_dir):
file_lst.sort() # 保证有序
for file_name in file_lst:
video_path = os.path.join(path, file_name)
cap = cv2.VideoCapture(video_path)
frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt_v.append(frame_cnt)
# print(frame_cnt)
# print(os.path.join(path, file_name))
frame_v_set = list(set(frame_cnt_v))
frame_v_set.sort()
# 标注帧数分布
for path, dir_lst, file_lst in os.walk(lable_dir):
file_lst.sort()
for file_name in file_lst:
label_path = os.path.join(path, file_name)
with open(label_path, 'r') as f:
content = f.read()
frame_list = json.loads(content)
frame_cnt_l.append(frame_list[-1]['frame_id']+1) # frame_id从0开始标,需要+1
for frame in frame_list:
labels.append(frame['category'])
frame_l_set = list(set(frame_cnt_l))
frame_l_set.sort()
labels_set = list(set(labels))
下面验证标注的帧数和实际视频的帧数是否相同,这一步是确保所有的视频帧都得到了标记:
# 判断视频和标注是否一致
if frame_cnt_v == frame_cnt_l:
print('标注帧数和视频帧数一致')
else:
print('标注帧数和视频帧数不一致')
print(frame_cnt_v)
print(frame_cnt_l)
输出结果:不一致
可以发现部分视频漏标了最后一帧,因此在数据处理的时候,最好能将视频中漏标的帧去除掉,以免影响模型性能。
接着观察样本分布:
# 标注类别
for i in labels_set:
print(f"{i}: {labels.count(i)}")
输出结果:
违法经营: 946 机动车违停: 70722 非机动车违停: 217776 垃圾桶满溢: 2737
可以发现样本个数差距较大,分布极不平衡,“非机动车违停”出现了21万多次,而“违法经营”仅出现了946次,在训练时很可能由于“违法经营”样本数少而导致检测结果差。
评分规则
使用F1score、MOTA指标来评估模型预测结果。
对每个json文件得到两个指标的加权求和,最终得分为所有文件得分取均值。
注1:若真实目标框与预测框IOU大于0.5,则判定目标正确识别。若MOTA指标为负,则该类别精度得分为0。
注2:若该视频中没有某个类别的目标,则此类别计算均值时,忽略该视频。
IoU
IoU,又称交并比,是一种检测物体位置准确度的标准。顾名思义,其实就是两框的重叠部分的面积,与两框总共部分的面积之比。
IoU越接近于0,两框重叠越少;IoU越接近于1,两框的重叠程度越高,当IoU等于1时,两框完全重叠。
举例如下:绿色框是准确值,红色框是预测值。
赛题要求IoU>0.5时才判定目标正确识别,需要注意。
F1-score
F1分数(F1-score)是分类问题的一个衡量指标,是精确率和召回率的调和平均数,最大为1,最小为0。F1分数越高说明分类效果越好。
计算过程:
1.首先定义以下几个概念:
TP(True Positive):预测答案正确
FP(False Positive):错将其他类预测为本类
FN(False Negative):本类预测为其他类
2. 通过第一步的统计值计算每个类别下的precision和recall
精准度 / 查准率(precision):指被分类器判定正例中的正样本的比重
召回率 / 查全率 (recall):指的是被预测为正例的占总的正例的比重
3. 通过第二步计算结果计算每个类别下的F1-score,计算方式如下:
MOTA
MOTA是多目标跟踪中最重要的一个指标,计算公式如下:
其中GT表示第t帧中Ground truth的个数,FN表示第t帧中漏检的个数,FP表示第t帧中虚检的个数。IDSW表示第t帧中轨迹的id号发生转变的个数。
GT:对应label中第t帧有几个实例,那么该值就是标注的实例的个数。总数只需要把每一帧的个数求和即可。
FN:漏检个数,假如GT在第t帧本应该有5个实例,但是得到的检测结果中只有四个能与之匹配(例如匹配可以等价于IoU>0.5),这个时候就存在一个GT的bbox漏检,此时FN为1。总数把每一帧的FN求和即可。
FP:虚检表示检测出来的bbox不在GT里面的情况,例如我某一帧检测到了6个框,但GT只有5个instance,且6个框中只能与GT匹配4个,这种情况下显然有两个框不在GT中,因此FP等于2,相应地FN=1;
IDSW:通过tracking可以得到若干条轨迹,假设GT中只有一条轨迹,那么这条轨迹可能在不同的时间对应不同的得到的tracking的轨迹。例如前一半时间GT轨迹对应的位置离轨迹1近,此时id号就属于1;而随着时间的推移,轨迹1与GT的位置不断偏离,而轨迹2与GT越来越近,在某个时刻GT就与轨迹2相对应了,此时就发生了id switch。
下面这个例子清晰地展示了各个指标:
以a图为例:
1时刻检测的框与GT对应的框太远(IoU过大),导致GT漏检,且检测框虚检;
2时刻检测框离GT近了一些,此时检测框和GT box的IOU大于阈值,该检测框为TP;
3时刻出现了两个检测框(红&蓝),此时红框为TP,蓝框过远为FP(此时虚检的蓝色框又开启了一条轨迹);
4时刻红色检测框离GT很远,但蓝色的很近,因此红色轨迹的检测框为FP,而蓝色的为TP,注意此时该GT轨迹的id从红色切换到了蓝色,即发生了id switch。
最后两帧红色轨迹消失(检测框不存在了),蓝色始终为TP。因此整个过程GT=6,FN=1(第1帧),FP=4(蓝色和红色的空心点),idsw=1(第4帧)。
MOTA值越大越好,最大为100(一般采用百分数),最小因为FP和ids的关系,可能为负数。
使用MOTA指标说明还需要考虑检测的违规行为的id变换,值得注意。
Baseline简介
数据处理:
# 读取训练集视频
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)
# 计算yolo标注格式
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
可以发现在处理数据的时候已经考虑到了缺少标注的问题,由于yolo的标注格式(中心的x坐标,中心的y坐标,宽,高)和给出的标注格式不同,所以需要进行转化。
训练部分:
下载yolov8n权重:
!wget http://mirror.coggle.club/yolo/yolov8n-v8.2.0.pt -O yolov8n.pt
下载需要用到的字体
(这部分其实可以在YOLO的源代码中注释掉,避免运行时下载字体超时报错)
!mkdir -p ~/.config/Ultralytics/
!wget http://mirror.coggle.club/yolo/Arial.ttf -O ~/.config/Ultralytics/Arial.ttf
!wget http://mirror.coggle.club/yolo/Arial.Unicode.ttf -O ~/.config/Ultralytics/Arial.Unicode.ttf # 加上这条
在厚德云上运行时还需要加上最后一条,否则出现下图下载超时报错(好像其他同学没有遇到?)
开始训练:
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
# 图像大小1080, 训练轮数2, 每个批次16张图片
results = model.train(data="yolo-dataset/yolo.yaml", epochs=2, imgsz=1080, batch=16)
训练过程:
YOLOv8简介
Task01的baseline使用的是YOLOv8目标检测模型,按照模型参数量大小可以分为以下几种:
n(nano)、s(small)、m(medium)、l(large)、x(extra large)
参数量越大效果越好,训练和推理的速度也会越慢。
YOLOv8的整体结构如下图所示:
YOLO算法流程
YOLO算法的基本流程:
输入一个图像,首先将图像划分成S*S的网格;每个网格都要预测B个bounding box,每个bounding box都包含5个值:x坐标、y坐标、宽度、高度、置信度,同时还要预测该网格所属类别的概率(共C个概率,对应各个类别);最终预测出S*S*B个检测框,输出然后根据阈值除去置信度比较低的检测框,最后使用NMS(非极大值抑制)去除冗余窗口即可。
将图片输入模型,最终会得到一个S*S*(10+C)的Tensor(张量),分别对应每个网格对应的特征向量,如下图所示。
对于每个网格,特征向量的维度是1*1*(10+C),其中前5项为纵向检测框的x,y,w,h,c,即x坐标、y坐标、宽度、高度、置信度。置信度包含两个方面,一是检测框含有目标的可能性大小,二是这个检测框的准确度;前者记为Pr(Object),当该检测框是背景时(不包含目标)Pr(Object)=0,检测框包含目标时Pr(Object)=1;检测框的准确度可以用预测框和实际框(ground truth)的IoU(交并比交并比,衡量重合程度)来表示。之后的5项表示横向检测框的x,y,w,h,c,剩余的C项表示该网格属于各个类别的概率值。
通过模型输出的特征向量可以推断出损失函数所具有的形式,由下图所示,训练时的损失函数由5项均方差损失组成,每项设置了不同的权重。
其中,前两项分别为坐标损失和大小损失,负责检测框位置的预测;之后的两项分别为“含有目标”与“不含目标”的检测框的置信度预测;最后一项为目标类别预测。
得到模型输出的检测框之后,还需要根据阈值去除置信度较低的检测框,之后进行NMS去除冗余框。对于同一类别的同一目标,模型可能输出很多相似的检测框,这时需要使用非极大值抑制来去除冗余框。
对于每一个类别,首先选取概率最大的检测框,之后遍历其他所有检测框,如果某个检测框与概率最大检测框的IoU(交并比,衡量重合程度)大于某一阈值,则将该检测框对应此类别的概率设置为0,遍历一边之后选取该类别概率第二大的检测框,重复这个过程,直到选不出新的检测框为止。非极大值抑制后,某一类别的所有检测框的IoU都小于给定阈值,并且留下来的是周围概率最大的检测框。之后根据设置的概率阈值对检测过滤即可得到最终结果。
YOLOv8新特性
相较于YOLO之前的版本,YOLOv8主要做出了以下改进:
Backbone主干网络部分:使用CSP的思想(Cross Stage Partial,融合不同阶段的特征),并使用C2f模块替换之前的C3模块,实现了进一步的轻量化;同时,YOLOv8使用了SPP模块(Spatial Pyramid Pooling,空间金字塔池化,提取多尺度特征。
C2f与C3结构对比:
SSP空间金字塔池化:
PAN-FPN:YOLOv8使用了PAN(Path Aggregation Network)的思想。如图所示,PAN-FPN使用FPN(a)、自底向上路径增强(b)、自适应特征池化(c)、盒状分支(d)、全连接融合(e)等多种结构进行特征融合。YOLOv8删除了PAN-FPN上采样阶段中的CBS 1*1的卷积结构,同时也将C3模块替换为了轻量化的C2f模块。
PAN(Path Aggregation Network)结构:
Decoupled-Head:YOLOv8使用了Decoupled-Head;即通过两个解耦头分别输出类别与检测框位置。传统的图像分割网络通常将特征提取和像素预测过程集成在同一个网络中,而Decoupled Head则将这两个过程进行解耦,分别处理。Decoupled Head的核心思想是通过引入额外的分支网络来进行像素级的预测。具体而言,Decoupled Head网络在主干网络的特征图上添加一个或多个额外的分支,用于预测像素的类别。Decoupled Head的优势在于可以更好地处理不同尺度和精细度的语义信息。通过将像素级的预测与特征提取分开,可以更好地利用底层和高层特征之间的语义信息,从而提高分割的准确性和细节保留能力。
Decoupled-Head结构图:
Anchor-Free:YOLOv8抛弃了以往的Anchor-Base,使用了Anchor-Free的思想。Anchor-Free通过不依赖数据集中的先验知识,使网络对“物体形状”有更好的表达能力,更具泛化潜能。在运动物体、尺寸不一物体检测上有所提升,同时检测被遮挡物体时也能更加灵活。