从零开始制作自己的数据集(超详细,保姆级教学)

在这里插入图片描述

Github源码库
https://github.com/Seeed-Studio/OSHW-reCamera-Series
推荐阅读WIKI:
https://wiki.seeedstudio.com/real_time_yolo_object_detection_using_recamera_based_on_cpp/

内含超多AI Sensing相关资料,欢迎关注和讨论!将视觉检测算法真正部署于终端,将你的idea应用于生活。

数据集制作与获取

神经网络本质上是通过数据学习输入与输出之间映射关系的参数化函数。与人类不同,模型无法通过观察世界或逻辑推理获取知识,数据是其获取信息的唯一途径。例如ImageNet中的1,400万标注图像就是计算机视觉模型的"百科全书"。
获取数据集的方式可以分为自制数据集与Online Download网上标注好的数据集,接下来我们将分别讲解两种途径的具体方法。

下载公开数据集

通用目标检测数据集的选择

对于常见目标识别任务(如动物、食品、交通工具等类别),我们强烈建议优先使用成熟的公开数据集,原因如下:
1.质量保证:主流数据集(如COCO、PASCAL VOC等)经过学术社区多年验证标注工作由专业团队完成,标注一致性和准确性有保障,通常包含完整的标注格式文档和配套工具链。
2.效率优势:省去从零开始的数据采集和标注成本,可直接复用现有的数据预处理流程和模型配置,便于与其他研究进行横向对比。
典型数据集推荐:

  1. COCO (Common Objects in Context)
  • 330K+图像,80个日常物体类别
  • 提供实例分割、关键点等丰富标注
  1. Open Images V7
  • 1.7M训练图像,600个类别
  • 包含层级分类体系
  1. PASCAL VOC 2012
  • 经典基准数据集
  • 20个常见类别,标注密度高

特定场景下的数据集优化

当目标检测任务满足以下条件时,可考虑对公开数据集进行裁剪:任务定义明确且类别范围有限(如仅需检测"猫"/“狗”)存在明确的类别排除需求(如需要忽略人脸检测)
数据过滤方法:

# COCO数据集过滤示例(保留'dog','cat',排除'person')
from pycocotools.coco import COCO

coco = COCO('annotations/instances_train2017.json')
cat_ids = coco.getCatIds(catNms=['dog','cat'])
img_ids = coco.getImgIds(catIds=cat_ids)

# 进一步排除含人脸的图像
person_ids = coco.getCatIds(catNms=['person'])
person_img_ids = set(coco.getImgIds(catIds=person_ids))
filtered_ids = [id for id in img_ids if id not in person_img_ids]

具体方法

COCO数据集:
主要使用的是COCO2014和COCO2017,因为是国外数据集,因此下载需要翻墙下载。注意COCO数据集属于大体量数据库,可以识别80个种类,数据图片超过33万张,完整版数据集不适合用来完成简单的检测任务(如猫狗检测,食物腐烂检测,杀鸡用牛刀,会占用大量的计算资源与时间。)
COCO数据集的官网为:https://cocodataset.org/#download
Github网址 - https://github.com/cocodataset/cocoapi

下载完COCO2014后进行解压后,目录如下:

coco2014/                  # 数据集根目录
├── images/                # 所有图片文件
│   ├── train2014/         # 训练集图片 (82,783张)
│   │   ├── COCO_train2014_000000000001.jpg
│   │   ├── COCO_train2014_000000000002.jpg
│   │   └── ...            # 共82,783张JPEG图片
│   ├── val2014/           # 验证集图片 (40,504张)
│   │   ├── COCO_val2014_000000000001.jpg
│   │   └── ...            # 共40,504张JPEG图片
│   └── test2014/          # 测试集图片 (40,670张)
│       ├── COCO_test2014_000000000001.jpg
│       └── ...            # 共40,670张JPEG图片(无标注)
│
└── annotations/           # 所有标注文件
    ├── instances_train2014.json  # 训练集实例级标注(物体检测/分割)
    ├── instances_val2014.json    # 验证集实例级标注
    ├── captions_train2014.json   # 训练集标题标注(图像描述)
    ├── captions_val2014.json     # 验证集标题标注
    ├── person_keypoints_train2014.json  # 训练集人体关键点标注
    └── person_keypoints_val2014.json    # 验证集人体关键点标注

其中,images中的文件夹各自放置了训练、验证和测试的数据集图片。annotations文件夹中放置了标签文件,可以理解为Label,简要的来说,就是包含了某一类在图片中的具体位置的信息,

设置coco数据集以供训练:
首先,确保你的环境中安装了以下Python库:

pip install numpy matplotlib pycocotools

以下是数据准备预处理代码:
见文件夹的DatasetPre.py代码

