某滑块验证码识别思路(附完备代码)

打印 上一主题 下一主题

主题 708|帖子 708|积分 2124

思路

验证码范比方下:

大概搜刮了下,有两种主流思路:yolo目标检测算法和opencv模版匹配。很明显第二种本钱远小于第一种,也不需要训练。
而且这种验证码有干扰(两个目标点),yolo一次还不能直接到位,还得进一步处置惩罚。我在搜刮的时候还有用轮廓匹配做识别的,但是实测下来准确率很低,这里就不说了。
识别

背景预处置惩罚

先对图片做一些预处置惩罚,移除多余的干扰项, 提高准确率。比如先简单将图片切割一下,只保留包含滑块的那一部分。这么说大概不太理解,不识别之前怎么知道哪一部分包含滑块?我截图标一下大家就明白了,可以只保留中间这一块。

网页知道滑块放置的位置,说明服务器告诉了它准确的y坐标,看了下接口返回的结果里有一个tip_y应该是跟滑块放置的y坐标有关。滑块的具体位置可以在元素一栏里看到(em这个单位和px换算规则是 px = em * 字体大小,从网页上看字体大小是100px)

但tip_y的值是69,和85px对不上。这里可以打上属性修改断点,看一下属性是怎么生成的,但我找了半天没找到,最后复制多个值发给gpt让他说一下有什么规律。它说比例是固定的,也就是tip_y乘以1.23就是放置的y坐标

固然还有个简单的方法就是用浏览器获取滑块的坐标,这样就不用关心两个值有啥规律。
那就可以得到裁剪的位置了:
  1. from PIL import Image
  2. def crop_main_loc(background_path:Image, slide_path:Image, tip_y:int):
  3.     background_img = Image.open(background_path)
  4.     slide_img = Image.open(slide_path)
  5.     top_y_212 = tip_y * (85 / 69)
  6.     top_y_344 = int(top_y_212 * (344 / 212))
  7.     crop_size = (0, top_y_344, background_img.width, top_y_344+slide_img.height)
  8.     cropped_image = background_img.crop(crop_size)
  9.     cropped_image.show()
  10. if __name__ == '__main__':
  11.     crop_main_loc('background.jpeg', 'slide.png', 69)
复制代码

滑块预处置惩罚

先提取一下滑块的轮廓,抖音的滑块特征很明显,可以不用用cv2.Canny来提取边缘特征。
具体步骤如下:

  • 去除外围透明像素点(滑块外层的像素点的a值都是0)
  • 将图片转成灰度图并举行二值化操纵(0和255)
  • 只保留二值化为255的像素点
  • 去除多余噪声
代码

读取rgba格式的滑块
  1. import cv2
  2. input_img = cv2.imread("slide.png", cv2.IMREAD_UNCHANGED)
复制代码
将透明值为0的像素点设置为纯玄色
  1. # 取透明维度的值
  2. alpha_channel = input_img[:, :, 3]
  3. # 只使用rgb三个维度的值
  4. rgb_image = input_img[:, :, :3]
  5. rgb_image[alpha_channel == 0] = [0, 0, 0]
复制代码
提取白色边缘并设置成玄色,将其他像素点设置为白色
  1. gray = cv2.cvtColor(rgb_image, cv2.COLOR_BGR2GRAY)
  2. _, thresholded = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)
  3. white_img = np.ones_like(rgb_image) * 255
  4. white_img[thresholded == 255] = [0, 0, 0]  
复制代码
去除噪声(判断某个玄色像素点周围3x3范围内有多少个玄色像素点,少于阈值认为是噪声)
  1. def count_black_neighbors_by_cv2(gray_image):
  2.     if gray_image.ndim == 3:
  3.         gray_image = cv2.cvtColor(gray_image, cv2.COLOR_BGR2GRAY)
  4.     _, binary_image = cv2.threshold(gray_image, 240, 255, cv2.THRESH_BINARY_INV)
  5.     binary_image = binary_image // 255  
  6.     kernel = np.ones((3, 3), dtype=np.uint8)
  7.     kernel[1, 1] = 0
  8.     black_neighbors = cv2.filter2D(binary_image, -1, kernel)
  9.     # 设置边缘为0
  10.     black_neighbors[:, 0] = 0
  11.     black_neighbors[:, 109] = 0
  12.     return black_neighbors
