系列文章目录
Task1
Task2 (loading…)
Task3 (loading…)
Task 1
文章目录
前言
本次夏令营以2024大运河杯数据开发应用创新大赛城市治理赛道为实践基础,通过代码讲解与技术分享,学习如何开发一套智能识别系统,在自动检测和分类摄像头捕获的视频中识别和分类城市管理中的违规行。该任务通过自动准确识别违规行为,并及时向管理部门发出告警,来提高城市管理的效率,具有一定社会价值。
本篇文章主要是记录和分享自己在夏令营中的学习过程和遇到的困难
一、跑通Baseline
step 1: 环境搭建
这里可以参考Datawhale官方文件:https://linklearner.com/activity/16/16/49
文件可能需要登陆后学习,里面有详细的使用指导,关于如何租用GPU,如何选择环境等等。
step 2: 安装所需依赖库和工具,准备数据集
安装所需的Python库(opencv-python, pandas, matplotlib, ultralytics),并导入必要的库(os, sys, cv2, glob, json, numpy, pandas, matplotlib.pyplot)
!/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
使用apt命令安装zip和unzip工具,以便后续操作数据
!apt install zip unzip -y
!apt install unar -y
使用wget命令下载训练集(带有第一批标注)和测试集并解压缩
!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
step 3: 数据读取与处理
从指定路径加载训练集标注文件中的数据到train_anno变量中,并检查数据格式信息
train_anno = json.load(open('训练集(有标注第一批)/标注/45.json', encoding='utf-8'))
train_anno[0], len(train_anno)
使用pandas库中的read_json函数读取训练集标注文件内容
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))
定义矩形框,在当前帧上绘制矩形框并将其从BGR颜色空间转换为RGB颜色空间,使用imshow函数显示当前帧
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)
检查并创建’yolo-dataset/'、'yolo-dataset/train’和’yolo-dataset/val’目录,获取绝对路径,并将路径信息写入’yolo-dataset/yolo.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('./') + '/'
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: 违法经营
''')
获取训练集中的标注文件和视频文件路径,并按文件名排序
train_annos = glob.glob('训练集(有标注第一批)/标注/*.json')
train_videos = glob.glob('训练集(有标注第一批)/视频/*.mp4')
train_annos.sort()
train_videos.sort()
定义类别标签列表category_labels
category_labels = ["非机动车违停", "机动车违停", "垃圾桶满溢", "违法经营"]
对前5个训练集数据进行处理:获取每一帧的高度,宽度和标注信息并保存为.jpg格式;对有标注信息的帧,遍历每个目标的类别和边界框信息,计算归一化后的中心坐标、宽度和高度,并写入.txt文件
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
对后3个训练集数据进行类似的处理,但保存到’yolo-dataset/val/'目录下
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
step 4: 训练模型并测试效果
从指定URL下载yolov8模型文件并保存为yolov8n.pt,并下载Arial字体文件
!wget http://mirror.coggle.club/yolo/yolov8n-v8.2.0.pt -O yolov8n.pt
!mkdir -p ~/.config/Ultralytics/
!wget http://mirror.coggle.club/yolo/Arial.ttf -O ~/.config/Ultralytics/Arial.ttf
设置环境变量,指定第一个GPU进行训练,并忽略警告信息
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
import warnings
warnings.filterwarnings('ignore')
加载模型,使用yolo-dataset/yolo.yaml配置文件进行训练(设置训练轮数为2,图像尺寸为1080,批量大小为16),并定义类别标签列表
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model.train(data="yolo-dataset/yolo.yaml", epochs=2, imgsz=1080, batch=16)
定义类别标签列表,并检查是否存在’result/'目录,如果不存在则创建
category_labels = ["非机动车违停", "机动车违停", "垃圾桶满溢", "违法经营"]
if not os.path.exists('result/'):
os.mkdir('result')
测试模型:遍历测试集中的视频文件,对每个视频进行目标检测,将检测结果submit_json保存为JSON格式的提交文件
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)
清理不需要的文件和文件夹,并将’result/'目录压缩为result.zip文件
!\rm result/.ipynb_checkpoints/ -rf
!\rm result.zip
!zip -r result.zip result/
二、学习与相关问题
1. 重难点学习
2. 问题与初步解答
问题1:为什么代码中出现了两次定义类别标签列表?
回答:第一次定义是在模型训练之前,用于指定模型训练时的类别标签,以便模型能够识别;第二次定义是在处理测试集数据时,可能是为了在生成JSON格式的提交文件时,将模型检测到的类别标签转换为可读的类别名称
问题2:为什么下载依赖库时出现如下报错:
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
回答:这个warning是在使用root用户身份运行pip时出现的。在Linux系统中,使用root用户身份运行pip可能会导致权限问题和与系统包管理器冲突的行为。可以更换虚拟环境(virtual environment)来代替,在其中安装和管理项目所需的依赖包不会影响系统的Python环境。通过虚拟环境,可以避免全局安装包可能带来的问题,同时也更容易管理项目的依赖关系。
问题3:为什么训练模型时出现如下报错:
WARNING: ⚠️ imgsz=[1080] must be multiple of max stride 32, updating to [1088]
回答:这个warning是由于在训练卷积神经网络模型时,特征图的大小在经过多次卷积和池化操作后会逐渐减小,而这些操作通常会使用固定的步幅(stride)值,例如32。因此,为了确保特征图大小能够被32整除,输入图像的尺寸需要是32的倍数。当前图像的输入尺寸为1080,不符合这个要求。在这种情况下,系统会发出警告并将图像尺寸从1080更新为1088,以确保满足模型对输入图像尺寸的要求。其实,如果能够确定步幅,在数据预处理阶段可以直接将输入图像尺寸修改为符合模型要求的值(例如将输入图像尺寸调整为最接近且符合32的倍数的值),而不等待系统自动更新。这是一种更主动和预防性的做法,可以在数据输入模型之前就确保输入数据的合法性,避免后续出现警告或错误,提高代码的健壮性和可靠性。
问题4:为什么测试模型时出现如下报错:
WARNING: ⚠️ inference results will accumulate in RAM unless `stream=True` is passed, causing potential out-of-memory
errors for large sources or long-running streams and videos. See https://docs.ultralytics.com/modes/predict/ for help.
Example:
results = model(source=..., stream=True) # generator of Results objects
for r in results:
boxes = r.boxes # Boxes object for bbox outputs
masks = r.masks # Masks object for segment masks outputs
probs = r.probs # Class probabilities for classification outputs
回答:这个warning是在提醒我们,在推理(inference)过程中,如果不传递stream=True参数,推理结果将会在内存中累积,可能导致内存不足的错误(对于大型数据源或长时间运行的流和视频而言)。为了避免这种情况,建议传递stream=True参数,以便在推理过程中及时释放内存。上述警告中的示例将推理结果作为生成器(generator)逐步生成,而不是一次性存储在内存中,从而有效减少内存压力,提高系统的稳定性和性能。但是,使用该参数可能会带来一些负面影响,如延迟问题(逐步生成结果可能会导致推理速度变慢,特别是在需要实时性能的情况下)和代码复杂性(使用生成器需要额外的代码来处理生成器对象。因此,在决定是否传递该参数时,需要权衡内存占用和推理速度之间的平衡。如果内存占用不是问题,并且需要快速获取所有结果,则可以选择不传递;如果内存有限或需要处理大量数据时,可以考虑传递该参数以减少内存占用。
总结
只是本篇针对Task1的总结,系列总结会在第三篇
以上就是对本赛题baseline的详细讲解,也进行了一定的学习与问题分享,后续会继续进阶学习,优化模型性能,持续分享。