"""
COCO数据集加载与预处理脚本
适用于YOLOv5模型训练前的数据准备
运行环境要求:pycocotools, torch>=1.7, opencv-python
"""

import os
import json
import numpy as np
import torch
import torch.nn.functional as F
from pycocotools.coco import COCO
from torch.utils.data import Dataset, DataLoader
import cv2
from sklearn.model_selection import train_test_split

# 1. 数据集路径配置
class CocoConfig:
    def __init__(self):
        # 修改为你的实际路径
        self.data_dir = '/path/to/coco'  # COCO根目录
        self.annotation_path = {
            'train': os.path.join(self.data_dir, 'annotations/instances_train2017.json'),
            'val': os.path.join(self.data_dir, 'annotations/instances_val2017.json')
        }
        self.image_dir = {
            'train': os.path.join(self.data_dir, 'images/train2017'),
            'val': os.path.join(self.data_dir, 'images/val2017')
        }
        
        # 训练参数
        self.img_size = 640  # YOLOv5默认输入尺寸
        self.batch_size = 16
        self.num_workers = 4  # 数据加载线程数

# 2. 自定义数据集类
class CocoDataset(Dataset):
    def __init__(self, coco: COCO, image_dir: str, img_size=640, augment=True):
        """
        Args:
            coco: pycocotools COCO对象
            image_dir: 图像目录路径
            img_size: 输出图像尺寸
            augment: 是否启用数据增强
        """
        self.coco = coco
        self.image_dir = image_dir
        self.img_size = img_size
        self.augment = augment
        self.ids = list(sorted(self.coco.imgs.keys()))
        
        # 类别ID映射(COCO原始ID转连续ID)
        self.cat_ids = coco.getCatIds()
        self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)}
        
        # 预定义颜色空间增强参数
        self.hsv_h = 0.015  # 色调变化幅度
        self.hsv_s = 0.7    # 饱和度变化幅度
        self.hsv_v = 0.4    # 明度变化幅度

    def __len__(self):
        return len(self.ids)

    def __getitem__(self, index):
        """
        返回格式:
            img: 归一化后的图像张量 (3, H, W)
            target: 标注张量 (N, 5) [x_center, y_center, width, height, class_id]
            img_info: 图像原始信息字典
        """
        coco = self.coco
        img_id = self.ids[index]
        
        # 2.1 加载图像
        img_info = coco.loadImgs(img_id)[0]
        img_path = os.path.join(self.image_dir, img_info['file_name'])
        img = cv2.imread(img_path)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # YOLOv5使用RGB格式
        
        # 2.2 加载标注
        ann_ids = coco.getAnnIds(imgIds=img_id)
        anns = coco.loadAnns(ann_ids)
        
        # 过滤无效标注(面积过小或为crowd区域)
        anns = [ann for ann in anns if ann['iscrowd'] == 0 and ann['area'] > 10]
        
        # 2.3 转换为YOLO格式标注
        boxes = []
        labels = []
        for ann in anns:
            # COCO格式: [x_min, y_min, width, height]
            x, y, w, h = ann['bbox']
            
            # 转换为YOLO格式: [x_center, y_center, width, height] (归一化坐标)
            x_center = (x + w / 2) / img.shape[1]
            y_center = (y + h / 2) / img.shape[0]
            box_w = w / img.shape[1]
            box_h = h / img.shape[0]
            
            # 验证标注有效性
            if box_w <= 0 or box_h <= 0:
                continue
                
            boxes.append([x_center, y_center, box_w, box_h])
            labels.append(self.cat2label[ann['category_id']])
        
        # 转换为numpy数组
        boxes = np.array(boxes, dtype=np.float32)
        labels = np.array(labels, dtype=np.int64)
        
        # 合并为YOLO格式: [x_center, y_center, width, height, class_id]
        if len(boxes) > 0:
            target = np.column_stack((boxes, labels))
        else:
            target = np.zeros((0, 5), dtype=np.float32)
        
        # 2.4 数据增强
        if self.augment:
            img, target = self.augment_hsv(img, target)
            img, target = self.random_flip(img, target)
        
        # 2.5 图像预处理
        img = self.preprocess(img)
        
        return torch.from_numpy(img), torch.from_numpy(target), img_info

    def augment_hsv(self, img, target):
        """HSV颜色空间增强"""
        if np.random.random() < 0.5:
            img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
            h, s, v = cv2.split(img_hsv)
            
            # 随机调整各通道
            h = (h * (1 + np.random.uniform(-self.hsv_h, self.hsv_h))).clip(0, 179)
            s = (s * (1 + np.random.uniform(-self.hsv_s, self.hsv_s))).clip(0, 255)
            v = (v * (1 + np.random.uniform(-self.hsv_v, self.hsv_v))).clip(0, 255)
            
            img_hsv = cv2.merge((h, s, v))
            img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2RGB)
        return img, target

    def random_flip(self, img, target):
        """随机水平翻转"""
        if np.random.random() < 0.5:
            img = cv2.flip(img, 1)
            if len(target) > 0:
                target[:, 0] = 1.0 - target[:, 0]  # 翻转x_center坐标
        return img, target

    def preprocess(self, img):
        """图像预处理流程"""
        # 调整尺寸并保持宽高比
        h, w = img.shape[:2]
        scale = min(self.img_size / h, self.img_size / w)
        new_h, new_w = int(h * scale), int(w * scale)
        
        img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
        
        # 填充到正方形
        top = (self.img_size - new_h) // 2
        bottom = self.img_size - new_h - top
        left = (self.img_size - new_w) // 2
        right = self.img_size - new_w - left
        
        img = cv2.copyMakeBorder(
            img, top, bottom, left, right, 
            cv2.BORDER_CONSTANT, value=(114, 114, 114)
        )
        
        # 归一化 [0,255] -> [0,1] 并转为CHW格式
        img = img.astype(np.float32) / 255.0
        img = img.transpose(2, 0, 1)  # HWC -> CHW
        
        return img

