Ubuntu安装YOLOv8进行菌落识别训练

文章目录

  • 一、概述
  • 二、安装YOLOv8
    • [2.1 在家目录创建python虚拟环境](#2.1 在家目录创建python虚拟环境)
    • [2.2 安装YOLOv8](#2.2 安装YOLOv8)
    • [2.3 验证YOLO](#2.3 验证YOLO)
  • 三、配置训练项目
    • [3.1 配置项目目录](#3.1 配置项目目录)
    • [3.2 添加数据集配置colony.yaml](#3.2 添加数据集配置colony.yaml)
    • [3.3 编写混放数据拆分脚本scripts/split_mixed.py](#3.3 编写混放数据拆分脚本scripts/split_mixed.py)
    • [3.4 添加校验计数脚本scripts/check_labels.py](#3.4 添加校验计数脚本scripts/check_labels.py)
    • [3.5 添加训练命令脚本](#3.5 添加训练命令脚本)
    • [3.6 添加导出ONNX脚本scripts/export_onnx.py](#3.6 添加导出ONNX脚本scripts/export_onnx.py)
  • 四、训练
    • [4.1 下载预训练权重](#4.1 下载预训练权重)
    • [4.2 安装依赖环境](#4.2 安装依赖环境)
    • [4.3 自动化训练脚本](#4.3 自动化训练脚本)
    • [4.4 数据准备](#4.4 数据准备)
    • [4.5 训练](#4.5 训练)

生命练习生

诗号:

①步乱世之劫,横扫武道顶峰;辟黑夜之光,一问天下英雄。

②征尘幂,孤锋披,今古几人堪敌兮,仰空长啸万里,再战天下境逆。

③半壁翻云刃,满江酩酊红。千古光阴一霎时,且醉浪涛中。

来历成谜的枪界高人,为人正直勇敢、豪放不羁,枪法修为高深莫测,外表看似黩武无文,实则博览群书,常弄墨吟诗,自有一套探索生命的人生哲理,坚持以生命练习追求武道巅峰。

一、概述

YOLOv8 是由 Ultralytics 公司(YOLOv5 的开发者)于 2023 年 1 月 发布的目标检测模型,是该系列的最新旗舰版本。它并非由 YOLO 原作者 Joseph Redmon 或 YOLOv4 团队开发,而是 Ultralytics 基于 YOLOv5 架构深度改进的成果。

YOLOv8 原生支持多种视觉任务,无需切换代码库:

  • 目标检测(Detection)
  • 实例分割(Segmentation)
  • 姿态估计(Pose/Keypoints)
  • 图像分类(Classification)
  • 旋转框检测(OBB,后续版本加入)

配置环境:

  • 系统:Ubuntu 22.04.4 LTS
  • 主机:dell-PowerEdge-R740
  • Linux内核:6.8.0-138-generic

二、安装YOLOv8

2.1 在家目录创建python虚拟环境

需要先安装python3,可以直接安装即可。

通过"cd ~"切换到家目录,避开权限要求。

bash 复制代码
cd ~
python3 -m venv yolov8_env

安装后启动下虚拟环境,虚拟环境下的activate

bash 复制代码
source ~/yolov8_env/bin/activate

控制台显示虚拟环境的名字就标识虚拟环境启动成功了:

查看python是否时虚拟环境的python

bash 复制代码
which python

添加虚拟环境别名,方便后续调用

bash 复制代码
# 添加别名
echo "alias yoloenv='source ~/yolov8_env/bin/activate'" >> ~/.bashrc
# 生效别名
source ~/.bashrc
# 查看别名是否注册
alias yoloenv

直接输入别名,即可使用别名,会自动激活环境

bash 复制代码
yoloenv

2.2 安装YOLOv8

接上步,激活虚拟环境

升级pip

bash 复制代码
pip install --upgrade pip

安装PyTorch

bash 复制代码
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121

等待安装完毕,时间有点久,慢慢等

安装ultralytics

这个要很久,非常久,慢慢等吧。

bash 复制代码
pip install ultralytics

2.3 验证YOLO

bash 复制代码
# 用别称激活虚拟环境
yoloenv 
# 导入库
python -c "import torch; print('PyTorch:', torch.__version__); print('CUDA:', torch.cuda.is_available()); print('GPU:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'N/A')"
# 查看yolo版本
yolo version

如下图,虚拟环境正常启动,相关类库安装完毕:

三、配置训练项目

3.1 配置项目目录

在账号目录下创建项目文件夹

text 复制代码
~/colony_yolov8/
├── images/
│   ├── train/                     # 训练图
│   └── val/                       # 验证图
├── labels/
│   ├── train/                     # 训练标注(与图同名)
│   └── val/
├── colony.yaml                    # 数据集配置
├── scripts/                       # 工具脚本
│   ├── split_mixed.py             # 混放数据 → 标准结构
│   ├── check_labels.py            # 标注校验 + 每类统计
│   ├── train.sh                   # 训练命令
│   └── export_onnx.py             # 导出 ONNX(后续部署用)
└── runs/                          # 训练输出(自动生成)
~/colony_raw/										# 原始数据文件夹

生成项目目录:

bash 复制代码
mkdir -p ~/colony_yolov8/{images/{train,val},labels/{train,val},scripts,runs}

创建完毕后可以输出下文件夹结构,确认下:

bash 复制代码
cd ~/colony_yolov8
ls -R

这是项目文件夹结构:

生成原始数据目录:

bash 复制代码
mkdir ~/colony_raw

3.2 添加数据集配置colony.yaml

直接添加配置

bash 复制代码
cat > ~/colony_yolov8/colony.yaml << 'EOF'
# YOLOv8 7 类菌落数据集配置
path: /home/dell/colony_yolov8
train: images/train
val: images/val

names:
  0: TPC
  1: Mold
  2: Yeast
  3: Coliforms
  4: TAB
  5: TMold
  6: ARB
EOF

3.3 编写混放数据拆分脚本scripts/split_mixed.py

参考YOLO的推荐,将原始数据图和 txt 混放在一个目录的,拆到 images/ + labels/,并按 8:2 分 train/val。

创建py文件,然后将脚本写进去:

python 复制代码
#!/usr/bin/env python3
"""
把混放的图+txt 拆到 YOLOv8 标准结构(images/ 和 labels/ 分离),并按比例分 train/val。
用法:
  python split_mixed.py --src ~/colony_raw --dst ~/colony_yolov8 --val-ratio 0.2
"""
import random, shutil, argparse, sys
from pathlib import Path

def main():
    p = argparse.ArgumentParser()
    p.add_argument("--src", default=str(Path.home() / "colony_raw"))
    p.add_argument("--dst", default=str(Path.home() / "colony_yolov8"))
    p.add_argument("--val-ratio", type=float, default=0.2)
    p.add_argument("--seed", type=int, default=42)
    a = p.parse_args()

    src = Path(a.src)
    dst = Path(a.dst)
    random.seed(a.seed)

    for s in ["train", "val"]:
        (dst / "images" / s).mkdir(parents=True, exist_ok=True)
        (dst / "labels" / s).mkdir(parents=True, exist_ok=True)

    imgs = sorted([p for p in src.iterdir() if p.suffix.lower() in (".jpg", ".jpeg", ".png")])
    if not imgs:
        print(f"❌ {src} 里没有图片")
        sys.exit(1)

    random.shuffle(imgs)
    n_val = int(len(imgs) * a.val_ratio)

    missing = []
    for i, img in enumerate(imgs):
        txt = img.with_suffix(".txt")
        if not txt.exists():
            missing.append(img.name)
            continue
        split = "val" if i < n_val else "train"
        shutil.copy2(img, dst / "images" / split / img.name)
        shutil.copy2(txt, dst / "labels" / split / txt.name)

    n_train = len(imgs) - n_val - len(missing)
    print(f"✅ train: {n_train}  val: {n_val}  缺标注跳过: {len(missing)}")
    if missing:
        print("缺标注示例:", missing[:5])

if __name__ == "__main__":
    main()

3.4 添加校验计数脚本scripts/check_labels.py

python 复制代码
#!/usr/bin/env python3
"""校验 7 类标注格式 + 统计每类数量"""
from pathlib import Path
from collections import Counter
import sys

ROOT = Path.home() / "colony_yolov8"
NAMES = ["TPC", "Mold", "Yeast", "Coliforms", "TAB", "TMold", "ARB"]

def check(split):
    img_dir = ROOT / "images" / split
    lbl_dir = ROOT / "labels" / split
    imgs = {p.stem for p in img_dir.glob("*.*") if p.suffix.lower() in (".jpg", ".jpeg", ".png")}
    txts = {p.stem for p in lbl_dir.glob("*.txt")}

    print(f"\n=== {split} ===")
    print(f"图: {len(imgs)}  标注: {len(txts)}")

    miss_lbl = imgs - txts
    miss_img = txts - imgs
    if miss_lbl:
        print(f"❌ 缺标注 {len(miss_lbl)} 张: {sorted(miss_lbl)[:5]}")
    if miss_img:
        print(f"❌ 缺图 {len(miss_img)} 个: {sorted(miss_img)[:5]}")

    counter = Counter()
    errs = 0
    for txt in lbl_dir.glob("*.txt"):
        for ln, line in enumerate(txt.read_text().strip().splitlines(), 1):
            if not line.strip():
                continue
            parts = line.split()
            if len(parts) != 5:
                print(f"❌ {txt.name}:{ln} 字段数={len(parts)}")
                errs += 1; continue
            try:
                cid = int(parts[0])
                vals = list(map(float, parts[1:]))
            except ValueError:
                print(f"❌ {txt.name}:{ln} 非数字")
                errs += 1; continue
            if not (0 <= cid <= 6):
                print(f"❌ {txt.name}:{ln} class_id={cid} 越界(0-6)")
                errs += 1; continue
            if not all(0 <= v <= 1 for v in vals):
                print(f"❌ {txt.name}:{ln} 坐标越界")
                errs += 1; continue
            if vals[2] <= 0 or vals[3] <= 0:
                print(f"❌ {txt.name}:{ln} 宽高<=0")
                errs += 1; continue
            counter[cid] += 1

    print(f"错误: {errs}")
    print("每类目标数:")
    for i, name in enumerate(NAMES):
        print(f"  {i} {name:<10} {counter[i]}")
    return errs

if __name__ == "__main__":
    total = check("train") + check("val")
    sys.exit(1 if total else 0)

3.5 添加训练命令脚本

bash 复制代码
cat > ~/colony_yolov8/scripts/train.sh << 'SHEOF'
#!/bin/bash
set -e
source ~/yolov8_env/bin/activate
cd ~/colony_yolov8

yolo detect train \
  data=colony.yaml \
  model=yolov8s.pt \
  epochs=150 \
  imgsz=640 \
  batch=16 \
  device=0 \
  project=runs \
  name=colony_7cls_v1 \
  patience=30 \
  augment=True
SHEOF

3.6 添加导出ONNX脚本scripts/export_onnx.py

这一步主要是出于合规考虑:如果后续需要商用,推理阶段改用 ONNX 模型即可规避相关争议。

python 复制代码
#!/usr/bin/env python3
"""导出 best.pt → best.onnx"""
import argparse
from pathlib import Path
from ultralytics import YOLO

def main():
    p = argparse.ArgumentParser()
    p.add_argument("--weights", default=str(Path.home() / "colony_yolov8/runs/colony_7cls_v1/weights/best.pt"))
    p.add_argument("--imgsz", type=int, default=640)
    p.add_argument("--out", default=str(Path.home() / "colony_yolov8/best.onnx"))
    a = p.parse_args()

    model = YOLO(a.weights)
    onnx_path = model.export(format="onnx", imgsz=a.imgsz, simplify=True)
    print(f"✅ ONNX 导出: {onnx_path}")

if __name__ == "__main__":
    main()

四、训练

4.1 下载预训练权重

地址:https://github.com/ultralytics/assets/releases/download/v8.4.0/yolov8s.pt

建议直接手动下载,我直接用迅雷下载下载了,再拷贝到服务器的,github网络太不稳定了。

4.2 安装依赖环境

onnx和onnxslim

我使用清华镜像源下载,加快速度

bash 复制代码
yoloenv # 启动虚拟环境
pip install onnx onnxslim -i https://pypi.tuna.tsinghua.edu.cn/simple

4.3 自动化训练脚本

python 复制代码
#!/usr/bin/env python3
"""
终极版自动化训练流水线 (高健壮性、实时进度条、防崩溃)
"""
import os
import shutil
import subprocess
import sys
import time
import argparse
from pathlib import Path

# ================= 基础路径配置 =================
HOME = Path.home()
RAW_DIR = HOME / "colony_raw"                  # 原始数据目录
PROJECT_DIR = HOME / "colony_yolov8"           # 项目根目录
SCRIPTS_DIR = PROJECT_DIR / "scripts"          # 脚本目录
YAML_PATH = PROJECT_DIR / "colony.yaml"        # 数据配置
RUNS_DIR = PROJECT_DIR / "runs"                # 训练输出根目录
PROD_ONNX_DIR = HOME / "colony_service"        # 生产部署目录

def run_cmd(cmd, desc):
    """
    静默运行命令(适用于快速的数据划分、校验等)
    """
    print(f"\n>>> {desc}...")
    try:
        result = subprocess.run(cmd, shell=True, check=True, text=True, 
                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        lines = result.stdout.strip().split('\n')
        for line in lines[-5:]:
            print(f"    {line}")
        print(f"✅ {desc} 成功")
        return True
    except subprocess.CalledProcessError as e:
        print(f"\n❌ 【{desc} 失败】")
        print("-" * 50)
        err_lines = e.stdout.strip().split('\n')
        for line in err_lines[-20:]:
            print(f"  {line}")
        print("-" * 50)
        print(f"💡 建议:请检查上方报错信息。如果是数据问题(如缺标注、格式错),请修正后重试。")
        return False

def run_cmd_realtime(cmd, desc):
    """
    实时运行命令(适用于 YOLO 训练,会逐行刷新显示进度条)
    """
    print(f"\n>>> {desc} (实时进度展示)...")
    try:
        # 直接开启子进程,不捕获到内存,而是流式读取
        process = subprocess.Popen(
            cmd, shell=True, text=True, 
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1
        )
        
        # 逐行读取并立刻打印到屏幕
        for line in iter(process.stdout.readline, ''):
            print(line, end='', flush=True)
            
        process.stdout.close()
        return_code = process.wait()
        
        if return_code != 0:
            print(f"\n❌ 【{desc} 失败】进程退出码: {return_code}")
            return False
            
        print(f"\n✅ {desc} 成功")
        return True
    except Exception as e:
        print(f"\n❌ 【{desc} 发生异常】: {e}")
        return False

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--force-retrain", action="store_true", help="强制从 yolov8s.pt 重新训练,不基于旧模型增量")
    args = parser.parse_args()

    print("="*60)
    print(" 🚀 菌落识别 YOLOv8 终极自动化流水线")
    print("="*60)

    try:
        # 1. 检查原始数据
        if not RAW_DIR.exists():
            print(f"❌ 找不到原始数据目录: {RAW_DIR}")
            return 1
        
        # 支持 jpg, png, jpeg, bmp
        imgs = (list(RAW_DIR.glob("*.jpg")) + list(RAW_DIR.glob("*.png")) + 
                list(RAW_DIR.glob("*.jpeg")) + list(RAW_DIR.glob("*.bmp")))
        if not imgs:
            print(f"❌ {RAW_DIR} 里没有图片,流程终止。")
            return 1
        
        txts = list(RAW_DIR.glob("*.txt"))
        print(f"📦 发现原始数据: {len(imgs)} 张图, {len(txts)} 个标注文件")

        # 2. 动态清理旧数据目录
        print("\n>>> 清理旧的 images/train, images/val 目录...")
        for split in ["train", "val"]:
            img_dir = PROJECT_DIR / "images" / split
            lbl_dir = PROJECT_DIR / "labels" / split
            if img_dir.exists(): shutil.rmtree(img_dir)
            if lbl_dir.exists(): shutil.rmtree(lbl_dir)
            img_dir.mkdir(parents=True)
            lbl_dir.mkdir(parents=True)

        # 3. 自动划分数据
        if not run_cmd(f"python {SCRIPTS_DIR}/split_mixed.py --src {RAW_DIR} --dst {PROJECT_DIR} --val-ratio 0.2", "划分训练集与验证集"):
            return 1

        # 4. 校验标注格式
        if not run_cmd(f"python {SCRIPTS_DIR}/check_labels.py", "校验标注格式"):
            print("💡 提示:校验未通过。请修复对应 txt 文件的格式或坐标越界问题。")
            return 1

        # 5. 决定训练起点
        run_name = f"colony_{time.strftime('%Y%m%d_%H%M')}"
        run_dir = RUNS_DIR / run_name
        
        prev_bests = sorted(RUNS_DIR.glob("colony_*/weights/best.pt"), key=os.path.getmtime, reverse=True)
        
        if args.force_retrain or not prev_bests:
            model_arg = "yolov8s.pt"
            mode_msg = "从头开始训练 (基于 yolov8s.pt)"
        else:
            model_arg = str(prev_bests[0])
            mode_msg = f"增量训练 (基于上次最优 {prev_bests[0].parent.parent.name})"
        
        print(f"\n🧠 模式: {mode_msg}")

        # 6. 启动训练 (此处改为 run_cmd_realtime 以显示进度条)
        train_cmd = (
            f"yolo detect train "
            f"data={YAML_PATH} "
            f"model={model_arg} "
            f"epochs=150 "
            f"imgsz=640 "
            f"batch=16 "
            f"device=0 "
            f"project={RUNS_DIR} "
            f"name={run_name} "
            f"patience=30 "
            f"augment=True"
        )
        if not run_cmd_realtime(train_cmd, f"YOLOv8 模型训练 ({run_name})"):
            print("💡 提示:训练过程出错。请检查是否显存溢出 (OOM) 或数据集 yaml 有误。")
            return 1

        # 7. 获取最佳模型
        best_pt = run_dir / "weights" / "best.pt"
        if not best_pt.exists():
            print(f"❌ 训练结束但找不到 best.pt ({best_pt})")
            return 1

        # 8. 评估模型
        if not run_cmd(f"yolo detect val model={best_pt} data={YAML_PATH} imgsz=640", "验证集 mAP 评估"):
            return 1

        # 9. 导出 ONNX
        PROD_ONNX_DIR.mkdir(parents=True, exist_ok=True)
        if not run_cmd(f"python {SCRIPTS_DIR}/export_onnx.py --weights {best_pt} --out {run_dir}/best.onnx", "导出 ONNX 模型"):
            return 1
        
        # 复制到生产目录
        try:
            shutil.copy2(run_dir / "best.onnx", PROD_ONNX_DIR / "best.onnx")
            print(f"🚀 ONNX 已部署至: {PROD_ONNX_DIR}/best.onnx")
        except Exception as e:
            print(f"⚠️ ONNX 复制到生产目录失败: {e}")

        # 10. 打印总结
        print("\n" + "="*60)
        print(" 🎉 全流程完成!")
        print(f" 📁 历史存档: {run_dir}")
        print(f" 📁 PyTorch:  {best_pt}")
        print(f" 📁 部署 ONNX: {PROD_ONNX_DIR}/best.onnx")
        print("="*60)

        # 11. 清理临时切分目录,释放空间(保护核心原始数据 colony_raw)
        print("\n>>> 清理临时切分目录,释放磁盘空间...")
        try:
            for split in ["train", "val"]:
                img_dir = PROJECT_DIR / "images" / split
                lbl_dir = PROJECT_DIR / "labels" / split
                if img_dir.exists(): shutil.rmtree(img_dir)
                if lbl_dir.exists(): shutil.rmtree(lbl_dir)
            print("✅ 临时切分目录已清理。原始数据安全保留在 colony_raw 中。")
        except Exception as e:
            print(f"⚠️ 清理失败: {e}")

        # 返回结果
        return 0

    except KeyboardInterrupt:
        print("\n\n🛑 用户手动中断了流水线 (Ctrl+C)。")
        return 130
    except Exception as e:
        print(f"\n❌ 发生意外错误: {e}")
        import traceback
        traceback.print_exc()
        return 1

    

if __name__ == "__main__":
    # 使用 sys.exit 传递返回码,方便外部监控脚本判断是否成功
    sys.exit(main())

4.4 数据准备

参考下文进行数据标记准备:

WSL下Ubuntu系统安装label studio用来标记训练图形

通过scp等将数据直接拷到colony_raw文件夹中。

4.5 训练

python 复制代码
# 启动虚拟环境
yoloenv
# 启动python脚本自动计算
python ~/amas-train-pipeline/auto_train.py 

训练结果,能正常跑完:

相关推荐
测试界的小码农18 分钟前
【Linux 命令实战(04):文件内容查看,掌握 cat、less、head、tail】
linux·服务器·less
ZhengEnCi32 分钟前
Agent 查不出代码 Bug 怎么办:一份带时间戳的调试日志与读日志排查流程
python·ai编程
威哥爱编程1 小时前
模拟面试智能体开发实战 05:给面试官接上题库和检索,题目结构化、大纲可溯源,AI 不再凭印象出题
python·agent·harmonyos
威哥爱编程1 小时前
模拟面试智能体开发实战 06:答得好怎么追问、答得错怎么点破,追问引擎让面试官会深挖会纠错
python·agent·harmonyos
CodeYp20191 小时前
第13讲:HTTPS 协议原理
linux·https
codists2 小时前
《crafting interpreters》阅读笔记:4.6.2
python
一条破秋裤2 小时前
【1112】Linux中断【整理】
linux·运维·服务器
2601_967979122 小时前
机器学习必修课:经典算法与Python实战
python·算法·机器学习·实战·课程
cpolar技术支持2 小时前
listen(5) 只排得住 5 个连接:第 6 个客户端卡在 connect 超时,进程活着端口却显示假活不放行
python·socket·tcp·cpolar·故障排查