专业编程基础技术教程

网站首页 > 基础教程 正文

菜品目标检测数据集标注及处理Yolo

ccvgpt 2024-12-12 11:08:37 基础教程 1 ℃

本文摘要

本文主要演示如何使用LabelImg标注菜品数据集,目前数据集有2000+已标注的菜品目标图片,用于后续的菜品目标深度学习模型训练(包含已标注数据集,点击原文链接,关注回复“源码或数据集”,免费获取)

原文链接:Yolov10菜品的目标检测模型训练及推理(包括数据集、训练及推理源码)

菜品目标检测数据集标注及处理Yolo

数据格式

目标检测的数据,在一张图像中,需要以最小外接矩形标记出各个目标区域的位置和类别,

一般的目标区域位置用一个矩形框来表示,一般用以下3种方式表达:

表达方式

说明

x1,y1,x2,y2

(x1,y1)为左上角坐标,(x2,y2)为右下角坐标

x1,y1,w,h

(x1,y1)为左上角坐标,w为目标区域宽度,h为目标区域高度

xc,yc,w,h

(xc,yc)为目标区域中心坐标,w为目标区域宽度,h为目标区域高度

常见的目标检测数据集:

  • VOC采用的[x1,y1,x2,y2],表示物体的最小外接矩形框,VOC数据指的是Pascal VOC比赛使用的数据。VOC数据是每个图像文件对应一个同名的xml文件,xml文件中标记物体框的坐标和类别等信息。
  • COCO采用的[x1,y1,w,h],表示物体的最小外接矩形框,COCO数据是COCO比赛使用的数据。以json文件记录数据格式。

LabelImg可以标注VOC格式的数据,对图像做目标框的标注。

LabelImg标注工具下载

  • 目标检测的数据标注,可以用LabelImg,下载地址:https://github.com/HumanSignal/labelImg/releases
  • 建议直接下载其可执行程序,而不是通过pip安装使用。
  • 是一款开源的图像标注工具,专门用于辅助计算机视觉任务的标注工作。它由python编写,并采用pyqt5作为图形用户界面的框架, LabelImg 是跨平台的,它可以在Windows、Linux 和 macOS 上运行,适应性强。

数据集准备与标注

目前对于菜品目标检测的已标注数据有2000+

打开labelImg,并打开images文件夹和xml文件夹,如下图所示:

点击画框create\nRectBox,调出十字光标,框”最小外接矩形框“,并输入标签,然后点击save,就保存了.xml标注文件

xml文件格式如下所示:

将LabelImg标注的数据集转为Yolo训练格式

yolo训练格式为与文件同名的.txt文件

0 0.5079166666666667 0.37916666666666665 0.0825 0.15
参数解释,从左到右,分别是:
class类名,例如:0表示label
中心横坐标与图像宽度比值
中心纵坐标与图像高度比值
bbox宽度与图像宽度比值
bbox高度与图像高度比值

知道规则后,我们使用如下脚本进行批量转换即可,核心代码如下所示:

import os
from lxml import etree
import shutil

def delete_folder(folder_path):
    try:
        shutil.rmtree(folder_path)
        print(f'文件夹: {folder_path} 所有内容已删除!')
    except Exception as e:
        print(f'删除文件夹及其所有内容报错: {e}')

def xml_to_yolotxt(source_path, label_path, cls=''):
    if not os.path.exists(label_path):
        os.mkdir(label_path)
        # 获取xml文件名称列表
    files = os.listdir(source_path)
    classes = get_classes(files, source_path)
    print('---获取所有xml文件中的cls=', classes)
    # classes = ['fire']
    # 生成分类字典,列如{'apple':0,'banana':1}
    class_dict = dict(zip(classes,range(len(classes))))
    print('------生成分类字典=', class_dict)
    # class_dict = {'fire': 1}
    # class_dict = {cls: 0}
    print('---------------begin convert----')
    count = 0
    if 1:
        for file in files:
            count = count + 1
            # print('------------', file)
            convert_xml2txt(file, source_path, label_path, class_dict, norm=True)
    print('---------------finish convert----,count=', count)

def convert_xml2txt(file_name, source_path, label_path, class_dict, norm=False):
    # 创建txt文件,并打开、写入
    new_name = file_name.split('.')[0] + '.txt'
    f = open(label_path+'/'+new_name,'w')
    print('-----------------------name=', new_name)
    with open(source_path+file_name,'rb') as fb:
        # 开始解析xml文件,获取图像尺寸
        xml = etree.HTML(fb.read())
        width = int(xml.xpath('//size/width/text()')[0])
        height = int(xml.xpath('//size/height/text()')[0])
        # 获取对象标签
        labels = xml.xpath('//object') # 单张图片中的目标数量 len(labels)
        for label in labels:
            name = label.xpath('./name/text()')[0]
            label_class = class_dict[name]
            xmin = int(label.xpath('./bndbox/xmin/text()')[0])
            xmax = int(label.xpath('./bndbox/xmax/text()')[0])
            ymin = int(label.xpath('./bndbox/ymin/text()')[0])
            ymax = int(label.xpath('./bndbox/ymax/text()')[0])
 
            # xyxy-->xywh,且归一化
            if norm :
                dw = 1 / width
                dh = 1 / height
                x_center = (xmin + xmax) / 2
                y_center = (ymax + ymin) / 2
                w = (xmax - xmin)
                h = (ymax - ymin)
                x, y, w, h = x_center * dw, y_center * dh, w * dw, h * dh
                f.write(str(label_class)+' '+str(x)+' '+str(y)+' '+str(w)+' '+str(h)+' '+'\n')
    #关闭文件
    f.close()
    