# 3. 数据加载器构建
def build_dataloader(config):
    """创建训练集和验证集的数据加载器"""
    # 加载COCO标注
    coco_train = COCO(config.annotation_path['train'])
    coco_val = COCO(config.annotation_path['val'])
    
    # 创建数据集
    train_dataset = CocoDataset(
        coco_train, config.image_dir['train'], 
        img_size=config.img_size, augment=True
    )
    val_dataset = CocoDataset(
        coco_val, config.image_dir['val'],
        img_size=config.img_size, augment=False
    )
    
    # 创建数据加载器
    train_loader = DataLoader(
        train_dataset,
        batch_size=config.batch_size,
        shuffle=True,
        num_workers=config.num_workers,
        pin_memory=True,
        collate_fn=collate_fn  # 自定义批次整理函数
    )
    
    val_loader = DataLoader(
        val_dataset,
        batch_size=config.batch_size,
        shuffle=False,
        num_workers=config.num_workers,
        pin_memory=True,
        collate_fn=collate_fn
    )
    
    return train_loader, val_loader

def collate_fn(batch):
    """
    自定义批次整理函数,处理不同数量的标注框
    输入: list of (img, target, img_info)
    输出: 
        imgs: 图像张量 (B, 3, H, W)
        targets: 列表长度B,每个元素是(N, 5)的标注张量
        img_infos: 图像信息列表
    """
    imgs, targets, img_infos = zip(*batch)
    imgs = torch.stack(imgs, 0)  # 堆叠图像
    return imgs, targets, img_infos

# 4. 主程序
if __name__ == '__main__':
    # 初始化配置
    config = CocoConfig()
    
    # 构建数据加载器
    train_loader, val_loader = build_dataloader(config)
    
    # 示例:遍历训练集
    print(f"训练集批次数量: {len(train_loader)}")
    for batch_idx, (imgs, targets, _) in enumerate(train_loader):
        print(f"批次 {batch_idx}:")
        print(f"  图像尺寸: {imgs.shape}")  # 应为 (B, 3, H, W)
        print(f"  标注示例: 第一个图像有 {len(targets[0])} 个标注")
        
        if batch_idx >= 2:  # 只查看前3个批次
            break

需要根据不同的模型修改输入图片的尺寸。
如果是使用云平台(如Nvidia GPU Clouds)训练代码,则下载好数据集分好类和文件夹位置就好。

云训练平台 Roboflow 下载数据集

Roboflow 推理服务器是一个易于使用的、面向生产环境的推理服务器,支持多种流行的计算机视觉模型架构和微调后的模型部署。用户可以上传自己的数据集进行标注,也可以下载公开数据集再进行标注,之后利用平台的云服务器进行模型训练。
平台网站:https://roboflow.com/

工作界面
在这里插入图片描述

左侧工作台创建项目
在这里插入图片描述

工作台界面,在此可以上传自己的数据集(里面包含了训练集、测试集等,每个文件夹里包含了images和labels。上传完会显示已标注(如果只有图片则是未标注))
在这里插入图片描述

下载公开数据集
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

添加预处理步骤

可以选择下载数据集到本地用本地资源进行训练
在这里插入图片描述

之后会推荐模型给你使用,选择好之后就会自动开启训练
在这里插入图片描述

实时查看训练进度
在这里插入图片描述

模型测试
在这里插入图片描述

弊端在于无法生成权重文件,后续调用只能在云平台运行,我们可以只将其作为数据集下载与标注平台使用,使用本地计算资源进行模型训练

自制数据集

有些特定的任务或领域没有现成的公开数据集可用,或者现有数据集无法满足特定需求。自制数据集可以确保数据的质量和相关性,从而提高模型的性能。

