分割线

一、寻找数据集

在此之前我已经将yolo5的环境装配好了。下面就开始训练模型
训练模型第一步应该干什么呢?

找数据!!!
数据无价,最后在我的几经筛选之下,找到了这个数据

https://www.kaggle.com/datasets/tuyenldvn/falldataset-imvia
有关检测跌倒的数据,10G开下。

下载下来发现数据集全是视频和txt文档,文档经过我的查阅是记录跌倒的时间帧和方位。


以video(1).avi为例,

前两行4880 通常表示该视频跌倒事件的开始帧与结束帧(可根据官方文档或 README 确认)。

后续每行frame_index, class_id, x_min, y_min, x_max, y_max

  • 例如 5,1,292,152,311,240 表示第5帧类别为1、**左上角(292,152)右下角(311,240)**。

二、处理数据集

需要将每个视频拆分成视频帧,我们就以每秒25帧来切割,正好我的电脑上就有ffmepg,我就用了命令

ffmpeg -i "video (1).avi" -r 25 frames_%04d.jpg

但是视频的数量也太多了,所以我又写了一个批处理的来处理文件里的所有同类视频

@echo off
setlocal enabledelayedexpansion

REM =============================
REM 配置路径
REM =============================
REM 这里是视频所在目录
set "VIDEOS_DIR=E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Videos"

REM 这里是想要存放提取帧的根目录
set "FRAMES_DIR=E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Frames"

REM =============================
REM 批量处理 .avi 文件
REM =============================
for %%f in ("%VIDEOS_DIR%\*.avi") do (
REM 1) %%f 表示每个视频的完整路径(含文件名)
REM 2) %%~nf 表示文件名不带扩展名,例如 "video (1)"
set "BASENAME=%%~nf"

REM 构造输出文件夹: <FRAMES_DIR>/<视频文件名>_frames
set "OUTPUT_DIR=%FRAMES_DIR%\!BASENAME!_frames"

REM 如果不存在,则创建
if not exist "!OUTPUT_DIR!" (
mkdir "!OUTPUT_DIR!"
echo Created directory: !OUTPUT_DIR!
)

REM 使用 ffmpeg 提取帧
REM -r 25 表示每秒提取 25 帧,可根据需求调整
ffmpeg -i "%%f" -r 25 "!OUTPUT_DIR!\frame_%%04d.jpg"

echo Processed %%f -> !OUTPUT_DIR!
)

pause

下一步开始使用yolo5训练

三、疑问

发现数据集中的数据存在一定的问题。查阅readme文档也没有定义。readme文档中说有关数据集的文档中存储的数据依次为:

帧,框高,框宽,框的中心坐标。

image-20250319131251374

以一为例,数据中一行出现了6个数据,我理解为

帧编号,未知,框高,框宽,中心坐标

未知:我觉得是状态显示

  • 1——normal(正常)
  • 8——fall(摔倒)
  • 7——(倒地)

但是存在问题我在下面好几个文档中发现了1、2、3、4

以下是我猜测

  • 2——应当为跌倒的过程
  • 4——应当为侧躺在地上
  • 5——应当为平趴在地上

这些只是假设。


先不管状态对应的什么意思,我们先把数据处理成yolo格式的,进行归一化处理。

image-20250319172028751

划分数据集,下一步进行yolo训练

train: E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Dataset\train\images
val: E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Dataset\val\images

# 这里根据你的类别数量定义类别名称,假设使用标签中原始的数字:
# 如果你有6个状态,则可能需要如下定义(请根据实际映射修改名称):
names:
0: "state_1"
1: "state_7"
2: "state_8"
3: "state_2"
4: "state_3"
5: "state_4"
6: "state_5"

撰写yaml的文件,方便yolo推理

下一步开始推理

python train.py --img 320 --batch 16 --epochs 50 --data fall.yaml --weights yolov5s.pt

下面详细解读一下命令:

python train.py --img 320 --batch 16 --epochs 50 --data fall.yaml --weights yolov5s.pt

1. python train.py

  • 这是运行 YOLOv5 训练脚本的入口文件。train.py 是 YOLOv5 提供的训练脚本,负责加载数据、构建模型、进行训练并保存权重。

2. 参数 --img 320

  • 指定输入图像的尺寸为 320×320(YOLOv5 会将原始图像按比例缩放到该尺寸)。
  • 如果原始图像分辨率为 320×240,也可以用 320 作为短边尺寸。一般建议选择与训练数据相似的尺寸以兼顾计算效率和细节保留。

