训练模型——数据集入门:

  1. 有没有现成的数据集?
  2. 如何获取图片?
  3. 如何获取标签?
  4. 怎么划分数据集?
  5. 怎么训练自己的模型?
  6. 训练好模型后的工作?

一、数据集来源

(1)roboflow

网址:https://universe.roboflow.com/

        如想要预测车,直接用对应英文car搜索:

        Project Type可以选择任务类型,Model Type可以选择模型类型:

        如目标检测(Object Detection)和YOLOv11:

        点击一个进入数据集主页,可以查看信息(有四种车辆类型):

        点击左边工具栏中的Images可以预览数据集图片和标注:

        点击左边工具栏中的Dataset可以进入数据集下载页面:

        点击右上方的YOLOv11就是下载YOLOv11格式的数据集。需要注册登录后才能下载。

        选择Download dataset点击Continue

        选择第一个 Download zip to computer 点击Continue下载。

        将下载好的压缩包统一放在源码的datasets文件夹下并解压。

        打开Pycharm查看数据集信息。data.yaml是数据集自带的配置文件:

        可以复制一份配置文件,把要用的配置文件放在ultralytics-8.3.163\ultralytics\cfg\datasets下,并修配置文件car-lmfdq.yaml:

        ncroboflow部分都可以删除,增加path

path: D:\deeplearning\ultralytics-8.3.163\datasets\car.v9i.yolov11
train: train/images
val: valid/images
test: test/images

names: ['sedan', 'suv', 'truck', 'van']

        修改训练脚本mytrain.pydata替换为car-lmfdq.yaml

from ultralytics import  YOLO
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'

if __name__ == '__main__':
    model = YOLO(r"yolo11n.pt")

    model.train(
        data=r"car-lmfdq.yaml",
        epochs=30,
        imgsz=640,
        batch=-1,
        workers=2,
        cache="ram",
    )

这里把batch设置为-1,因为新数据集和原来的图片数量不同,批次要重新设置。

        可以看到正常开始训练:

        资源占用也比较合适:

(2)kaggle

网址:https://www.kaggle.com/datasets

相比robotflow,kaggle上的数据集内容更杂一些,有的需要收费。

        同样,可以搜索car object detection,并选择一个YOLO格式的:

        然后点击右上角的Download进行下载,后续操作和之前一样,这里就不再重复。

(3)其他来源

        其他开源数据集、可以使用智能体帮助查找需要的数据集,如Deepseek,Chatgpt等等。也可以直接自己建立数据集。

二、数据集图片来源

图片来源

  • 网上搜索关键词下载图片,或者在开源数据集里寻找需要图片。
  • 如果有实物,可以进行实物拍摄采集图片。具体为先拍摄几段视频,然后从视频中提取图片。

(1)拍摄并保存视频

        希望模型预测什么样的目标就拍摄什么样的视频,尽可能兼顾各种角度、光照、背景等情况。后续可以慢慢补充。

        新建文件夹make_dataset/cs/videos,将拍摄好的视频统一放在电脑一个文件夹下。(记得重命名视频,不要带中文、括号、空格)

(2)从视频提取图片

        在Pycharm中打开make_dataset文件夹

        右下角选择解释器为yolo

        在cs文件夹下新建一个文件夹,命名为images

        在cs文件夹下新建一个Python文件,命名为extract

        可以使用AI帮你生成代码,提问词放下面了:

我需要一些Python代码
有一些.mp4视频文件存放在这里:
D:\deeplearning\make_dataset\cs\videos
帮我每隔固定几帧提取一张图片出来
一秒提取几帧作为一个可以调整的参数
图片统一命名为aaa_xxxxx.jpg格式
aaa代表视频的名字,xxxxx表示图片的序号
比如视频001.mp4生成的图片就是001_00001.jpg, 001_00002.jpg...
处理每个视频的上海给我进度条
所以图片统一保存到这个文件夹:
D:\deeplearning\make_dataset\cs\images
多写点注释,我是Python小白。

        可以得到:

import os
import cv2
from tqdm import tqdm


