目标检测Faster R-CNN教程之环境配置+训练模型Pytorch版

目录

1. 前言

2. 查看电脑状况

3. 安装所需软件

3.1 Anaconda3安装

3.2 Pycharm安装

4. 安装环境

4.1 安装cuda及cudnn

4.1.1 下载及安装cuda

4.1.2 cudnn安装

4.2 创建虚拟环境

4.3 安装pytorch

4.4 下载Faster R-CNN代码

4.5 安装剩余库

5. 使用Pycharm

5.1 pycharm导入环境

5.2 验证环境

6. 模型训练

6.1 数据集处理

6.2 训练

7. 模型验证


尽管当前目标检测领域的研究热点集中在YOLO系列,但诸多经典算法在架构设计和性能对比中仍具有不可替代的价值。为弥补现有教程的不足——付费内容居多或细节缺失——本文将提供PyTorch版Faster R-CNN的完整训练指南,涵盖环境配置、训练及验证全流程。

本文的教程只包括基础的训练及验证精度,适合有对比实验需求的使用,鉴于现有的经典模型的教程存在付费或者不够详细等问题,因此写了本文方便有一定深度学习基础的同学学习,因为经典模型的训练较慢,因此本文不对只有CPU的单独介绍,有相关需求可联系我代训练。

1. 前言

2015年,由微软亚洲研究院的Shaoqing Ren、Kaiming He等学者提出的Faster R-CNN,以革命性的区域提议网络(RPN) 设计,解决了长期困扰检测领域的效率瓶颈。它将候选区域生成时间从秒级压缩至毫秒级,实现了惊人的端到端联合训练,使检测精度与速度首次达到工业级应用标准。其创新性的"注意力机制"思想,更成为后续YOLO、SSD等经典模型的灵感源泉。

本文使用代码为bubbliiiing大佬的开源代码

https://github.com/bubbliiiing/faster-rcnn-pytorch

代码及预训练文件我已经打包上传到网盘 夸克网盘分享,方便无法访问外网的情况下使用。

2. 查看电脑状况

深度学习这些经典模型如Faster R-CNN和SSD的训练对于电脑显卡要求较高,与YOLOv8同样的数据集,训练速度可能会有10倍左右的差距,因此只推荐使用NVIDIA显卡训练。若不清楚电脑有无显卡可以打开任务管理器,点击性能往下找GPU,就可以看到是否有独立显卡,不清楚是否为独立显卡则搜索型号。

图中我的显卡为RTX 3070,8G显存,一般常用笔记本上有MX系列、GTX系列和RTX系列,MX系列的性能相对较低,但是跟着教程往下做也可以使用。

嫌麻烦或者教程看不懂或者没有NVIDIA显卡的友友可以私信我或者关注公众号-笑脸惹桃花找我配置环境或者代训练模型哦~

3. 安装所需软件

推荐安装Anaconda3+Pycharm,都需要加入环境变量,会安装或者安装过了则跳过这一步骤,跟着我的教程安装过anaconda、pycharm和cuda11.8的可以直接从创建虚拟环境开始看。

3.1 Anaconda3安装

Anaconda3由于是国外网站下载较慢,推荐通过清华镜像源安装。

Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

选择带有Anaconda3...Windows...exe 字样的进行下载,建议寻找我图片上同版本的文件下载,不然安装后软件界面不一致,容易出现看不懂的情况,可以点击网盘下载相同版本。最好下载与我相同的版本。

 下载完成之后打开Anaconda3进行安装,一直点下一步,选Just Me,安装路径不建议安装到c盘,可以直接复制粘贴修改到 D:\Anaconda3 ,也可以修改到其他路径,最好纯英文路径。

​​

​​

点击下一步后,需要选择添加到环境变量,如下图前三个一定要勾选,也可以按照我图中全选。

​​

点击install安装后耐心等待进度条满安装完成即可,进度较慢,耐心等待。

​​

3.2 Pycharm安装

安装Pycharm可以直接去官网下载,速度较快。

下载 PyCharm:JetBrains 出品的用于数据科学和 Web 开发的 Python IDE

往下拉下载第二个Community Edition社区免费版就可以,也可以网盘下载相同版本。

​​

下载完之后打开安装,点下一步,遇到选择路径 修改路径到D盘或者其它除C盘外的文件夹,可以建个自己喜欢的全英文名字。

​​

需要勾选这些选项,五角星必勾选,建议全选。

​​

再点下一步,直接安装就可以了,耐心等待进度条满安装完成即可。

​​

4. 安装环境

