#Datawhale AI夏令营 #CV赛道 #目标检测 #视频处理方法 #物体检测模型
CSDN社区的各位朋友们好久不见了,本次AI夏令营笔记将专注于违规问题智能检测的解决方案,希望能给你一些启示和参考,以作为官方讲解的有益补充
由于datawhale更改了笔记发布政策,本次笔记分享将分为三个部分发布,我将逐步介绍违规问题智能检测任务的实现过程。在本次分享中,我将对赛题进行简单解读,并着重介绍视频文件的处理方法以及物体检测模型,而第二次和第三次分享我将着重介绍YOLO检测框架的原理、调参方法以及上分经验与技巧。
研究背景与意义
研究意义
本次赛题求选手研究开发高效可靠的计算机视觉算法,提升违规行为检测识别的准确度,降低对大量人工的依赖,提升检测效果和效率,从而推动城市治理向更高效、更智能、更文明的方向发展,为居民创造一个安全、和谐、可持续的居住环境。
初赛任务则是根据给定的城管视频监控数据集,进行城市违规行为的检测。选手需要能够从视频中分析并标记出违规行为,提供违规行为发生的时间和位置信息。违规行为包括垃圾桶满溢、机动车违停、非机动车违停等。
数据格式
视频数据为mp4格式,标注文件为json格式,每个视频对应一个json文件。
json文件的内容是每帧检测到的违规行为,包括以下字段:
frame_id | 违规行为出现的帧编号 |
event_id | 违规行为ID |
category | 违规行为类别 |
bbox | 检测到的违规行为矩形框的坐标,[xmin,ymin,xmax,ymax]形式 |
评估指标
【初赛】
使用F1score、MOTA指标来评估模型预测结果。
对每个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 模型的一个最新版本,在速度和准确性上都有显著提升,非常适合实时应用。
!/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'))
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:
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.加载模型
!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)
调参下次笔记再讲咯~
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:
json.dump(submit_json, up, indent=4, ensure_ascii=False)
下次笔记,我将带着大伙一起炼丹,争取获得更好的分数和排名!
笔者水平有限,欢迎各位大佬批判指正!