def extract_frames_from_videos(video_folder, output_folder, frames_per_second):
    """
    从视频文件夹中提取帧图片

    Args:
        video_folder: 视频文件夹路径
        output_folder: 输出图片文件夹路径
        frames_per_second: 每秒提取的帧数
    """

    # 创建输出文件夹(如果不存在)
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
        print(f"创建输出文件夹: {output_folder}")

    # 获取所有.mp4文件
    video_files = [f for f in os.listdir(video_folder) if f.lower().endswith('.mp4')]

    if not video_files:
        print("在指定文件夹中没有找到.mp4文件")
        return

    print(f"找到 {len(video_files)} 个视频文件")

    # 处理每个视频文件
    for video_file in video_files:
        video_path = os.path.join(video_folder, video_file)
        video_name = os.path.splitext(video_file)[0]  # 获取视频名称(不含扩展名)

        print(f"\n开始处理视频: {video_file}")

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

        if not cap.isOpened():
            print(f"无法打开视频文件: {video_file}")
            continue

        # 获取视频信息
        fps = cap.get(cv2.CAP_PROP_FPS)  # 视频的原始帧率
        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))  # 总帧数
        duration = total_frames / fps  # 视频时长(秒)

        print(f"视频信息: 帧率={fps:.2f}fps, 总帧数={total_frames}, 时长={duration:.2f}秒")

        # 计算帧间隔(每隔多少帧提取一张)
        frame_interval = int(fps / frames_per_second)
        if frame_interval < 1:
            frame_interval = 1

        print(f"提取设置: 每秒{frames_per_second}帧, 帧间隔={frame_interval}")

        # 初始化计数器
        frame_count = 0
        saved_count = 0

        # 创建进度条
        pbar = tqdm(total=total_frames, desc=f"处理 {video_name}")

        while True:
            ret, frame = cap.read()

            if not ret:
                break

            # 检查是否达到提取间隔
            if frame_count % frame_interval == 0:
                # 生成文件名
                image_filename = f"{video_name}_{saved_count + 1:05d}.jpg"
                image_path = os.path.join(output_folder, image_filename)

                # 保存图片
                cv2.imwrite(image_path, frame)
                saved_count += 1

            frame_count += 1
            pbar.update(1)

        # 关闭资源
        pbar.close()
        cap.release()

        print(f"完成! 从视频 {video_file} 中提取了 {saved_count} 张图片")

    print(f"\n所有视频处理完成! 图片保存在: {output_folder}")


def main():
    # 设置路径参数
    video_folder = r"D:\deeplearning\make_dataset\cs\videos"
    output_folder = r"D:\deeplearning\make_dataset\cs\images"

    # 设置提取参数(每秒提取几帧)
    frames_per_second = 2  # 可以调整这个参数

    print("开始视频帧提取...")
    print(f"视频文件夹: {video_folder}")
    print(f"输出文件夹: {output_folder}")
    print(f"提取频率: {frames_per_second} 帧/秒")

    # 执行提取
    extract_frames_from_videos(video_folder, output_folder, frames_per_second)


if __name__ == "__main__":
    main()

        点击运行,没有问题:

D:\anaconda3\envs\yolo\python.exe D:\deeplearning\make_dataset\cs\extract.py 
开始视频帧提取...
视频文件夹: D:\deeplearning\make_dataset\cs\videos
输出文件夹: D:\deeplearning\make_dataset\cs\images
提取频率: 2 帧/秒
找到 6 个视频文件

开始处理视频: 001.mp4
视频信息: 帧率=30.00fps, 总帧数=328, 时长=10.93秒
提取设置: 每秒2帧, 帧间隔=15
处理 001: 100%|██████████| 328/328 [00:00<00:00, 666.05it/s]
处理 002:   0%|          | 0/474 [00:00<?, ?it/s]完成! 从视频 001.mp4 中提取了 22 张图片

开始处理视频: 002.mp4
视频信息: 帧率=30.00fps, 总帧数=474, 时长=15.80秒
提取设置: 每秒2帧, 帧间隔=15
处理 002: 100%|██████████| 474/474 [00:00<00:00, 660.30it/s]
处理 003:   0%|          | 0/183 [00:00<?, ?it/s]完成! 从视频 002.mp4 中提取了 32 张图片

