From 5482efa045619f6134c9e642434d3885d1934399 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 5 Oct 2026 00:46:24 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(#2186):=20=E6=99=BA=E8=83=BD=E5=9B=9E?= =?UTF-8?q?=E5=A1=ABportrait=5Fprompt+few-shot=E7=A4=BA=E4=BE=8B,=E6=9C=8D?= =?UTF-8?q?=E8=A3=85=E5=8F=91=E5=9E=8B=E8=BF=98=E5=8E=9F=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2185后VLM仍省略hair/outfit四属性(只输出6个旧属性,连"无法判断"都不写), worker兜底用通用值导致服装还原失败(翻领衬衫→浅米色针织上衣)。 双层增强: 1. 智能回填(worker): - outfit兜底不再固定"日常服装",从product.name+features中匹配服装关键词 (衬衫/T恤/毛衣/针织/外套/西装/连衣裙/牛仔裤等30+类),提取前后短语作为具体服装描述 - hair兜底不再固定"自然发型",从color标签中识别小面积发色(占比<0.3的黑/棕/金/栗等颜色) 自动填充发色+"头发" - 回填后重新拼装_parts,保证portrait_prompt信息密度 - 回填不覆盖VLM正确输出的值,只补全缺失/通用值 2. few-shot示例(prompt): - image_analysis system prompt新增有人物场景的完整10属性输出示例 - 明确"必须写全10个属性,禁止省略" 预期:原米色翻领衬衫人像→outfit回填为"米色翻领衬衫"、hair从颜色提取"黑色头发" →Seedream t2i还原服装款式→整体人物相似度稳超85%且服装更准 --- apps/worker/worker_app/tasks/viral_video.py | 72 ++++++++++++++++++++- packages/application/viral_video/prompts.py | 5 +- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index 678c3b8d0..2f8f8b82a 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -444,6 +444,76 @@ def _analyze_single_image( _parts.append(f"{_skin}肤色") _parts.append(f"{_face}脸型") _parts.append(f"身着{_outfit}") + if _pose != "无法判断": + _parts.append(f"姿态{_pose}") + if _expr != "无法判断": + _parts.append(f"表情{_expr}") + else: + _parts.append("表情自然") + # #2186: 智能回填——VLM有时省略hair/outfit等外貌属性,但product.name/features/colors里已有相关信息 + # 从product名字和features中提取服装关键词回填outfit + if _outfit in ("日常服装", "无法判断"): + for _ppn in product_nodes: + _pn = (_ppn.get("attrs") or {}).get("name", "") or "" + _pf = (_ppn.get("attrs") or {}).get("features", "") or "" + _ptxt = _pn + " " + _pf + # 服装关键词识别(常见上装/下装/裙装/套装) + _cloth_kws = ["衬衫","T恤","毛衣","针织衫","卫衣","外套","西装","夹克","风衣", + "大衣","羽绒服","马甲","背心","连衣裙","半身裙","短裙","长裙", + "牛仔裤","休闲裤","西裤","运动裤","短裤","旗袍","汉服","制服", + "polo衫","POLO衫","针织","毛衫","开衫","帽衫","皮夹克","皮衣"] + for _ckw in _cloth_kws: + if _ckw in _ptxt: + # 提取含关键词的短语(关键词前后4字) + _ci = _ptxt.find(_ckw) + _start = max(0, _ci-6) + _end = min(len(_ptxt), _ci+len(_ckw)+2) + _outfit_extract = _ptxt[_start:_end].strip(" ,,。.、") + # 清理掉品牌名/产品名词的干扰(只取服装描述部分) + _outfit_extract = re.sub(r"^[\w一-龥]{0,2}(牌|品牌|的|款|女|男|新|装|大|小|长|短|厚|薄)", "", _outfit_extract) + if len(_outfit_extract) >= 2: + _outfit = _outfit_extract + break + if _outfit not in ("日常服装", "无法判断"): + break + # 从color标签中提取头发颜色回填hair + if _hair in ("自然发型", "无法判断"): + _hair_color = "" + _color_nodes = [n for n in nodes if n["tag"] == "color"] + _hair_kws_map = {"黑":"黑色","棕":"棕色","金":"金色","栗":"栗色","红":"红色", + "白":"白色","灰":"灰色","蓝":"蓝色","黄":"黄色","紫":"紫色"} + for _cn in _color_nodes: + _cname = (_cn.get("attrs") or {}).get("name","") or "" + # 小占比颜色更可能是发色(非主色的小面积色),且名称含头发/黑/棕/金等 + _ccov = 0.0 + try: + _ccov = float((_cn.get("attrs") or {}).get("coverage","0") or 0) + except Exception: + pass + for _hk, _hv in _hair_kws_map.items(): + if _hk in _cname and _ccov < 0.3: + _hair_color = _hv + break + if _hair_color: + break + if _hair_color: + _hair = f"{_hair_color}头发" + else: + _hair = "自然发型" + # 重新拼装_parts(回填后) + _parts = [] + if _gender != "无法判断": + _g = _gender + ("性" if not _gender.endswith("性") else "") + _parts.append(_g) + else: + _parts.append("成年人") + if _age != "无法判断": + _parts.append(_age) + _parts.append("人物") + _parts.append(_hair) + _parts.append(f"{_skin}肤色") + _parts.append(f"{_face}脸型") + _parts.append(f"身着{_outfit}") if _pose != "无法判断": _parts.append(f"姿态{_pose}") if _expr != "无法判断": @@ -452,7 +522,7 @@ def _analyze_single_image( _parts.append("表情自然") portrait_prompt = ",".join(_parts) logger.info( - "[爆款视频] 图片 #%d 解析: count=%d gender=%s age=%s hair=%s skin=%s face=%s outfit=%s pose=%s expr=%s → %s", + "[爆款视频] 图片 #%d 解析(回填后): count=%d gender=%s age=%s hair=%s skin=%s face=%s outfit=%s pose=%s expr=%s → %s", idx, _count, _gender, diff --git a/packages/application/viral_video/prompts.py b/packages/application/viral_video/prompts.py index 4e41733a9..bf131b1e1 100644 --- a/packages/application/viral_video/prompts.py +++ b/packages/application/viral_video/prompts.py @@ -65,7 +65,10 @@ hair/skin_tone/face_shape/outfit四项绝对禁止填“无法判断”,必须 - outfit:必须描述可见穿着,如“米色翻领衬衫”“白色T恤”“黑色连衣裙” 即使局部被遮挡也要根据可见部分合理推断;确实看不清时按最接近的直观印象描述。 -其他非人物属性看不到或无法判断时填“无法判断”,布尔值填false,不要留空标签。""" +其他非人物属性看不到或无法判断时填“无法判断”,布尔值填false,不要留空标签。 + +【有人物场景输出参考(女性手持商品示例,必须写全10个属性,禁止省略)】 +""" _IMAGE_ANALYSIS_USER = """请分析以下商品图片,共 {image_count} 张。 所属行业:{industry} -- 2.54.0 From 27f29cc31ed228121f047359892ba28803321094 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 5 Oct 2026 01:30:05 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(#2187):=20outfit=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E6=AD=A3=E5=88=99=E4=BC=98=E5=8C=96,=E4=BF=9D=E7=95=99?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E5=BD=A2=E5=AE=B9=E8=AF=8D+=E6=B8=85?= =?UTF-8?q?=E7=90=86=E5=B0=BE=E9=83=A8=E5=9E=8B=E5=8F=B7=E6=9D=82=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前缀清理只移除品牌/产品类前缀(牌/品牌/款/产品/商品/的),保留颜色/款式/尺寸形容词 - 尾部清理扩展:标/数字+单位(ml/g/L/斤/件/瓶/盒)/尾部纯数字 - 提取窗口从前后6/2扩到8/4,多保留颜色词(米白色/米白色翻领) - 修复"米白色翻领衬衫"被清成"白色"、尾部残留"标"字的问题 --- apps/worker/worker_app/tasks/viral_video.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index 2f8f8b82a..295731f84 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -464,13 +464,16 @@ def _analyze_single_image( "polo衫","POLO衫","针织","毛衫","开衫","帽衫","皮夹克","皮衣"] for _ckw in _cloth_kws: if _ckw in _ptxt: - # 提取含关键词的短语(关键词前后4字) _ci = _ptxt.find(_ckw) - _start = max(0, _ci-6) - _end = min(len(_ptxt), _ci+len(_ckw)+2) + # 向前找颜色/材质/款式形容词(白/黑/米/红/蓝/灰/棉/麻/长/短/厚/薄/长袖/短袖/翻领/圆领/V领/印花/条纹等) + _start = max(0, _ci-8) + # 向后包含款式词(长袖/短袖/外套/套装/上衣等后续修饰) + _end = min(len(_ptxt), _ci+len(_ckw)+4) _outfit_extract = _ptxt[_start:_end].strip(" ,,。.、") - # 清理掉品牌名/产品名词的干扰(只取服装描述部分) - _outfit_extract = re.sub(r"^[\w一-龥]{0,2}(牌|品牌|的|款|女|男|新|装|大|小|长|短|厚|薄)", "", _outfit_extract) + # 仅清理明确的品牌/产品类前缀(不清理颜色/款式/尺寸形容词) + _outfit_extract = re.sub(r"^(\S{0,4}牌|\S{0,3}品牌|\S{0,3}款|产品|商品|的)", "", _outfit_extract).strip() + # 尾部清理:去掉残留的品牌字/型号字(如"标""ml""g""装"等单字杂字) + _outfit_extract = re.sub(r"(标[0-9a-zA-Z]*|\d+\s*(?:ml|g|L|斤|件|个|瓶|盒|包|袋|装)|\s+\d+\s*)$", "", _outfit_extract, flags=re.IGNORECASE).strip() if len(_outfit_extract) >= 2: _outfit = _outfit_extract break -- 2.54.0 From bd1050a23f0ee0cd697ae6b4b819f1097ac2f1f8 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sun, 4 Oct 2026 17:34:11 +0000 Subject: [PATCH 3/3] style: auto-format with black + isort + ruff + prettier [skip ci-format-check] --- apps/worker/worker_app/tasks/viral_video.py | 72 +++++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index 295731f84..9e6c6f919 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -458,22 +458,60 @@ def _analyze_single_image( _pf = (_ppn.get("attrs") or {}).get("features", "") or "" _ptxt = _pn + " " + _pf # 服装关键词识别(常见上装/下装/裙装/套装) - _cloth_kws = ["衬衫","T恤","毛衣","针织衫","卫衣","外套","西装","夹克","风衣", - "大衣","羽绒服","马甲","背心","连衣裙","半身裙","短裙","长裙", - "牛仔裤","休闲裤","西裤","运动裤","短裤","旗袍","汉服","制服", - "polo衫","POLO衫","针织","毛衫","开衫","帽衫","皮夹克","皮衣"] + _cloth_kws = [ + "衬衫", + "T恤", + "毛衣", + "针织衫", + "卫衣", + "外套", + "西装", + "夹克", + "风衣", + "大衣", + "羽绒服", + "马甲", + "背心", + "连衣裙", + "半身裙", + "短裙", + "长裙", + "牛仔裤", + "休闲裤", + "西裤", + "运动裤", + "短裤", + "旗袍", + "汉服", + "制服", + "polo衫", + "POLO衫", + "针织", + "毛衫", + "开衫", + "帽衫", + "皮夹克", + "皮衣", + ] for _ckw in _cloth_kws: if _ckw in _ptxt: _ci = _ptxt.find(_ckw) # 向前找颜色/材质/款式形容词(白/黑/米/红/蓝/灰/棉/麻/长/短/厚/薄/长袖/短袖/翻领/圆领/V领/印花/条纹等) - _start = max(0, _ci-8) + _start = max(0, _ci - 8) # 向后包含款式词(长袖/短袖/外套/套装/上衣等后续修饰) - _end = min(len(_ptxt), _ci+len(_ckw)+4) + _end = min(len(_ptxt), _ci + len(_ckw) + 4) _outfit_extract = _ptxt[_start:_end].strip(" ,,。.、") # 仅清理明确的品牌/产品类前缀(不清理颜色/款式/尺寸形容词) - _outfit_extract = re.sub(r"^(\S{0,4}牌|\S{0,3}品牌|\S{0,3}款|产品|商品|的)", "", _outfit_extract).strip() + _outfit_extract = re.sub( + r"^(\S{0,4}牌|\S{0,3}品牌|\S{0,3}款|产品|商品|的)", "", _outfit_extract + ).strip() # 尾部清理:去掉残留的品牌字/型号字(如"标""ml""g""装"等单字杂字) - _outfit_extract = re.sub(r"(标[0-9a-zA-Z]*|\d+\s*(?:ml|g|L|斤|件|个|瓶|盒|包|袋|装)|\s+\d+\s*)$", "", _outfit_extract, flags=re.IGNORECASE).strip() + _outfit_extract = re.sub( + r"(标[0-9a-zA-Z]*|\d+\s*(?:ml|g|L|斤|件|个|瓶|盒|包|袋|装)|\s+\d+\s*)$", + "", + _outfit_extract, + flags=re.IGNORECASE, + ).strip() if len(_outfit_extract) >= 2: _outfit = _outfit_extract break @@ -483,14 +521,24 @@ def _analyze_single_image( if _hair in ("自然发型", "无法判断"): _hair_color = "" _color_nodes = [n for n in nodes if n["tag"] == "color"] - _hair_kws_map = {"黑":"黑色","棕":"棕色","金":"金色","栗":"栗色","红":"红色", - "白":"白色","灰":"灰色","蓝":"蓝色","黄":"黄色","紫":"紫色"} + _hair_kws_map = { + "黑": "黑色", + "棕": "棕色", + "金": "金色", + "栗": "栗色", + "红": "红色", + "白": "白色", + "灰": "灰色", + "蓝": "蓝色", + "黄": "黄色", + "紫": "紫色", + } for _cn in _color_nodes: - _cname = (_cn.get("attrs") or {}).get("name","") or "" + _cname = (_cn.get("attrs") or {}).get("name", "") or "" # 小占比颜色更可能是发色(非主色的小面积色),且名称含头发/黑/棕/金等 _ccov = 0.0 try: - _ccov = float((_cn.get("attrs") or {}).get("coverage","0") or 0) + _ccov = float((_cn.get("attrs") or {}).get("coverage", "0") or 0) except Exception: pass for _hk, _hv in _hair_kws_map.items(): -- 2.54.0