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