Datawhale AI 夏令营-CV 方向-Task 1

前言

这是我的Datawhale AI 夏令营 第五期 CV赛道:智能识别系统学习笔记,记录了从基础模型的构建到逐步优化的过程。作为AI领域的新人,如果笔记中存在任何技术和文字上的不足之处,还请各位读者不吝指出。

当前上分记录:

一、赛题解读

1.赛题描述

随着城市化的快速发展,城市管理面临着巨大挑战。为实现城市管理的精细化和智能化,本赛题目的是开发一套智能识别系统,自动检测和分类城市管理中的违规行为,例如机动车和非机动车违停、占道经营等,从而提升城市治理的效率和水平。

2.赛题题目

城市管理违规行为智能识别

3.赛题任务

  • 【初赛】:选手需使用城管视频监控数据集,检测诸如垃圾桶满溢、机动车违停等违规行为,并标记违规行为的时间和位置。

  • 【复赛】:在初赛基础上增加如违法经营等其他类型的违规行为的检测任务,选手需要在大赛提供的环境中建立并优化模型。

  • 【决赛】:通过线上或线下答辩的形式,展示团队的模型和成果。

4.数据描述

  • 初赛数据:包括mp4格式的视频和json格式的标注文件,标注文件中记录了违规行为的详细信息,如发生时间、位置、类别等。

5.评估指标

  • 【初赛】:使用F1 Score和MOTA指标来评估模型的准确性和跟踪准确性。F1 Score衡量精准率和召回率的平衡,MOTA衡量目标跟踪的准确性。

6.提交说明

  • 【初赛】:选手需要提交包含json结果文件的result.zip压缩包,每个视频对应一个json文件,其中记录检测到的每一个违规行为的具体信息。

7.违法标准

  • 机动车违停:在禁停区域、人行道、机动车道等非法停车。

  • 非机动车违停:非机动车未按规定停放在指定泊位。

  • 垃圾满溢:垃圾超过容器三分之二,周边环境脏乱。

  • 占道经营:在公共场所非法经营。

二、跑通Baseline

1.报名赛事

赛事链接:https://www.marsbigdata.com/competition/details?id=3839107548872(报名需填写Datawhale)

 2.领取GPU算力

链接:https://portal.houdeyun.cn/register?from=Datawhale

3.运行baseline

3.1下载baseline相关文件

apt install git-lfs
git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/AI_Camp5_baseline_CV.git

 3.2运行baseline

 3.3下载结果文件result.zip

 4.提交运行结果

三、YOLO学习

Yolo算法采用一个单独的CNN模型实现end-to-end的目标检测,整个系统如图5所示:首先将输入图片resize到448x448,然后送入CNN网络,最后处理网络预测结果得到检测的目标。相比R-CNN算法,其是一个统一的框架,其速度更快,而且Yolo的训练过程也是end-to-end的。

1. 基本思想

YOLO 将目标检测问题视为一个单一的回归问题,而不是将其分解为多个阶段(例如候选区域生成、分类等)。在 YOLO 中,整个图像被分割成一个固定大小的网格(如 S×S),并且每个网格负责预测固定数量的边界框及其对应的类别概率。

2. 算法步骤

2.1 图像分割与网格划分

  • 输入图像被划分为一个 S×S 的网格(例如,7x7)。

  • 每个网格单元负责检测其中心点落在该网格中的目标。

2.2 边界框预测

  • 每个网格单元预测 B 个边界框,每个边界框由以下五个值组成:

    • (x,y):边界框中心相对于网格单元的坐标。

    • w,h:边界框的宽度和高度,相对于整张图像的大小。

    • pobj:该边界框内是否存在目标的置信度得分。

2.3 类别概率预测

  • 每个网格单元还预测目标属于各个类别的概率 C 个值。假设目标有 C 个类别,那么每个网格单元预测 C 个概率值。

  • 每个类别的条件概率为 p,表示在目标存在的情况下,该目标属于类别 i 的概率。

2.4 最终的输出

  • 每个边界框的置信度得分由以下公式给出:

其中 IOU是预测的边界框与真实边界框的交并比(Intersection Over Union)。

  • 最终的输出是 S×S×(B×5+C)的张量,其中 B×5是每个网格单元的 B 个边界框及其置信度得分,而 C 是每个网格单元的类别概率。

3. 损失函数

YOLO 使用一个单一的损失函数来优化整个网络。这个损失函数包括以下几部分:

  • 定位损失:用于惩罚预测的边界框位置与真实边界框之间的偏差。包括中心坐标 (x,y) 和宽高 (w,h)的误差。

  • 置信度损失:用于惩罚预测的边界框置信度与真实 IOU 之间的差异。

  • 类别损失:用于惩罚预测的类别概率与真实类别之间的差异。

4. YOLO 的变种与改进

YOLO 在发展过程中有多个版本和改进:

  • YOLOv1:最初的 YOLO 版本,较快但精度相对较低。

  • YOLOv2(YOLO9000):在 YOLOv1 的基础上,增加了批量归一化、更精确的边界框预测等,提升了准确性和速度。

  • YOLOv3:进一步改进了网络结构,引入了残差块和多尺度预测,显著提升了精度。

  • YOLOv4、YOLOv5:引入了更多优化技巧,如 CSPNet、Mosaic 数据增强等,进一步提高了检测效果和实时性能。

5. 优点

  • 实时性:YOLO 可以在一次前向传播中同时预测多个目标,处理速度非常快,适合实时应用。

  • 全局推理:YOLO 在全局图像上进行推理,有助于减少错误检测。

6. 缺点

  • 小目标检测性能较差:由于每个网格单元只负责检测一个目标,YOLO 在处理小目标时性能不佳。

  • 定位不够精确:与基于候选区域的方法(如 Faster R-CNN)相比,YOLO 的定位精度略低。

四、baseline精读解析

1.环境数据设置

# # 安装相关库
# !/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 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.数据读取

# 从指定的 JSON 文件中加载训练集的标注数据
train_anno = json.load(open('训练集(有标注第一批)/标注/45.json', encoding='utf-8'))

# 输出第一个标注数据和标注数据的总长度
train_anno[0], len(train_anno)
# 使用 pandas 读取 JSON 文件并将其转换为 DataFrame
pd.read_json('训练集(有标注第一批)/标注/45.json')
# 指定视频文件的路径
video_path = '训练集(有标注第一批)/视频/45.mp4'

# 使用 OpenCV 打开视频文件
cap = cv2.VideoCapture(video_path)

# 无限循环读取视频帧
while True:
    # 读取下一帧
    ret, frame = cap.read()
    
    # 如果读取失败(视频结束),则退出循环
    if not ret:
        break
    
    # 这里只读取一帧后就退出循环
    break
# 获取当前帧的形状(高度, 宽度, 通道数)
frame.shape
plt.imshow(frame[:, :, ::-1])
# 获取视频的总帧数,并将其转换为整数
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(total_frames)
# 定义一个边界框,格式为 [x1, y1, x2, y2]
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)

# 将帧从 BGR 颜色空间转换为 RGB 颜色空间
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

# 使用 matplotlib 显示帧
plt.imshow(frame)

