guanghulab/video-ai-system/tools/qc_char004_r2.py

185 lines
6.5 KiB
Python
Raw Normal View History

#!/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())