目录

开发环境

一、准备数据集

1. 数据集文件夹目录

2. yolo格式转json格式

3. 文件夹重命名

4. json格式转mask图片

5. 生成txt文件

二、修改配置

1. 在mypath.py中添加自己的数据集名称与路径

2. 在train.py约185行添加自己数据集的名称

3. 在dataloaders/datasets目录下添加文件

4. 在dataloaders目录下修改__init__.py

5. 修改dateloaders目录下utils.py

三、训练并测试

1. 开始训练

2. 模型测试


开发环境

  • windows11

  • pytorch2.2

  • vscode

  • python3.8

一、准备数据集

1. 数据集文件夹目录

  • ImageSets 文件夹内还有一个文件夹Segmentation,里面存放的是训练集、测试集txt文件

  • JPEGImages 存放原图image

  • SegmentationClass 存放原图对应的mask,要和JPEGImages里的图片一一对应

2. yolo格式转json格式

我使用的标注方式是:Sign in to Roboflow,大大减少了标注时间,推荐!!!

导出yolo格式之后进行转json格式,代码如下:

import json
import os

# 输入TXT标注文件夹路径
txt_folder_path = "E:/VScode project/pytorch-deeplab-xception-master111/Seg552/txt/"
# 输出JSON文件夹路径
json_folder_path = "E:/VScode project/pytorch-deeplab-xception-master111/Seg552/json/"

# 确保输出文件夹存在
os.makedirs(json_folder_path, exist_ok=True)

# 类别ID映射(如有需要可以修改)
label_mapping = {1: "ripe fruits", 0: "branch"}

# 遍历TXT文件夹
for txt_file in os.listdir(txt_folder_path):
    if txt_file.endswith('.txt'):
        txt_file_path = os.path.join(txt_folder_path, txt_file)
        json_file_path = os.path.join(json_folder_path, txt_file.replace('.txt', '.json'))

        # JSON模板
        json_data = {
            "version": "5.2.1",
            "flags": {},
            "shapes": [],
            "imagePath": txt_file.replace('.txt', '.jpg'),  # 假设图像名与TXT文件名相同
            "imageHeight": 1080,
            "imageWidth": 1920
        }

        # 解析TXT文件并构造JSON结构
        with open(txt_file_path, 'r') as file:
            for line in file:
                # 分割类别ID和坐标数据
                parts = line.strip().split()
                class_id = int(parts[0])  # 类别ID
                label = label_mapping.get(class_id, "unknown")  # 类别名称
                coordinates = list(map(float, parts[1:]))  # 坐标数据

                # 将坐标数据转换为(x, y)点对,并按比例转换为实际像素位置
                points = []
                for i in range(0, len(coordinates), 2):
                    x = coordinates[i] * json_data["imageWidth"]
                    y = coordinates[i + 1] * json_data["imageHeight"]
                    points.append([x, y])

                # 添加标注信息到JSON
                shape_data = {
                    "label": label,
                    "points": points,
                    "group_id": None,
                    "description": "",
                    "shape_type": "polygon",
                    "flags": {}
                }
                json_data["shapes"].append(shape_data)

        # 保存为JSON文件
        with open(json_file_path, 'w') as json_file:
            json.dump(json_data, json_file, indent=2)

        print(f"已成功将 {txt_file} 转换为 JSON 文件:{json_file_path}")

3. 文件夹重命名

虽然用网页标注导出来的image和TXT文件的名称是一致的,但为了避免在后续格式转换中出现冲突,现在需要将image图片和txt文件重新命名。相应代码:

import os

# 文件夹路径
folder1 = 'E:/VScode project/pytorch-deeplab-xception-master111/Seg552/txt/'  # 替换为您的文件夹路径
folder2 = 'E:/VScode project/pytorch-deeplab-xception-master111/Seg552/img/'  # 替换为您的文件夹路径

# 获取文件名列表
files1 = os.listdir(folder1)
files2 = os.listdir(folder2)

# 对文件进行排序,确保顺序一致
files1.sort()
files2.sort()

# 确保两个文件夹的文件数相同
if len(files1) != len(files2):
    print("警告:两个文件夹的文件数量不同!")

# 重命名第一个文件夹的文件
for idx, filename in enumerate(files1):
    new_name = f"{idx:03d}{os.path.splitext(filename)[1]}"  # 保留后缀
    os.rename(os.path.join(folder1, filename), os.path.join(folder1, new_name))

