某金采购平台滑块验证码识别
前言
遇到滑块验证码,使用ddddocr
和cv2
一般都能快速搞定,它们处理滑块验证码的原理无外乎是:
- 读取背景图片和缺口图片
- 识别图片边缘
- 转换图片格式
- 缺口匹配
这篇博客提供了cv2
处理滑块验证码的几个案例:不想用selenium处理滑块验证码?教你用cv2解决
不过某金采购平台滑块的验证码使用上面两个方式识别成功率太低了,干扰点太多导致匹配不对。
下图所示,灰色方块就是我们要定位到的缺口位置:
解决方案
如果从该站点来说,其实它已经有提供缺口位置了,它的反爬主要还是在参数加密上。
本着学习的态度假设下在未知的情况下自己要怎么解决:
- 使用yolo训练图片拿到缺口检测模型,这种实现起来不难,只要标记好数据集,百来张样本训练快成功率高,只是还要安装yolo环境,使用时还得加载模型。
- 前面opencv和ddddocr用缺口拼接的方式效果不好,那如果我们通过找到灰色区域面积最大的方式确定
缺口位置呢?这种方式有可行性。
方案实现
这里我就演示下方案2的实现方式,这里肯定没法使用ddddocr来实现,需要用opencv自定义开发实现。
将图片非灰色区域全部置为白色
这里要将图片转换为HSV色彩空间,这样就能使用掩码了,掩码能保留想要保存的像素区域
def gery_grap(image_path, lower_sat=0, upper_sat=50, lower_val=50, upper_val=200):
# 读取图片
img = cv2.imread(image_path)
# 转换为HSV色彩空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
# 创建掩码:低饱和度(灰色)且中等亮度
mask = (s >= lower_sat) & (s <= upper_sat) & (v >= lower_val) & (v <= upper_val)
result = np.full_like(img, 255)
result[mask] = img[mask]
cv_show(result)
效果:
找到所有灰色区域轮廓
前面我们拿到的是灰色与白色区域图,由于灰色区域像素不一,这里我们要把所有灰色区域转成黑色,这样差异性更大,便于查找
灰度转换
gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
图像二值化
ret, binary = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY)
反转阈值
_, binary = cv2.threshold(binary, 180, 255, cv2.THRESH_BINARY_INV)
查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
这一步就能拿到所有白色区域的轮廓了,接下来就是遍历所有轮廓区域,计算得到最大面积的轮廓就是了。
最终结果
完整代码
def gery_grap(image_path, lower_sat=0, upper_sat=50, lower_val=50, upper_val=200):
# 读取图片
img = cv2.imread(image_path)
# 转换为HSV色彩空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
# 创建掩码:低饱和度(灰色)且中等亮度
mask = (s >= lower_sat) & (s <= upper_sat) & (v >= lower_val) & (v <= upper_val)
result = np.full_like(img, 255)
result[mask] = img[mask]
gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY) # 图像转为灰度图
ret, binary = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY) # 图像二值化
_, binary = cv2.threshold(binary, 180, 255, cv2.THRESH_BINARY_INV) # 反转阈值
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
# 初始化最大面积和对应轮廓
max_area = 0
max_contour = None
# 遍历轮廓并计算面积
for contour in contours:
area = cv2.contourArea(contour)
if area > max_area:
max_area = area
max_contour = contour
M = cv2.moments(max_contour)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
cv2.circle(img, (cx, cy), 5, (0, 255, 0), -1)
return [cx, cy]