From 06a66462d1422cb3dae12f773fb903085d41bd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Tue, 1 Sep 2026 17:08:45 +0800 Subject: [PATCH] 1 --- .../com/example/observer/CameraChannel.kt | 5 +++-- .../lib/detection/tflite_detector.dart | 8 ++++---- server/README.md | 2 +- server/biz/dao/model_training.go | 2 +- server/biz/service/training.go | 1 + server/common/training_runner.go | 6 ++++-- server/config.yml | 7 ++++--- server/data/observer.db | Bin 425984 -> 425984 bytes server/training/train_server.py | 9 +++++---- server/技术设计.md | 8 ++++++-- 10 files changed, 29 insertions(+), 19 deletions(-) diff --git a/flutter_app/android/app/src/main/kotlin/com/example/observer/CameraChannel.kt b/flutter_app/android/app/src/main/kotlin/com/example/observer/CameraChannel.kt index 2f9fcec..d694df0 100644 --- a/flutter_app/android/app/src/main/kotlin/com/example/observer/CameraChannel.kt +++ b/flutter_app/android/app/src/main/kotlin/com/example/observer/CameraChannel.kt @@ -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, analysisSizes: Array): Size { val common = previewSizes.filter { analysisSizes.contains(it) } diff --git a/flutter_app/lib/detection/tflite_detector.dart b/flutter_app/lib/detection/tflite_detector.dart index b4ecaf8..7f20b5e 100644 --- a/flutter_app/lib/detection/tflite_detector.dart +++ b/flutter_app/lib/detection/tflite_detector.dart @@ -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; diff --git a/server/README.md b/server/README.md index 2d5d46a..4b48c68 100644 --- a/server/README.md +++ b/server/README.md @@ -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) | diff --git a/server/biz/dao/model_training.go b/server/biz/dao/model_training.go index 6832fc7..e08a7f7 100644 --- a/server/biz/dao/model_training.go +++ b/server/biz/dao/model_training.go @@ -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', diff --git a/server/biz/service/training.go b/server/biz/service/training.go index dbd14f7..1720b36 100644 --- a/server/biz/service/training.go +++ b/server/biz/service/training.go @@ -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, diff --git a/server/common/training_runner.go b/server/common/training_runner.go index 1c0c24e..7a311eb 100644 --- a/server/common/training_runner.go +++ b/server/common/training_runner.go @@ -64,7 +64,7 @@ type TrainingJob struct { TaskId int64 DatasetName string // yolo 数据集目录名(训练机 datasetDir/yolo/) 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(), diff --git a/server/config.yml b/server/config.yml index d9ca12d..e04ee80 100644 --- a/server/config.yml +++ b/server/config.yml @@ -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 # 训练机数据集根目录(相对 workdir,yolo/ 为子目录) 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) diff --git a/server/data/observer.db b/server/data/observer.db index cd4b5a6d4899c61900772c5fc475c046ca8e8eab..ef0418cdfcc6faa7685ba081e1a0623321c4cca9 100644 GIT binary patch delta 11599 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 diff --git a/server/training/train_server.py b/server/training/train_server.py index d44c028..44c9f20 100644 --- a/server/training/train_server.py +++ b/server/training/train_server.py @@ -6,9 +6,10 @@ python train_server.py --task-json tasks/.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, diff --git a/server/技术设计.md b/server/技术设计.md index 437a620..6fbcbd2 100644 --- a/server/技术设计.md +++ b/server/技术设计.md @@ -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 参数)。端侧量化保持 fp16(int8 对 <10px 小目标掉点明显)。 +- **预标注模型固定 700x700(2026-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` 配置**(见「全局训练配置」节)