# 重命名第二个文件夹的文件
for idx, filename in enumerate(files2):
    new_name = f"{idx:03d}{os.path.splitext(filename)[1]}"
    os.rename(os.path.join(folder2, filename), os.path.join(folder2, new_name))

print("重命名完成。")

4. json格式转mask图片

相应代码:

import argparse
import base64
import json
import os
import os.path as osp
import imgviz
import PIL.Image
from labelme.logger import logger
from labelme import utils
import glob
import yaml

def main():
    logger.warning(
        "This script is aimed to demonstrate how to convert the "
        "JSON file to a single image dataset."
    )
    logger.warning(
        "It will handle multiple JSON files to generate a "
        "real-use dataset."
    )

    parser = argparse.ArgumentParser()
    parser.add_argument("--json_dir", required=True)
    parser.add_argument("-o", "--out", required=True)
    args = parser.parse_args()

    json_dir = args.json_dir
    output_dir = args.out

    if osp.isfile(json_dir):
        json_list = [json_dir] if json_dir.endswith('.json') else []
    else:
        json_list = glob.glob(os.path.join(json_dir, '*.json'))

    for json_file in json_list:
        logger.info(f"Processing file: {json_file}")
        json_name = osp.basename(json_file).split('.')[0]
        out_dir = osp.join(output_dir, json_name)

        if not osp.exists(out_dir):
            os.makedirs(out_dir)

        try:
            data = json.load(open(json_file))
        except Exception as e:
            logger.error(f"Error loading JSON file {json_file}: {e}")
            continue  # Skip to the next file

        imageData = data.get("imageData")

        if not imageData:
            image_filename = osp.basename(data["imagePath"])
            imagePath = osp.join("E:/VScode project/pytorch-deeplab-xception-master111/Seg552/JPEGImages", image_filename)
            try:
                with open(imagePath, "rb") as f:
                    imageData = f.read()
                    imageData = base64.b64encode(imageData).decode("utf-8")
            except FileNotFoundError:
                logger.error(f"File not found: {imagePath}")
                continue  # Skip to the next JSON file
            except Exception as e:
                logger.error(f"Error reading image file {imagePath}: {e}")
                continue

        try:
            img = utils.img_b64_to_arr(imageData)

            label_name_to_value = {"_background_": 0}
            for shape in sorted(data["shapes"], key=lambda x: x["label"]):
                label_name = shape["label"]
                if label_name in label_name_to_value:
                    label_value = label_name_to_value[label_name]
                else:
                    label_value = len(label_name_to_value)
                    label_name_to_value[label_name] = label_value
            
            lbl, _ = utils.shapes_to_label(img.shape, data["shapes"], label_name_to_value)

            label_names = [None] * (max(label_name_to_value.values()) + 1)
            for name, value in label_name_to_value.items():
                label_names[value] = name

            lbl_viz = imgviz.label2rgb(lbl, imgviz.asgray(img), label_names=label_names, loc="rb")

            # Save files to corresponding subdirectory
            PIL.Image.fromarray(img).save(osp.join(out_dir, "img.png"))
            utils.lblsave(osp.join(out_dir, "label.png"), lbl)
            PIL.Image.fromarray(lbl_viz).save(osp.join(out_dir, "label_viz.png"))

            with open(osp.join(out_dir, "label_names.txt"), "w") as f:
                for lbl_name in label_names:
                    f.write(str(lbl_name if lbl_name is not None else "unknown") + "\n")

            yaml_data = {
                "label_name_to_value": label_name_to_value,
                "label_names": label_names
            }
            with open(osp.join(out_dir, "labels.yaml"), "w") as yaml_file:
                yaml.dump(yaml_data, yaml_file)

            logger.info(f"Saved to: {out_dir}")

        except Exception as e:
            logger.error(f"Error processing file {json_file}: {e}")

if __name__ == "__main__":
    main()

运行指令为:

python My_json_to_dataset.py --json_dir "E:/VScode project/pytorch-deeplab-xception-master111/Seg552/json" -o "E:/VScode project/pytorch-deeplab-xception-master111/Seg552/labelme_json"

生成的文件为:

需要将labelme_json文件下的每个文件中的label.png文件重新命名,相应代码:

import os

# 替换为你的json文件夹所在的目录
json_dir = "E:/VScode project/pytorch-deeplab-xception-master111/Seg552/labelme_json"

