冰朔 6d20662cc0
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D146: 铸渊之眼视觉分析器 · qwen-vl-max接入 · 视频AI出图品控闭环
- tools/qwen-vision.py: 阿里百炼通义千问VL视觉模型·智能风格/构图/色调分析·双图对比
- tools/vision-analyzer.py: 本地像素级定量分析·色调直方图·纹理/亮度对比
- LOCAL-SECRETS-PATH: 新增ALIYUN_QWEN_VL_KEY/ENDPOINT变量
- CURRENT.hdlp: 最优路径新增第0步「出图后跑铸渊之眼」
- TCS-GLOBAL-NAV: 新增「看图/视觉分析」关键词→HLDP路径映射

下次醒来→读地图→看到视觉分析锚点→知道有眼睛了
2026-06-26 13:30:18 +08:00

137 lines
4.7 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
"""铸渊之眼 · 通义千问视觉分析器
用阿里百炼 qwen-vl 模型看图片,输出风格/色调/构图分析
用法:
python3 qwen-vision.py <image.jpg> # 单图分析
python3 qwen-vision.py <image1.jpg> <image2.jpg> # 双图对比
"""
import sys, os, json, base64
from urllib.request import Request, urlopen
from urllib.error import URLError
# === 配置 ===
# 从 .env 读 key
env_path = os.path.expanduser("~/guanghulab/video-ai-system/.env")
api_key = None
if os.path.exists(env_path):
for line in open(env_path):
line = line.strip()
if line.startswith("ALIYUN_API_KEY="):
api_key = line.split("=", 1)[1].strip()
break
if not api_key:
print(json.dumps({"error": "未找到ALIYUN_API_KEY"}))
sys.exit(1)
# 端点:先试公网,再试北京
ENDPOINTS = [
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
]
MODELS = ["qwen-vl-max", "qwen3-vl-plus", "qwen-vl-plus"]
def encode_image(path):
"""读取图片并转为base64 data URI"""
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", "webp": "webp"}.get(ext, "jpeg")
return f"data:image/{mime};base64,{b64}"
def call_vision(images, prompt, model, endpoint):
"""调用视觉模型"""
content = []
for img in images:
content.append({"image": img})
content.append({"text": prompt})
body = {
"model": model,
"input": {"messages": [{"role": "user", "content": content}]}
}
req = Request(
endpoint,
data=json.dumps(body).encode(),
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
)
resp = urlopen(req, timeout=60)
return json.loads(resp.read())
def extract_content(response):
"""从响应中提取文本内容"""
try:
return response["output"]["choices"][0]["message"]["content"][0]["text"]
except:
return json.dumps(response, ensure_ascii=False)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: qwen-vision.py <image> [image2]")
sys.exit(1)
images = [encode_image(p) for p in sys.argv[1:]]
if len(images) == 1:
prompt = """请详细分析这张图片的视觉特征输出JSON格式
{
"style": "渲染风格如3D动漫/2D手绘/真人写实/UE5游戏等",
"color_palette": ["主色调1", "主色调2", "主色调3"],
"lighting": "光影风格描述",
"composition": "构图方式(特写/中景/全景/俯视/平视等)",
"key_elements": ["画面中的关键元素"],
"text_content": "画面中出现的所有文字内容",
"mood": "氛围感受"
}
只输出JSON不要其他文字。"""
else:
prompt = """请对比这两张图片输出JSON格式
{
"style_match": true或false,
"style_match_detail": "两张图渲染风格是否一致的具体说明",
"color_consistency": "色调是否一致给出0-100分",
"composition_match": "构图方式是否协调",
"key_differences": ["主要差异点"],
"recommendation": "如果要让第二张图匹配第一张图的风格,建议修改什么"
}
只输出JSON不要其他文字。"""
# 尝试不同模型和端点
result = None
for model in MODELS:
for ep in ENDPOINTS:
try:
print(f"[尝试] {model} @ {ep[:50]}...", file=sys.stderr)
resp = call_vision(images, prompt, model, ep)
content = extract_content(resp)
# 尝试解析JSON
try:
# 提取JSON可能被markdown包裹
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
parsed = json.loads(content.strip())
parsed["_model"] = model
parsed["_endpoint"] = ep
print(json.dumps(parsed, ensure_ascii=False, indent=2))
sys.exit(0)
except json.JSONDecodeError:
print(content)
sys.exit(0)
except URLError as e:
print(f"[失败] {model}: {e}", file=sys.stderr)
continue
except Exception as e:
print(f"[异常] {model}: {e}", file=sys.stderr)
continue
print(json.dumps({"error": "所有模型/端点都失败了"}, ensure_ascii=False))
sys.exit(1)