This commit is contained in:
2026-09-01 17:08:45 +08:00
parent abccda0bd6
commit 06a66462d1
10 changed files with 29 additions and 19 deletions
@@ -362,8 +362,9 @@ class CameraChannel(
/**
* 从设备流配置挑预览/分析共用分辨率(均为传感器方向尺寸,宽≥高):
* 取两者交集,16:9 优先、长边 ≤1920 内取最大(推理输入 640x640
* 更高只增帧传输与旋转开销,无精度收益);无 16:9 时退回最大交集尺寸。
* 取两者交集,16:9 优先、长边 ≤1920 内取最大(推理输入长边 1280
* 1920x1080 已留 1.5 倍降采样余量,更高只增帧传输与旋转开销,无精度收益);
* 无 16:9 时退回最大交集尺寸。
*/
private fun pickSize(previewSizes: Array<Size>, analysisSizes: Array<Size>): Size {
val common = previewSizes.filter { analysisSizes.contains(it) }
@@ -6,14 +6,14 @@ import 'package:tflite_flutter/tflite_flutter.dart';
import 'detection_result.dart';
import 'nms.dart';
/// YOLOv8n 端侧推理实现(对应 Kotlin TFLiteDetector)。
/// YOLOv8s 端侧推理实现(对应 Kotlin TFLiteDetector)。
/// 模型输出布局(ultralytics litert 导出):[1, 4 + nc, anchors]
/// cx/cy/w/h 已归一化,类别得分已过 sigmoid;按 out[dim][anchor] 索引。
/// 输入为 NCHW [1, 3, 704, 704]litert 导出保留 torch 布局)。
/// 输入为 NCHW [1, 3, 1280, 1280]litert 导出保留 torch 布局)。
class TfliteDetector {
// 输入尺寸取自模型本身(ultralytics litert 导出 NCHW [1,3,H,W],各数据集
// 训练 imgsz 可不同),默认 704 兜底
static const int defaultInputSize = 704;
// 训练 imgsz 可不同),默认 1280 兜底
static const int defaultInputSize = 1280;
// 环颈雉鸡数据置信度普遍偏低(0.1~0.2 量级),保留低分池供运动检测提升;
// 可运行时调整(设置页滑块),默认 0.10
double minScore = 0.10;
+1 -1
View File
@@ -200,7 +200,7 @@ APK 下载引导页(静态页面,源码在 `h5/index.html`,由后端 `/dow
| GET | `/admin/label-tasks` | 标注任务列表:`page/size` 分页(含 status/total/done |
| GET | `/admin/label-tasks/detail` | 标注任务详情:返回数据集全部图片 + 每张标注框(`boxes`YOLO 归一化 xywh + 置信度 + 类别) |
| POST | `/admin/label-tasks/save` | 保存单张标注 `{"datasetId":1,"filename":"a.jpg","boxes":[{"class":0,"cx":0.5,"cy":0.4,"w":0.1,"h":0.2}]}`:整体覆写该图 `labels_json`(空 boxes=清空标注),返回该数据集当前 `labeledCount` |
| POST | `/admin/trainings` | 发起训练 `{"dataset":"yolo","imgsz":704,"epochs":150,"batch":16,"device":"0","name":"..."}`:先同步数据集到训练机 → 校验目录存在 → runner 启动训练;**并发度 1**,已有 running 任务时返回错误 |
| POST | `/admin/trainings` | 发起训练 `{"dataset":"yolo","imgsz":1280,"epochs":150,"batch":16,"device":"0","name":"..."}`imgsz/model 实际以 `config.yml` training 节点为准):先同步数据集到训练机 → 校验目录存在 → runner 启动训练;**并发度 1**,已有 running 任务时返回错误 |
| GET | `/admin/trainings` | 训练任务列表:`page/size` 分页,按下发时间倒序,含 status/进度/指标 |
| GET | `/admin/trainings/detail` | 任务详情 `{"id":1}`:参数快照 + 进度 + 指标 + 日志尾部 |
| POST | `/admin/trainings/cancel` | 取消训练 `{"id":1}`(仅 running):杀训练进程,状态置 failed(记录 error |
+1 -1
View File
@@ -24,7 +24,7 @@ func init() {
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'running',
dataset_id INTEGER NOT NULL,
imgsz INTEGER NOT NULL DEFAULT 704,
imgsz INTEGER NOT NULL DEFAULT 1280,
epochs INTEGER NOT NULL DEFAULT 150,
batch INTEGER NOT NULL DEFAULT 16,
device TEXT NOT NULL DEFAULT '0',
+1
View File
@@ -425,6 +425,7 @@ func (s *trainingService) AdminStartTraining(ctx context.Context, req *dto.Admin
taskJSON, _ := json.Marshal(map[string]any{
"workdir": cfg.Workdir,
"yolo": filepath.ToSlash(filepath.Join(cfg.DatasetDir, "yolo", dataset.Name)),
"model": cfg.Model,
"imgsz": imgsz,
"epochs": epochs,
"batch": batch,
+4 -2
View File
@@ -64,7 +64,7 @@ type TrainingJob struct {
TaskId int64
DatasetName string // yolo 数据集目录名(训练机 datasetDir/yolo/<name>
Python string // 训练机 venv python 路径
Workdir string // 训练机工作目录(train_server.py / yolov8n.pt 所在)
Workdir string // 训练机工作目录(train_server.py / yolov8s.pt 所在)
DatasetDir string // 训练机数据集根目录(相对 workdir)
}
@@ -87,6 +87,7 @@ type TrainingConfig struct {
DatasetDir string
Python string
TimeoutMins int
Model string // 训练基座权重(训练机 workdir 下,如 yolov8s.pt
Imgsz int
Epochs int
Batch int
@@ -100,7 +101,8 @@ func TrainingConfigOf(ctx context.Context) (TrainingConfig, bool) {
DatasetDir: g.Cfg().MustGet(ctx, "training.datasetDir", "datasets").String(),
Python: g.Cfg().MustGet(ctx, "training.venvPython").String(),
TimeoutMins: g.Cfg().MustGet(ctx, "training.timeoutMinutes", 600).Int(),
Imgsz: g.Cfg().MustGet(ctx, "training.imgsz", 704).Int(),
Model: g.Cfg().MustGet(ctx, "training.model", "yolov8s.pt").String(),
Imgsz: g.Cfg().MustGet(ctx, "training.imgsz", 1280).Int(),
Epochs: g.Cfg().MustGet(ctx, "training.epochs", 150).Int(),
Batch: g.Cfg().MustGet(ctx, "training.batch", 16).Int(),
Device: g.Cfg().MustGet(ctx, "training.device", "0").String(),
+4 -3
View File
@@ -70,11 +70,12 @@ training:
port: 22
privateKeyPath: "" # 私钥路径与 password 二选一
password: "123"
workdir: /opt/pheasant_data # 训练机工作目录(train_server.py / yolov8n.pt 所在)
workdir: /opt/pheasant_data # 训练机工作目录(train_server.py / yolov8s.pt 所在)
venvPython: /opt/pheasant_data/venv/bin/python
datasetDir: datasets # 训练机数据集根目录(相对 workdiryolo/<name> 为子目录)
timeoutMinutes: 240 # 训练超时判死(started_at 起算;实测 100 张约 3 分钟,千图级约 1 小时)
imgsz: 704 # 训练/导出分辨率(须与 App 端推理输入对齐
model: yolov8s.pt # 训练基座权重(须已同步到训练机 workdir;s 容量适配小目标+遮挡,详见技术设计
imgsz: 1280 # 训练/导出分辨率(须与 App 端推理输入对齐;1280 提升远距离小目标保留像素)
epochs: 150 # 训练轮数(patience 30 早停,设大可自动停)
batch: 16 # 批大小(按训练机显存调整)
device: "0" # GPU 编号(cpu 用 cpu
@@ -87,7 +88,7 @@ localAi:
threshold: 0.08 # 候选置信度阈值(宁多勿漏)
confConfirmed: 0.2 # 高于此视为确认(class 0),否则疑似(class 1)
overlapThreshold: 0.3 # 重叠去重阈值(交叠/较小框面积,NMS 风格;RF-DETR 同目标常输出一大一小两框,minIoU 比 IoU 更能命中)
inputSize: 700 # 提交前整图等比缩放最长边
inputSize: 700 # 提交前整图等比缩放最长边RF-DETR 模型包固定 700x700 输入(stretch 拉伸、不支持动态尺寸,见模型包 inference_config.json),提交更大最终也拉伸到 700 无增益,2026-09-01 实测回退
# 标注类别名(写入 data.yaml,随 result.json 存模型 labels)从数据集表取:
# 第一类 = gen_species(空回退数据集名),第二类 = gen_classes(空回退 class0/class1
Binary file not shown.
+5 -4
View File
@@ -6,9 +6,10 @@
python train_server.py --task-json tasks/<taskId>.json
任务参数(Go 侧写入,字段相对训练机 workdir):
workdir 训练机工作目录(脚本 / yolov8n.pt / venv 所在),启动即 chdir
workdir 训练机工作目录(脚本 / yolov8s.pt / venv 所在),启动即 chdir
yolo 训练集目录(含 dataset.yaml),相对 workdir
imgsz 训练/导出分辨率(默认 704,与端侧推理对齐
model 训练基座权重文件名(workdir 下,默认 yolov8s.pt
imgsz 训练/导出分辨率(默认 1280,与端侧推理对齐)
epochs 训练轮数
batch 批大小
device GPU 编号或 cpu
@@ -236,7 +237,7 @@ def main():
except Exception:
pass # 日志不可写(如 workdir 缺失)不阻断错误路径,traceback 仍走 stderr
imgsz = int(task.get("imgsz") or 704)
imgsz = int(task.get("imgsz") or 1280)
epochs = int(task.get("epochs") or 150)
batch = int(task.get("batch") or 16)
device = task.get("device") or "0"
@@ -245,7 +246,7 @@ def main():
try:
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
model = YOLO(task.get("model") or "yolov8s.pt")
register_callback(model)
model.train(
data=data, imgsz=imgsz, epochs=epochs,
+6 -2
View File
@@ -96,7 +96,7 @@ CREATE TABLE IF NOT EXISTS model_training (
name TEXT NOT NULL, -- 任务名(默认「数据集+时间」)
status TEXT NOT NULL DEFAULT 'running', -- running | success | failed
dataset TEXT NOT NULL, -- 训练机数据集名(datasetDir 下子目录名)
imgsz INTEGER NOT NULL DEFAULT 704,
imgsz INTEGER NOT NULL DEFAULT 1280,
epochs INTEGER NOT NULL DEFAULT 150,
batch INTEGER NOT NULL DEFAULT 16,
device TEXT NOT NULL DEFAULT '0',
@@ -360,12 +360,16 @@ training:
datasetDir: datasets # 训练机数据集根目录(相对 workdir,数据集为子目录)
concurrency: 1 # GPU 独占:同时仅一个 running,新任务排队
timeoutMinutes: 240 # 超时判死(started_at 起算;100 张实测约 3 分钟)
imgsz: 704 # 训练/导出分辨率(须与 App 端推理输入对齐
model: yolov8s.pt # 训练基座权重(须已同步到训练机 workdir
imgsz: 1280 # 训练/导出分辨率(须与 App 端推理输入对齐)
epochs: 150 # 训练轮数(patience 30 早停,设大可自动停)
batch: 16 # 批大小(按训练机显存调整)
device: "0" # GPU 编号(cpu 用 cpu
```
- **模型与分辨率升级(2026-09-01,用户定案)**:基座 `yolov8n.pt``yolov8s.pt``imgsz` 704 → **1280**——场景为远距离小目标 + 遮挡多,nano 容量不足且 704 输入下小目标仅剩 5~15 像素;s@1280 是端侧延迟可接受内的最优效果(中端机 70-120ms/帧,预览不卡、框 8-14fps 更新;高端机 30-45ms 流畅)。改动链路:`config.yml`training.model/imgsz)→ task.json 传 model → `train_server.py` 读 task model → 导出 litert 输入 NCHW [1,3,1280,1280] → App 端 `TfliteDetector` 输入尺寸从模型形状动态读取(`defaultInputSize` 兜底 1280)。相机侧无需改:Android 分析帧固定 16:9 ≤1920(典型 1920x1080)、拍照原图 4000px+letterbox 后均满足 1280。显存评估:12G 跑 s@1280 batch 8~16 可行(yolov8s 11.2M 参数)。端侧量化保持 fp16int8 对 <10px 小目标掉点明显)。
- **预标注模型固定 700x7002026-09-01 实测确认)**local-ai 的 rfdetr-xlarge 模型包 `inference_config.json` 声明固定输入 700x700`training_input_size` 700、`dynamic_spatial_size_supported:false``resize_mode:stretch`),提交任何分辨率最终都拉伸到 700x700 推理——`localAi.inputSize` 只是提交前等比缩放,改大无增益(曾误改 1280,实测后回退 700)。**标注框精度天花板 = 700 分辨率**:极小目标(<5px)框不准是标注侧固有噪声;训练 imgsz=1280 与标注 700 并存可行(框坐标按比例映射到 1280 图),突破标注精度需换支持高分辨率输入的检测模型(RT-DETR / YOLO 动态输入 ONNX),未实施。
- **训练参数默认走配置(2026-08-26**imgsz/epochs/batch/device 不随管理端请求传(界面一键开始),由 `training` 节点统一配置——device 取决于训练机硬件、imgsz 必须与端侧推理对齐、epochs 取决于算力预期,均为部署级参数;任务记录仍存各值(`model_training.imgsz/epochs/batch/device`)供列表展示
- service 内 `Runner` 接口:`Start(ctx, *TrainingJob) (pid, error)` / `FetchLogTail(ctx, job)` / `IsAlive(ctx, job) bool` / `Cancel(ctx, job)` / `FetchArtifacts(ctx, job, destDir)``subprocess``ssh` 两个实现,按 config `mode` 选择;**ssh 凭据直接读本节点 `training.ssh` 配置**(见「全局训练配置」节)