爬取图像

爬取图像是自制数据集的常用方法之一。可以使用Python的爬虫库(如Scrapy、BeautifulSoup等)从互联网上抓取特定类别的图像。
以下是一个简单的爬虫示例,使用requests和BeautifulSoup库从Google图片搜索中爬取图像:

import os
import requests
import random
import time
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm
import shutil
from bs4 import BeautifulSoup
from urllib.parse import quote  

# 配置项
class Config:
    # 每个类别的图片数量
    CATEGORIES = {
        'chair': 2000,
        'sofa': 2000,
        'table': 1000


    }
    SEARCH_ENGINES = [
        'https://www.bing.com/images/search?q={}&first={}',
        'https://image.baidu.com/search/flip?tn=baiduimage&word={}&pn={}'
    ]
    USER_AGENTS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15'
    ]
    MAX_WORKERS = 5
    TIMEOUT = 10
    SAVE_PATH = 'data'
    DELAY = (1, 3)  # 随机延迟范围(秒)

# 创建存储目录
def create_dirs():
    os.makedirs(Config.SAVE_PATH, exist_ok=True)

# 获取随机请求头
def get_headers():
    return {
        'User-Agent': random.choice(Config.USER_AGENTS),
        'Accept': 'image/webp,*/*',
        'Referer': 'https://www.google.com/'
    }

# 下载单张图片
def download_image(url, filename):
    try:
        # 跳过已存在的文件
        save_path = os.path.join(Config.SAVE_PATH, filename)
        if os.path.exists(save_path):
            return False
            
        # 下载图片
        response = requests.get(
            url, 
            headers=get_headers(),
            stream=True,
            timeout=Config.TIMEOUT
        )
        
        if response.status_code == 200:
            with open(save_path, 'wb') as f:
                response.raw.decode_content = True
                shutil.copyfileobj(response.raw, f)
                
            # 验证图片有效性(大于5KB)
            if os.path.getsize(save_path) > 5120:
                return True
            os.remove(save_path)
    except Exception:
        pass
    return False

# 爬取单个类别
def crawl_category(category, count):
    downloaded = 0
    page = 0
    progress = tqdm(total=count, desc=f"{category}")
    
    while downloaded < count:
        try:
            # 选择搜索引擎
            engine_url = random.choice(Config.SEARCH_ENGINES)
            if 'bing.com' in engine_url:
                url = engine_url.format(quote(category), page*30)
            else:
                url = engine_url.format(quote(category), page*20)
            
            # 获取页面内容
            response = requests.get(url, headers=get_headers(), timeout=Config.TIMEOUT)
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # 提取图片URL(简化版解析)
            img_urls = []
            for img in soup.find_all('img'):
                src = img.get('src') or img.get('data-src') or img.get('data-imgurl')
                if src and src.startswith('http'):
                    img_urls.append(src)
            
            # 多线程下载
            with ThreadPoolExecutor(max_workers=min(Config.MAX_WORKERS, len(img_urls))) as executor:
                futures = []
                for url in img_urls:
                    if downloaded >= count:
                        break
                    filename = f"{category}_{downloaded+1}.jpg"
                    futures.append(executor.submit(download_image, url, filename))
                
                for future in futures:
                    if future.result():
                        downloaded += 1
                        progress.update(1)
            
            page += 1
            time.sleep(random.uniform(*Config.DELAY))
            
        except Exception as e:
            time.sleep(2)
            continue

# 主函数
def main():
    create_dirs()
    print("🚀 开始爬取家具图片...")
    
    with ThreadPoolExecutor(max_workers=3) as executor:
        futures = []
        for category, count in Config.CATEGORIES.items():
            futures.append(executor.submit(crawl_category, category, count))
        
        for future in futures:
            future.result()
    
    # 统计结果
    print("\n📊 下载完成统计:")
    for category in Config.CATEGORIES:
        count = len([f for f in os.listdir(Config.SAVE_PATH) if f.startswith(category)])
        print(f"{category.rjust(6)}: {count}张")
    
    print(f"\n保存路径: {os.path.abspath(Config.SAVE_PATH)}")

if __name__ == "__main__":
    main()

运行结果如下
在这里插入图片描述
在这里插入图片描述

自行拍摄

针对身边实物(无网络现成图片)的数据集制作,以下是系统化的解决方案:


图像采集设备准备
  • 手机/相机:优先使用高分辨率设备,确保光线充足时拍摄。
  • 三脚架+遥控器:固定设备避免抖动,遥控拍摄减少人为干扰(可用手机蓝牙遥控或定时快门)。
  • 多角度支架:若物体可旋转(如杯子),使用转盘固定物体,实现自动多角度拍摄。