3.数据处理与转换(已做修改

import os, sys
import glob, json
import numpy as np
import pandas as pd
import cv2

# 如果 'yolo-dataset/' 目录不存在,则创建该目录
if not os.path.exists("yolo-dataset/"):
    os.mkdir("yolo-dataset/")

# 如果 'yolo-dataset/train' 目录不存在,则创建该目录
if not os.path.exists("yolo-dataset/train"):
    os.mkdir("yolo-dataset/train")

# 如果 'yolo-dataset/val' 目录不存在,则创建该目录
if not os.path.exists("yolo-dataset/val"):
    os.mkdir("yolo-dataset/val")

# 获取当前目录的绝对路径
dir_path = os.path.abspath("./") + "/"

# 创建并写入 YOLO 数据集配置文件
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 = ["非机动车违停", "机动车违停", "垃圾桶满溢", "违法经营"]
import os
import cv2
import pandas as pd
import glob

# 获取所有训练集标注文件的路径列表
train_annos = glob.glob("训练集(有标注第一批)/标注/*.json")

# 获取所有训练集视频文件的路径列表
train_videos = glob.glob("训练集(有标注第一批)/视频/*.mp4")

# 对标注文件和视频文件的路径列表进行排序
train_annos.sort()
train_videos.sort()

# 定义类别标签列表
category_labels = ["非机动车违停", "机动车违停", "垃圾桶满溢", "违法经营"]

# 训练集
# 遍历标注文件和对应的视频文件
for anno_path, video_path in zip(train_annos[:40], train_videos[:40]):
    # 打印当前处理的视频文件路径
    print(f"Processing video: {video_path}")

    # 读取标注文件并转换为 DataFrame
    anno_df = pd.read_json(anno_path)

    # 打开视频文件
    cap = cv2.VideoCapture(video_path)

    # 获取视频的帧率和总帧数
    fps = cap.get(cv2.CAP_PROP_FPS)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

    # 计算视频时长(秒)
    duration = total_frames / fps

    # 根据视频时长设置帧跳步长
    if duration <= 5:
        frame_step = 2
    elif 5 < duration <= 10:
        frame_step = 5
    elif 10 < duration <= 20:
        frame_step = 10
    else:
        frame_step = 20  # 可以根据需要调整这个步长

    # 初始化帧索引
    frame_idx = 0

    # 循环读取视频帧
    while True:
        # 读取当前帧
        ret, frame = cap.read()

        # 如果读取失败(视频结束),则退出循环
        if not ret:
            break

        # 只处理符合步长的帧
        if frame_idx % frame_step == 0:
            # 获取当前帧的高度和宽度
            img_height, img_width = frame.shape[:2]

            # 获取当前帧的标注数据
            frame_anno = anno_df[anno_df["frame_id"] == frame_idx]

            # 构建图像文件路径
            img_filename = os.path.join(
                "yolo-dataset",
                "train",
                f"{os.path.basename(anno_path)[:-5]}_{frame_idx}.jpg",
            )

            # 保存当前帧为图像文件
            cv2.imwrite(img_filename, frame)

            # 如果当前帧有标注数据
            if len(frame_anno) != 0:
                # 构建标注文件路径
                anno_filename = os.path.join(
                    "yolo-dataset",
                    "train",
                    f"{os.path.basename(anno_path)[:-5]}_{frame_idx}.txt",
                )

                # 创建并写入标注文件
                with open(anno_filename, "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

                        # 计算 YOLO 格式的边界框参数
                        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

                        # 检查 x_center 是否大于 1(调试用)
                        if x_center > 1:
                            print(bbox)

                        # 写入标注数据到文件
                        up.write(
                            f"{category_idx} {x_center} {y_center} {width} {height}\n"
                        )

        # 增加帧索引
        frame_idx += 1

    cap.release()
# 验证集
# 遍历最后13个标注文件和对应的视频文件
for anno_path, video_path in zip(train_annos[-13:], train_videos[-13:]):
    # 打印当前处理的视频文件路径
    print(f"Processing video: {video_path}")

    # 读取标注文件并转换为 DataFrame
    anno_df = pd.read_json(anno_path)

    # 打开视频文件
    cap = cv2.VideoCapture(video_path)

    # 获取视频的帧率和总帧数
    fps = cap.get(cv2.CAP_PROP_FPS)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

    # 计算视频时长(秒)
    duration = total_frames / fps

    # 根据视频时长设置帧跳步长
    if duration <= 5:
        frame_step = 2
    elif 5 < duration <= 10:
        frame_step = 5
    elif 10 < duration <= 20:
        frame_step = 10
    else:
        frame_step = 20  # 可以根据需要调整这个步长

    # 初始化帧索引
    frame_idx = 0

    # 循环读取视频帧
    while True:
        # 读取当前帧
        ret, frame = cap.read()

        # 如果读取失败(视频结束),则退出循环
        if not ret:
            break

        # 只处理符合步长的帧
        if frame_idx % frame_step == 0:
            # 获取当前帧的高度和宽度
            img_height, img_width = frame.shape[:2]

            # 获取当前帧的标注数据
            frame_anno = anno_df[anno_df["frame_id"] == frame_idx]

            # 构建图像文件路径
            img_filename = os.path.join(
                "yolo-dataset",
                "val",
                f"{os.path.basename(anno_path)[:-5]}_{frame_idx}.jpg",
            )

            # 保存当前帧为图像文件
            cv2.imwrite(img_filename, frame)

            # 如果当前帧有标注数据
            if len(frame_anno) != 0:
                # 构建标注文件路径
                anno_filename = os.path.join(
                    "yolo-dataset",
                    "val",
                    f"{os.path.basename(anno_path)[:-5]}_{frame_idx}.txt",
                )

                # 确保目录存在
                os.makedirs(os.path.dirname(anno_filename), exist_ok=True)

                # 创建并写入标注文件
                with open(anno_filename, "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

                        # 计算 YOLO 格式的边界框参数
                        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

    cap.release()

4.模型训练与预测

# # 使用 wget 命令从指定的 URL 下载 YOLOv8 模型文件,并将其保存为 yolov8n.pt
# !wget http://mirror.coggle.club/yolo/yolov8n-v8.2.0.pt -O yolov8n.pt
import os
import urllib.request

# 创建目录 C:\Users\<你的用户名>\.config\Ultralytics\,如果该目录不存在
config_dir = os.path.join(os.path.expanduser('~'), '.config', 'Ultralytics')
os.makedirs(config_dir, exist_ok=True)

# 从指定的 URL 下载 Arial.ttf 字体文件,并将其保存到 C:\Users\<你的用户名>\.config\Ultralytics\ 目录下
url = 'http://mirror.coggle.club/yolo/Arial.ttf'
output_path = os.path.join(config_dir, 'Arial.ttf')
urllib.request.urlretrieve(url, output_path)

print(f"文件已下载到: {output_path}")
import os

# 禁用 wandb
os.environ["WANDB_MODE"] = "disabled"

# 设置 GPU 设备
os.environ["CUDA_VISIBLE_DEVICES"] = "0"

import warnings

# 忽略所有警告
warnings.filterwarnings("ignore")

from ultralytics import YOLO

# 加载 YOLOv8 模型
model = YOLO("yolov8n.pt")

# 使用指定的数据集配置文件训练模型,训练2个周期,图像尺寸为1080,批量大小为16
results = model.train(
    data="yolo-dataset/yolo.yaml", epochs=20, imgsz=1080, batch=16, project=None
)
# 定义类别标签列表
category_labels = ["非机动车违停", "机动车违停", "垃圾桶满溢", "违法经营"]

# 如果 'result/' 目录不存在,则创建该目录
if not os.path.exists("result/"):
    os.mkdir("result")
from ultralytics import YOLO
import glob
import json

# 加载训练好的 YOLOv8 模型
model = YOLO("runs/detect/train/weights/best.pt")

# 遍历测试集目录中的所有 mp4 视频文件
for path in glob.glob("测试集/*.mp4"):
    submit_json = []  # 初始化提交结果的列表

    # 使用模型对视频进行推理,设置置信度阈值为 0.05,图像尺寸为 1080
    results = model(path, conf=0.05, imgsz=1080, verbose=False)

    # 遍历每一帧的检测结果
    for idx, result in enumerate(results):
        boxes = result.boxes  # 获取边界框输出
        masks = result.masks  # 获取分割掩码输出
        keypoints = result.keypoints  # 获取关键点输出
        probs = result.probs  # 获取分类概率输出
        obb = result.obb  # 获取定向边界框输出

        # 如果没有检测到任何目标,跳过该帧
        if len(boxes.cls) == 0:
            continue

        # 获取边界框的坐标、类别和置信度,并转换为 numpy 数组
        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),  # 置信度
                }
            )

    # 将提交结果保存为 JSON 文件
    with open(
        "./result/" + path.split("/")[-1][:-4] + ".json", "w", encoding="utf-8"
    ) as up:
        json.dump(submit_json, up, indent=4, ensure_ascii=False)

5.结果提交

# 删除 result 目录中的 .ipynb_checkpoints 子目录及其内容
!rm -rf result/.ipynb_checkpoints/

# 删除 result.zip 文件
!rm result.zip

# 将 result 目录及其内容压缩成 result.zip 文件
!zip -r result.zip result/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值