import cv2
import numpy as np
from typing import Optional, Tuple
class SlideCaptchaSolver:
def __init__(self, debug: bool = False):
self.debug = debug
def solve(self, image_path: str) -> Optional[int]:
"""
识别滑动验证码缺口的起始X坐标
:param image_path: 验证码原图路径(必须是有缺口的完整图,不是缺口+背景分离的)
:return: 缺口起始X坐标(像素),识别失败返回None
"""
# 1. 读取图像并校验
image = cv2.imread(image_path)
if image is None:
raise ValueError(f"无法加载图像:{image_path}")
img_h, img_w = image.shape[:2]
# 2. 2023优化版预处理:偏暗/偏亮自适应增强+高斯模糊降噪
processed = self._preprocess(image)
# 3. 边缘检测+形态学闭运算填充轮廓缝隙
edges = self._edge_detection(processed)
# 4. 查找轮廓+按2023主流规则筛选
contours, _ = cv2.findContours(edges, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
targets = self._filter_contours(contours, img_w, img_h)
if not targets:
print("未找到符合条件的缺口!")
return None
# 取外接矩形面积最大的那个(最可能是缺口)
target = sorted(targets, key=lambda x: x[2] * x[3], reverse=True)[0]
x, _, w, _ = target
if self.debug:
self._debug_show(image, edges, target)
return x + w // 2 # 通常返回缺口中心点X坐标,更贴合拖动需求
def _preprocess(self, image: np.ndarray) -> np.ndarray:
"""预处理:自适应亮度增强+高斯模糊"""
# 转LAB颜色空间(L通道是亮度,A/B是颜色,处理亮度不会影响颜色)
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
# 2023推荐参数:clipLimit=3.0(对比度别太高,避免产生噪点)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
l_enhanced = clahe.apply(l)
# 合并LAB通道+转回BGR
merged = cv2.merge([l_enhanced, a, b])
bgr_enhanced = cv2.cvtColor(merged, cv2.COLOR_LAB2BGR)
# 高斯模糊:(5,5)核,刚好过滤高频噪点又保留边缘
return cv2.GaussianBlur(bgr_enhanced, (5, 5), 0)
def _edge_detection(self, image: np.ndarray) -> np.ndarray:
"""边缘检测+形态学闭运算填充缝隙"""
# Canny参数:100低阈值,200高阈值(常规缺口通用)
edges = cv2.Canny(image, 100, 200)
# 形态学闭运算:(5,5)矩形核,填充缺口轮廓的小断裂
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
return cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
def _filter_contours(
self, contours: list, img_w: int, img_h: int
) -> list[Tuple[int, int, int, int]]:
"""按2023主流缺口规则筛选轮廓"""
targets = []
right_area_start = img_w * 0.5 # 只看右侧50%的区域
for cnt in contours:
# 计算外接矩形
x, y, w, h = cv2.boundingRect(cnt)
# 过滤左侧区域
if x < right_area_start:
continue
# 过滤宽高比太奇怪的
aspect_ratio = w / h
if not (0.8 < aspect_ratio < 1.2): # 现在主流缺口更接近正方形
continue
# 过滤尺寸太小/太大的
if not (0.1 * img_w < w < 0.3 * img_w):
continue
# 过滤填充度太低/太高的(排除文字、实心块)
area = cv2.contourArea(cnt)
rect_area = w * h
extent = area / rect_area
if not (0.6 < extent < 0.9):
continue
targets.append((x, y, w, h))
return targets
def _debug_show(self, image: np.ndarray, edges: np.ndarray, target: Tuple[int, int, int, int]) -> None:
"""调试模式:展示原图、边缘图、带框目标图"""
x, y, w, h = target
# 在原图上画矩形+中心点
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.circle(image, (x + w // 2, y + h // 2), 5, (0, 0, 255), -1)
# 展示
cv2.imshow("Debug-Original", image)
cv2.imshow("Debug-Edges", edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == "__main__":
# 测试
solver = SlideCaptchaSolver(debug=True)
result = solver.solve("test_captcha.png")
print(f"缺口中心点X坐标:{result}")