复制代码
固然也可以通过遍历来实现,这样更容易理解点
  1. def count_black_neighbors_by_range(gray_image):
  2.     # 将图像转换为灰度图
  3.     if len(gray_image.shape) == 3:
  4.         gray_image = cv2.cvtColor(gray_image, cv2.COLOR_BGR2GRAY)
  5.     # 二值化图像
  6.     _, binary_image = cv2.threshold(gray_image, 240, 255, cv2.THRESH_BINARY_INV)
  7.     binary_image = binary_image // 255
  8.     # 创建一个与输入图像大小相同的全零数组
  9.     black_neighbors = np.zeros_like(binary_image)
  10.     # 遍历图像中的3x3邻域,计算每个像素
  11.     neighbor_offsets = [(-1, -1), (-1, 0), (-1, 1),
  12.                         (0, -1),          (0, 1),
  13.                         (1, -1), (1, 0), (1, 1)]
  14.     # 遍历每个像素
  15.     rows, cols = binary_image.shape
  16.     for row in range(1, rows - 1):
  17.         for col in range(1, cols - 1):
  18.             # 当它本身不是黑色像素点的时候,就不计算
  19.             if binary_image[row, col] != 1:
  20.                 continue
  21.             count = 0
  22.             for offset in neighbor_offsets:
  23.                 neighbor_row, neighbor_col = row + offset[0], col + offset[1]
  24.                 if binary_image[neighbor_row, neighbor_col] == 1:
  25.                     count += 1
  26.             black_neighbors[row, col] = count
  27.     return black_neighbors
  28.    
  29. black_neighbors = count_black_neighbors_by_range(white_img)
  30. output = np.ones_like(rgb_image) * 255
  31. output[black_neighbors > 4] = 0
复制代码
正题

好了,现在可以把上面看到的内容忘掉了,因为在实际识别的时候用不到(我发现不做处置惩罚比做处置惩罚识别的准确率要高很多),直接识别准确率乃至接近百分百了。
至于为啥还写上面的内容,主要是我花时间研究了,总要写出来,万一下次用到又忘了呢。还有就是凑个字数。
完备代码

下面是识别的完备代码
  1. import os
  2. import cv2
  3. def get_slide_distance(bg_path, slide_path):
  4.     '''
  5.     识别滑块具体位置,返回位置比例: 位置/图片宽度
  6.     使用的时候再乘以实际图片宽度即可
  7.     '''
  8.     bg_img = cv2.imread(bg_path)
  9.     sd_img = cv2.imread(slide_path)
  10.     bg_gray = cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY)
  11.     bg_gray = cv2.GaussianBlur(bg_gray, (5, 5), 0)
  12.     bg_edge = cv2.Canny(bg_gray, 30, 100)
  13.     rgb_bg_gray = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
  14.    
  15.     sd_gray = cv2.cvtColor(sd_img, cv2.COLOR_BGR2GRAY)
  16.     sd_gray = cv2.GaussianBlur(sd_gray, (5, 5), 0)
  17.     sd_edge = cv2.Canny(sd_gray, 30, 100)
  18.     rgb_sd_gray = cv2.cvtColor(sd_edge, cv2.COLOR_GRAY2RGB)
  19.     result = cv2.matchTemplate(rgb_bg_gray, rgb_sd_gray, cv2.TM_CCORR_NORMED)
  20.     _, _, _, max_loc = cv2.minMaxLoc(result)
  21.     cv2.rectangle(bg_img, (max_loc[0], max_loc[1]), (max_loc[0]+110, max_loc[1] + 110),
  22.         (0, 255, 0), 2)
  23.     result_path = os.path.join(os.path.dirname(bg_path), "result.png")
  24.     cv2.imwrite(result_path, bg_img)
  25.     return max_loc[0]/bg_gray.shape[1]
复制代码
cv2.matchTemplate

核心函数就是cv2.matchTemplate,它是用来做模版匹配的,通俗点说是在一个图中找出另一张图,看一下gpt的参数表明:

不知道哪个参数更好,可以都测试一下。我看网上用的都是cv2.TM_CCORR_NORMED,效果如下:

测试下来后面四个效果都不错,只有cv2.TM_SQDIFF和cv2.TM_SQDIFF_NORMED效果很差:

``
流程图

为了更清晰的知道这段代码做了什么,可以将中间步骤处置惩罚过程都保存下来:
cv2.cvtColor(cv2.COLOR_BGR2GRAY是将bgr格式的图片转为灰度图):

cv2.GaussianBlur(高斯滤波做含糊处置惩罚):

cv2.Canny(边缘检测,参数可以自己调节看看,第一个是最小值,第二个是最大值,如果值给的太高保留下来的线就很少):

本文由博客一文多发平台 OpenWrite 发布!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连密封材料

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表