4.1 安装cuda及cudnn

4.1.1 下载及安装cuda

在安装pytorch前需要安装cuda,下载cuda前需要先查看显卡支持的CUDA版本最高是多少,按下win+r键,输入cmd,在打开的页面输入:nvidia-smi ,即可查看。

上图红框位置显示即为cuda最高支持版本,本教程cuda最高版本达到11.8即可,若没有达到则需要更新显卡驱动。

CUDA Toolkit Archive | NVIDIA Developer

在这个网站挑选下载或者直接点击夸克网盘下载下载或者点此下载 。

下载完之后运行安装,建议安装到默认路径,所以C盘需要留有20G以上的存储空间,一直点击下一步,直到出现这个界面点击自定义,然后全部勾选即可。

​​

将下图中选项全部勾选安装。

安装完成后可以再次在cmd里输入命令:nvcc -V 查看,如下显示即安装成功

​​

4.1.2 cudnn安装

进入cudnn官网,选择合适版本的文件。

cuDNN Archive | NVIDIA Developer

进入后在文件列表中选择cudnn版本与上面cuda安装相匹配的版本。

​​

下载Windows版本的压缩包文件。

​​

下载需要登录,也可以复制下载链接打开迅雷下载,或者点击链接下载下载cudnn

将得到的压缩文件进行解压,解压后得到下图三个文件夹,全选复制进cuda的文件夹中进行覆盖替换,替换完成后即cudnn安装完成。按照本文教程安装的cuda的文件夹默认在 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 目录下。

​​

4.2 创建虚拟环境

按下Win键,输入anaconda prompt,打开下图所示的应用。

​​

默认进入的是base环境,base环境的版本与下载的anaconda3版本有关,因此不建议直接使用,需要新建环境,在新建环境之前建议更改默认的pip源和conda源可加速下载速度。

更改代码如下,直接输入即可,这里选择更换的是中科大源,建议使用,亲测完美运行。

conda config --remove-key channels
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/pytorch/
conda config --set show_channel_urls yes
pip config set global.index-url https://mirrors.ustc.edu.cn/pypi/web/simple

此时新建虚拟环境(需要关闭加速软件),这里创建一个名为frcnn,python版本为3.8的虚拟环境,也可以修改为其他名,本文所用为python3.8,同时本教程所用pytorch源及python版本只支持3.8。

conda create -n frcnn python=3.8

回车后出现新建环境提醒输入y继续,耐心等待全部下载完成后自动安装。

注意:此时如果报错

UnavailableInvalidChannel: HTTP 404 NOT FOUND for channel anaconda/pkgs/free <https://mirrors.ustc.edu.cn/anaconda/pkgs/free>

The channel is not accessible or is invalid.

You will need to adjust your conda configuration to proceed.
Use `conda config --show channels` to view your configuration's current state,
and use `conda config --show-sources` to view config file locations.

等错误,需要修改隐藏文件 .condarc文件的内容,具体路径为:c:\users\用户名\.condarc

使用记事本打开,复制下面这一段进去替换掉原本所有的内容,替换完之后保存即可

channels:
  - defaults
show_channel_urls: true
channel_alias: http://mirrors.tuna.tsinghua.edu.cn/anaconda
default_channels:
  - http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  - http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
  - http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
  - http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro
  - http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
  conda-forge: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  msys2: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  bioconda: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  menpo: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  simpleitk: http://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud

此时再重复上面新建环境的指令。

出现这样的done即为创建完成。

​创建完之后输入

conda activate frcnn

4.3 安装pytorch

打开prompt 输入conda activate frcnn进入frcnn环境,之后输入下方命令即可安装pytorch,耐心等待安装完成。

pip install torch==2.0.0+cu118 torchvision==0.15.1+cu118 --extra-index-url https://download.pytorch.org/whl/cu118

出现ERROR: No matching distribution found for torch==2.0.0+cu118等是因为python版本不对!

torch及相关库比较大,需要耐心等待下载完之后出现 done 则安装完成,因为是外网,如果下载不下来,可以点此下载torch 夸克网盘下载,然后通过pip install安装本地文件的方式安装,可以参考下图的方式,将torch放入d盘的test文件夹下,通过cd将文件目录跳转,再输入如下指令。(只支持python3.8版本,其余版本无法安装)

pip install torch-2.0.0+cu118-cp38-cp38-win_amd64.whl

下图为相对路径安装torch的演示方法,也可以直接使用绝对路径安装。

耐心等待安装完成后,再输入下面的指令通过pip安装其余库。

