guanghulab/video-ai-system/tools/qc_char004_r2.py
冰朔 c81307004d
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
铸渊自检 / health-check (push) Has been cancelled
D158 · 会话收尾 · 进度+脚本+QC报告入库(不含候选图)
- 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)保留本地不入库
2026-07-02 01:07:43 +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())