3. 参数 --batch 16

  • 指定每个训练批次(batch)的图片数为 16。
  • 批次大小与 GPU 显存有关:RTX 3070 通常能够支持 16 的 batch size;如果显存不足可以减小该值。

4. 参数 --epochs 50

  • 表示整个训练过程中遍历整个数据集 50 轮(epoch)。
  • 训练轮数决定了模型训练的充分程度,初次实验可以设置 50 轮,之后根据验证集表现和收敛情况再做调整。

5. 参数 --data fall.yaml

  • 指定数据集的配置文件为 fall.yaml

  • 该 YAML 文件通常包含以下信息:

    • trainval 两个路径,分别指向训练集和验证集的图片目录(例如:Dataset/images/trainDataset/images/val)。

    • names
      

      字段:定义所有类别的名称,例如:

      ```yaml
      names:
      0: "state_1"
      1: "state_7"
      2: "state_8"
      3: "state_2"
      4: "state_3"
      5: "state_4"
      6: "state_5"
    • 确保 YAML 文件的格式和路径与实际数据集结构一致。

image-20250320205405309

image-20250320205524463

image-20250320210133476

很遗憾,报错了,状态映射不对。

train: E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Dataset\train\images
val: E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Dataset\val\images

# 这里根据你的类别数量定义类别名称,假设使用标签中原始的数字:
# 如果你有6个状态,则可能需要如下定义(请根据实际映射修改名称):
names:
1: "state_1"
2: "state_2"
3: "state_3"
4: "state_4"
5: "state_5"
6: "state_6"
7: "state_7"
8: "state 8"

四、处理错误

1.在训练时,YOLOv5 检查到部分图片对应的标签文件中的坐标数据存在问题,具体问题是“non-normalized or out of bounds coordinates”(未归一化或超出边界的坐标)。


1. 什么是“non-normalized or out of bounds coordinates”

  • 归一化坐标要求
    YOLO 格式要求标签中的坐标值必须归一化到 [0, 1] 范围内。也就是说,x_center、y_center、宽度和高度的值都应该是图像宽度和高度的比例,而不是绝对像素值。

  • 超出边界
    如果标签中的归一化坐标超过了 1 或小于 0(比如 1.2167 或 1.0042),则表示目标框的坐标计算有误或者没有进行正确归一化。

  • 确保在转换标签时,对所有边界框的中心坐标和宽高都进行了归一化处理。例如,如果图像尺寸为 320×240,那么中心坐标应除以 320 和 240,宽度和高度也应除以对应尺寸。

  • 检查转换脚本,确保计算公式正确:

    x_center_norm = center_x / IMG_WIDTH
    y_center_norm = center_y / IMG_HEIGHT
    width_norm = box_width / IMG_WIDTH
    height_norm = box_height / IMG_HEIGHT

标签文件数值看起来并不是“框高、框宽、中心坐标”,而更像是“框左上角和右下角的坐标”。例如,对于行

5,1,292,152,311,240

如果解释为:

  • frame_number = 5
  • state = 1
  • x_min = 292, y_min = 152
  • x_max = 311, y_max = 240

那么:

  • 计算框宽 = 311 – 292 = 19
  • 计算框高 = 240 – 152 = 88
  • 中心坐标 = ((292+311)/2, (152+240)/2) = (301.5, 196)

归一化后:

  • x_center_norm = 301.5 / 320 ≈ 0.942
  • y_center_norm = 196 / 240 ≈ 0.8167
  • width_norm = 19 / 320 ≈ 0.0594
  • height_norm = 88 / 240 ≈ 0.3667

这就符合要求,不会超过 1。而目前的转换函数直接把第三、四列当作 box_height 和 box_width、第五、第六列当作中心坐标计算归一化,结果就会出现 292/240 ≈ 1.2167 这样的数值,从而导致“non-normalized or out of bounds coordinates”的错误。

因此,需要修改转换函数,使其先根据 x_min、y_min、x_max、y_max 计算出中心坐标和宽高,再归一化。

下面是修改后的完整代码,重点在于更新转换函数以及处理标签文件时的字段解释:

import os
import glob

# --------------------------
# 1. 配置参数:请根据实际情况修改
# --------------------------
annotations_dir = r"E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Annotation_files"
frames_root_dir = r"E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Frames"
output_labels_root = r"E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Labels"

# 图像尺寸(Le2i 数据集一般为 320x240)
IMG_WIDTH, IMG_HEIGHT = 320, 240

# --------------------------
# 2. 定义边界框转换函数(修改后)
# --------------------------
def convert_bbox_from_corners(x_min, y_min, x_max, y_max, img_w, img_h):
"""
根据左上角 (x_min, y_min) 和右下角 (x_max, y_max) 计算 YOLO 格式的归一化坐标:
输出:x_center_norm, y_center_norm, width_norm, height_norm
"""
box_width = x_max - x_min
box_height = y_max - y_min
x_center = (x_min + x_max) / 2.0
y_center = (y_min + y_max) / 2.0

x_center_norm = x_center / img_w
y_center_norm = y_center / img_h
width_norm = box_width / img_w
height_norm = box_height / img_h
return x_center_norm, y_center_norm, width_norm, height_norm

# --------------------------
# 3. 定义处理单个标签文件的函数
# --------------------------
def process_annotation_file(annotation_path):
"""
处理单个标签文件,将其转换为 YOLO 格式。
标签文件格式:假设每行记录为
frame_number, state, x_min, y_min, x_max, y_max
如果文件前两行只包含单个数字,则认为是跌倒开始和结束帧,否则,所有行均为记录。
"""
base_name = os.path.splitext(os.path.basename(annotation_path))[0] # 例如 "video (1)"

# 构造对应的视频帧文件夹路径,假设命名规则为 Frames\{base_name}_frames
video_frames_dir = os.path.join(frames_root_dir, f"{base_name}_frames")
# 构造输出标签文件夹路径
output_label_dir = os.path.join(output_labels_root, base_name)
os.makedirs(output_label_dir, exist_ok=True)

with open(annotation_path, "r") as f:
lines = f.readlines()

if not lines:
print(f"文件 {annotation_path} 为空,跳过")
return

# 判断文件格式:如果第一行只有一个字段,则认为前两行是跌倒开始和结束帧
first_line_parts = lines[0].strip().split(',')
if len(first_line_parts) == 1:
# 读取跌倒开始和结束帧(暂不使用)
fall_start = int(lines[0].strip())
fall_end = int(lines[1].strip())
annotation_lines = lines[2:]
else:
annotation_lines = lines

# 逐行处理记录
for line in annotation_lines:
line = line.strip()
if not line:
continue
parts = line.split(',')
if len(parts) != 6:
print(f"格式错误,跳过行:{line}")
continue
try:
frame_number = int(parts[0])
state = int(parts[1])
x_min = float(parts[2])
y_min = float(parts[3])
x_max = float(parts[4])
y_max = float(parts[5])
except Exception as e:
print(f"解析错误,跳过行 {line}: {e}")
continue

# 如果无有效框,则跳过
if x_min == 0 and y_min == 0 and x_max == 0 and y_max == 0:
continue

# 直接使用原始状态作为类别
yolo_class = state

# 使用新的转换函数,将左上角和右下角转换为YOLO格式
x_c_norm, y_c_norm, w_norm, h_norm = convert_bbox_from_corners(x_min, y_min, x_max, y_max, IMG_WIDTH, IMG_HEIGHT)

# 构造对应帧图像文件名,假设图像命名为 frame_0001.jpg, frame_0002.jpg, ...
frame_filename = f"frame_{frame_number:04d}.jpg"
frame_path = os.path.join(video_frames_dir, frame_filename)
if not os.path.exists(frame_path):
print(f"警告:帧图像 {frame_path} 不存在,跳过帧 {frame_number}")
continue

label_filename = f"frame_{frame_number:04d}.txt"
label_filepath = os.path.join(output_label_dir, label_filename)

with open(label_filepath, "w") as fw:
fw.write(f"{yolo_class} {x_c_norm:.6f} {y_c_norm:.6f} {w_norm:.6f} {h_norm:.6f}\n")
print(f"{base_name}: 处理帧 {frame_number} 成功,生成 {label_filepath}")

# --------------------------
# 4. 批量处理所有标签文件
# --------------------------
all_files = glob.glob(os.path.join(annotations_dir, "*.txt"))
annotation_files = [f for f in all_files if os.path.basename(f).lower().startswith("video")]
print("找到的标签文件:")
for f in annotation_files:
print(f)

if not annotation_files:
print("未找到标签文件,请检查路径和文件名格式。")
else:
for ann_file in annotation_files:
print(f"开始处理 {ann_file} ...")
process_annotation_file(ann_file)

print("所有标签文件处理完成。")

详细解读

  1. 路径配置部分

    • annotations_dir 指标签文件所在目录。
    • frames_root_dir 指向视频帧所在的根目录(每个视频的帧文件夹命名如 “video (N)_frames”)。
    • output_labels_root 指向输出的转换后标签存放目录(每个视频输出到 “video (N)” 文件夹中)。
  2. 图像尺寸

    • 根据 Le2i 数据集设置为 320×240。
  3. 转换函数 convert_bbox_from_corners

    • 新函数接受 x_min, y_min, x_max, y_max 四个值,并计算:
      • 框宽 = x_max - x_min
      • 框高 = y_max - y_min
      • 中心坐标 = ((x_min + x_max) / 2, (y_min + y_max) / 2)
    • 然后归一化各值。
  4. 处理单个标签文件函数

    • 根据文件名确定视频文件夹名称(如 “video (1)”),然后构造视频帧文件夹路径(Frames\video (1)_frames)和输出标签文件夹路径(Labels\video (1))。
    • 判断标签文件格式:如果第一行只包含一个数字,则跳过前两行,否则所有行都是记录。
    • 逐行解析后,根据 x_min, y_min, x_max, y_max 计算 YOLO 格式的归一化坐标,写入对应帧图像的标签文件。
  5. 批量处理部分

    • 使用 glob 递归查找所有以 “video” 开头的标签文件,然后逐个调用 process_annotation_file 处理。
  6. 将以上代码保存为 batch_convert.py

  7. 在命令行中运行:

    python batch_convert.py
  8. 脚本会输出处理情况,并在输出目录下生成转换后的 YOLO 格式标签文件。

2.Label class 8 exceeds nc=8

直接使用原始标签(即 1 到 8),那么你需要调整 YOLO 数据配置文件,使得允许的类别范围覆盖这些数字。YOLOv5 要求标签数字必须在 0 到 nc-1 内。如果标签中出现 1 到 8,则需要将类别数量设置为 9,并在 names 中提供 9 个条目。

一种常见的做法是:

  • 把索引 0 作为占位符(因为你的数据中没有标签 0),然后索引 1 到 8 分别对应你的 8 个状态。

例如,可以修改 fall.yaml 文件如下:

train: E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Dataset\train\images
val: E:\IDMdownload\archive\Coffee_room_01\Coffee_room_01\Dataset\val\images

names:
0: "unused" # 占位符,不使用
1: "state_1"
2: "state_7"
3: "state_8"
4: "state_2"
5: "state_3"
6: "state_4"
7: "state_5"
8: "state_6"

这样,训练时有效标签就是 1 到 8(共 8 个类别),并且标签数字均在 0 到 8 的范围内(虽然索引 0 不会出现在数据中)。这种方法避免了在预处理阶段做减一操作,同时满足 YOLOv5 的要求。

请注意:

  • 需要确保数据中的标签都是在 1 到 8 之间。
  • 如果以后确认了每个状态的具体意义,也可以对 names 中的名称进行相应修改。

这样修改后,训练时就不会报错“Label class 8 exceeds nc=8”了,因为允许的类别范围是 0 到 8(即 9 个类别)。

五、训练情况

image-20250321093843078

开始训练。

image-20250321094106666

这段输出是 YOLOv5 在训练或验证阶段输出的性能指标和进度信息,我们来逐项解释:


1. 上半部分的输出

with torch.cuda.amp.autocast(amp):
0/49 0.96G 0.0494 0.01337 0.03241 12 320: 100%|██████████| 894/894 [01:52<00:00,
  • with torch.cuda.amp.autocast(amp):
    这表示训练过程中启用了混合精度训练(Automatic Mixed Precision, AMP),利用 GPU 的半精度运算以加速训练并减少显存占用。
  • 后面跟着的进度条和数值(例如 0/490.96G 等)通常表示当前 batch、当前进度、GPU 占用、损失值等信息。这部分内容根据具体版本和配置略有不同。

2. 后半部分的性能指标

Class     Images  Instances          P          R      mAP50   mAP50-95: 100%|██████████| 220/220 [00:
all 7012 2883 0.482 0.411 0.142 0.0958
  • Class:这里显示的是统计类别,“all” 表示所有类别的综合指标。
  • Images:7012 表示总共参与评估的图片数量。
  • Instances:2883 表示所有图片中目标实例的总数(例如,所有检测到的物体总数)。
  • **P (Precision)**:0.482 表示精度为 48.2%。精度(Precision)反映了检测器预测的目标中有多少比例是真正的正确目标。
  • **R (Recall)**:0.411 表示召回率为 41.1%。召回率(Recall)反映了所有真实目标中被检测到的比例。
  • mAP50:0.142 表示在 IoU 阈值为 0.50 时,平均精度均值(mean Average Precision, mAP)为 14.2%。这意味着当预测框与真实框的 IoU 大于等于 0.50 时,模型的整体检测精度大约是 14.2%。
  • mAP50-95:0.0958 表示在多个 IoU 阈值(从 0.50 到 0.95,通常以 0.05 为步长取平均)下计算的 mAP 值为 9.58%。这是更严格的指标,反映了模型在更高重叠要求下的性能。

总结

  • 混合精度(AMP):利用 torch.cuda.amp.autocast 实现混合精度训练,提高计算速度并降低显存占用。

  • 进度信息:训练或验证过程中显示了 GPU 占用、当前进度等。

  • 性能指标

    • 精度(P)约 48.2%,召回率(R)约 41.1%,
    • mAP50(IoU≥0.50)约 14.2%,
    • mAP50-95(多个 IoU 阈值的平均)约 9.58%。

这是第一轮的训练结果。还挺慢,需要等到五十轮结束看看指标。

测试结果

python detect.py --weights E:\Gitcage\yolov5\runs\train\exp5\weights\best.pt --source E:\IDMdownload\archive\Office\Office\video (1).avi --img 320 --conf 0.25

不是很理想,需要改进。

image-20250321155908873

  • 现尝试使用yolov5m模型进行训练,如果效果还不理想将采用yolov8的模型进行推理测试

同时为了更美观的展示训练结果,

修改相关代码,将旧的调用方式替换为新版推荐的方式。例如,在 train.py(或 common.py 中)找到类似下面的代码:

with torch.cuda.amp.autocast(amp):

将其修改为:

with torch.amp.autocast('cuda', enabled=amp):

这样就能消除 FutureWarning。


等我整理完结果数据再展示吧。

现阶段我觉得训练的结果只有40%上下的精度,我觉得不够,我只用了这个数据集的一个文件夹进行训练。现在我尝试将四个文件夹的数据合并然后进行训练看看效果。

由原来的一万多张图片一下扩充到三万多张,期待。

新的训练代码记录一下。

python train.py --img 320 --batch 16 --epochs 50 --data bigfall.yaml --weights yolov5s.pt

image-20250322233810973

完了,出现数据集错误了,又白干了,又要重新查看数据集哪里错了

啊这。。。。
数据集出错也能训练的吗?

image-20250322233947086

我先在这里记一下,100、106、121、72、73几个视频都存在数据处理问题。

简单筛查了一下发现是数据集没有配对上,等我把数据对其看看具体是哪里有问题。


yolov5训练结果

image-20250323083553936

结果非常好由原来的40直接上升到90+。马上再用yolov8测试看看效果如何。
yolov8的训练结果比v5的训练结果要好一点但是不是很明显。


数据没贴上,等会训练的时候贴上。

yolo8训练

首先是训练代码

yolo detect train model=yolov8s.yaml data=bigfall.yaml epochs=50 imgsz=320 batch=16 name=bigfall_s_v2 pretrained=True device=0

💡 参数说明(因为你肯定忘了):

  • model=yolov8s.yaml:使用哪个模型结构,可以换成 yolov8s.yamlyolov8m.yaml 等等。

  • data=your_dataset.yaml:数据配置文件,别给错路径,别写错名字。

  • epochs=100:训练 100 轮,够你煎出炼丹炉底。

  • imgsz=640:输入图像大小。

  • batch=16:每批处理 16 张图,没显卡别乱写。

  • name=bigfall_s_v1:结果保存目录 runs/detect/bigfall_s_v1/

  • pretrained=True:是否用 COCO 预训练模型打底,建议 True。

  • device=0:用第一个 GPU。如果你根本没 GPU,那你只能 device=cpu,然后关上电脑哭去。


保存一个cmd命令,怕等会忘记,由于自身笔记本的高度太低,使用手边的rv1106的板子加上sc3336的摄像头直接架高模拟摄像头查看推理效果如何。

yolo task=detect mode=predict model=runs/detect/train/weights/best.pt data=bigfall.yaml source="rtsp://172.32.0.93/live/0" show=true conf=0.25

yolo task=detect mode=predict model=runs/detect/bigfall_s_v1/weights/best.pt   data=bigfall.yaml source="rtsp://172.32.0.93/live/0" show=true conf=0.25