for root, dirs, files in os.walk(json_dir):
    for dr in dirs:
        file_dir = os.path.join(root, dr)

        # 确认label.png文件存在
        label_file = os.path.join(file_dir, 'label.png')
        if os.path.exists(label_file):
            # 将文件夹名分割,得到原图名
            original_name = dr.split('_')[0] + '.png'
            new_file_name = os.path.join(file_dir, original_name)

            # 执行重命名操作
            os.rename(label_file, new_file_name)
            print(f"Renamed '{label_file}' to '{new_file_name}'")

最后将提取出所有文件夹中的000.png,并放在指定目录中,相应代码:

import os
from shutil import copyfile

for root, dirs, names in os.walk(
        "E:/VScode project/pytorch-deeplab-xception-master111/Seg552/labelme_json"):  # 改成你自己的json文件夹所在的目录
    for dr in dirs:
        file_dir = os.path.join(root, dr)
        print(dr)
        file = os.path.join(file_dir, dr + '.png')
        print(file)
        new_name = dr.split('_')[0] + '.png'
        new_file_name = os.path.join(file_dir, new_name)
        print(new_file_name)

        tar_root = 'E:/VScode project/pytorch-deeplab-xception-master111/Seg552/Segmentationclass'  # 目标路径
        tar_file = os.path.join(tar_root, new_name)
        copyfile(new_file_name, tar_file)

该代码运行得到的文件,就是我们所需要的SegmentationClass

5. 生成txt文件

生成的训练集txt和验证集txt,里面的图片名称(去掉后缀)的分配是随机的。相应代码:

import os
import random

# 设置图像目录和输出目录
image_dir = 'E:/VScode project/pytorch-deeplab-xception-master111/Seg552/JPEGImages'  # 替换为你的图像目录
output_dir = 'E:/VScode project/pytorch-deeplab-xception-master111/Seg552/ImageSets/Segmentation'  # 替换为输出目录

# 获取所有图像文件名(去掉后缀)
image_files = [f.split('.')[0] for f in os.listdir(image_dir) if f.endswith(('.jpg', '.png'))]

# 随机打乱图像文件名
random.shuffle(image_files)

# 分割训练集和验证集
train_files = image_files[:int(len(image_files) * 0.8)]  # 80% 为训练集
val_files = image_files[int(len(image_files) * 0.8):]  # 20% 为验证集

# 写入 train.txt
with open(os.path.join(output_dir, 'train.txt'), 'w') as f:
    for file in train_files:
        f.write(f"{file}\n")

# 写入 val.txt
with open(os.path.join(output_dir, 'val.txt'), 'w') as f:
    for file in val_files:
        f.write(f"{file}\n")

print("train.txt 和 val.txt 已生成。")

二、修改配置

使用的代码是这个deeplab-pytorch

参考博客:Pytorch版deeplabv3+环境配置训练自己的数据集_pytorch deeplabv3+训练自己的数据集自定义init len getitem-CSDN博客

1. 在mypath.py中添加自己的数据集名称与路径

class Path(object):
    @staticmethod
    def db_root_dir(dataset):
        if dataset == 'pascal':
            return '/path/to/datasets/VOCdevkit/VOC2012/'  # folder that contains VOCdevkit/.
        elif dataset == 'sbd':
            return '/path/to/datasets/benchmark_RELEASE/'  # folder that contains dataset/.
        elif dataset == 'cityscapes':
            return '/path/to/datasets/cityscapes/'     # foler that contains leftImg8bit/
        elif dataset == 'coco':
            return '/path/to/datasets/coco/'
        elif dataset == 'Seg552':     #在这里添加自己数据集的名称
            return 'E:/VScode project/pytorch-deeplab-xception-master/Seg552'    #在这里添加自己数据集的路径
        else:
            print('Dataset {} not available.'.format(dataset))
            raise NotImplementedError

2. 在train.py约185行添加自己数据集的名称

3. 在dataloaders/datasets目录下添加文件

复制一份pascal.py,并重命名文件为Seg552

三个修改地方:修改类为Seg552Segmentation,修改类别数、数据集名称

部分代码如下:

from __future__ import print_function, division
import os
from PIL import Image
import numpy as np
from torch.utils.data import Dataset
from mypath import Path
from torchvision import transforms
from dataloaders import custom_transforms as tr