开始处理视频: 003.mp4
视频信息: 帧率=29.00fps, 总帧数=183, 时长=6.31秒
提取设置: 每秒2帧, 帧间隔=14
处理 003: 100%|██████████| 183/183 [00:00<00:00, 651.30it/s]
处理 004:   0%|          | 0/337 [00:00<?, ?it/s]完成! 从视频 003.mp4 中提取了 14 张图片

开始处理视频: 004.mp4
视频信息: 帧率=30.00fps, 总帧数=337, 时长=11.23秒
提取设置: 每秒2帧, 帧间隔=15
处理 004: 100%|██████████| 337/337 [00:00<00:00, 625.65it/s]
完成! 从视频 004.mp4 中提取了 23 张图片

开始处理视频: 005.mp4
视频信息: 帧率=30.00fps, 总帧数=324, 时长=10.80秒
提取设置: 每秒2帧, 帧间隔=15
处理 005: 100%|██████████| 324/324 [00:00<00:00, 684.72it/s]
完成! 从视频 005.mp4 中提取了 22 张图片

开始处理视频: 006.mp4
视频信息: 帧率=30.00fps, 总帧数=456, 时长=15.20秒
提取设置: 每秒2帧, 帧间隔=15
处理 006: 100%|██████████| 456/456 [00:00<00:00, 651.54it/s]
完成! 从视频 006.mp4 中提取了 31 张图片

所有视频处理完成! 图片保存在: D:\deeplearning\make_dataset\cs\images

进程已结束,退出代码为 0

        查看左边文件栏,可以看到处理好的文件:

        打开文件夹,查看图片,发现每张图片差异不大,可以调整一下参数。我调整为1:

    frames_per_second = 1  # 可以调整这个参数

三、数据集图片标注

(1)新建一个文件夹,命名为labels

        在labels文件夹下新建一个classes.txt文件。根据需要一行放一个类型,顺序不重要:

我这只要预测一种类型,所以只有一行。

(2)使用labelimg可视化标签

        打开Anaconda Prompt,激活labelimg环境:

        输入:

labelimg D:\deeplearning\make_dataset\cs\images D:\deeplearning\make_dataset\cs\labels\classes.txt D:\deeplearning\make_dataset\cs\labels

即:labelimg <你的图片文件夹路径> <你的classes.txt路径> <你的标签文件夹路径>

        确保左边显示的是YOLO,这样保存的标签才是YOLO:

(3)开始标注图片

        按键盘W,鼠标左边开始拉框,然后选择预测类型,点击OK,完成一个标注:

        同样的,完成一张图片的所有标注:

        按AD可以快速切换上一张和下一张图片。第一次切换选择保存Yes:

        点击上方工具栏view->勾选Auto Save mode

        完成所有图片标注!!!

        当然是不可能的,所以下面引入半自动标注。

(4)半自动标注

半自动标注:

                            部分数据集

                   【手动标注部分图片】

                                    || 训练

                                    V

所有图片  -预测-> 【粗模型】-预测-> 【所有图片的伪标注】---------> 得到所有图片的标注

        在文件栏新建xz_dataset文件夹,并在下面新建几个文件夹,结构如下:

        在cs文件夹下新建一个Python文件,命名为make_xz.py

        从cs/images提取一些图片放在cs/xz_dataset/images/traincs/xz_dataset/images/val里。训练集多一些,验证集少一些。这个工作同样可以用AI编程完成。提示词如下:

给我一些Python代码
我有几百张图片放在这个文件夹里:
D:\deeplearning\make_dataset\cs\images
帮我随机提取80%的图片出来
其中的70%放到这个文件夹里:
D:\deeplearning\make_dataset\cs\xz_dataset\images\train
剩下的30%放在这个文件夹下:
D:\deeplearning\make_dataset\cs\xz_dataset\images\val
注意要把文件移动出来

根据图片数量调整随机提取的比例。

        获得代码并复制到make_xz.py

import os
import random
import shutil