拍摄策略设计
  • 多角度:绕物体360°拍摄(每30°一张,共12张),俯视+平视+仰视。
  • 多距离:近景(细节)、中景(整体)、远景(环境关系)。
  • 多光照:自然光(白天窗边)、室内灯光(暖光/冷光)、阴影对比。
  • 背景变化:同一物体在不同背景(纯色布、桌面、户外)下拍摄,增强泛化性。
  • 删除模糊、过曝、反光严重的图片
  • 确保物体始终在画面中心,占画面比例一致(如60%-80%)。

录像后抽帧形成数据集
  • 录像:使用手机或相机录制视频,确保稳定性和清晰度。
  • 抽帧:使用Python脚本从视频中提取关键帧,确保每帧图像质量良好。

当你使用不同的图像尺寸(例如1280)进行预测时,YOLOv8会自动对输入图像进行适当的预处理以适配模型。这通常包括缩放和填充操作,确保图像不会发生畸变,同时保持原始宽高比。

对于使用OpenCV进行预处理,你可以按照以下步骤来模拟YOLOv8的预处理过程:

保持图像的宽高比,将图像缩放到模型的输入尺寸(例如640x640)中较短的一边。对缩放后的图像进行填充,以达到所需的输入尺寸,通常填充的是图像的右侧和底部。确保填充值(通常是灰色,即(114, 114, 114))与训练时使用的填充值相匹配。

import cv2
import os

def extract_frames_yolo_style(
    video_path, 
    output_folder, 
    class_name="object",  # 默认类别名称(可修改为你需要的类别,如"chair")
    frame_interval=30, 
    max_frames=None,
    target_size=(640, 640),
    fill_color=(114, 114, 114)
):
    """
    从视频中抽帧并模拟YOLOv8的预处理,输出文件名为"类别+编号.jpg"
    :param class_name: 物体类别名称(如"chair")
    """
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        print(f"无法打开视频: {video_path}")
        return

    frame_count = 0
    saved_count = 0

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

        if frame_count % frame_interval == 0:
            # YOLOv8风格预处理
            frame = preprocess_yolo_style(frame, target_size, fill_color)

            # 生成自定义文件名(类别+编号)
            filename = f"{class_name}{saved_count + 1}.jpg"  # 编号从1开始
            output_path = os.path.join(output_folder, filename)
            
            cv2.imwrite(output_path, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 95])
            saved_count += 1

            if max_frames and saved_count >= max_frames:
                break

        frame_count += 1

    cap.release()
    print(f"完成!共抽取 {saved_count} 张图片到 {output_folder}")

def preprocess_yolo_style(frame, target_size, fill_color):
    """YOLOv8风格的预处理(保持宽高比缩放+灰色填充)"""
    h, w = frame.shape[:2]
    target_w, target_h = target_size
    scale = min(target_w / w, target_h / h)
    new_w, new_h = int(w * scale), int(h * scale)
    resized = cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_AREA)
    pad_w = max(target_w - new_w, 0)
    pad_h = max(target_h - new_h, 0)
    top, bottom = pad_h // 2, pad_h - (pad_h // 2)
    left, right = pad_w // 2, pad_w - (pad_w // 2)
    padded = cv2.copyMakeBorder(
        resized, 
        top, bottom, left, right, 
        cv2.BORDER_CONSTANT, 
        value=fill_color
    )
    return padded

# 使用示例
video_file = "testvideo.mp4"
output_dir = "extracted_frames_chair"
extract_frames_yolo_style(
    video_file, 
    output_dir, 
    class_name="chair",  # 指定类别名称为"chair"
    frame_interval=30,
    target_size=(640, 640)
)

以下图片是处理后得到的视频抽帧图像:
在这里插入图片描述

在这里插入图片描述

数据集预处理

将采集到的图像进行预处理是数据集制作的重要步骤。预处理可以包括图像的裁剪、缩放、旋转、翻转等操作,以增强模型的鲁棒性和泛化能力。同时也对它们进行编号和分类,以便后续的训练和测试。

import os
import cv2
import random
import requests
import shutil
from tqdm import tqdm
import numpy as np


# 配置项
class Config:
    INPUT_DIR = "data"  # 原始数据路径
    OUTPUT_DIR = "processed_data"  # 输出路径
    TARGET_SIZE = (640, 640)  # 目标尺寸
    CLASS_MAP = {  # 文件名前缀映射
        "chair": "chair",
        "table": "table",
        "sofa": "sofa"
    }