class Seg552Segmentation(Dataset):  #修改类为Seg552Segmentation
    """
    PascalVoc dataset
    """
    NUM_CLASSES = 3    #修改类别数

    def __init__(self,
                 args,
                 base_dir=Path.db_root_dir('Seg552'), #修改数据集名称
                 split='train',
                 ):

4. 在dataloaders目录下修改__init__.py

在第三行导入Seg552Segmentation,复制'pascal.py'数据集描述,三个修改地方:修改

train_set = Seg552Segmentation(args, split='train'),

val_set = Seg552Segmentation(args, split='val'),

修改为自己数据集的名字Seg552

部分代码如下:

from dataloaders.datasets import cityscapes, coco, combine_dbs, pascal, sbd
from torch.utils.data import DataLoader
from dataloaders.datasets.Seg552 import Seg552Segmentation

def make_data_loader(args, **kwargs):

    if args.dataset == 'pascal':
        train_set = pascal.VOCSegmentation(args, split='train')
        val_set = pascal.VOCSegmentation(args, split='val')
        if args.use_sbd:
            sbd_train = sbd.SBDSegmentation(args, split=['train', 'val'])
            train_set = combine_dbs.CombineDBs([train_set, sbd_train], excluded=[val_set])

        num_class = train_set.NUM_CLASSES
        train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
        val_loader = DataLoader(val_set, batch_size=args.batch_size, shuffle=False, **kwargs)
        test_loader = None

        return train_loader, val_loader, test_loader, num_class
    
    elif args.dataset == 'Seg552':    #修改数据名称
        train_set = Seg552Segmentation(args, split='train')  # 自定义数据集类  修改为类:Seg552Segmentation
        val_set = Seg552Segmentation(args, split='val')   #修改为类:Seg552Segmentation
        num_class = train_set.NUM_CLASSES
        train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=True, **kwargs)
        val_loader = DataLoader(val_set, batch_size=args.batch_size, shuffle=False, **kwargs)
        test_loader = None
        return train_loader, val_loader, test_loader, num_class

5. 在dateloaders目录下修改utils.py

在74行左右添加代码,设置每一类别的颜色显示;也可以在里面定义颜色映射,即修改原始的decode_segmap,并修改类别数,这段代码根据实际情况改变,具体代码如下:

import matplotlib.pyplot as plt
import numpy as np
import torch

def decode_seg_map_sequence(label_masks, dataset='pascal'):
    rgb_masks = []
    for label_mask in label_masks:
        rgb_mask = decode_segmap(label_mask, dataset)
        rgb_masks.append(rgb_mask)
    rgb_masks = torch.from_numpy(np.array(rgb_masks).transpose([0, 3, 1, 2]))
    return rgb_masks

def decode_segmap(label_mask, dataset):
    # 你需要为 Seg552 定义颜色映射
    # 假设你有 3 类:背景、分支、成熟水果
    label_colors = np.array([(0, 0, 0),    # 背景
                             (0, 255, 0),  # branch
                             (255, 0, 0)]) # ripe fruit
    r = np.zeros_like(label_mask).astype(np.uint8)
    g = np.zeros_like(label_mask).astype(np.uint8)
    b = np.zeros_like(label_mask).astype(np.uint8)

    for ll in range(0, len(label_colors)):
        mask = (label_mask == ll)
        r[mask] = label_colors[ll][0]
        g[mask] = label_colors[ll][1]
        b[mask] = label_colors[ll][2]

    rgb_mask = np.zeros((label_mask.shape[0], label_mask.shape[1], 3))
    rgb_mask[:, :, 0] = r
    rgb_mask[:, :, 1] = g
    rgb_mask[:, :, 2] = b

    return rgb_mask

def encode_segmap(mask):
    """Encode segmentation label images as pascal classes
    Args:
        mask (np.ndarray): raw segmentation label image of dimension
          (M, N, 3), in which the Pascal classes are encoded as colours.
    Returns:
        (np.ndarray): class map with dimensions (M,N), where the value at
        a given location is the integer denoting the class index.
    """
    mask = mask.astype(int)
    label_mask = np.zeros((mask.shape[0], mask.shape[1]), dtype=np.int16)
    for ii, label in enumerate(get_pascal_labels()):
        label_mask[np.where(np.all(mask == label, axis=-1))[:2]] = ii
    label_mask = label_mask.astype(int)
    return label_mask