# 设置路径
source_dir = r"D:\deeplearning\make_dataset\cs\images"
train_dir = r"D:\deeplearning\make_dataset\cs\xz_dataset\images\train"
val_dir = r"D:\deeplearning\make_dataset\cs\xz_dataset\images\val"

# 创建目标文件夹(如果不存在)
os.makedirs(train_dir, exist_ok=True)
os.makedirs(val_dir, exist_ok=True)

# 获取所有图片文件
image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp']
image_files = [f for f in os.listdir(source_dir) 
               if os.path.isfile(os.path.join(source_dir, f)) and 
               os.path.splitext(f)[1].lower() in image_extensions]

print(f"找到 {len(image_files)} 张图片")

# 随机打乱图片列表
random.shuffle(image_files)

# 计算80%的图片数量
total_extract = int(len(image_files) * 0.8)
extracted_files = image_files[:total_extract]

print(f"提取 {len(extracted_files)} 张图片")

# 计算训练集和验证集的数量
train_count = int(len(extracted_files) * 0.7)
val_count = len(extracted_files) - train_count

train_files = extracted_files[:train_count]
val_files = extracted_files[train_count:]

print(f"训练集: {len(train_files)} 张图片")
print(f"验证集: {len(val_files)} 张图片")

# 移动文件到训练集文件夹
for filename in train_files:
    src_path = os.path.join(source_dir, filename)
    dst_path = os.path.join(train_dir, filename)
    shutil.move(src_path, dst_path)

# 移动文件到验证集文件夹
for filename in val_files:
    src_path = os.path.join(source_dir, filename)
    dst_path = os.path.join(val_dir, filename)
    shutil.move(src_path, dst_path)

print("文件移动完成!")
print(f"训练集文件已移动到: {train_dir}")
print(f"验证集文件已移动到: {val_dir}")

        点击运行:

        打开文件夹,发现成功完成任务。

        然后再分别标注训练集和验证集图片。先复制classes.txtlabels的两个文件夹下,然后使用labelimg进行标注。

        在Anaconda Prompt中激活labelimg环境后,输入:

labelimg D:\deeplearning\make_dataset\cs\xz_dataset\images\train D:\deeplearning\make_dataset\cs\xz_dataset\labels\train\classes.txt D:\deeplearning\make_dataset\cs\xz_dataset\labels\train

        标注过程和之前一样。完成训练集所有图片标注。

        标注好训练集后,进行验证集的图片标注。

labelimg D:\deeplearning\make_dataset\cs\xz_dataset\images\val D:\deeplearning\make_dataset\cs\xz_dataset\labels\val\classes.txt D:\deeplearning\make_dataset\cs\xz_dataset\labels\val

        至此,图片标注完成。

(5)添加新建的数据集

        在Pycharm中打开ultralytics-8.3.163项目,将前面准备好的xz_dataset数据集文件夹复制到ultralytics-8.3.163datasets文件夹下:

        在ultralytics/cfg/datasets下新建一个对应的配置文件xm_dataset.yaml,文件内容(可以复制其他配置文件,然后修改内容)为:

# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: D:\deeplearning\ultralytics-8.3.163\datasets\xz_dataset # dataset root dir
train: images/train
val: images/val

# Classes
names:
  0: bucket

(6)修改训练脚本文件

        修改mytrain.py文件:

from ultralytics import  YOLO
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'

if __name__ == '__main__':
    model = YOLO(r"yolo11n.pt")

    model.train(
        data=r"xz_dataset.yaml", # 修改
        epochs=100,    # 修改
        imgsz=640,
        batch=-1,
        workers=1,
        cache="ram",
    )

        点击运行,可以看到开始训练:

        训练完成:

        左边文件栏查看训练结果:

        点击图片BoxPR_curve.pngall classes项越接近1训练效果越好:

        图片上看起来训练结果(0.995)接近1,下面用这个模型对未标注的图片进行标注。

(7)图片预测并标注

        复制刚刚训练好的模型路径:

        修改mypredict.py文件,将模型路径替换为训练好的模型路径,将source的路径替换为未标注的图片文件夹路径:

from ultralytics import  YOLO