def preprocess_image(img_path, save_path):
    """图像预处理流水线"""
    try:
        # 1. 读取图像 (自动处理中文路径)
        img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), cv2.IMREAD_COLOR)
        if img is None:
            return False

        # 2. 调整尺寸 (保持比例并填充)
        h, w = img.shape[:2]
        scale = min(Config.TARGET_SIZE[0]/h, Config.TARGET_SIZE[1]/w)
        new_h, new_w = int(h*scale), int(w*scale)
        img_resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)

        # 3. 填充到目标尺寸
        top = (Config.TARGET_SIZE[0] - new_h) // 2
        bottom = Config.TARGET_SIZE[0] - new_h - top
        left = (Config.TARGET_SIZE[1] - new_w) // 2
        right = Config.TARGET_SIZE[1] - new_w - left
        img_padded = cv2.copyMakeBorder(
            img_resized, top, bottom, left, right, 
            cv2.BORDER_CONSTANT, value=(114, 114, 114)  # 灰色填充
        )

        # 4. 可选: 图像增强
        img_padded = apply_augmentations(img_padded)

        # 5. 保存处理后的图像 (支持中文路径)
        cv2.imencode('.jpg', img_padded)[1].tofile(save_path)
        return True

    except Exception as e:
        print(f"处理失败 {img_path}: {str(e)}")
        return False

def classify_and_rename_files():
    """分类并重命名文件"""
    # 创建输出目录
    os.makedirs(Config.OUTPUT_DIR, exist_ok=True)
    
    # 初始化计数器
    counters = {cls: 1 for cls in Config.CLASS_MAP.values()}
    
    # 遍历原始文件
    for filename in tqdm(os.listdir(Config.INPUT_DIR), desc="处理进度"):
        if not filename.lower().endswith(('.png', '.jpg', '.jpeg')):
            continue
            
        # 1. 确定类别
        file_lower = filename.lower()
        for key in Config.CLASS_MAP:
            if key in file_lower:
                cls = Config.CLASS_MAP[key]
                break
        else:  # 未匹配到类别
            cls = "other"
            if cls not in counters:
                counters[cls] = 1

        # 2. 生成新文件名
        new_name = f"{cls}{counters[cls]}.jpg"
        counters[cls] += 1

        # 3. 处理并保存
        src_path = os.path.join(Config.INPUT_DIR, filename)
        dst_path = os.path.join(Config.OUTPUT_DIR, new_name)
        if not preprocess_image(src_path, dst_path):
            counters[cls] -= 1  # 回滚计数器

def apply_augmentations(img):
    """可选: 图像增强"""
    # 1. 颜色空间增强
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    img_hsv = img_hsv.astype("float32")
    img_hsv[..., 1] *= random.uniform(0.8, 1.2)  # 随机调整饱和度
    img_hsv[..., 2] *= random.uniform(0.9, 1.1)  # 随机调整亮度
    img_hsv = np.clip(img_hsv, 0, 255)
    img_hsv = img_hsv.astype("uint8")
    img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)

    # 2. 随机水平翻转
    if random.random() > 0.5:
        img = cv2.flip(img, 1)

    return img

if __name__ == "__main__":
    import numpy as np  # 用于图像解码
   
    classify_and_rename_files()
    
    # 打印统计结果
    print("\n处理结果统计:")
    for cls in Config.CLASS_MAP.values():
        count = len([f for f in os.listdir(Config.OUTPUT_DIR) if f.startswith(cls)])
        print(f"{cls.rjust(6)}: {count}张")
    print(f"\n输出目录: {os.path.abspath(Config.OUTPUT_DIR)}")

本地标注数据集

下载离线标注软件Labelimg

pip install labelimg

#将以下路径添加到系统环境变量 PATH 中:
C:\Users\seeed\AppData\Roaming\Python\Python313\Scripts #地址更换为你的Python/Scripts文件夹



#win+R cmd 直接输入labelimg 来启动软件标注程序
labelimg

#如果在标注时发生了闪退,可能是因为python版本过高导致的,可以尝试使用Python 3.7或3.8版本。
# 创建新的虚拟环境(推荐)
conda create -n labelimg_env python=3.8
conda activate labelimg_env

# 安装labelImg
pip install labelImg

# 运行
labelImg

labelimg标注软件界面,左侧"Open Dir"打开图片所在的文件夹,右侧"Change Save Dir"选择保存标注文件的目录,点击左上角的"Create RectBox"开始标注,标注完成后点击左上角的Save按钮保存标注结果。点击YOLO按钮可以选择YOLO格式保存标注文件,点击PascalVOC按钮可以选择Pascal VOC格式保存标注文件。
快捷键w创建矩形框。
快捷键操作
Ctrl + u 选择要标注的文件目录;
Ctrl + r 选择标注好的标签存放的目录;
Ctrl + s 保存标注好的标签(自动保存模式下会自动保存);
Ctrl + d 复制当前标签和矩形框;
Ctrl + Shift + d 删除当前图片;
Space 将当前图像标记为已验证;
w 开始创建矩形框;
d 切换到下一张图;
a 切换到上一张图;
del 删除选中的标注矩形框;
Ctrl++ 放大图片;
Ctrl-- 缩小图片;
↑→↓← 移动选中的矩形框的位置;