pip install torch==2.0.0+cu118 torchvision==0.15.1+cu118 --extra-index-url https://download.pytorch.org/whl/cu118

出现successfully即安装torch成功。

4.4 下载Faster R-CNN代码

所使用代码为bubbliiiing大佬的代码,Faster R-CNN源代码地址:

https://github.com/bubbliiiing/faster-rcnn-pytorch

点进去下载代码,进不去可能需要科学上网,或者点击下载  夸克网盘分享,我上传了两个预训练权重文件,方便测试环境使用。链接资源失效请评论区反馈,看到会补,或者至公众号下载。

可以在这两个中挑选一个作为自己训练的,我一般使用resnet。

4.5 安装剩余库

此时需要安装源代码中的requirements内的库。

先找个位置将下载得到的faster-rcnn源代码解压,然后cd到faster-rcnn所在根目录,比如我这里放到了D盘的根目录下,那么我先到D盘,再进入faster-rcnn的根目录,如下图。

进入当前文件夹后,先打开requirements.txt,将下面的内容覆盖并保存,也可以直接新建一个requirements.txt文件,然后将下面的内容复制进去,

torch
torchvision
tensorboard
scipy==1.10.1
numpy==1.24.4
matplotlib==3.1.2
opencv_python==4.1.2.30
tqdm==4.60.0
Pillow==8.2.0
h5py==2.10.0

输入如下指令安装剩余python库

pip install -r requirements.txt

 耐心等待,出现下图即安装完成。

5. 使用Pycharm

5.1 pycharm导入环境

​下载完成之后解压到D盘或其它盘文件夹内,此时点击鼠标右键文件夹通过pycharm打开,打开后需要配置虚拟环境。打开文件夹后如果弹出创建虚拟环境,点取消

新版pycharm可选中文语言,点击 文件-设置,点击 项目:faster-rcnn,点击python解释器,点击右边添加解释器-添加本地解释器。

​点击Virtualenv环境 - 现有,点击右边三个点,找到刚才添加的frcnn环境的位置,按照本文配置即是D:\Anaconda3\envs\frcnn\python.exe 路径,之后一直点确定,点应用,再点确定即可。

或者新版本的界面更为简单,按照下图方式选择就可以。

 选择python,接着选择刚刚添加的frcnn环境,选择python.exe。

如果找不到环境所在位置,可以输入

conda env list

在这里面可以找到虚拟环境所在路径,再进pycharm里选择对应的python.exe即可。

正确选择的界面如下

点击确定后耐心等待解释器配置完成。

5.2 验证环境

这里使用预训练权重,并将其放置在model_data文件夹内,并修改predict.py,将里面的mode修改为dir_predict,然后运行,出现下图且没有报错则环境可以正常使用。

打开img_out文件夹,点开图片,可以看到有检测框,环境正常!

6. 模型训练

6.1 数据集处理

这里默认所有都已经有数据集的情况

需要图片+VOC格式的标签,因此如果之前是YOLO格式的文件就需要转换

# 作者:CSDN-笑脸惹桃花 https://blog.csdn.net/qq_67105081?type=blog
# github:peng-xiaobai https://github.com/peng-xiaobai/Dataset-Conversion
import os
import cv2
import numpy as np
 
#xml文件格式
out0 = '''<annotation>
    <folder>%(folder)s</folder>
    <filename>%(name)s</filename>
    <path>%(path)s</path>
    <source>
        <database>None</database>
    </source>
    <size>
        <width>%(width)d</width>
        <height>%(height)d</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
'''
out1 = '''    <object>
        <name>%(class)s</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>%(xmin)d</xmin>
            <ymin>%(ymin)d</ymin>
            <xmax>%(xmax)d</xmax>
            <ymax>%(ymax)d</ymax>
        </bndbox>
    </object>
'''
 
out2 = '''</annotation>
'''
 
def upp2low(directory):
    converted_count = 0
    # 检查目录是否存在
    if not os.path.exists(directory):
        raise FileNotFoundError(f"Directory {directory} does not exist.")
    # 遍历文件夹中的所有文件
    for filename in os.listdir(directory):
        file_path = os.path.join(directory, filename)
        # 仅处理文件
        if os.path.isfile(file_path):
            # 拆分文件名和后缀
            name, extension = os.path.splitext(filename)
            # 检查后缀是否为大写
            if extension.isupper():
                new_filename = name + extension.lower()
                new_file_path = os.path.join(directory, new_filename)
                # 重命名文件
                os.rename(file_path, new_file_path)
                converted_count += 1
                print(f"Renamed: {filename} -> {new_filename}")
    print(f"All file suffixes in the folder are lowercase, and a total of {converted_count} files have been processed")
    return converted_count
 