# 获取分类名称列表
def get_classes(files, source_path):
    class_set = set([])
    for file in files:
        with open(source_path+file,'rb') as fb:
            #解析xml文件
            xml = etree.HTML(fb.read())
            labels = xml.xpath('//object')
            for label in labels:
                name = label.xpath('./name/text()')[0] 
                class_set.add(name)
    return list(class_set)

if __name__ == '__main__':
    source_path = 'C:/mysel/food_plate_images/annotations_xmls/'
    label_path = 'C:/mysel/food_plate_images/labels/'
    delete_folder(label_path)
    xml_to_yolotxt(source_path, label_path)

Yolo

yolo训练目录文件结构

|── train    # 训练集
|   |── labels
│       ├── road0.txt
│       ├── road1.txt
│       ├── road2.txt
│       |   ...
|   |── images
│       ├── road0.jpg
│       ├── road1.jpg
│       ├── road2.jpg
│       |   ...
|── val  # 验证集
|   |── labels
│       ├── road3.txt
│       ├── ...
|   |── images
│       ├── road3.jpg
│       |   ...
|── test # 测试集
|   |── labels
│       ├── road4.txt
│       |   ...
|   |── images
│       ├── road4.jpg
│       |   ...

使用脚本分数据集

使用脚本将所有数据按照80%、10%、10%分成训练集、验证集、测试集

import os
import shutil
import random
from math import ceil

# 定义源文件夹路径和目标文件夹路径
source_path_images = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/food_plate_images/images/'
source_path_labels = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/food_plate_images/labels/'

# 80% 的文件
train_folder_images = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/yolo_images/train/images'
train_folder_labels = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/yolo_images/train/labels'

# 10% 的文件   
val_folder_images = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/yolo_images/val/images'
val_folder_labels = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/yolo_images/val/labels'

 # 10% 的文件
test_folder_images = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/yolo_images/test/images'
test_folder_labels = 'C:/ai_datasets/菜品目标检测数据集(餐盘标注2000+)/yolo_images/test/labels'

def delete_folder(folder_path):
    try:
        shutil.rmtree(folder_path)
        print(f'文件夹: {folder_path} 所有内容已删除!')
    except Exception as e:
        print(f'删除文件夹及其所有内容报错: {e}')
        
delete_folder(train_folder_images)
delete_folder(train_folder_labels)
delete_folder(val_folder_images)
delete_folder(val_folder_labels)
delete_folder(test_folder_images)
delete_folder(test_folder_labels)

# 确保目标文件夹存在,不存在则创建
os.makedirs(train_folder_images, exist_ok=True)
os.makedirs(train_folder_labels, exist_ok=True)

os.makedirs(val_folder_images, exist_ok=True)
os.makedirs(val_folder_labels, exist_ok=True)

os.makedirs(test_folder_images, exist_ok=True)
os.makedirs(test_folder_labels, exist_ok=True)

# 获取所有 .jpg 文件
jpg_files = [f for f in os.listdir(source_path_images) if f.endswith('.jpg')]
# 随机打乱文件顺序
random.shuffle(jpg_files)
# 计算每个文件夹的分配数量
total_files = len(jpg_files)
train_count = ceil(total_files * 0.8)
val_count = ceil(total_files * 0.1)
test_count = total_files - train_count - val_count

# 分配文件
train_files = jpg_files[:train_count]
val_files = jpg_files[train_count:train_count + val_count]
test_files = jpg_files[train_count + val_count:]

# 复制文件到对应文件夹
def copy_files(file_list, source_image_folder, destination_image_folder, source_txt_folder, dest_txt_folder):
    for file_name in file_list:
        print(f'处理---- file=', file_name)
        # 处理 .jpg 文件
        jpg_source_file = os.path.join(source_image_folder, file_name)
        jpg_destination_file = os.path.join(destination_image_folder, file_name)
        shutil.copy(jpg_source_file, jpg_destination_file)
        
        # 处理对应的 .txt 文件
        txt_file_name = file_name.replace('.jpg', '.txt')
        txt_source_file = os.path.join(source_txt_folder, txt_file_name)
        if os.path.exists(txt_source_file):  # 如果存在对应的 .txt 文件
            txt_destination_file = os.path.join(dest_txt_folder, txt_file_name)
            shutil.copy(txt_source_file, txt_destination_file)

# 执行复制
copy_files(train_files, source_path_images, train_folder_images, source_path_labels, train_folder_labels)
copy_files(val_files, source_path_images, val_folder_images, source_path_labels, val_folder_labels)
copy_files(test_files, source_path_images, test_folder_images, source_path_labels, test_folder_labels)

Yolo data.yaml

train: C:/xxx/菜品目标检测数据集2600+/images/train
val: C:/xxx/菜品目标检测数据集2600+/images/val
test: C:/xxx/菜品目标检测数据集2600+/images/test

nc: 1
names: ["label"]

最近发表
标签列表