在这里插入图片描述

标注后会获得xml文件
格式:
<class_id> <x_center> <y_center> width height

在这里插入图片描述

数据集划分

使用代码进行数据集划分,通常将数据集划分为训练集、验证集和测试集。以下是一个简单的Python脚本示例,用于将数据集划分为训练集、验证集和测试集:

import os
import random
import shutil
from tqdm import tqdm

# ====================== 配置部分 ======================
# 原始数据路径(包含图片和YOLO格式的TXT文件的文件夹)
INPUT_DIR = r"E:\Important_Project\从零开始的模型部署-recameraV1\数据集制作\processed_data"  # 替换为您的原始数据文件夹路径

# 划分比例 (train:valid:test)
SPLIT_RATIO = (0.7, 0.2, 0.1)  # 训练集70%,验证集20%,测试集10%

# 输出根目录
OUTPUT_ROOT = "dataset"  # 最终会生成dataset/train, dataset/valid, dataset/test

# 类别列表 (根据您的需求手动指定,因为YOLO格式的TXT文件不包含类别名称)
# 如果已知类别,可以直接指定:
CLASS_NAMES = ["chair", "sofa", "table"]  # 根据您的实际情况修改

# 类别到ID的映射
CLASS_TO_ID = {name: idx for idx, name in enumerate(CLASS_NAMES)}

# ====================== 开始处理 ======================
def create_dataset_structure():
    """创建数据集目录结构"""
    # 创建主目录
    os.makedirs(OUTPUT_ROOT, exist_ok=True)
    
    # 创建train/valid/test子目录
    for split in ['train', 'valid', 'test']:
        os.makedirs(os.path.join(OUTPUT_ROOT, split, 'images'), exist_ok=True)
        os.makedirs(os.path.join(OUTPUT_ROOT, split, 'labels'), exist_ok=True)
    
    return OUTPUT_ROOT

def get_image_txt_pairs(input_dir):
    """获取所有图片和对应的YOLO格式TXT文件对"""
    image_files = []
    txt_files = []
    
    # 获取所有图片文件
    image_extensions = ['jpg', 'jpeg', 'png', 'JPG', 'JPEG', 'PNG']  # 支持多种图片扩展名
    for ext in image_extensions:
        image_files.extend([f for f in os.listdir(input_dir) if f.lower().endswith(f'.{ext}')])
    
    # 获取所有TXT文件
    txt_files = [f for f in os.listdir(input_dir) if f.lower().endswith('.txt')]
    
    print("\n调试信息:")
    print(f"找到的图片文件 ({len(image_files)} 个): {image_files}")
    print(f"找到的TXT文件 ({len(txt_files)} 个): {txt_files}")
    
    # 匹配图片和TXT文件
    pairs = []
    for txt_file in txt_files:
        # 假设TXT文件名与图片文件名相同(仅扩展名不同)
        img_name = os.path.splitext(txt_file)[0]  # 去掉.txt扩展名
        # 查找匹配的图片文件(可能有多种扩展名)
        for ext in image_extensions:
            possible_img = f"{img_name}.{ext}"
            if possible_img in image_files:
                pairs.append((possible_img, txt_file))
                break  # 找到匹配的图片后跳出循环
    
    print(f"匹配到的文件对 ({len(pairs)} 对): {pairs}")
    
    return pairs

def split_dataset(pairs):
    """随机划分数据集"""
    random.shuffle(pairs)
    total = len(pairs)
    
    # 计算各部分数量
    train_num = int(total * SPLIT_RATIO[0])
    valid_num = int(total * SPLIT_RATIO[1])
    test_num = total - train_num - valid_num
    
    # 划分数据集
    train_pairs = pairs[:train_num]
    valid_pairs = pairs[train_num:train_num+valid_num]
    test_pairs = pairs[train_num+valid_num:]
    
    return train_pairs, valid_pairs, test_pairs

def copy_files(pairs, split_name, output_root):
    """复制文件到指定目录"""
    images_dir = os.path.join(output_root, split_name, 'images')
    labels_dir = os.path.join(output_root, split_name, 'labels')
    
    for img_file, txt_file in tqdm(pairs, desc=f"Processing {split_name} set"):
        # 复制图片
        src_img = os.path.join(INPUT_DIR, img_file)
        dst_img = os.path.join(images_dir, img_file)
        shutil.copy2(src_img, dst_img)
        
        # 复制TXT文件
        src_txt = os.path.join(INPUT_DIR, txt_file)
        dst_txt = os.path.join(labels_dir, txt_file)
        shutil.copy2(src_txt, dst_txt)