model = YOLO(r"D:\deeplearning\ultralytics-8.3.163\runs\detect\train21\weights\best.pt")

model.predict(
    source=r"D:\deeplearning\make_dataset\cs\images",
    save=True,
    show=False,
    save_txt=True, # 新增
)

        save_txt=True, 是把预测结果保存为.txt文件。

        点击运行:

        对应文件夹查看预测结果:

        大部分图片效果还可以,小部分后面进行调整。将预测得到的标签复制到之前的D:\deeplearning\make_dataset\cs\labels文件夹下,同时把xz_dataset数据集里的图片和标签也分别放在D:\deeplearning\make_dataset\cs\labelsD:\deeplearning\make_dataset\cs\labels下:

        注意文件数量一致。

(8)用labelimg调整标注

labelimg D:\deeplearning\make_dataset\cs\images D:\deeplearning\make_dataset\cs\labels\classes.txt D:\deeplearning\make_dataset\cs\labels

        快速浏览,并对有瑕疵图片标注进行修改调整:

        至此,完成了所有图片的标注。

四、划分数据集

(1)划分数据集

        在PyCharm返回make_dataset项目,新建一个Python文件split.py

        同样可以用AI编写:

我现在需要一些Python代码随机划分一些数据集
images文件夹里存放.jpg文件
labels文件夹里存放.txt标签文件(YOLO格式)
比如imges/abc.jpg对应labels/abc.txt
划分结果统一保存到bucket_cs文件夹里
用这样的目录结构
bucket_cs\
  images\
     train\
     val\
     test\
  labels\
     train\
     val\
     test\
划分比例设置为可以调整的参数
只复制粘贴文件,不要乱动原本的图片和标签
不要用sklearn这个包
划分的时候用进度条提示一些进度
注释写清楚点,我是Python小白

        代码内容为:

import os
import random
import shutil
import time

