141 lines
5.3 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""QC: PROP-002牌匾 + PROP-003广告牌 R2候选图 · 闸门1"""
import sys, os, json, base64, time, subprocess
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from secrets_loader import secret, endpoint
AK = secret("SC-004")
URL = endpoint("EPT-002")
ROOT = Path(__file__).resolve().parents[1]
# 候选图路径
CANDIDATES = {
"PROP-002": {
"name": "天道宗无字牌匾",
"spec": "深棕色木质无字牌匾长方形横版边缘古朴雕花装饰3D动漫卡通渲染风格纯白背景无文字",
"reject": ["写实", "照片感", "真实木纹", "产品摄影", "文字", "汉字", "字母"],
"dir": ROOT / "assets/candidates/D157-PROP-002-R2/PROP-002-Tiandao-Plaque",
"files": [f"PROP-002-R2-candidate-{i:02d}-1080x1920.jpg" for i in range(1, 5)]
},
"PROP-003": {
"name": "免费招徒广告牌",
"spec": "木制立式支架+米黄色空白宣纸底板原木色支架朴素稳固宣纸干净无字墨迹修仙世界摊位宣传木板3D动漫卡通渲染纯白背景",
"reject": ["写实", "照片感", "画架", "easel", "美术画架", "真实木纹", "产品图", "文字", "墨迹"],
"dir": ROOT / "assets/candidates/D157-PROP-003-R2/PROP-003-Recruit-Board",
"files": [f"PROP-003-R2-candidate-{i:02d}-1080x1920.jpg" for i in range(1, 5)]
}
}
def encode_image(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
def qwen_vl_qc(img_b64, asset_name, spec, reject_words):
"""调用Qwen-VL质检单张图"""
prompt = f"""你是一个动态漫资产生成质检员。请严格检查这张图片。
资产名称{asset_name}
规范要求{spec}
请逐项检查并打分0-100
1. 风格匹配度是否为3D动漫卡通渲染风格非写实/照片感
2. 内容准确性是否包含规范要求的所有元素
3. 一票否决项检查以下任意一项出现直接判不合格
- 是否有文字/汉字/字母
- 是否是写实/照片感风格
- 是否像产品摄影图
- 对于PROP-003是否是画架(easel)样式而不是摊位木板
输出格式严格JSON
{{"score": <0-100>, "style_ok": <bool>, "content_ok": <bool>, "has_text": <bool>, "is_photorealistic": <bool>, "is_easel": <bool>, "issues": ["问题列表"], "verdict": "PASS/FAIL"}}"""
payload = {
"model": "qwen-vl-max-latest",
"input": {
"messages": [
{"role": "user", "content": [
{"image": f"data:image/jpeg;base64,{img_b64}"},
{"text": prompt}
]}
]
},
"parameters": {"enable_thinking": False}
}
r = subprocess.run(
["curl", "-s", "-m", "60", "--noproxy", "*", "-X", "POST", URL,
"-H", f"Authorization: Bearer {AK}",
"-H", "Content-Type: application/json",
"-d", json.dumps(payload)],
capture_output=True, text=True, timeout=65
)
try:
d = json.loads(r.stdout)
content = d["output"]["choices"][0]["message"]["content"]
# 提取JSON
import re
m = re.search(r'\{[^{}]*\}', content)
if m:
return json.loads(m.group())
return {"score": 0, "verdict": "FAIL", "issues": ["无法解析VL响应"], "raw": content[:200]}
except Exception as e:
return {"score": 0, "verdict": "FAIL", "issues": [f"API错误: {e}"]}
def main():
if not AK:
print("✗ SC-004密钥未找到"); return 1
results = {}
for aid, cfg in CANDIDATES.items():
print(f"\n{'='*50}")
print(f"QC: {aid} · {cfg['name']}")
print(f"{'='*50}")
results[aid] = []
for fname in cfg["files"]:
fpath = cfg["dir"] / fname
if not fpath.exists():
print(f" ✗ 文件不存在: {fname}")
continue
print(f"\n 检查: {fname} ...", end=" ", flush=True)
img_b64 = encode_image(str(fpath))
qc = qwen_vl_qc(img_b64, cfg["name"], cfg["spec"], cfg["reject"])
v = qc.get("verdict", "?")
s = qc.get("score", "?")
issues = qc.get("issues", [])
print(f"[{v}] score={s}")
for iss in issues:
print(f" ⚠️ {iss}")
qc["_file"] = fname
results[aid].append(qc)
time.sleep(1)
# 输出汇总
print(f"\n\n{'='*60}")
print("QC 汇总报告")
print(f"{'='*60}")
for aid, qlist in results.items():
print(f"\n{aid}")
for q in qlist:
fn = q.get("_file", "?")
v = q.get("verdict", "?")
s = q.get("score", "?")
ph = q.get("is_photorealistic", False)
easel = q.get("is_easel", False)
has_txt = q.get("has_text", False)
flag = ""
if ph: flag += " 写实"
if easel: flag += " 画架"
if has_txt: flag += " 有字"
print(f" {fn}: {v} (score={s}){flag}")
# 写入QC报告
report_path = ROOT / "assets/candidates/D157-PROP-QC-report.json"
with open(report_path, "w") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"\n📋 QC报告已写入: {report_path}")
return 0
if __name__ == "__main__":
sys.exit(main())