This commit is contained in:
HuangHai
2026-01-13 08:20:28 +08:00
parent b07dca28b5
commit ed4a240325
10 changed files with 266 additions and 182 deletions

View File

@@ -8,8 +8,11 @@ import aiohttp
import logging
import base64
from openai import AsyncOpenAI, BadRequestError
from Apps.XinDianTu.Kit import (
read_image, save_image, detect_rabbit_ad_close
)
from Config.Config import (
ALY_LLM_API_KEY, VL_MODEL_NAME, VL_MODEL_NAME_AD
ALY_LLM_API_KEY, VL_MODEL_NAME, VL_MODEL_NAME_AD, TEMP_IMAGE_DIR
)
from Apps.XinDianTu.Config.Setting import (
SAFE_EXCLUDE_RATIO, FALLBACK_WIDTH, FALLBACK_HEIGHT,
@@ -77,11 +80,28 @@ class ReadImageKit:
@classmethod
async def detect_ad_popup(cls, image_path: str, device_info=None):
"""
使用 VL 模型检测是否存在弹窗广告,并返回关闭按钮坐标
使用 VL 模型 + 图形学算法检测是否存在弹窗广告,并返回关闭按钮坐标
"""
if not os.path.exists(image_path):
return None
# 1. 优先使用图形学算法检测“新电兔AI”广告,因为它的特征非常明显
cv_rabbit_point = detect_rabbit_ad_close(image_path, debug_dir=TEMP_IMAGE_DIR)
if cv_rabbit_point:
logger.info(f">>> 图形学算法精准捕捉到兔子广告关闭按钮: {cv_rabbit_point}")
norm_point = cv_rabbit_point
ad_type = "rabbit"
# 转换坐标并返回
if not device_info:
device_info = cls._FALLBACK_DEVICE_INFO
w = device_info.get("displayWidth", FALLBACK_WIDTH)
h = device_info.get("displayHeight", FALLBACK_HEIGHT)
x = int(norm_point[0] / 1000 * w)
y = int(norm_point[1] / 1000 * h)
return {"x": x, "y": y, "ad_type": ad_type}
# 2. 如果图形学没找到,再请求大模型进行全量检测
with open(image_path, "rb") as image_file:
encoded_image = base64.b64encode(image_file.read()).decode("utf-8")
img_input = {"url": f"data:image/jpeg;base64,{encoded_image}"}
@@ -91,26 +111,21 @@ class ReadImageKit:
"请仔细检查这张图片中是否存在**弹窗广告**或**悬浮广告**。\n"
"广告可能有以下几种形式:\n"
"1. **屏幕中央的大型弹窗广告**:通常遮挡了页面内容,内容多为优惠券、活动推广等。\n"
"2. **'新电兔AI'专属悬浮广告**(高频):\n"
" - **位置范围**:严格锁定在**屏幕左侧边缘**(水平坐标约 0-250),**垂直高度处于中下部**(垂直坐标约 600-900)。\n"
" - **视觉特征**:一个卡通兔子头,或带有'新电兔AI'字样的气泡图标。\n"
" - **关闭按钮**:务必寻找位于该图标**正下方**的一个**黑色圆形小按钮(内含白色X)**。注意:关闭按钮通常与图标本体分离,悬浮在图标下方约 20-50 像素处。请精确返回这个黑色圆形按钮的中心坐标。\n"
"2. **'新电兔AI'专属悬浮广告**(高频):一个卡通兔子头,带有'新电兔AI'字样,关闭按钮是一个黑色圆圈内含白色X。\n"
"3. **底部的横幅广告**:带有明显的关闭按钮。\n\n"
"如果发现了上述任何一种广告,请务必找到该广告的**关闭按钮**(通常是一个'X'符号,或者'关闭'字样,或者位于广告下方/右上角的圆圈叉号)。\n"
"请返回该关闭按钮的**中心坐标**。\n\n"
"请返回关闭按钮的中心坐标。\n\n"
"请以纯 JSON 格式输出:\n"
"{\n"
" \"has_ad\": true/false,\n"
" \"ad_type\": \"rabbit\", // \"rabbit\" (新电兔AI/兔子) | \"center\" (中央弹窗) | \"bottom\" (底部横幅) | \"other\"\n"
" \"ad_type\": \"rabbit\" | \"center\" | \"bottom\" | \"other\",\n"
" \"close_point\": [x, y] // 0-1000 归一化坐标\n"
"}\n"
"如果没有广告,请返回 `{\"has_ad\": false}`。\n"
"只输出 JSON,不要包含任何其他文字。"
"如果没有广告,请返回 `{\"has_ad\": false}`。"
)
try:
resp = await cls._client.chat.completions.create(
model=VL_MODEL_NAME, # 使用默认 VL 模型即可,或者使用 AD 专用
model=VL_MODEL_NAME,
timeout=30,
messages=[
{
@@ -128,10 +143,9 @@ class ReadImageKit:
if result.get("has_ad") and result.get("close_point"):
if result.get("ad_type") == "rabbit":
logger.info("大模型找到了兔子广告!")
# 强制使用固定坐标 (94, 830) - 归一化 0-1000
# 根据多次 CV 测试结果 (94, 828) 和 (93, 830) 的综合评估
logger.info(">>> 采用强制固定坐标策略 (94, 830) 以确保点击准确性")
logger.info("大模型找到了兔子广告,但 CV 没找到。")
# 如果大模型找到了但 CV 没找到,说明可能 CV 阈值没对上,此时采用固定坐标作为兜底
logger.info(">>> 采用兜底固定坐标策略 (94, 830)")
norm_point = [94, 830]
else:
norm_point = result["close_point"]
@@ -144,7 +158,6 @@ class ReadImageKit:
w = device_info.get("displayWidth", FALLBACK_WIDTH)
h = device_info.get("displayHeight", FALLBACK_HEIGHT)
# 假设模型返回的是 0-1000 的坐标
x = int(norm_point[0] / 1000 * w)
y = int(norm_point[1] / 1000 * h)