- CURRENT-STATE 更新至D158 - 生成脚本: char004/char004_r3/prop002/prop002_r3/prop003 全量 - QC脚本: qc_char004_r2/r3 + qc_prop_r2 - QC报告: CHAR-004 R2~R5 + PROP全量 - generation-meta: 所有候选批次元数据 - 候选图(JPG)保留本地不入库
185 lines
6.5 KiB
Python
185 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
||
"""CHAR-004 R2 候选质检 · Qwen-VL · VA-05-001-R2标准"""
|
||
import sys, os, json, base64, subprocess
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
from secrets_loader import secret, endpoint
|
||
|
||
API_KEY = secret("SC-004")
|
||
EP = endpoint("EPT-002")
|
||
FALLBACK_EP = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
|
||
|
||
CANDIDATES_DIR = os.path.expanduser("~/guanghulab/video-ai-system/assets/candidates/D157-VA-05-001-R2/CHAR-004-ZhugeFeng")
|
||
CANDIDATES = [f"{CANDIDATES_DIR}/CHAR-004-R2-candidate-{i:02d}-1080x1920.jpg" for i in range(1, 5)]
|
||
|
||
QC_PROMPT = """你是动画资产质量检查员。检查这张CHAR-004诸葛风立绘候选图,按以下标准逐项打分。
|
||
|
||
## R2规范必查项
|
||
|
||
1. 服装完整性(一票否决):
|
||
- 裤子膝盖是否有破洞/破损?是/否
|
||
- 衣服下摆是否有毛边/破损/撕裂?是/否
|
||
- 鞋子是否有泥污/脏污?是/否
|
||
- 服装是否有污渍?是/否
|
||
- 是否有补丁?是/否
|
||
- 整体是否"乞丐化"(像乞丐而不是清贫少年)?
|
||
|
||
2. 服装部件准确性:
|
||
- 上衣是否为灰蓝色交领短打、右衽?是/否
|
||
- 是否系深棕色粗麻绳腰带?是/否
|
||
- 裤子是否深灰蓝色、裤脚塞进靴子?是/否
|
||
- 鞋子是否黑色粗布短靴?是/否
|
||
- 腰间是否有旧布包?是/否
|
||
- 有无不该有的配饰(玉佩、香囊等)?是/否
|
||
|
||
3. 面部质量:
|
||
- 五官是否清晰无崩坏?是/否
|
||
- 是否少年感强、气质偏冷硬?是/否
|
||
- 有没有和苏白(温润仙气型)相似?是/否
|
||
|
||
4. 整体:
|
||
- 人物肢体比例是否正常?是/否
|
||
- 画面是否清晰无噪点?是/否
|
||
- 是否全身从头到鞋完整展示?是/否
|
||
|
||
输出严格JSON,不要额外文字:
|
||
{
|
||
"candidate": N,
|
||
"pass": true/false,
|
||
"score": 0-100,
|
||
"hole_in_pants": true/false,
|
||
"frayed_hem": true/false,
|
||
"dirty_shoes": true/false,
|
||
"stains": true/false,
|
||
"patches": true/false,
|
||
"beggar_look": true/false,
|
||
"correct_top": true/false,
|
||
"correct_belt": true/false,
|
||
"correct_pants": true/false,
|
||
"correct_shoes": true/false,
|
||
"has_bag": true/false,
|
||
"extra_accessories": true/false,
|
||
"face_intact": true/false,
|
||
"cold_temperament": true/false,
|
||
"similar_to_su_bai": true/false,
|
||
"anatomy_ok": true/false,
|
||
"clear_image": true/false,
|
||
"full_body": true/false,
|
||
"summary": "一句话总结"
|
||
}"""
|
||
|
||
def encode_image(path):
|
||
with open(path, "rb") as f:
|
||
b64 = base64.b64encode(f.read()).decode()
|
||
ext = path.rsplit(".", 1)[-1].lower()
|
||
mime = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png"}.get(ext, "jpeg")
|
||
return f"data:image/{mime};base64,{b64}"
|
||
|
||
def call_qwen(image_b64):
|
||
body = json.dumps({
|
||
"model": "qwen-vl-max",
|
||
"input": {
|
||
"messages": [{
|
||
"role": "user",
|
||
"content": [
|
||
{"image": image_b64},
|
||
{"text": QC_PROMPT}
|
||
]
|
||
}]
|
||
}
|
||
})
|
||
|
||
endpoints = [EP, FALLBACK_EP]
|
||
for ep in endpoints:
|
||
try:
|
||
proc = subprocess.run(
|
||
["curl", "-s", "-m", "60", "--noproxy", "*",
|
||
"-X", "POST", ep,
|
||
"-H", f"Authorization: Bearer {API_KEY}",
|
||
"-H", "Content-Type: application/json",
|
||
"--data-binary", "@-"],
|
||
input=body, capture_output=True, text=True, timeout=65
|
||
)
|
||
if proc.returncode != 0 or not proc.stdout.strip():
|
||
continue
|
||
resp = json.loads(proc.stdout)
|
||
if "output" in resp:
|
||
content = resp["output"]["choices"][0]["message"]["content"][0]["text"]
|
||
# Clean markdown wrappers
|
||
if "```" in content:
|
||
content = content.split("```")[1]
|
||
if content.startswith("json"):
|
||
content = content[4:]
|
||
content = content.split("```")[0]
|
||
return json.loads(content.strip())
|
||
elif "error" in resp:
|
||
print(f" [Qwen error] {resp['error']}", file=sys.stderr)
|
||
continue
|
||
except Exception as e:
|
||
print(f" [fail {ep}] {e}", file=sys.stderr)
|
||
continue
|
||
return None
|
||
|
||
def main():
|
||
print("=" * 60)
|
||
print("CHAR-004 R2 候选质检 · Qwen-VL · VA-05-001-R2标准")
|
||
print("=" * 60)
|
||
|
||
results = []
|
||
for i, path in enumerate(CANDIDATES):
|
||
print(f"\n[{i+1}/4] 质检候选 {i+1}...")
|
||
img = encode_image(path)
|
||
qc = call_qwen(img)
|
||
|
||
if qc:
|
||
print(f" score={qc.get('score')} pass={qc.get('pass')}")
|
||
print(f" 破洞={qc.get('hole_in_pants')} 毛边={qc.get('frayed_hem')} 脏鞋={qc.get('dirty_shoes')}")
|
||
print(f" 乞丐化={qc.get('beggar_look')} 和苏白像={qc.get('similar_to_su_bai')}")
|
||
print(f" {qc.get('summary')}")
|
||
results.append(qc)
|
||
else:
|
||
print(f" ✗ Qwen-VL调用失败")
|
||
results.append({"candidate": i+1, "pass": False, "error": "Qwen-VL call failed"})
|
||
|
||
# 汇总
|
||
print("\n" + "=" * 60)
|
||
print("质检汇总")
|
||
print("=" * 60)
|
||
|
||
passed = sum(1 for r in results if r.get("pass"))
|
||
print(f"通过: {passed}/4")
|
||
|
||
for r in results:
|
||
if not r.get("pass"):
|
||
issues = []
|
||
if r.get("hole_in_pants"): issues.append("裤子破洞")
|
||
if r.get("frayed_hem"): issues.append("下摆毛边")
|
||
if r.get("dirty_shoes"): issues.append("鞋子脏污")
|
||
if r.get("stains"): issues.append("污渍")
|
||
if r.get("patches"): issues.append("补丁")
|
||
if r.get("beggar_look"): issues.append("乞丐化")
|
||
if r.get("extra_accessories"): issues.append("多余配饰")
|
||
print(f" 候选{r.get('candidate')} ✗: {', '.join(issues) if issues else 'other'}")
|
||
|
||
# 写入JSON
|
||
out_path = os.path.join(CANDIDATES_DIR, "QC-R2-BATCH.json")
|
||
report = {
|
||
"spec": "VA-05-001-R2",
|
||
"asset_id": "CHAR-004-ZhugeFeng",
|
||
"qc_tool": "Qwen-VL (qwen-vl-max)",
|
||
"results": results,
|
||
"summary": {
|
||
"passed": passed,
|
||
"total": 4,
|
||
"verdict": "APPROVED" if passed >= 4 else "REJECTED"
|
||
}
|
||
}
|
||
with open(out_path, "w") as f:
|
||
json.dump(report, f, ensure_ascii=False, indent=2)
|
||
print(f"\nQC报告已保存: {out_path}")
|
||
|
||
return 0 if passed >= 4 else 1
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|