首页
社区
课程
招聘
[原创] Python OpenCV 过点击式和滑动式图形验证码的校验
发表于: 2024-3-26 17:49 18952

[原创] Python OpenCV 过点击式和滑动式图形验证码的校验

2024-3-26 17:49
18952

最近在给一个app抓包的时候发现App在特定时间会弹出验证码,验证之后会给一个token,需要携带token才能发起能正常请求。

文章源码地址:点击查看

验证码如下:

弹出验证码的Response如下:

完成验证码的Request如下

由此观察到只要将验证码的点击坐标post到完成验证码的接口,就可以获取到token,即现在的目标就是提取坐标。

观察了下这个验证码不是很难,因为它没有图案扭曲,所以还是比较好过的,同时我也想起了以前过滑动验证码的一个方案(同时给出)。

要过验证码,就是将目标图案在背景图片上找到,并且将其像素点找到就可以。

于是我使用Python的OpenCV进行图片的识别。

首先观察发现目标图片都是黑色图案,且背景为透明地址,当我直接使用 cv2.imread(front_image) 来加载图片时,会显示一片漆黑:

即使后来我使用了保留透明通道的加载 cv2.imread(front_path, cv2.IMREAD_UNCHANGED),依旧是一片漆黑。

于是我想可以将透明通道剥离,然后将目标图案透明色设置为白色,那么目标图案就自然显现了,成品如下:

我先要将目标图片的三个图案分割出来,对每一个图案分别找出他的像素位置。
本来想通过颜色精准分割,但有些图案并不是整体粘连的,经过观察发现目标图片的三个图案排列位置都是固定的,所以直接记录出他的坐标进行像素分割:

rectangle_list每一个元素是[x1,y1,x2,y2]

分割后我们将目标图和背景图都转化为灰度,防止颜色碍事:

然后直接进行最佳匹配:

最后发现匹配结果欠佳,总是不能完整的将3个目标图案都准确找到,所以还需要再优化。

继续观察,发现背景图片中的目标图案总是白色的,所以我们放弃使用默认的灰度,转而将背景图片上所有的白色部分保留,其余全部转为黑色,这样不就完全没有杂色了。

为了尽可能保留完整的图案,经过多次RGB颜色的尝试,发现250-255区间可以保留大部分目标图案的白色:

同时为了和背景图片上的黑色色块一致,我再将黑色的目标图案反转为白色:
由于要获取的是点击坐标,所以我们将x1,y1(即左上角坐标)进行+20的偏移,来移动到图案本身上面

经过验证,现在的识别就能正常过点击验证码了。
贴出代码:

滑动验证码与上同理,甚至现在比较常见的一种滑动验证码已经有了通用的代码,如:

这种滑动验证码已经是无脑式 matchTemplate 、minMaxLoc 就可以,非常方便:

贴出代码:

背景图 目标图
{
    "data": {
        "errorCode": 0,
        "errorMsg": "",
        "bg": "https://example.com/ENZg076503962.jpg",
        "front": "https://example.com/ENZg076503963.png",
        "token": "eyJhbGci...",
        "width": 765,
        "height": 396
    }
}
{
    "data": {
        "errorCode": 0,
        "errorMsg": "",
        "bg": "https://example.com/ENZg076503962.jpg",
        "front": "https://example.com/ENZg076503963.png",
        "token": "eyJhbGci...",
        "width": 765,
        "height": 396
    }
}
{
    "m": "[{\"x\":395,\"y\":256},{\"x\":89,\"y\":273},{\"x\":670,\"y\":140}]",
    "token": "eyJhbGci..."
}
{
    "m": "[{\"x\":395,\"y\":256},{\"x\":89,\"y\":273},{\"x\":670,\"y\":140}]",
    "token": "eyJhbGci..."
}
rectangle_list = [[9, 9, 75, 75], [109, 9, 175, 75], [209, 9, 275, 75]] 
for idx, rectangle in enumerate(rectangle_list, start=1): 
    cropped_image = white_front[rectangle[1]:rectangle[3], rectangle[0]:rectangle[2]]
rectangle_list = [[9, 9, 75, 75], [109, 9, 175, 75], [209, 9, 275, 75]] 
for idx, rectangle in enumerate(rectangle_list, start=1): 
    cropped_image = white_front[rectangle[1]:rectangle[3], rectangle[0]:rectangle[2]]
gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
gray_front = cv2.cvtColor(front, cv2.COLOR_BGR2GRAY)
gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
gray_front = cv2.cvtColor(front, cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(gray_front, gray_bg, cv2.TM_CCOEFF_NORMED) 
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
cv2.putText(bg, str(idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (7, 249, 151), 2)
show(bg)
res = cv2.matchTemplate(gray_front, gray_bg, cv2.TM_CCOEFF_NORMED) 
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
cv2.putText(bg, str(idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (7, 249, 151), 2)
show(bg)
gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 
_, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY)
gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 
_, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY)
gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 
_, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY) 
   
res = cv2.matchTemplate( 
    strong_contrast_bg, 
    cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)), 
    cv2.TM_CCOEFF_NORMED 
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 
   
x, y = max_loc 
x, y = x + 20, y + 20
gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 
_, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY) 
   
res = cv2.matchTemplate( 
    strong_contrast_bg, 
    cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)), 
    cv2.TM_CCOEFF_NORMED 
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 
   
x, y = max_loc 
x, y = x + 20, y + 20
import logging 
   
import cv2 
import numpy as np
 
def ProcessCaptcha(bg_path: str, front_path: str): 
    result = [] 
   
    """ 
    加载图像 
    背景为jpg格式的普通图像 像素为 765x396
    目标为png格式的包含透明通道图像 像素为 300x84   
    """   
    bg = cv2.imread(bg_path) 
    front = cv2.imread(front_path, cv2.IMREAD_UNCHANGED) 
   
    """ 
    由于目标图为透明黑色图片,直接加载会导致图像全黑, 
    为了避免全黑情况,创建与图像尺寸相同的白色背景图像,再提取图像的透明度通道,将透明部分的像素值设置为白色
    这样加载完后的图像就变成了白底黑色目标的图像 
    """   
    white_front = np.ones_like(front) * 255 
    alpha_channel = front[:, :, 3
    white_front[:, :, 0:3] = cv2.bitwise_and( 
        white_front[:, :, 0:3], 
        white_front[:, :, 0:3], 
        mask=cv2.bitwise_not(alpha_channel) 
    
   
    """ 
    为了按序点击,需要提取目标区域的矩形方块 
    由于目标图像较为规律有序,于是计算出3个目标图像像素坐标,直接按像素截取 
    """
    rectangle_list = [[9, 9, 75, 75], [109, 9, 175, 75], [209, 9, 275, 75]] 
    for rectangle in rectangle_list: 
        cropped_image = white_front[rectangle[1]:rectangle[3], rectangle[0]:rectangle[2]] 
   
        """ 
        将背景图片转换为黑白两色的图片,只保留RGB(250-255)的图像 
        如此能保留绝大部分目标图像轮廓
        将目标图像转换为黑色背景白色轮廓 
        如此便与背景的颜色逻辑一致 
        """
        gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 
        _, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY) 
        strong_contrast_front = cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)) 
   
        res = cv2.matchTemplate( 
            strong_contrast_bg, 
            strong_contrast_front, 
            cv2.TM_CCOEFF_NORMED 
        
        """ 
        使用 TM_CCOEFF_NORMED 算法匹配到最佳 
        由于此时的 X,Y 坐标为左上角坐标,需要加20进行偏移处理,获取到点击坐标 
        """
        x, y = cv2.minMaxLoc(res)[3]
        result.append((x + 20, y + 20)) 
   
    logging.info(f"Done process: "+str(result).replace('\n', ' ')) 
    return result
import logging 
   
import cv2 
import numpy as np
 
def ProcessCaptcha(bg_path: str, front_path: str): 
    result = [] 
   
    """ 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 3
支持
分享
最新回复 (5)
雪    币: 3004
活跃值: (30866)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-3-27 09:30
1
雪    币: 1583
活跃值: (1079)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
秋狝 感谢分享
2024-3-28 10:58
0
雪    币: 434
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2024-4-30 13:48
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
请问作者对这个平台风控可以说说吗
2024-5-6 16:23
0
雪    币: 1671
活跃值: (215817)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
位置是找到了,加速度模拟就难受了
2024-5-7 13:24
0
游客
登录 | 注册 方可回帖
返回
//