def yolo2voc(dir1,dir2,dir3,Class):
    file = os.listdir(dir1)
 
    source = {}
    label = {}
    for img in file:
        print(img)
        if img.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tif')):
            img1 = os.path.join(dir1, img)
            image = cv2.imread(img1)  # 路径不能有中文
            h, w, _ = image.shape
            name, extension = os.path.splitext(img)
            name1 = name + '.xml'
            name2 = name + '.txt'
            fxml = os.path.join(dir2, name1)
            txt = os.path.join(dir3, name2)
            if not os.path.exists(txt):
                print(f"{name2}未找到,已跳过")
                continue
 
            fxml = open(fxml, 'w')
            source['name'] = img
            source['path'] = img1
            source['folder'] = os.path.basename(dir1)
            source['width'] = w
            source['height'] = h
 
            fxml.write(out0 % source)
            lines = np.loadtxt(txt)
            flag = 0
            for box in lines:
                if box.shape != (5,):
                    box = lines
                    flag = 1
                '''把txt上的第一列(类别)转成xml上的类别'''
                box_index = int(box[0])
                label['class'] = Class[box_index] # 类别索引从1开始
                '''把txt上的数字(归一化)转成xml上框的坐标'''
                xmin = float(box[1] - 0.5 * box[3]) * w
                ymin = float(box[2] - 0.5 * box[4]) * h
                xmax = float(xmin + box[3] * w)
                ymax = float(ymin + box[4] * h)
                label['xmin'] = xmin
                label['ymin'] = ymin
                label['xmax'] = xmax
                label['ymax'] = ymax
 
                keys = ['xmin', 'ymin', 'xmax', 'ymax']
                limits = [w, h, w, h]
                for i, key in enumerate(keys):
                    if label[key] >= limits[i]:
                        label[key] = limits[i]
                    elif label[key] < 0:
                        label[key] = 0
 
                fxml.write(out1 % label)
                if flag == 1:
                    break
            fxml.write(out2)
 
 
if __name__ == '__main__':
    l = ["hat","nohat"] #所有类别
    file_dir1 = r' ' #图像文件夹
    file_dir2 = r' '  #xml存放文件夹
    file_dir3 = r' '  #txt存放文件夹
    if not os.path.exists(file_dir2):
        os.makedirs(file_dir2)
    upp2low(file_dir1)
    yolo2voc(file_dir1,file_dir2,file_dir3,l)
    print('转换已结束')

根据代码自行替换路径。

如果训练的图片不是jpg格式的则需要先转换为jpg格式,使用下面的代码

import os
from PIL import Image
import sys


def convert_to_jpg(input_folder, output_folder):
    # 确保输出文件夹存在
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    # 支持的图片格式
    supported_formats = ('.png', '.bmp', '.gif', '.tiff', '.webp')

    # 遍历输入文件夹中的所有文件
    for filename in os.listdir(input_folder):
        # 检查文件是否为支持的图片格式
        if filename.lower().endswith(supported_formats):
            # 构建输入和输出文件路径
            input_path = os.path.join(input_folder, filename)
            # 去掉原文件扩展名,添加 .jpg
            output_filename = os.path.splitext(filename)[0] + '.jpg'
            output_path = os.path.join(output_folder, output_filename)

            try:
                # 打开图片
                with Image.open(input_path) as img:
                    # 转换为 RGB 模式(JPG 不支持透明通道)
                    if img.mode in ('RGBA', 'LA', 'P'):
                        img = img.convert('RGB')
                    # 保存为 JPG
                    img.save(output_path, 'JPEG', quality=95)
                print(f"成功转换: {filename} -> {output_filename}")
            except Exception as e:
                print(f"转换 {filename} 失败: {str(e)}")


if __name__ == "__main__":
    input_folder = r'D:\faster-rcnn-pytorch-master\VOCdevkit\VOC2007\JPEGImages1'  #输入文件夹
    output_folder = r'D:\faster-rcnn-pytorch-master\VOCdevkit\VOC2007\JPEGImages'  #输出文件夹
    # 确保输入文件夹存在
    if not os.path.exists(input_folder):
        print(f"错误: 输入文件夹 {input_folder} 不存在")
        sys.exit(1)

    convert_to_jpg(input_folder, output_folder)
    print("转换完成!")

