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

@@ -44,10 +44,11 @@ def test_click():
# Convert to grayscale
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
# The close button is a black circle. So it should be very dark.
# Try multiple thresholds to find the best candidates
# The close button is a black circle with a white X.
# We look for a dark circle, and then check if it contains something bright.
candidates = []
for threshold_val in [50, 70, 90, 110]:
# 尝试多个阈值以应对不同的亮度环境
for threshold_val in [40, 60, 80, 100]:
_, thresh = cv2.threshold(gray, threshold_val, 255, cv2.THRESH_BINARY_INV)
# Find contours
@@ -55,82 +56,114 @@ def test_click():
for cnt in contours:
area = cv2.contourArea(cnt)
# Approximate circle check
# 圆形度检查
perimeter = cv2.arcLength(cnt, True)
if perimeter == 0: continue
circularity = 4 * np.pi * (area / (perimeter * perimeter))
# Filter by size and circularity
# Size: it's a small button
if 50 < area < 6000 and circularity > 0.35:
M = cv2.moments(cnt)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"]) + roi_x1
cY = int(M["m01"] / M["m00"]) + roi_y1
# Calculate normalized coordinates
norm_x = int(cX / w * 1000)
norm_y = int(cY / h * 1000)
# Avoid duplicates
if not any(abs(cX - c[0]) < 10 and abs(cY - c[1]) < 10 for c in candidates):
candidates.append((cX, cY, area, norm_x, norm_y))
# 兔子广告关闭按钮通常很小 (40x40 左右在 1080p 下是 1600 面积)
if 100 < area < 4000 and circularity > 0.4:
# 获取该候选区域的 bounding box
x, y, w_cnt, h_cnt = cv2.boundingRect(cnt)
# 在这个黑色圆内部,检查是否有亮色的 'X'
# 我们可以对这个区域做反向阈值,找亮色物体
padding = 2
inner_roi = gray[max(0, y-padding):min(roi.shape[0], y+h_cnt+padding),
max(0, x-padding):min(roi.shape[1], x+w_cnt+padding)]
# 找亮色物体 (X)
_, inner_thresh = cv2.threshold(inner_roi, 180, 255, cv2.THRESH_BINARY)
inner_contours, _ = cv2.findContours(inner_thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
has_x = False
for i_cnt in inner_contours:
i_area = cv2.contourArea(i_cnt)
# X 应该比圆小很多
if 10 < i_area < area * 0.5:
has_x = True
break
if has_x:
M = cv2.moments(cnt)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"]) + roi_x1
cY = int(M["m01"] / M["m00"]) + roi_y1
norm_x = int(cX / w * 1000)
norm_y = int(cY / h * 1000)
# 避免重复
if not any(abs(cX - c[0]) < 15 and abs(cY - c[1]) < 15 for c in candidates):
candidates.append((cX, cY, area, norm_x, norm_y, True)) # True means found X
# 保存候选区域图片以便调试
cv2.imwrite(rf"d:\dsWork\aiData\Output\cand_{len(candidates)-1}_roi.jpg", inner_roi)
else:
# 如果没找到 X但圆形度很高也可以作为一个低优先级候选
if circularity > 0.7:
M = cv2.moments(cnt)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"]) + roi_x1
cY = int(M["m01"] / M["m00"]) + roi_y1
norm_x = int(cX / w * 1000)
norm_y = int(cY / h * 1000)
if not any(abs(cX - c[0]) < 15 and abs(cY - c[1]) < 15 for c in candidates):
candidates.append((cX, cY, area, norm_x, norm_y, False))
cv2.imwrite(rf"d:\dsWork\aiData\Output\cand_{len(candidates)-1}_roi.jpg", inner_roi)
# Save thresh image for debugging
_, debug_thresh = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
cv2.imwrite(r"d:\dsWork\aiData\Output\debug_thresh.jpg", debug_thresh)
cv2.imwrite(r"d:\dsWork\aiData\Output\debug_thresh.jpg", thresh)
# Sort by proximity to expected location (Left side, Middle-Bottom)
# Expected norm: (93, 830)
# 评分逻辑
def score_candidate(c):
# Weighting: X proximity is important, Y proximity is also important, Area around 400-500 is ideal
dist_x = abs(c[3] - 93)
dist_y = abs(c[4] - 830)
area_diff = abs(c[2] - 450) / 450.0
return dist_x * 2 + dist_y + area_diff * 100
# c = (cx, cy, area, nx, ny, has_x)
has_x = c[5]
# 基础分:如果有 X大幅加分
score = 1000 if has_x else 0
# 距离分:越靠近预期的 (93, 830) 分越高
dist = np.sqrt((c[3] - 93)**2 + (c[4] - 830)**2)
score -= dist * 2
# 面积分:理想面积在 500-1500 之间
if 500 < c[2] < 1500:
score += 200
return score
candidates.sort(key=score_candidate)
candidates.sort(key=score_candidate, reverse=True)
target_x, target_y = int(w * 0.08), int(h * 0.835) # Default fallback
norm_target_x, norm_target_y = 80, 835
if candidates:
print(f"Found {len(candidates)} candidate close buttons via CV:")
for i, (cx, cy, area, nx, ny) in enumerate(candidates):
score = score_candidate((cx, cy, area, nx, ny))
print(f" Candidate {i}: ({cx}, {cy}), Area: {area}, Norm: ({nx}, {ny}), Score: {score:.2f}")
for i, (cx, cy, area, nx, ny, has_x) in enumerate(candidates):
score = score_candidate((cx, cy, area, nx, ny, has_x))
print(f" Candidate {i}: ({cx}, {cy}), Area: {area}, Norm: ({nx}, {ny}), HasX: {has_x}, Score: {score:.2f}")
# Pick the best one
best_c = candidates[0]
target_x, target_y = best_c[0], best_c[1]
norm_target_x, norm_target_y = best_c[3], best_c[4]
print(f"Selected CV target: ({target_x}, {target_y}), Norm: ({norm_target_x}, {norm_target_y})")
print(f"Selected CV target: ({target_x}, {target_y}), Norm: ({norm_target_x}, {norm_target_y}), HasX: {best_c[5]}")
# Visualize
viz_img = cv2.imread(screenshot_path)
cv2.circle(viz_img, (target_x, target_y), 3, (0, 0, 255), -1)
debug_path = r"d:\dsWork\aiData\Output\debug_ad_point.jpg"
red_point_path = r"d:\dsWork\aiData\Output\_redpoint.jpg"
cv2.imwrite(debug_path, viz_img)
cv2.imwrite(red_point_path, viz_img)
print(f"Debug images saved to {debug_path} and {red_point_path}")
# 4. Perform click
print(f"Clicking ({target_x}, {target_y}) using single click...")
d.click(target_x, target_y)
print("Waiting 3s for user to observe if the ad is closed...")
time.sleep(3)
# print("Pressing BACK to return to main page...")
# d.press("back")
else:
print("No CV candidates found. Using fixed coordinates.")
# Visualize
cv2.circle(img, (target_x, target_y), 20, (0, 255, 0), -1)
cv2.putText(img, f"Click ({target_x}, {target_y})", (target_x + 30, target_y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
# Also draw the fixed point for comparison (Blue)
fixed_x = int(w * 0.08)
fixed_y = int(h * 0.835)
cv2.circle(img, (fixed_x, fixed_y), 15, (255, 0, 0), -1)
debug_path = r"d:\dsWork\aiData\Output\debug_ad_point.jpg"
cv2.imwrite(debug_path, img)
print(f"Debug image saved to {debug_path}")
# 4. Perform click
print(f"Clicking ({target_x}, {target_y}) using double_click...")
# Use d.double_click for a faster interaction
d.double_click(target_x, target_y, duration=0.05)
# NEW: Handle potential background click as per user's latest request
print("Waiting 2s for potential background card to load...")
time.sleep(2)
print("Pressing BACK to return to main page...")
d.press("back")
print("No CV candidates found. Skipping click to avoid accidental background interaction.")
time.sleep(2)