def split_dataset(images_dir, labels_dir, output_dir, train_ratio=0.7, val_ratio=0.2, test_ratio=0.1):
    """
    随机划分数据集并复制文件到指定目录
    
    参数:
    images_dir: 原始图片文件夹路径
    labels_dir: 原始标签文件夹路径  
    output_dir: 输出文件夹路径
    train_ratio: 训练集比例 (默认0.7)
    val_ratio: 验证集比例 (默认0.2)
    test_ratio: 测试集比例 (默认0.1)
    """
    
    # 检查比例之和是否为1
    total_ratio = train_ratio + val_ratio + test_ratio
    if abs(total_ratio - 1.0) > 0.001:
        print(f"错误:比例之和应为1.0,当前为{total_ratio}")
        return
    
    # 创建输出目录结构
    dirs_to_create = [
        os.path.join(output_dir, 'images', 'train'),
        os.path.join(output_dir, 'images', 'val'),
        os.path.join(output_dir, 'images', 'test'),
        os.path.join(output_dir, 'labels', 'train'),
        os.path.join(output_dir, 'labels', 'val'),
        os.path.join(output_dir, 'labels', 'test')
    ]
    
    for dir_path in dirs_to_create:
        os.makedirs(dir_path, exist_ok=True)
        print(f"创建目录: {dir_path}")
    
    # 获取所有图片文件(不包含子目录)
    image_files = [f for f in os.listdir(images_dir) if f.endswith('.jpg')]
    
    # 移除文件扩展名,获取基础文件名
    base_names = [os.path.splitext(f)[0] for f in image_files]
    
    print(f"找到 {len(base_names)} 个图片文件")
    
    # 随机打乱文件列表
    random.shuffle(base_names)
    
    # 计算各集合的数量
    total_files = len(base_names)
    train_count = int(total_files * train_ratio)
    val_count = int(total_files * val_ratio)
    test_count = total_files - train_count - val_count
    
    print(f"划分数量 - 训练集: {train_count}, 验证集: {val_count}, 测试集: {test_count}")
    
    # 划分数据集
    train_files = base_names[:train_count]
    val_files = base_names[train_count:train_count + val_count]
    test_files = base_names[train_count + val_count:]
    
    # 复制文件的函数
    def copy_files(file_list, split_type):
        """
        复制指定文件列表到对应的训练/验证/测试目录
        """
        total_files = len(file_list)
        print(f"\n开始复制 {split_type} 集文件...")
        
        for i, base_name in enumerate(file_list):
            # 源文件路径
            src_image = os.path.join(images_dir, base_name + '.jpg')
            src_label = os.path.join(labels_dir, base_name + '.txt')
            
            # 目标文件路径
            dst_image = os.path.join(output_dir, 'images', split_type, base_name + '.jpg')
            dst_label = os.path.join(output_dir, 'labels', split_type, base_name + '.txt')
            
            # 复制图片文件
            if os.path.exists(src_image):
                shutil.copy2(src_image, dst_image)
            else:
                print(f"警告: 图片文件不存在 - {src_image}")
            
            # 复制标签文件
            if os.path.exists(src_label):
                shutil.copy2(src_label, dst_label)
            else:
                print(f"警告: 标签文件不存在 - {src_label}")
            
            # 显示进度条
            progress = (i + 1) / total_files * 100
            bar_length = 30
            filled_length = int(bar_length * (i + 1) // total_files)
            bar = '█' * filled_length + '-' * (bar_length - filled_length)
            
            print(f'\r{split_type}进度: |{bar}| {i+1}/{total_files} ({progress:.1f}%)', end='', flush=True)
            
            # 添加小延迟以便看清进度(可选)
            time.sleep(0.01)
        
        print(f"\n{split_type}集复制完成!")
    
    # 复制各集合文件
    copy_files(train_files, 'train')
    copy_files(val_files, 'val')
    copy_files(test_files, 'test')
    
    print(f"\n数据集划分完成!")
    print(f"输出目录: {output_dir}")
    print(f"训练集: {len(train_files)} 个文件")
    print(f"验证集: {len(val_files)} 个文件") 
    print(f"测试集: {len(test_files)} 个文件")

# 使用示例
if __name__ == "__main__":
    # 设置路径和参数
    IMAGES_DIR = "images"      # 原始图片文件夹
    LABELS_DIR = "labels"      # 原始标签文件夹
    OUTPUT_DIR = "bucket_cs"   # 输出文件夹
    
    # 设置划分比例 (训练集:验证集:测试集)
    TRAIN_RATIO = 0.7   # 70% 训练集
    VAL_RATIO = 0.2     # 20% 验证集  
    TEST_RATIO = 0.1    # 10% 测试集
    
    # 检查原始目录是否存在
    if not os.path.exists(IMAGES_DIR):
        print(f"错误: 图片目录 '{IMAGES_DIR}' 不存在!")
    elif not os.path.exists(LABELS_DIR):
        print(f"错误: 标签目录 '{LABELS_DIR}' 不存在!")
    else:
        # 执行数据集划分
        split_dataset(
            images_dir=IMAGES_DIR,
            labels_dir=LABELS_DIR,
            output_dir=OUTPUT_DIR,
            train_ratio=TRAIN_RATIO,
            val_ratio=VAL_RATIO,
            test_ratio=TEST_RATIO
        )

        点击运行:

        左边文件栏中生成了对应文件夹和文件:

        别忘了在labels文件夹下每个子文件夹下增加classes.txt文件:

bucket

(2)完成数据集配置

        PyCharm切换为ultralytics-8.3.163项目,将完成的bucket_cs数据集复制到datasets文件夹下,并在ultralytics/cfg/datasets新建对应的配置文件bucket_cs.yaml

# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: D:\deeplearning\ultralytics-8.3.163\datasets\bucket_cs # dataset root dir
train: images/train
val: images/val
test: images/test

# Classes
names:
  0: bucket

        修改mytrain.py文件:

from ultralytics import  YOLO
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'

if __name__ == '__main__':
    model = YOLO(r"yolo11n.pt")

    model.train(
        data=r"bucket_cs.yaml",
        epochs=5,
        imgsz=640,
        batch=-1,
        workers=1,
        cache="ram",
    )

        点击运行:

        完成训练并获得了训练结果。下面可以准备训练了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