def generate_yaml(class_names):
    """生成YOLO格式的yaml配置文件"""
    yaml_content = f"""train: {os.path.abspath(os.path.join(OUTPUT_ROOT, 'train'))}/images
val: {os.path.abspath(os.path.join(OUTPUT_ROOT, 'valid'))}/images
test: {os.path.abspath(os.path.join(OUTPUT_ROOT, 'test'))}/images

nc: {len(class_names)}
names: {class_names}
"""
    
    yaml_file = os.path.join(OUTPUT_ROOT, 'dataset.yaml')
    with open(yaml_file, 'w', encoding='utf-8') as f:
        f.write(yaml_content)
    
    print(f"YAML文件已生成: {yaml_file}")

def main():
    # 1. 创建数据集目录结构
    output_root = create_dataset_structure()
    
    # 2. 获取所有图片和TXT文件对
    print("正在扫描图片和TXT文件...")
    pairs = get_image_txt_pairs(INPUT_DIR)
    print(f"找到 {len(pairs)} 对图片和TXT文件")
    
    if len(pairs) == 0:
        print("❌ 未找到任何图片和TXT文件对!请检查:")
        print("1. 确保INPUT_DIR路径正确")
        print("2. 确保图片和TXT文件同名(仅扩展名不同)")
        return
    
    # 3. 划分数据集
    print("正在划分数据集...")
    train_pairs, valid_pairs, test_pairs = split_dataset(pairs)
    print(f"划分结果: 训练集 {len(train_pairs)} 对, 验证集 {len(valid_pairs)} 对, 测试集 {len(test_pairs)} 对")
    
    # 4. 复制文件到对应目录
    print("正在复制训练集文件...")
    copy_files(train_pairs, 'train', output_root)
    
    print("正在复制验证集文件...")
    copy_files(valid_pairs, 'valid', output_root)
    
    print("正在复制测试集文件...")
    copy_files(test_pairs, 'test', output_root)
    
    # 5. 生成YAML文件
    generate_yaml(CLASS_NAMES)
    
    print("\n数据集划分完成!")
    print(f"输出目录: {os.path.abspath(OUTPUT_ROOT)}")
    print(f"类别列表: {CLASS_NAMES}")

if __name__ == "__main__":
    main()

划分后的文件夹结构如下:

在这里插入图片描述

然后就可以直接执行yolo训练命令了,注意修改yaml文件的路径为划分后的数据集yaml文件路径。

使用Roboflow云平台进行标注与数据集导出

可以在Roboflow上进行数据集的标注,Roboflow提供了一个易于使用的界面来标注图像数据集。用户可以上传自己的图像,然后使用Roboflow的工具进行标注。标注完成后,可以将数据集导出为多种格式(如YOLO、COCO等),以便在不同的机器学习框架中使用。
导入目标文件夹后,双击任何一张图片即可进入标注,这里我们以第一张图片为例,操作步骤如图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图片标注完成,返回后点击右上方进行保存。选择“Split Images Between Train/Vaild/Test”,根据系统推荐自动划分训练集、验证集和测试集,最后导出文件压缩包至电脑。
在这里插入图片描述
在这里插入图片描述

建立一个BBS论坛需要考虑以下几个方面: 1. 确定论坛类型和功能 2. 设计数据库结构 3. 编写前端界面 4. 编写后端逻辑 5. 部署和维护 下面是一个从零开始基于Python开发BBS论坛的保姆教学: 1. 确定论坛类型和功能 首先要确定你要创建的论坛类型和功能。是一个简单的问答论坛,还是一个社区论坛,还是一个资讯论坛?你需要确定你要实现的功能,例如用户注册、登录、发帖、回复、私信、搜索等等。 2. 设计数据库结构 我们需要设计数据库来存储用户信息、论坛帖子、回复等数据。可以使用MySQL、SQLite等关系型数据库。设计数据库结构时需要考虑到数据的关联性和一致性。 3. 编写前端界面 接下来,我们需要设计前端界面,包括网页布局、样式和交互效果。可以使用HTML、CSS和JavaScript等技术来实现。建议使用Bootstrap等前端框架来快速搭建界面。 4. 编写后端逻辑 编写后端逻辑需要使用Python开发框架,比如Django、Flask等。后端逻辑包括用户认证、数据读写、业务逻辑等。我们需要根据前端界面设计API接口,通过Python编写后端逻辑来实现这些功能。 5. 部署和维护 最后,我们需要将BBS论坛部署到服务器上,并对其进行维护。可以选择使用云服务器,比如AWS、阿里云等。并且需要定期更新代码、备份数据和优化性能等。 总结: 以上就是基于Python开发BBS论坛的保姆教学。需要注意的是,BBS论坛涉及到用户隐私和数据安全等问题,需要谨慎处理。另外,由于开发BBS论坛需要包括前端、后端和数据库等多个方面的知识,所以建议有一定的编程经验的人才尝试开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值