def get_cityscapes_labels():
    return np.array([
        [128, 64, 128],
        [244, 35, 232],
        [70, 70, 70],
        [102, 102, 156],
        [190, 153, 153],
        [153, 153, 153],
        [250, 170, 30],
        [220, 220, 0],
        [107, 142, 35],
        [152, 251, 152],
        [0, 130, 180],
        [220, 20, 60],
        [255, 0, 0],
        [0, 0, 142],
        [0, 0, 70],
        [0, 60, 100],
        [0, 80, 100],
        [0, 0, 230],
        [119, 11, 32]])

def get_Seg552_labels():
    return np.array([
        [0, 0, 0],
        [128, 0, 0],
        [0, 128, 0]])

def get_pascal_labels():
    """Load the mapping that associates pascal classes with label colors
    Returns:
        np.ndarray with dimensions (21, 3)
    """
    return np.asarray([[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
                       [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
                       [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
                       [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
                       [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
                       [0, 64, 128]])

三、训练并测试

1. 开始训练

运行指令为:

python train.py --backbone mobilenet --lr 0.007 --workers 1 --epochs 50 --batch-size 8 --gpu-ids 0 --checkname deeplab-mobilenet
  • –backbone mobilenet 指的是使用mobilenet作为backbone
  • –gpu-ids 0 指定gpu
  • –checkname deeplab-mobilenet 使用mobilenet预训练模型

2. 模型测试

需要修改代码中输入测试图片的路径,测试代码:

import argparse
import os
import time
from PIL import Image
import torch
from torchvision import transforms
from torchvision.utils import make_grid, save_image
from modeling.deeplab import DeepLab
from dataloaders import custom_transforms as tr
from dataloaders.utils import decode_seg_map_sequence

def main():
    parser = argparse.ArgumentParser(description="PyTorch DeeplabV3Plus Testing")
    parser.add_argument('--in-path', type=str, default='E:/VScode project/pytorch-deeplab-xception-master/Seg552/test', help='input image directory')
    parser.add_argument('--backbone', type=str, default='resnet', choices=['resnet', 'xception', 'drn', 'mobilenet'], help='backbone name (default: resnet)')
    parser.add_argument('--ckpt', type=str, default='E:/test/pytorch-deeplab-xception-masterrun/Seg552/deeplab-mobilenet/experiment_9/checkpoint.pth.tar', help='saved model path')
    parser.add_argument('--out-stride', type=int, default=16, help='network output stride (default: 8)')
    parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training')
    parser.add_argument('--num_classes', type=int, default=3, help='number of classes')
    
    args = parser.parse_args()
    args.cuda = not args.no_cuda and torch.cuda.is_available()
    
    model = DeepLab(num_classes=args.num_classes, backbone=args.backbone, output_stride=args.out_stride)
    
    try:
        ckpt = torch.load(args.ckpt, map_location='cpu')
        model.load_state_dict(ckpt['state_dict'])
        print("Model loaded successfully.")
    except Exception as e:
        print(f"Error loading model: {e}")
        return
    
    model = model.cuda() if args.cuda else model

    composed_transforms = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
    ])
    
    for name in os.listdir(args.in_path):
        try:
            image_path = os.path.join(args.in_path, name)
            image = Image.open(image_path).convert('RGB')
            
            # 直接将图像转换为张量
            tensor_in = composed_transforms(image).unsqueeze(0)
            
            model.eval()
            if args.cuda:
                tensor_in = tensor_in.cuda()
                
            with torch.no_grad():
                output = model(tensor_in)

            decoded_output = decode_seg_map_sequence(torch.max(output[:3], 1)[1].detach().cpu().numpy())
            grid_image = make_grid(decoded_output, 3, normalize=False)
            save_image(grid_image, os.path.join(args.in_path, f"{name[:-4]}_mask.png"))
        except Exception as e:
            print(f"Error processing {name}: {e}")

    print("Processing complete.")

if __name__ == "__main__":
    main()

运行指令为:

python testdemo.py --ckpt run/Seg552/deeplab-mobilenet/experiment/checkpoint.pth.tar --backbone mobilenet

修改–in-path为数据集的测试图片,结果保存在–in-path中

参考博客:

【windows10】使用pytorch版本deeplabv3+训练自己数据集_deeplab pytorch-CSDN博客

Pytorch版deeplabv3+环境配置训练自己的数据集_pytorch deeplabv3+训练自己的数据集自定义init len getitem-CSDN博客

Logo

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

更多推荐