文章日期:2024.03.23
使用工具:Python
类型:图片滑块验证的处理(不限于识别距离)
使用场景:?
文章全程已做去敏处理!!! 【需要做的可联系我】
AES解密处理(直接解密即可)(crypto-js.js 标准算法):在线AES加解密工具
今天的这个案例的主要作用是方便大家对滑块的研究、使用、学习、原理理解等.....
源码在文章结尾,源码有详细注释,有任何问题直接私信!!! 持续更新中........
目录
1、极验3.0滑块 【还原】【距离计算】- 还原滑块背景图,并计算出缺口距离
2、极验4.0滑块 【转换】- 小拼图转长拼图
3、简书滑块 【切割】- 切割出仅有缺口的滑道
4、网易易盾滑块 【转换】- 长拼图转小拼图
5、极验4.0滑块 【去除】- 拼图去除半透明像素
【附上源码-python】- 持续更新
使用场景:
1、极验3.0滑块 【还原】【距离计算】- 还原滑块背景图,并计算出缺口距离
需要材料:完整滑块背景图(图片)、有缺口的滑块背景图(图片)
# 还原 完整滑块背景图 将还原后的结果保存为名字为:a1 默认会自动添加文件后缀名png
Slide_processing().background_reduction('1.jpg',True,'a1')
# 还原 有缺口的滑块背景图 将还原后的结果保存为名字为:a2 默认会自动添加文件后缀名png
Slide_processing().background_reduction('2.jpg',True,'a2')
# 放入 【完整滑块背景图】 和 【有缺口的滑块背景图】 计算缺口的距离 strict:容错值,默认为0
print(Slide_processing().slider_identify_background('a1.png','a2.png',strict=200))
2、极验4.0滑块 【转换】- 小拼图转长拼图
需要材料:小拼图(图片)、背景图的总高度(数字距离)、服务器返回的拼图放置高度(数字距离)
'''
:param p_puzzle_path: 本地小拼图的的路径
:param total_height: 背景图图片的总高度 - 上下距离 - 最终拼图的高度 - 要和滑块的背景图的高度一致
:param starting_height: 拼图图片的放置位置,从上倒下计算距离,设置20则表示从上到下,距离20个像素点开始放置拼图 - 通常服务器会返回此参数
:param save_file: 是否保存最终修改后的图片 默认False不保存
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成 (使用此参数请先开启save_file)
:return: base64图片
'''
print(Slide_processing().slider_puzzle_big(p_puzzle_path='a1.png',
total_height=200,
starting_height=95,
save_file=True,
save_file_path='q1'))
3、简书滑块 【切割】- 切割出仅有缺口的滑道
需要材料:有缺口的滑块背景图(图片)、长拼图(图片)
# 将正常大拼图转换为小拼图 - 放置长拼图 - 文章下面有此函数的详细讲解
G = Slide_processing().slider_puzzle_qg('a2.png')
print(G)
'''
:param b_puzzle_path: 本地滑块背景图的路径
:param b_size_h: 小拼图在背景图中的所在高度,上下距离
:param p_size_h: 小拼图的高度大小
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
print(Slide_processing().background_cutting(b_puzzle_path='a1.png',
b_size_h=G['最顶层-顶层距离'],
p_size_h=G['中间层-拼图的高度'],
save_file=True,
save_file_path='s1'))
4、网易易盾滑块 【转换】- 长拼图转小拼图
需要材料:长拼图(图片)
# 将正常大拼图转换为小拼图 - 放置长拼图
'''
:param p_puzzle_path: 本地长拼图的路径
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: {
'最顶层-顶层距离'
'中间层-拼图的高度'
'最底层-底层距离'
'base64'
}
'''
G = Slide_processing().slider_puzzle_qg(p_puzzle_path='a1.png',
save_file=True,
save_file_path='s1')
print(G)
5、极验4.0滑块 【去除】- 拼图去除半透明像素
需要材料:拼图(图片)
'''
:param p_puzzle_path: 本地小拼图的路径
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
print(Slide_processing().slider_puzzle_remove_tm(p_puzzle_path='a1.png',
save_file=True,
save_file_path='s1'))
【附上源码-python】- 持续更新
import numpy as np
from PIL import Image
import io
import time
import base64
class Slide_processing:
def __init__(self):
pass
# 将小拼图转为正常大拼图
def slider_puzzle_big(self,
p_puzzle_path: str = '',
total_height: int = 300,
starting_height: int = 50,
save_file: bool = False,
save_file_path: str = ''
):
'''
:param p_puzzle_path: 本地小拼图的的路径
:param total_height: 设置图片总高度 - 上下距离 - 最终拼图的高度 - 要和滑块的背景图的高度一致
:param starting_height: 设置拼图图片的放置位置,从上倒下计算距离,设置20则表示从上到下,距离20个像素点开始放置拼图
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
# 打开小拼图图片
image = Image.open(p_puzzle_path)
# 获取图片的宽高
width, height = image.size
# 获取图片的像素数据
pixels = image.load()
# 创建空白图片 - 高:自定义 宽:拼图的宽度 颜色:透明 格式:png
new_image = Image.new('RGBA', (width, total_height), color=(0, 0, 0, 0))
# 开始循环读取拼图的每一个像素值,然后放到空白图片中
# 循环读取拼图的高度(上下距离)
for x in range(height):
# 循环读取拼图的宽度(左右距离)
for y in range(width):
# 读取png像素点 RGBA 值 (png是4通道、jpg是3通道) PNG:RGBA JPG:RGB
r, g, b, a = pixels[x, y]
# starting_height 是像素开始更改的位置,也是我们设置拼图要放的地方(高度)
# 将空白图片的某像素点,更改为当前拼图的像素点,以达到小拼图变长图的目的
new_image.putpixel((x, starting_height + y), (r, g, b, a))
if save_file:
if save_file_path:
new_image.save(save_file_path + '.png')
else:
# 保存最终图片 随机名称
new_image.save(''.join(str(time.time()).split('.') + [".png"]))
buffered = io.BytesIO()
# 将数据写入buffered里
new_image.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode()
# 将小拼图内透明不一的杂质全部改为透明
def slider_puzzle_remove_tm(self,
p_puzzle_path: str = '',
save_file: bool = False,
save_file_path: str = ''):
'''
:param p_puzzle_path: 本地小拼图的路径
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
# 打开小拼图 图片
image = Image.open(p_puzzle_path)
width, height = image.size
# 转为RGBA格式
image = image.convert("RGBA")
# 获取图片的像素数据
pixels_1 = image.load()
for x in range(height):
for y in range(width):
r, g, b, a = pixels_1[y,x]
if a != 255:
r, g, b, a = 0,0,0,0
# 将空白图片的某像素点,更改为当前拼图的像素点,以达到小拼图变长图的目的
image.putpixel((y,x), (r, g, b, a))
if save_file:
if save_file_path:
image.save(save_file_path + '.png')
else:
# 保存最终图片 随机名称
image.save(''.join(str(time.time()).split('.') + [".png"]))
buffered = io.BytesIO()
# 将数据写入buffered里
image.save(buffered, format="PNG")
return {'base64': base64.b64encode(buffered.getvalue()).decode()}
# 将正常大拼图转换为小拼图, 使用前先去掉多余的透明
def slider_puzzle_qg(self,
p_puzzle_path: str = '',
save_file: bool = False,
save_file_path: str = ''):
'''
:param p_puzzle_path: 本地长拼图的路径
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
# 打开大拼图图片
image = Image.open(p_puzzle_path)
width, height = image.size
pixels = image.load()
# 采用红绿灯模式 存储和记录信息
# _a:第一层-行数 _b:第二层-行数 _c:第三层-行数 k:记录拼图的像素数据
_a = 0 # 红灯-最顶层 - 透明
_b = 0 # 黄灯-中间层 - 数据核心 - 拼图的像素
_c = 0 # 绿灯-最底层 - 透明
k = [] # 将拼图的像素存储, 存储为一维数组
buffered = io.BytesIO()
for x in range(height):
# 临时的行数据像素 - 左右
data_pixel = []
for y in range(width):
# 读取png像素点 RGBA 值 (png是4通道、jpg是3通道) PNG:RGBA JPG:RGB
r, g, b, a = pixels[y, x]
data_pixel += [(r, g, b, a)]
# 转换为NumPy数组
NumPy_data_pixel = np.array(data_pixel)
# 计算一整行像素值是否为透明, 是透明则跳过并记录在红绿灯内, 如果不是透明则将数据进行存储并记录在红绿灯内
# True:透明(无数据) False:不透明(有数据)
if np.all(NumPy_data_pixel == 0):
if _b == 0:
# 记录最顶层的行数 - 小拼图距离顶部的距离
_a += 1
else:
# 当开始底层为0时,则表示第一次执行此命令,则要存储刚刚所保存的一维像素数组数据, 保存为图片
if _c == 0:
# 创建空白图片 - 宽:拼图的宽度 高:通过数据层获取 颜色:透明 格式:png
new_image = Image.new('RGBA', (width, _b), color=(0, 0, 0, 0))
# 采用一维的像素组数据写入图片
new_image.putdata(k)
# 将数据写入buffered里
new_image.save(buffered, format="PNG")
if save_file:
if save_file_path:
new_image.save(save_file_path + '.png')
else:
# 保存最终图片 随机名称
new_image.save(''.join(str(time.time()).split('.') + [".png"]))
# 记录最底层的行数 - 小拼图距离底部的距离
_c += 1
else:
# 记录拼图的有多少行-高度-上下距离-仅拼图的高度
_b += 1
# 拼图的像素数据 一维
k += data_pixel
# 最后返回字典格式数据
return {
'最顶层-顶层距离': _a,
'中间层-拼图的高度': _b,
'最底层-底层距离': _c,
'base64': base64.b64encode(buffered.getvalue()).decode()
}
# 滑块识别距离, 放入完整滑块背景图 和 残缺滑块背景图
def slider_identify_background(self,file_1, file_2,strict=0):
'''
:param file_1: 完整滑块背景图
:param file_2: 残缺滑块背景图
:param strict: 严格模式 针对极验, 当残缺滑块和完整滑块的像素有差别时,需要填写最多不能相差多少像素, 建议200以下,200即可
:return: {w:滑道距离}
'''
# 打开图片
image_1 = Image.open(file_1)
image_2 = Image.open(file_2)
# 获取图片的像素数据
pixels_1 = image_1.load()
pixels_2 = image_2.load()
# 循环图片的宽度 左右距离
for w in range(239):
# 循环图片的长度/高度 上下距离
for h in range(149):
# 第一张缺口图 循环每一个像素
color1 = pixels_1[w, h]
# 第二张完整图 循环每一个像素
color2 = pixels_2[w, h]
if strict:
# 严格模式,检测变化较大的参数, 如果存在则表示缺口很明显
if (-strict > sum(np.array(color1)-np.array(color2))) or ((sum(np.array(color1)-np.array(color2))) > strict):
return {'w': w}
else:
# 检测两张图的像素是否有变化,如果像素有变化,说明有缺口,则直接返回宽度即可,长度可以不要
if color1 != color2:
return {'w': w}
return {'w': '未计算出!!!'}
# 将滑块背景图切割为小滑道
def background_cutting(self,
b_puzzle_path: str = '',
b_size_h: int = 0,
p_size_h: int = 0,
save_file: bool = False,
save_file_path: str = ''):
'''
:param b_puzzle_path: 本地滑块背景图的路径
:param b_size_h: 小拼图在背景图中的所在高度,上下距离
:param p_size_h: 小拼图的高度大小
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
# 打开图片
image = Image.open(b_puzzle_path)
width, height = image.size
# 宽度,从左到右的距离
left, right = 0, width
# 高度,从上到下的距离
top, bottom = b_size_h, b_size_h + p_size_h
# 切割图片
cropped_image = image.crop((left, top, right, bottom))
if save_file:
if save_file_path:
cropped_image.save(save_file_path + '.png')
else:
# 保存最终图片 随机名称
cropped_image.save(''.join(str(time.time()).split('.') + [".png"]))
buffered = io.BytesIO()
# 将数据写入buffered里
cropped_image.save(buffered, format="PNG")
return {'base64': base64.b64encode(buffered.getvalue()).decode()}
# 滑块背景图还原 - 极验3.0
def background_reduction(self,
b_puzzle_path: str = '',
save_file: bool = False,
save_file_path: str = ''):
'''
:param b_puzzle_path: 本地滑块背景图的路径 可处理3.0极验
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
# 打开PNG图片
image = Image.open(b_puzzle_path)
# 创建新的空白图片
new_image = Image.new('RGBA', (260, 160))
# 坐标
k = 0
# 定值, 在极验3.0版本中, 通过画布断点获取数据
ut = [39,38,48,49,41,40,46,47,35,34,50,51,33,32,28,29,27,26,36,37,31,30,44,45,43,42,12,13,23,22,14,15,21,20,8,9,25,24,6,7,3,2,0,1,11,10,4,5,19,18,16,17]
# ut有多少参数, 就有多少个小碎片拼图
for _ in range(len(ut)):
# 通过计算得出 小碎片的起始坐标
c = ut[_] % 26 * 12 + 1
# 计算是否当前小碎片在第一层还是第二层, 三元表达式
u = (lambda: 80 if 25 < ut[_] else 0)()
# 将裁切下来小碎片的数据存储
image2 = image.crop((c, u, c + 10, u + 80))
# 当存储量达到26, 则重置小碎片的放置坐标, 每层只能放26小碎片
if _ == 26:
k = 0
if _ > 25:
# 第二层
new_image.paste(image2, (k, 80))
k += 10
else:
# 第一层
new_image.paste(image2, (k, 0))
k += 10
if save_file:
if save_file_path:
new_image.save(save_file_path + '.png')
else:
# 保存最终图片 随机名称
new_image.save(''.join(str(time.time()).split('.') + [".png"]))
buffered = io.BytesIO()
# 将数据写入buffered里
new_image.save(buffered, format="PNG")
return {'base64': base64.b64encode(buffered.getvalue()).decode()}