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

#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 模型的一个最新版本,在速度和准确性上都有显著提升,非常适合实时应用。

!/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)

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

笔者水平有限,欢迎各位大佬批判指正!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虢子仪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值