303 lines
8.6 KiB
Python
303 lines
8.6 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
图片理解核心模块
|
|||
|
|
基于 OpenAI GPT-4 Vision API 实现图片理解功能
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import base64
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
import requests
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ImageUnderstander:
|
|||
|
|
"""图片理解器"""
|
|||
|
|
|
|||
|
|
def __init__(self, api_key: str, verbose: bool = False):
|
|||
|
|
"""
|
|||
|
|
初始化图片理解器
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
api_key: OpenAI API Key
|
|||
|
|
verbose: 是否显示详细日志
|
|||
|
|
"""
|
|||
|
|
self.api_key = api_key
|
|||
|
|
self.verbose = verbose
|
|||
|
|
self.api_url = "https://api.openai.com/v1/chat/completions"
|
|||
|
|
|
|||
|
|
def _encode_image(self, image_path: str) -> str:
|
|||
|
|
"""
|
|||
|
|
将图片编码为 base64 格式
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
image_path: 图片路径
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
base64 编码的字符串
|
|||
|
|
"""
|
|||
|
|
with open(image_path, "rb") as f:
|
|||
|
|
return base64.b64encode(f.read()).decode("utf-8")
|
|||
|
|
|
|||
|
|
def _call_api(self, messages: list, max_tokens: int = 2000) -> str:
|
|||
|
|
"""
|
|||
|
|
调用 OpenAI API
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
messages: 消息列表
|
|||
|
|
max_tokens: 最大返回token数
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
API 返回的文本内容
|
|||
|
|
"""
|
|||
|
|
headers = {
|
|||
|
|
"Authorization": f"Bearer {self.api_key}",
|
|||
|
|
"Content-Type": "application/json"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data = {
|
|||
|
|
"model": "gpt-4o",
|
|||
|
|
"messages": messages,
|
|||
|
|
"max_tokens": max_tokens,
|
|||
|
|
"temperature": 0.2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if self.verbose:
|
|||
|
|
print(f"📡 正在调用 OpenAI API...")
|
|||
|
|
|
|||
|
|
response = requests.post(self.api_url, headers=headers, json=data)
|
|||
|
|
|
|||
|
|
if response.status_code != 200:
|
|||
|
|
raise Exception(f"API 调用失败: {response.status_code} - {response.text}")
|
|||
|
|
|
|||
|
|
result = response.json()
|
|||
|
|
return result["choices"][0]["message"]["content"]
|
|||
|
|
|
|||
|
|
def describe(self, image_path: str) -> str:
|
|||
|
|
"""
|
|||
|
|
描述图片内容
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
image_path: 图片路径
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
图片描述文本
|
|||
|
|
"""
|
|||
|
|
if self.verbose:
|
|||
|
|
print(f"🖼️ 正在描述图片...")
|
|||
|
|
|
|||
|
|
base64_image = self._encode_image(image_path)
|
|||
|
|
|
|||
|
|
messages = [
|
|||
|
|
{
|
|||
|
|
"role": "user",
|
|||
|
|
"content": [
|
|||
|
|
{
|
|||
|
|
"type": "text",
|
|||
|
|
"text": "请详细描述这张图片,包括:场景、人物、物体、颜色、氛围等所有可见元素。"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "image_url",
|
|||
|
|
"image_url": {
|
|||
|
|
"url": f"data:image/jpeg;base64,{base64_image}"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
return self._call_api(messages)
|
|||
|
|
|
|||
|
|
def extract_text(self, image_path: str) -> str:
|
|||
|
|
"""
|
|||
|
|
提取图片中的文字 (OCR)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
image_path: 图片路径
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
提取的文字内容
|
|||
|
|
"""
|
|||
|
|
if self.verbose:
|
|||
|
|
print(f"🔤 正在提取文字...")
|
|||
|
|
|
|||
|
|
base64_image = self._encode_image(image_path)
|
|||
|
|
|
|||
|
|
messages = [
|
|||
|
|
{
|
|||
|
|
"role": "user",
|
|||
|
|
"content": [
|
|||
|
|
{
|
|||
|
|
"type": "text",
|
|||
|
|
"text": "请提取图片中的所有文字,包括标题、正文、标签、logo文字等。如果有手写体,请尽量识别。如果图片中没有文字,请明确说明。"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "image_url",
|
|||
|
|
"image_url": {
|
|||
|
|
"url": f"data:image/jpeg;base64,{base64_image}"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
return self._call_api(messages, max_tokens=3000)
|
|||
|
|
|
|||
|
|
def identify_objects(self, image_path: str) -> str:
|
|||
|
|
"""
|
|||
|
|
识别图片中的物体
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
image_path: 图片路径
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
识别出的物体列表
|
|||
|
|
"""
|
|||
|
|
if self.verbose:
|
|||
|
|
print(f"🎯 正在识别物体...")
|
|||
|
|
|
|||
|
|
base64_image = self._encode_image(image_path)
|
|||
|
|
|
|||
|
|
messages = [
|
|||
|
|
{
|
|||
|
|
"role": "user",
|
|||
|
|
"content": [
|
|||
|
|
{
|
|||
|
|
"type": "text",
|
|||
|
|
"text": "请识别图片中的所有物体和元素,并列出它们。建议以列表形式输出,每个物体加上简短描述。"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "image_url",
|
|||
|
|
"image_url": {
|
|||
|
|
"url": f"data:image/jpeg;base64,{base64_image}"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
return self._call_api(messages)
|
|||
|
|
|
|||
|
|
def answer_question(self, image_path: str, question: str) -> str:
|
|||
|
|
"""
|
|||
|
|
针对图片回答问题
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
image_path: 图片路径
|
|||
|
|
question: 问题
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
问题的答案
|
|||
|
|
"""
|
|||
|
|
if self.verbose:
|
|||
|
|
print(f"💬 正在回答问题: {question}")
|
|||
|
|
|
|||
|
|
base64_image = self._encode_image(image_path)
|
|||
|
|
|
|||
|
|
messages = [
|
|||
|
|
{
|
|||
|
|
"role": "user",
|
|||
|
|
"content": [
|
|||
|
|
{
|
|||
|
|
"type": "text",
|
|||
|
|
"text": f"请根据图片内容回答这个问题: {question}"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "image_url",
|
|||
|
|
"image_url": {
|
|||
|
|
"url": f"data:image/jpeg;base64,{base64_image}"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
return self._call_api(messages)
|
|||
|
|
|
|||
|
|
def analyze(self, image_path: str, analysis_type: str = "comprehensive") -> dict:
|
|||
|
|
"""
|
|||
|
|
综合分析图片
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
image_path: 图片路径
|
|||
|
|
analysis_type: 分析类型 (comprehensive/quick/deep)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
分析结果字典
|
|||
|
|
"""
|
|||
|
|
if self.verbose:
|
|||
|
|
print(f"🔬 正在进行{analysis_type}分析...")
|
|||
|
|
|
|||
|
|
base64_image = self._encode_image(image_path)
|
|||
|
|
|
|||
|
|
if analysis_type == "comprehensive":
|
|||
|
|
prompt = """
|
|||
|
|
请对这张图片进行全面分析,包括:
|
|||
|
|
1. 【场景描述】整体场景是什么?
|
|||
|
|
2. 【主要物体】列出主要物体和它们的位置
|
|||
|
|
3. 【文字内容】提取所有可见文字
|
|||
|
|
4. 【颜色氛围】主要色调和氛围
|
|||
|
|
5. 【推断信息】根据内容推断可能的主题/用途
|
|||
|
|
|
|||
|
|
请用JSON格式返回:
|
|||
|
|
{
|
|||
|
|
"description": "场景描述",
|
|||
|
|
"objects": ["物体1", "物体2", ...],
|
|||
|
|
"text": "提取的文字",
|
|||
|
|
"colors": "主要颜色",
|
|||
|
|
"mood": "氛围描述",
|
|||
|
|
"推断": "可能的主题/用途"
|
|||
|
|
}
|
|||
|
|
"""
|
|||
|
|
elif analysis_type == "quick":
|
|||
|
|
prompt = "请用一句话简单描述这张图片是什么。"
|
|||
|
|
else: # deep
|
|||
|
|
prompt = """
|
|||
|
|
请对这张图片进行深度分析,包括:
|
|||
|
|
- 详细场景描述
|
|||
|
|
- 每个物体的精确位置和特征
|
|||
|
|
- 图片的整体构图和视觉层次
|
|||
|
|
- 可能的隐含信息或象征意义
|
|||
|
|
- 图片的质量和技术特点
|
|||
|
|
|
|||
|
|
请详细回答。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
messages = [
|
|||
|
|
{
|
|||
|
|
"role": "user",
|
|||
|
|
"content": [
|
|||
|
|
{
|
|||
|
|
"type": "text",
|
|||
|
|
"text": prompt.strip()
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "image_url",
|
|||
|
|
"image_url": {
|
|||
|
|
"url": f"data:image/jpeg;base64,{base64_image}"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
result_text = self._call_api(messages, max_tokens=3000)
|
|||
|
|
|
|||
|
|
# 尝试解析JSON
|
|||
|
|
try:
|
|||
|
|
# 尝试从返回中找到JSON
|
|||
|
|
if "{" in result_text and "}" in result_text:
|
|||
|
|
start = result_text.find("{")
|
|||
|
|
end = result_text.rfind("}") + 1
|
|||
|
|
json_str = result_text[start:end]
|
|||
|
|
return json.loads(json_str)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 如果不是JSON,返回原始文本
|
|||
|
|
return {
|
|||
|
|
"result": result_text,
|
|||
|
|
"raw_format": True
|
|||
|
|
}
|