1
This commit is contained in:
@@ -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
@@ -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) |
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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/<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.
@@ -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
@@ -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` 配置**(见「全局训练配置」节)
|
||||
|
||||
Reference in New Issue
Block a user