1
This commit is contained in:
+1
-1
@@ -183,7 +183,7 @@ APK 下载引导页(静态页面,源码在 `h5/index.html`,由后端 `/dow
|
||||
| GET | `/admin/datasets` | 数据集列表:`page/size` 分页,返回 `{total, list}`(含 imageCount/labeledCount/status/cover/description/**training 聚合状态**:最新训练记录的 status/currentEpoch/totalEpochs) |
|
||||
| POST | `/admin/datasets/update` | 更新数据集配置 `{"id":1,"name":"新名","namePrefix":"pheasant","description":"...","cover":"a.jpg"}`:名称(改名)/文件名前缀/描述/封面,空值字段不覆盖原值;gen_* 生成参数池不在此维护(仅 VLM 生成,见 gen-pools);改名同步迁移图片目录与模型文件,标注/训练进行中拒绝 |
|
||||
| POST | `/admin/datasets/gen-pools` | 重新生成数据集生成参数池 `{"datasetId":1}`:按数据集名(物种)调 VLM(qwen3.6-35b-a3b) 生成轮廓色/站高/场景/动作/遮挡并写表(覆盖旧值);失败报错保留旧值 |
|
||||
| POST | `/admin/datasets/images/vlm-review` | VLM 藏匿位补检 `{"datasetId":1,"imageId":5}`(两阶段标注第二阶段,**须与图像生成显存互斥**):qwen3.6-35b-a3b(+mmproj) 排除已确认框,按环境/光线/习性推理藏匿位,追加 ≤3 个疑似框(class 1)进同一 labels_json |
|
||||
| POST | `/admin/datasets/images/vlm-review` | VLM 藏匿位补检 `{"datasetId":1,"imageId":5}`(两阶段标注第二阶段,**须与图像生成显存互斥**):qwen3.6-35b-a3b(+mmproj) 排除已确认框,按环境/季节/时间/天气/光线/地形/习性综合判读画面推理藏身位,追加 ≤3 个疑似框(class 1)进同一 labels_json |
|
||||
| POST | `/admin/datasets/cover` | 上传数据集封面(multipart:`datasetId`+`file`,jpg/jpeg/png ≤10MB):**自动缩放 1248x704 + 转 jpg + UUID 命名**落盘并覆盖旧封面 |
|
||||
| POST | `/admin/datasets/cover/generate` | 生成数据集封面 `{"datasetId":1}`(z-image 文生图:16:9(1248x704)、1 雄 1 雌,物种取 gen_species 空回退数据集名):覆盖旧封面,返回 `{cover}` 新文件名;`datasetId=0` 时传 `{"name":"家鸽"}` 新建预生成(数据集未创建,封面仅落盘不写库,创建请求带 cover 回传写库);生成任务进行中亦可直接调用(与任务图片同走 z-image、由 local-ai 服务端排队串行,2026-09-02 放开整任务拒绝),失败不覆盖旧封面 |
|
||||
| GET | `/admin/datasets/cover` | 封面文件(静态字节流,`datasetId` 定位;`datasetId=0` 时按 `name`+`filename` 直读——新建对话框预生成封面回显) |
|
||||
|
||||
@@ -374,8 +374,9 @@ type AdminLabelTaskStartRes struct {
|
||||
Id int64 `json:"id"`
|
||||
}
|
||||
|
||||
// AdminImageVlmReviewReq 单图 VLM 补检:qwen3.6-35b-a3b 按「环境/光线/习性」推理 RF-DETR 之外
|
||||
// 可能藏匿目标的位置(排除已确认坐标),最多补 3 个疑似框(class 1)追加到 labels_json。
|
||||
// AdminImageVlmReviewReq 单图 VLM 补检:qwen3.6-35b-a3b 按「环境/季节/时间/天气/光线/地形/习性」
|
||||
// 综合判读画面,推理 RF-DETR 之外可能藏身的位置(排除已确认坐标),最多补 3 个疑似框(class 1)
|
||||
// 追加到 labels_json。
|
||||
// 依赖 localAi.vlmModel(默认 qwen3.6-35b-a3b);VL 与 z-image 生成不能同时驻留显存,批量补检
|
||||
// 应在生成队列空闲时执行
|
||||
type AdminImageVlmReviewReq struct {
|
||||
|
||||
@@ -1007,7 +1007,7 @@ const singleAnimalClause = "画面中有且只有这一只动物,没有任何
|
||||
// 空则数据集名本身——生成表单无物种输入,不随机)。
|
||||
// 物种只写名字不写羽毛细节(模型对正确名称自带外观先验);场景/动作/遮挡从数据集表池随机
|
||||
// (创建时 VLM 生成,config.yml 不再兜底;池空报错——生成图按池组装,池质量决定训练数据质量);
|
||||
// 光线走 config 通用池 lights。
|
||||
// 场景池条目兼容捆绑组(树栖物种场景与动作/遮挡联动,见 parseScenePool);光线走 config 通用池 lights。
|
||||
func (s *datasetService) buildPromptFromTemplate(ctx context.Context, dataset *entity.Dataset, dist int, size string) (string, error) {
|
||||
tpl := g.Cfg().MustGet(ctx, "imageGen.promptTemplate").String()
|
||||
if tpl == "" {
|
||||
@@ -1017,7 +1017,8 @@ func (s *datasetService) buildPromptFromTemplate(ctx context.Context, dataset *e
|
||||
if species == "" {
|
||||
species = dataset.Name
|
||||
}
|
||||
// 场景/动作/遮挡按数据集习性取池(表存储,VLM 生成;池空报错——生成图按池组装,池质量决定训练数据质量)
|
||||
// 场景/动作/遮挡按数据集习性取池(表存储,VLM 生成;池空报错——生成图按池组装,池质量决定训练数据质量)。
|
||||
// 场景池条目兼容捆绑组对象:选中捆绑组时动作/遮挡仅从组内池随机(生境联动),否则走全局池
|
||||
poolOf := func(raw string) []string {
|
||||
var pool []string
|
||||
if json.Unmarshal([]byte(raw), &pool) != nil {
|
||||
@@ -1025,9 +1026,22 @@ func (s *datasetService) buildPromptFromTemplate(ctx context.Context, dataset *e
|
||||
}
|
||||
return pool
|
||||
}
|
||||
scene := pickCfgList(poolOf(dataset.GenScenes))
|
||||
action := pickCfgList(poolOf(dataset.GenActions))
|
||||
occlusion := pickCfgList(poolOf(dataset.GenOcclusions))
|
||||
scene, action, occlusion := "", "", ""
|
||||
if bundle := pickSceneEntry(parseScenePool(dataset.GenScenes)); bundle != nil {
|
||||
scene = bundle.Scene
|
||||
if len(bundle.Actions) > 0 {
|
||||
action = pickCfgList(bundle.Actions)
|
||||
}
|
||||
if len(bundle.Occlusions) > 0 {
|
||||
occlusion = pickCfgList(bundle.Occlusions)
|
||||
}
|
||||
}
|
||||
if action == "" {
|
||||
action = pickCfgList(poolOf(dataset.GenActions))
|
||||
}
|
||||
if occlusion == "" {
|
||||
occlusion = pickCfgList(poolOf(dataset.GenOcclusions))
|
||||
}
|
||||
if scene == "" || action == "" || occlusion == "" {
|
||||
return "", gerror.New("数据集「" + dataset.Name + "」未配置生成参数池(场景/动作/遮挡),请在编辑数据集生成或手填提示词")
|
||||
}
|
||||
@@ -1147,6 +1161,63 @@ func pickCfgList(pool []string) string {
|
||||
return pool[rand.Intn(len(pool))]
|
||||
}
|
||||
|
||||
// genSceneEntry 场景池条目:字符串条目解析为仅含 Scene 的本结构(weight=1,动作/遮挡走全局池);
|
||||
// 捆绑组对象 {scene, weight, actions, occlusions} 的动作/遮挡与场景联动(2026-09-04,
|
||||
// 斑鸠树栖 80%——三池独立随机会拼出「树上场景+地面动作/草丛遮挡」矛盾提示词,见技术设计.md)
|
||||
type genSceneEntry struct {
|
||||
Scene string `json:"scene"`
|
||||
Weight int `json:"weight"`
|
||||
Actions []string `json:"actions"`
|
||||
Occlusions []string `json:"occlusions"`
|
||||
}
|
||||
|
||||
// parseScenePool 解析场景池 JSON 数组:条目兼容字符串与捆绑组对象,非法条目跳过
|
||||
func parseScenePool(raw string) []genSceneEntry {
|
||||
var items []json.RawMessage
|
||||
if json.Unmarshal([]byte(raw), &items) != nil {
|
||||
return nil
|
||||
}
|
||||
entries := make([]genSceneEntry, 0, len(items))
|
||||
for _, it := range items {
|
||||
var s string
|
||||
if json.Unmarshal(it, &s) == nil {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
continue // 空串/null 条目(Unmarshal null 入 string 成功但为空)不进池
|
||||
}
|
||||
entries = append(entries, genSceneEntry{Scene: s, Weight: 1})
|
||||
continue
|
||||
}
|
||||
var b genSceneEntry
|
||||
if json.Unmarshal(it, &b) != nil || strings.TrimSpace(b.Scene) == "" {
|
||||
continue
|
||||
}
|
||||
if b.Weight < 1 {
|
||||
b.Weight = 1
|
||||
}
|
||||
entries = append(entries, b)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
// pickSceneEntry 场景池加权随机一项(weight 缺省 1;空池返回 nil)
|
||||
func pickSceneEntry(pool []genSceneEntry) *genSceneEntry {
|
||||
total := 0
|
||||
for i := range pool {
|
||||
total += pool[i].Weight
|
||||
}
|
||||
if total <= 0 {
|
||||
return nil
|
||||
}
|
||||
n := rand.Intn(total)
|
||||
for i := range pool {
|
||||
n -= pool[i].Weight
|
||||
if n < 0 {
|
||||
return &pool[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdminGenerateImages AI 生成图片(异步任务):校验通过后插 gen_task 立即返回 TaskId,
|
||||
// 后台协程逐张生成(不设调用超时,失败由 provider 返回错误决定),进度落库供前端轮询;
|
||||
// 完成(或部分失败)后对本次新增图触发自动标注(付费资产,失败保留已生成图不删除)。
|
||||
|
||||
@@ -297,8 +297,8 @@ func (s *labelTaskService) runDetection(ctx context.Context, taskId int64, datas
|
||||
}
|
||||
|
||||
// AdminImageVlmReview 单图 VLM 藏匿位补检(两阶段标注第二阶段,须与 z-image 生成显存互斥):
|
||||
// 以当前 labels_json(RF-DETR/人工框)为排除集,调 qwen3.6-35b-a3b(+mmproj) 按环境/光线/习性
|
||||
// 推理可能藏匿目标的位置,取前 3 个追加为疑似框(class 1)。
|
||||
// 以当前 labels_json(RF-DETR/人工框)为排除集,调 qwen3.6-35b-a3b(+mmproj) 按「环境/季节/
|
||||
// 时间/天气/光线/地形 + 物种习性」综合判读画面,推理可能藏身的位置,取前 3 个追加为疑似框(class 1)。
|
||||
func (s *labelTaskService) AdminImageVlmReview(ctx context.Context, req *dto.AdminImageVlmReviewReq) (*dto.AdminImageVlmReviewRes, error) {
|
||||
dataset, err := dao.Dataset.GetById(ctx, req.DatasetId)
|
||||
if err != nil {
|
||||
@@ -363,8 +363,8 @@ func (s *labelTaskService) AdminImageVlmReview(ctx context.Context, req *dto.Adm
|
||||
}
|
||||
prompt := fmt.Sprintf(
|
||||
"你是野生动物监测照片分析助手。\n物种:%s\n已确认目标位置(归一化 cx,cy,w,h):%s\n"+
|
||||
"不要出现与已有位置绘制矩形重叠的框。\n"+
|
||||
"请基于环境、天气、光线与该物种习性(觅食路线、隐蔽处、阴影边缘、植被深处),分析除已确认位置外还可能藏匿个体的位置。\n"+
|
||||
"不要输出与已有位置矩形重叠的框。\n"+
|
||||
"任务:寻找画面中还可能藏匿同类的位置。先综合判读画面条件——季节(植被状态/积雪等物候)、时间(影长/色温/明暗)、天气(阴晴/雨雾/雪)、光线(顺逆光/阴影分布)、地形(草地/灌丛/林地/岩石/水域边缘等),再结合该物种的习性(昼夜活动规律、喜藏身处:灌丛草丛/树影/岩缝/沟坎/林地边缘等)推理除已确认位置外还可能藏身的位置。\n"+
|
||||
"输出归一化 bbox 的 JSON 数组:[{\"cx\":0.5,\"cy\":0.5,\"w\":0.2,\"h\":0.15}],最多 %d 个,按可能性从高到低;确无可能则输出 []。只输出 JSON 数组本身,不要其他文字。",
|
||||
speciesLine, exclude, maxAdd)
|
||||
g.Log().Infof(ctx, "补标 %s: %s(可追加 %d 个疑似框)", dataset.Name, img.Filename, maxAdd)
|
||||
|
||||
+3
-1
@@ -40,7 +40,9 @@ imageGen:
|
||||
poolSize: 1 # 生成任务并发度(z-image 显存独占,默认 1)
|
||||
# 通用提示词模板:{species} 取数据集 gen_species(单值,空回退数据集名);{count} 为数量词
|
||||
# (固定 "1只",每张仅 1 个目标;原表单数量选择已取消);{scene}/{action}/{light} 池内随机
|
||||
# (scene/action/occlusion 从数据集表 gen_scenes/gen_actions/gen_occlusions 池随机,无 config 兜底);
|
||||
# (scene/action/occlusion 从数据集表 gen_scenes/gen_actions/gen_occlusions 池随机,无 config 兜底;
|
||||
# gen_scenes 条目兼容捆绑组对象 {scene,weight,actions,occlusions}——选中时动作/遮挡组内联动,
|
||||
# 见技术设计.md「场景捆绑组+权重」,斑鸠 80% 树上场景即此机制);
|
||||
# {tone} 取数据集表 gen_tone(默认深色,白化个体配浅色);{sizeHint} 按数据集表 gen_heights 站高与
|
||||
# 表单距离算出像素高,按画面占比分级尺寸词(像素数与固定物体类比词均不通用,2026-08-31)+ 占比。物种只写名字
|
||||
# 不写羽毛细节(细节会把镜头拉近,详见技术设计)。引导语「手机原相机主摄不变焦」(2026-08-31 v4 定案):
|
||||
|
||||
Binary file not shown.
+6
-1
@@ -335,7 +335,7 @@ Android 客户端启动 GET /api/v1/app/update(公开,无需 token;iOS 不
|
||||
- **生成异步化(2026-08-28,count 1..1000)**:`/datasets/generate` 仅做校验(provider/数据集/标注服务/Serial 内并发检查已有 running 生成任务)+ 插 `gen_task` 落 running 记录即返回 `{taskId, total}`(毫秒级,前端 10s 超时无忧);后台协程逐张生成(`context.Background()` 生命周期模式,与预标注 `runDetection` 同构;`imageGen.poolSize` 并发池,z-image 显存独占默认 1),**每张不设调用超时(2026-08-29)**——异步任务超时只会造成假失败:超时仅放弃等待、取消不了 local-ai backend,任务置 failed 而模型仍被占用、poolSize=1 的池被占死、后续请求连环超时;失败判定完全由 provider 真实返回决定(dashscope 内部自限 120s 轮询不受影响);极端情况 local-ai 挂死时任务保持 running(状态诚实),由启动恢复兜底;逐张落盘 + 入库(2026-09-02 起不记录 prompt——生成提示词临时使用不落库)+ Serial 更新进度,完成(或部分失败)后对本次新增图触发自动标注,置 done/failed 由前端轮询 `/datasets/gen-task` 呈现;中途失败保留已生成图(不删除——付费资产原则);启动恢复 `recoverGenTasks` 孤儿 running 置 failed;VLM 补检与生成显存互斥(该数据集有 running 生成任务时拒绝)
|
||||
- prompt 手填覆盖;留空走 `imageGen.promptTemplate` 通用模板逐张组装(**物种/场景/动作/遮挡/轮廓色/站高池均从数据集表读取(见下节),每张独立组装保持多样性**;**性别每张随机雄/雌(2026-08-28)**——两性体型大小与外观差异大(雉鸡雄艳雌褐、野鸭雄艳雌素等),随机让训练数据覆盖两性形态,`{species}` 替换为「雄/雌性+物种」;物种只写名字不写羽毛细节,细节会拉近镜头);**位置/镜头描述(2026-08-31 v4 最终定案:手机原相机主摄不变焦)**:2026-08-28 曾定「不得包含目标位置描述」(上部1/3 弱约束遵循力差、角度与位置耦合沉底);2026-08-31 用户确认恢复距离感锚定,镜头词四版演进:广角(构图先验=前景大主体,用户实测野鸡在画面下半部)→ 长焦(**长焦物理放大与 13px 像素数学矛盾**,用户实测野鸡占半图宽)→ 全删 → **「手机原相机主摄不变焦」**(用户提议;与 13px 像素数学完全自洽——公式即按主摄 vfov 52° 计算;手机拍远处目标天然显小,先验同向;顺带对齐推理端域——App 就是手机拍摄);保留「**在{distanceWord}的地平线附近**」位置锚定(地平线是远景场景锚点,不同于上部1/3 弱约束);**远景小目标模板 `promptTemplateTiny`(2026-08-31)**:尺寸占比 <2% 时切换——目标小到动作/遮挡/性别细节无法呈现,保留细节描写(低头啄食/只露出头背)会迫使模型把目标画大(语义矛盾:1% 目标不可见细节),只留「远到看不清任何细节,只是隐约可辨的小点」
|
||||
- **生成图距离/数量校验(2026-08-28)**:表单填 `distance`(米,固定值) 与 `animalCount` 后逐张启用;**距离校验已取消(2026-08-28)**:生成即入库,无逐张拒检;参数与公式见 git 历史(物理公式 d=体高×focalPx÷像素高)
|
||||
- **VLM 藏匿位补检(两阶段第二阶段)**:`/datasets/images/vlm-review` 单图接口;qwen3.6-35b-a3b(+mmproj) 以「图片+物种(数据集单物种 gen_species 注入;2026-09-02 prompt 不再存储,原「prompt 含物种名才注入」匹配逻辑删除)+已确认框坐标(排除)」推理藏匿位,输出归一化 bbox JSON(范围校验+与已有框重叠去重),追加 class=1 疑似框进同一 labels_json(**2026-09-02 上限动态化**:单图疑似框总数 ≤ `consts.VlmMaxSuspectPerImage`(上限定义于 consts.go),每次可追加数 = 上限 − 图内已有疑似框数,≤0 直接跳过模型调用;提示词「最多 N 个」与落库硬截断均按此数);VL 与 z-image 显存互斥(12G 装不下两者),批量补检在生成队列空闲后执行;qwen3.6-35b-a3b yaml 自带 mmproj 实为多模态模型(生成校验);**预标单图上限(2026-09-02:曾按置信度降序裁 ≤ animal_count 列值;生成声明 1 个目标不代表图内只有 1 个,改去重后按置信度取前 `consts.MaxPrelabelPerImage` 个,与补标上限同量级;animal_count 仅信息性存储,与声明数无关)**,估算距离 = 物种站高 × focalPx ÷ 最大框高像素(focalPx=`图高/2/tan(vfov/2)`,站高按物种查 `imageGen.speciesHeights`,单位 cm 换算为米,未配置物种代码兜底 35cm;**2026-08-28 语义修正**:speciesHeights 为站高(垂直尺度,鸟=脚到头顶、兽=蹲坐高),勿用体长——与提示词「高度」描述及检测框高自洽;避免跨物种共用一个标定系数)(K=1.8:6%≈30m、4.5%≈40m,K 隐含镜头视场角假设);**镜头固定主摄(2026-08-28 定案)**:vfov 固定 `imageGen.assumedVfovDeg=52°`(手机默认主摄;曾随机 [15,90] 覆盖面广但目标像素波动 7~57px 不可控,曾试超广角 90 亦否决);**场景/动作按数据集(2026-08-28)**:`imageGen.sceneByDataset`/`actionByDataset` 按数据集名配池(鸭→水面、雉鸡→灌丛草丛、兔→草坡荒地、鹌鹑→草丛),未配置回退通用 scenes/actions;**遮挡描述(2026-08-28)**:模板加 `{occlusion}` 占位(插在「在{action}」之后)——身体完全暴露的个体对识别/标注训练无意义,野外目标必然存在植被等遮挡;`imageGen.occlusionByDataset` 按数据集配遮挡池(雉鸡→草丛灌丛半掩、兔→荒草只露头耳、鸭→芦苇水面半没、鹌鹑→密草藏身),未配置回退通用 `imageGen.occlusions`;池内条目覆盖轻(身体半掩)~重(只露头背)遮挡,每张随机取一条,杜绝完全暴露;遮挡物多样(杂草/树叶/枝条,2026-08-28 补树叶)且须与 sceneByDataset 场景自洽;遮挡后可见部分小于站高,检测框高与距离估算按可见部分仍自洽(真实场景同此);**拍摄角度(2026-08-28,已废弃角度描述)**:不带任何拍摄角度描述——演进:固定「上部三分之一」区域描述对文生图模型遵循力弱(生成物落底)→ 加固定俯拍(目标上移成功但构图单一,用户否决)→ 池内随机俯/平/仰(仰拍使贴地目标必然沉底,用户否决)→ 只留俯拍/平视(用户仍嫌啰嗦)→ **完全去掉角度描述**(2026-08-28 定案):交由模型平视先验自由构图,目标自然落在画面中部/上部;模板只靠「{species}离镜头很远,只是很小的{tone}轮廓」(物种名替代泛称「目标」,不限定鸟类——物种池含兔子等非鸟物种;`{tone}` 轮廓色词按 `imageGen.speciesTone.<物种>` 取,默认深色剪影,**白雉鸡配浅色**——白羽个体用「深色轮廓」描述会与模型渲染矛盾,2026-08-28)维持远景感,手填提示词仅追加数量约束;**2026-08-31 改版(最终定案)**:镜头词全删(广角/长焦实测均失败);「{distanceWord}有且仅有」→「在{distanceWord}的地平线附近有且仅有」;{action} 从句末主体降级至遮挡之后(动作=叙事焦点=视觉焦点,弱化动作降低主体视觉权重);{sizeHint} 由「像素数+占比」改「画面占比分级尺寸词(微小几乎难辨/很小/较小/中等/较大)+占比」(量化像素数与固定物体类比词针尖/米粒/拳头均不跨物种/距离通用,用户否决);**远景小目标模板 `promptTemplateTiny`**:占比 <2% 时切换,动作/遮挡/性别细节全删(保留细节描写迫使模型画大);不合格丢弃重生成,单张上限 3 次(`consts.GenValidateMaxAttempts`),连续不合格整体报错(已合格张数保留)
|
||||
- **VLM 藏匿位补检(两阶段第二阶段)**:`/datasets/images/vlm-review` 单图接口;qwen3.6-35b-a3b(+mmproj) 以「图片+物种(数据集单物种 gen_species 注入;2026-09-02 prompt 不再存储,原「prompt 含物种名才注入」匹配逻辑删除)+已确认框坐标(排除)」按环境/季节/时间/天气/光线/地形/物种习性综合判读画面推理藏身位(2026-09-04 提示词定稿:先判读画面条件(季节物候/影长时间/天气/光线/地形),再结合习性推理——补检目的即综合信息找可能藏身处),输出归一化 bbox JSON(范围校验+与已有框重叠去重),追加 class=1 疑似框进同一 labels_json(**2026-09-02 上限动态化**:单图疑似框总数 ≤ `consts.VlmMaxSuspectPerImage`(上限定义于 consts.go),每次可追加数 = 上限 − 图内已有疑似框数,≤0 直接跳过模型调用;提示词「最多 N 个」与落库硬截断均按此数);VL 与 z-image 显存互斥(12G 装不下两者),批量补检在生成队列空闲后执行;qwen3.6-35b-a3b yaml 自带 mmproj 实为多模态模型(生成校验);**预标单图上限(2026-09-02:曾按置信度降序裁 ≤ animal_count 列值;生成声明 1 个目标不代表图内只有 1 个,改去重后按置信度取前 `consts.MaxPrelabelPerImage` 个,与补标上限同量级;animal_count 仅信息性存储,与声明数无关)**,估算距离 = 物种站高 × focalPx ÷ 最大框高像素(focalPx=`图高/2/tan(vfov/2)`,站高按物种查 `imageGen.speciesHeights`,单位 cm 换算为米,未配置物种代码兜底 35cm;**2026-08-28 语义修正**:speciesHeights 为站高(垂直尺度,鸟=脚到头顶、兽=蹲坐高),勿用体长——与提示词「高度」描述及检测框高自洽;避免跨物种共用一个标定系数)(K=1.8:6%≈30m、4.5%≈40m,K 隐含镜头视场角假设);**镜头固定主摄(2026-08-28 定案)**:vfov 固定 `imageGen.assumedVfovDeg=52°`(手机默认主摄;曾随机 [15,90] 覆盖面广但目标像素波动 7~57px 不可控,曾试超广角 90 亦否决);**场景/动作按数据集(2026-08-28)**:`imageGen.sceneByDataset`/`actionByDataset` 按数据集名配池(鸭→水面、雉鸡→灌丛草丛、兔→草坡荒地、鹌鹑→草丛),未配置回退通用 scenes/actions;**遮挡描述(2026-08-28)**:模板加 `{occlusion}` 占位(插在「在{action}」之后)——身体完全暴露的个体对识别/标注训练无意义,野外目标必然存在植被等遮挡;`imageGen.occlusionByDataset` 按数据集配遮挡池(雉鸡→草丛灌丛半掩、兔→荒草只露头耳、鸭→芦苇水面半没、鹌鹑→密草藏身),未配置回退通用 `imageGen.occlusions`;池内条目覆盖轻(身体半掩)~重(只露头背)遮挡,每张随机取一条,杜绝完全暴露;遮挡物多样(杂草/树叶/枝条,2026-08-28 补树叶)且须与 sceneByDataset 场景自洽;遮挡后可见部分小于站高,检测框高与距离估算按可见部分仍自洽(真实场景同此);**拍摄角度(2026-08-28,已废弃角度描述)**:不带任何拍摄角度描述——演进:固定「上部三分之一」区域描述对文生图模型遵循力弱(生成物落底)→ 加固定俯拍(目标上移成功但构图单一,用户否决)→ 池内随机俯/平/仰(仰拍使贴地目标必然沉底,用户否决)→ 只留俯拍/平视(用户仍嫌啰嗦)→ **完全去掉角度描述**(2026-08-28 定案):交由模型平视先验自由构图,目标自然落在画面中部/上部;模板只靠「{species}离镜头很远,只是很小的{tone}轮廓」(物种名替代泛称「目标」,不限定鸟类——物种池含兔子等非鸟物种;`{tone}` 轮廓色词按 `imageGen.speciesTone.<物种>` 取,默认深色剪影,**白雉鸡配浅色**——白羽个体用「深色轮廓」描述会与模型渲染矛盾,2026-08-28)维持远景感,手填提示词仅追加数量约束;**2026-08-31 改版(最终定案)**:镜头词全删(广角/长焦实测均失败);「{distanceWord}有且仅有」→「在{distanceWord}的地平线附近有且仅有」;{action} 从句末主体降级至遮挡之后(动作=叙事焦点=视觉焦点,弱化动作降低主体视觉权重);{sizeHint} 由「像素数+占比」改「画面占比分级尺寸词(微小几乎难辨/很小/较小/中等/较大)+占比」(量化像素数与固定物体类比词针尖/米粒/拳头均不跨物种/距离通用,用户否决);**远景小目标模板 `promptTemplateTiny`**:占比 <2% 时切换,动作/遮挡/性别细节全删(保留细节描写迫使模型画大);不合格丢弃重生成,单张上限 3 次(`consts.GenValidateMaxAttempts`),连续不合格整体报错(已合格张数保留)
|
||||
|
||||
### 生成参数存储于数据集表(2026-08-28)
|
||||
|
||||
@@ -350,6 +350,11 @@ Android 客户端启动 GET /api/v1/app/update(公开,无需 token;iOS 不
|
||||
- **重新生成接口**:`POST /datasets/gen-pools`(`{datasetId}`,物种取数据集名)——编辑对话框「VLM 重新生成参数」按钮入口,VLM 失败报错(不覆盖旧值,旧值保留)
|
||||
- **读取链(无 config 兜底)**:物种 = 表 `gen_species` 单值 → 空则数据集名本身(**固定,不随机**);场景/动作/遮挡 = 表池随机 → **空则模板组装报错**(提示手填提示词或编辑数据集补参数);`{tone}` = 表 `gen_tone` 单值 → 空默认「深色」;站高 = 表 `gen_heights` 数值(**创建时 VLM 生成,界面不维护,代码/配置不硬编码**)→ ≤0 兜底 35cm(物理默认,非配置回退);标注类别名 = 第一类 `gen_species` + 第二类 `gen_classes`(训练 data.yaml / 模型 labels)
|
||||
- **界面不维护 gen_*(2026-08-28 用户定案)**:`gen_species/gen_tone/gen_heights/gen_scenes/gen_actions/gen_occlusions/gen_classes` 全部仅由模型生成(创建数据集时 VLM 自动、`gen-pools` 重新生成),管理端编辑表单对 7 列只读展示、无输入框——`POST /datasets/update` 不接收任何 gen 字段;config.yml 无任何 per-dataset 生成参数节点(`speciesByDataset/speciesTone/speciesHeights/sceneByDataset/actionByDataset/occlusionByDataset/scenes/actions/occlusions/localAi.classNames` 已删,代码无引用),保留:`promptTemplate`(通用模板)与 `lights`(光线池)、`assumedVfovDeg`;数据源唯一入口为数据集表(本库存量 15 行已直接改单值 + 真实站高,无启动迁移);**雪地场景补齐(2026-08-31,按冬季习性)**:VLM 生成场景池缺冬季场景(全部为夏秋农田),13 数据集 `gen_scenes` 直接 SQL 追加 2~3 条雪地条目(农田类雉鸡/鸽子、兔子荒坡、白马鸡蓝马鸡高山林缘);**鸭子/鹌鹑不加**——北方冬季南迁(水面封冻、候鸟不越冬),雪地场景与习性矛盾(用户拍板);`genPoolsWithVLM` 提示词已加「按冬季习性配场景:本地活跃物种须含积雪场景,南迁物种不配雪地」防新数据集一刀切;**林缘/树栖场景补齐(2026-08-31,按栖息习性)**:雉类+鸽子共享场景池(10 数据集)缺林地交接处与树上落栖场景,SQL 追加 5 条(2 林缘 + 3 树上)+ 2 条树上动作(枝头停栖/树枝上歇息);**石鸡剔除树上条目(用户定案)**——岩石地栖不上树,3 树上场景 + 2 树上动作从石鸡池移除(保留 2 林缘);**总原则(用户定案):场景与遮挡物必须符合对应动物习性**——场景(雪地/林缘/树上/水面)与遮挡物(草丛/树叶/树枝/芦苇)均按物种习性逐条判断,习性与场景矛盾即剔除;**遮挡规则(适用于所有具备树上场景的动物)**:遮挡不一定是草丛,树栖/林缘物种遮挡池须含树叶/树枝类遮挡(雉类池原已含 2 条;白马鸡/蓝马鸡补 2 条;兔子原已有 1 条林缘树叶);`genPoolsWithVLM` 提示词同步加「树栖物种场景须含林缘与树上落栖」「具备树上/林缘场景的物种遮挡池须含树叶/树枝类遮挡」
|
||||
- **场景捆绑组+权重(2026-09-04,斑鸠树栖 80% 定案)**:`gen_scenes` 条目在字符串外兼容**捆绑组对象** `{scene, weight, actions[], occlusions[]}`——场景池加权随机(weight 缺省 1,普通字符串条目权重 1),选中捆绑组时动作/遮挡仅从组内池随机(与场景联动,杜绝「树上场景+地面动作/草丛遮挡」矛盾组合——三池独立随机按模板拼装矛盾率高);组内缺省或普通字符串条目回落全局 `gen_actions`/`gen_occlusions` 池。落地(2026-09-04 用户定案「每种动物生境权重不同:野鸡主要在地面、斑鸠在树上、鸭子在水里」):
|
||||
**斑鸠(id 133)** gen_scenes = 4 个树上捆绑组(各 weight 8)+ 8 条地面字符串(weight 1)→ 树上恰好 80%,gen_actions 收敛为纯地面 4 条(枝头/电线类动作移入捆绑组),树栖遮挡含树叶/树枝类;
|
||||
**雉类 8 数据集(环颈雉/红腹锦鸡/白腹锦鸡/白鹇/黄腹角雉/灰胸竹鸡/蓝鹇/褐马鸡)**:gen_scenes 剔除 4 条树上景观字符串、追加 1 个树上捆绑组 weight 3 → 地面 14/17 ≈ 82%、树上 ≈ 18%(雉类地面觅食为主、树上仅停栖/夜栖),gen_actions 剔除枝头停栖/树枝上歇息(移入捆绑组);
|
||||
**鸭子(108)** 全水面场景+游动/潜水动作+芦苇遮挡本就自洽,**白马鸡/蓝马鸡** 高山地面/林缘自洽,**兔子/鹌鹑/石鸡** 无树上条目——均不动。
|
||||
**gen-pools VLM 重新生成仍产纯字符串池(会重置捆绑组)**——树栖/水栖物种需重新 SQL 治理;解析与选取在 `service/dataset.go` parseScenePool/pickSceneEntry
|
||||
|
||||
### 训练通道(runner 抽象,决策:同机/异机不确定 → 可配置)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user