将数据集复制到目录下的VOCdevkit\VOC2007文件夹下,标注文件放到Annotations,图片文件放到JPEGImages,打开voc_classes.txt,修改为自己数据集的类别,每种一行。之后打开voc_annotation.py,如果之前没有划分过数据集,则mode使用0,再设置合适的比例,后运行代码。

如果之前划分过数据集,则需要先提前将数据集对应划分,使用如下代码

import os


def write_filenames_to_txt(folder_path, txt_path):
	# 获取文件夹中所有文件的文件名(不包含路径)
	filenames = os.listdir(folder_path)

	# 打开txt文件准备写入
	with open(txt_path, 'w') as f:
		for filename in filenames:
			if filename.endswith('.txt'):  # 假设标签文件是以.txt结尾
				f.write(filename[:-4] + '\n')  # 去掉扩展名并写入


def write_trainval_txt(train_folder, val_folder, trainval_txt_path):
	# 获取train和val文件夹的文件名
	train_filenames = os.listdir(train_folder)
	val_filenames = os.listdir(val_folder)

	# 打开trainval.txt文件准备写入
	with open(trainval_txt_path, 'w') as f:
		for filename in train_filenames:
			if filename.endswith('.txt'):
				f.write(filename[:-4] + '\n')  # 去掉扩展名并写入
		for filename in val_filenames:
			if filename.endswith('.txt'):
				f.write(filename[:-4] + '\n')  # 去掉扩展名并写入


# 设置文件夹路径
train_folder = r'H:\datasets\train\labels'  # 替换为traon实际路径
val_folder = r'H:\datasets\val\labels'  # 替换为val实际路径
test_folder = r'H:\datasets\test\labels'  # 替换为test实际路径

# 设置输出的txt文件路径
train_txt = r'H:\datasets\VOCdevkit\VOC2007\ImageSets\Main\train.txt'  # 替换为实际路径
val_txt = r'H:\datasets\VOCdevkit\VOC2007\ImageSets\Main\val.txt'  # 替换为实际路径
test_txt = r'H:\datasets\VOCdevkit\VOC2007\ImageSets\Main\test.txt'  # 替换为实际路径
trainval_txt = r'H:\datasets\VOCdevkit\VOC2007\ImageSets\Main\trainval.txt'  # 替换为实际路径

os.makedirs(r"H:\datasets\VOCdevkit\VOC2007\ImageSets\Main", exist_ok=True) #替换为Main路径

# 写入文件名到各自的txt
write_filenames_to_txt(train_folder, train_txt)
write_filenames_to_txt(val_folder, val_txt)
write_filenames_to_txt(test_folder, test_txt)

# 写入trainval.txt,包含train和val的所有文件名
write_trainval_txt(train_folder, val_folder, trainval_txt)

print("文件名已成功写入到txt文件中!")

使用代码后,将复制得到的Main文件夹内的txt全部复制到faster-rcnn-pytorch-master\VOCdevkit\VOC2007\ImageSets\Main 文件夹下,在voc_annotation.py中设置mode为2,运行,之后即可训练。

出现具体的类别及数量表示数据可以正常使用。

6.2 训练

打开train.py文件,本文使用voc_weights_resnet.pth训练,如果使用其他的则需要在大概83行修改model_path为其他模型。修改153行左右的Freeze_Epoch和Freeze_batch_size,往下还有UnFreeze_Epoch和UnFreeze_batch_size,这里分为冻结和解冻训练,根据不同的需要,如果训练200轮,则可以设置Freeze_Epoch为150,UnFreeze_Epoch设置为200,在最后50轮解冻训练,batch_size可以适当调整到适合自己显卡的合适数值,修改202行的save_period为25或其它,频繁保存易导致硬盘存储爆炸,217行的eval_period也可以修改为25或其他,减少评估频率,提高训练效率。其他的学习率,优化器等参数也可以根据代码中的注释进行适当修改。第60行的fp16可以开启,减少显存占用,第223行的num_workers设置为8,加快训练速度。

出现这个即可以正常训练。

7. 模型验证

打开predict.py文件,根据不同的需求修改不同的mode,可以使用dir_predict模式检测文件夹的内容并保存,将img文件夹内的图片修改为自己的数据集相关的图片,修改frcnn.py中的model_path为在logs文件夹下训练的最佳模型,一般是best_epoch_weights.pth,替换完就是下图。

之后运行predict代码,打开img_out即可查看检测后的图片。

遇到报错可以打开评论区交流。  关注微信公众号-笑脸惹桃花 快速联系我~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

笑脸惹桃花

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

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

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

打赏作者

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

抵扣说明:

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

余额充值