guanghulab/video-ai-system/tools/qc_char004_r2.py
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

185 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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