'commit'
This commit is contained in:
@@ -8,6 +8,7 @@ import aiohttp
|
||||
import logging
|
||||
import base64
|
||||
import cv2
|
||||
import uuid
|
||||
from openai import AsyncOpenAI, BadRequestError
|
||||
from Config.Config import (
|
||||
ALY_LLM_API_KEY, VL_MODEL_NAME, VL_MODEL_NAME_AD
|
||||
@@ -16,7 +17,6 @@ from Apps.AiTeJiYiChong.Config.Setting import (
|
||||
SAFE_EXCLUDE_RATIO, FALLBACK_WIDTH, FALLBACK_HEIGHT,
|
||||
BOTTOM_SAFE_EXCLUDE_RATIO
|
||||
)
|
||||
from Util.PaddleOCRKit import get_ocr_kit
|
||||
from Apps.AiTeJiYiChong import Kit
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -290,51 +290,107 @@ class ReadImageKit:
|
||||
logger.error(f"Error calling VL model for detail: {e}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def get_stations_hybrid(cls, image_path: str, device_info=None):
|
||||
@staticmethod
|
||||
async def recognize_card_with_vl(image_path: str):
|
||||
"""
|
||||
混合识别模式:图形学切片 + 本地 PaddleOCR 识别
|
||||
对卡片切片进行 VL 识别
|
||||
"""
|
||||
if not os.path.exists(image_path):
|
||||
logger.error(f"Image not found: {image_path}")
|
||||
return []
|
||||
return None
|
||||
|
||||
with open(image_path, "rb") as f:
|
||||
encoded = base64.b64encode(f.read()).decode("utf-8")
|
||||
img_input = {"url": f"data:image/jpeg;base64,{encoded}"}
|
||||
|
||||
prompt = (
|
||||
"请识别图片中的充电站卡片信息,并以 JSON 格式输出:\n"
|
||||
"{\n"
|
||||
" \"station_name\": \"场站名称\",\n"
|
||||
" \"price\": \"价格(数字)\",\n"
|
||||
" \"piles\": \"空闲/总数(如 4/4)\",\n"
|
||||
" \"distance\": \"距离(如 2.5km)\",\n"
|
||||
" \"parking\": \"停车描述\"\n"
|
||||
"}\n"
|
||||
"只输出 JSON,不要有其他文字。"
|
||||
)
|
||||
|
||||
try:
|
||||
resp = await ReadImageKit._client.chat.completions.create(
|
||||
model=VL_MODEL_NAME,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
{"type": "image_url", "image_url": img_input}
|
||||
]
|
||||
}
|
||||
],
|
||||
max_tokens=300,
|
||||
temperature=0.01
|
||||
)
|
||||
content = resp.choices[0].message.content.strip()
|
||||
raw = ReadImageKit._extract_json(content)
|
||||
return json.loads(raw)
|
||||
except Exception as e:
|
||||
logger.error(f"VL recognize_card_with_vl error: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
async def get_stations_hybrid(image_path, device_info=None):
|
||||
"""
|
||||
混合识别模式:图形学切片 + VL 模型识别
|
||||
"""
|
||||
if device_info is None:
|
||||
device_info = ReadImageKit._FALLBACK_DEVICE_INFO
|
||||
|
||||
# 1. 使用 Kit 中的图形学算法识别卡片区域
|
||||
# Kit.crop_cards_from_image 会生成 .json, _flag.jpg, _vl.jpg
|
||||
# 我们主要需要它返回的 json_data
|
||||
json_data = Kit.crop_cards_from_image(image_path, save_debug=True)
|
||||
if not json_data or not json_data.get("cards"):
|
||||
logger.warning("No cards detected by graphical slicing.")
|
||||
return []
|
||||
|
||||
# 2. 对每个卡片区域进行 VL 识别
|
||||
tasks = []
|
||||
card_metas = []
|
||||
|
||||
# 为了使用 VL,我们需要将每个卡片切出来保存到临时文件
|
||||
temp_dir = os.path.join(os.path.dirname(image_path), "Patches")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
|
||||
img = Image.open(image_path).convert("RGB")
|
||||
ocr_kit = get_ocr_kit()
|
||||
|
||||
final_stations = []
|
||||
|
||||
# 2. 对每个卡片区域进行 OCR 识别
|
||||
# 注意:PaddleOCR 识别过程较快,且通常不涉及网络请求,可以根据需要选择并行或串行
|
||||
# 这里使用串行以保证日志输出整齐,如果追求极致性能可改用 asyncio.to_thread 并行
|
||||
for card in json_data["cards"]:
|
||||
rect = card["rect"] # [x1, y1, x2, y2]
|
||||
# 裁剪卡片
|
||||
patch_path = os.path.join(temp_dir, f"patch_{card['id']}_{uuid.uuid4().hex[:8]}.jpg")
|
||||
|
||||
# 裁剪并保存
|
||||
patch = img.crop((rect[0], rect[1], rect[2], rect[3]))
|
||||
patch.save(patch_path)
|
||||
|
||||
# 转换为 ndarray 供 PaddleOCR 使用
|
||||
patch_cv = cv2.cvtColor(np.array(patch), cv2.COLOR_RGB2BGR)
|
||||
card_metas.append({
|
||||
"meta": card,
|
||||
"patch_path": patch_path
|
||||
})
|
||||
tasks.append(ReadImageKit.recognize_card_with_vl(patch_path))
|
||||
|
||||
# 并行执行 VL 识别
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
final_stations = []
|
||||
for i, res in enumerate(results):
|
||||
meta = card_metas[i]["meta"]
|
||||
patch_path = card_metas[i]["patch_path"]
|
||||
|
||||
# OCR 识别
|
||||
logger.info(f"正在识别卡片 {card['id']}: {rect}")
|
||||
res = ocr_kit.recognize(patch_cv)
|
||||
|
||||
if res and res.get("station_name"):
|
||||
if res and isinstance(res, dict) and res.get("station_name"):
|
||||
# 注入点击坐标和原始区域信息
|
||||
res["uia_center_x"] = card["click_point"][0]
|
||||
res["uia_center_y"] = card["click_point"][1]
|
||||
res["rect"] = rect
|
||||
res["uia_center_x"] = meta["click_point"][0]
|
||||
res["uia_center_y"] = meta["click_point"][1]
|
||||
res["rect"] = meta["rect"]
|
||||
|
||||
# 转换 bounds 到 0-1000 空间(保持与 VL 模式兼容)
|
||||
# 转换 bounds 到 0-1000 空间
|
||||
w, h = img.size
|
||||
rect = meta["rect"]
|
||||
res["bounds"] = [
|
||||
int(rect[0] * 1000 / w),
|
||||
int(rect[1] * 1000 / h),
|
||||
@@ -343,9 +399,16 @@ class ReadImageKit:
|
||||
]
|
||||
|
||||
final_stations.append(res)
|
||||
logger.info(f"卡片 {card['id']} 识别成功: {res['station_name']}")
|
||||
logger.info(f"卡片 {meta['id']} 识别成功: {res['station_name']}")
|
||||
else:
|
||||
logger.warning(f"卡片 {card['id']} 识别失败或无名称")
|
||||
logger.warning(f"卡片 {meta['id']} 识别失败或无名称")
|
||||
|
||||
# 清理临时切片文件
|
||||
try:
|
||||
if os.path.exists(patch_path):
|
||||
os.remove(patch_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
return final_stations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user