验证识别

验证识别

Yiuhang Chan

认识验证码

概念

验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写。
是一种用来区分用户是计算机还是人的公共全自动程序

作用

认证码是一种人机识别手段,最终目的是区分正常用户和机器的操作。
可以防止:恶意破解密码、注册、刷票、论坛灌水,防止黑客对用户的密码进行暴力破解。
一般是提出一个问题,这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答这个的问题,所以回答出问题的用户就可以被认为是人类。

类别

图形验证码:

这类验证码大多是计算机随机产生一个字符串,在把字符串增加噪点、干扰线、变形、重叠、不同颜色、扭曲组成一张图片来增加识别难度。

滑块验证码:

也叫行为验证码,比较流行的一种验证码,通过用户的操作行为来完成验证,其中最出名的就是极验。
滑动验证码的原理就是使用机器学习中的深度学习技术,根据一些特征来区分是否为正常用户。通过记录用户的滑动速度,还有每一小段时间的瞬时速度,用户鼠标点击情况,以及滑动后的匹配程度来识别。而且,不是说滑动到正确位置就是验证通过,而是根据特征识别来区分是否为真用户,滑到正确位置只是一个必要条件。

点触验证码:

点击类验证码都是给出一张包含文字的图片,通过文字提醒用户点击图中相同字的位置进行验证。

Pillow库

PIL库

PIL (Python Image Library) 已经算是 Python 处理图片的标准库了,兼具强大的功能和简洁的 API.
但是PIL库的更新非常缓慢, 并且它只支持到python2.7,不支python3

Pillow库

由于PIL库更新太慢了,于是一群志愿者在PIL库的基础上创建的分支版本,命名为Pillow.
Pillow目前最新支持到python3.6以上,它的维护和开发十分活跃,兼容PIL库的绝大多数语法,并且增加了许多新的特性,推荐直接使用Pillow

Pillow库安装

安装:pip install pillow

PIL与Pillow使用注意

Pillow和PIL不能共存在一个环境中,如果之前安装了PIL的话,需要删除掉才能在安装Pillow
由于是继承自PIL的分支, 所以Pillow库的导入是这样的

Import PIL

图形基本概念

因为要处理的是图形,所以需要了解一下基本概念

尺寸

图片尺寸(size)指的是图片的宽度和高度
通过size属性可以获取图片的尺寸,它的返回值是一个元组,
元组里面有两个值,分别是水平和垂直方向上的像素个数

坐标系统

笛卡尔像素坐标系
x轴从左到右,y轴从上到下增长
图像矩形区域的表示(左,顶,右,底);800*600像素表示(0,0,800,600)

通道

图片均是由一个或者多个数据通道构成
RGB图像,每张图片都是由三个数据通道叠加构成,分别为R 、G 、B
PNG图像有RGBA四个通道,A代表透明度
对于灰度图像(没有色彩的图片, RGB色彩分量全部相等),只有一个通道。
灰度指的是黑白图像中点的颜色深度,范围一般是0到255, 白色为255,黑色为0

图像获取

  1. 从文件中加载图像
1
picture=Image.open("test.png")
  1. 创建一个新的图像
1
picture=Image.new("RGB",(200,100),"red")
  1. 处理其他的图像获得
1
im=picture.crop((116,168,658,798))

获取图像通道

1
2
im.getbands()
("R","G","B")

简单验证码处理

灰度化

目的:为二值化做准备

彩色变黑白,三通道变成一个通道

图片的灰度化,就是让像素点矩阵中的每一个像素点满足 R=G=B,此时这个值叫做灰度值,白色为255,黑色为0

灰度转化一般公式为:
R=G=B = 处理前的 R * 0.3 + G * 0.59 + B * 0.11

1
2
3
4
0---255   白色

第一个像素点的灰度值税 187 123 改成 255
第一个像素点的灰度值税 34 123 改成 0

二值化

图像的二值化,就是将图像的像素点矩阵中的每个像素点的灰度值设置为0(黑色)或255(白色),从而实现二值化,将整个图像呈现出明显的只有黑和白的视觉效果。
二值化原理是利用设定的一个阈值来判断图像像素是0还是255, 一般小于阈值的像素点变为0, 大于的变成255
这个临界灰度值就被称为阈值,阈值的设置很重要,阈值过大或过小都会对图片造成损坏
选择阈值的原则是:既要尽可能保存图片信息,又要尽可能减少背景和噪声的干扰

常用阈值选择的方法是:

  • 灰度平均值值法: 取127 (0~255的中数, (0+255)/2 = 127)
  • 平均值法:
    计算像素点矩阵中的所有像素点的灰度值的平均值avg
  • 迭代法:
    选择一个近似阈值作为估计值的初始值(比如全图像的平均灰度),然后进行分割图像,产生两组像素,一组大于初始灰度值,另一组小于初始灰度值,根据产生的子图像的特征来选取新的阈值,在利用新的阈值分割图像,经过多次循环,使得错误分割的图像像素点降到最小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from PIL import Image

img = Image.open("1.png")
img_gray = img.convert("L")
# img_gray.save("img_gray.png")

def binarization(ig):
w,h=img_gray.size
tmp=0
for i in range(w):
for j in range(h):
tmp += ig.getpixel((i,j))
avg_pixel = tmp/w/h

# 二值化处理
for i in range(w):
for j in range(h):
p = ig.getpixel((i, j))
if p>avg_pixel:
ig.putpixel((i, j), 255)
else:
ig.putpixel((i, j), 0)

return ig
img_bz = binarization(img_gray)
img_bz.save('img_bz.png')

降噪

目的:清除干扰点,让图片更清晰,让计算机更利于识别
孤立的噪点,他的周围应该都是白色,或者大多数点都是白色的,所以在判断的时候条件应该放宽,一个点是黑色并且相邻的点为白色的点的个数大于一个固定的值,那么这个点就是噪点。

降噪原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
"""
安装的库名 不一定是使用的库名

pip install pillow

import PIL
"""

from PIL import Image

img = Image.open("1.png") # 创建一个对象img

print(img.getbands()) #('R', 'G', 'B') 三原色通道

img_gray = img.convert("L")


def binarization(ig):
w, h = img_gray.size # 获取长宽
tmp = 0
for i in range(w): #循环宽
for j in range(h): #循环高
# 通过嵌套循环获取所有像素点的坐标
# getpixel 方法是用来获取图像中某一点像素的rgb颜色
# 需要传元组形式的坐标
tmp += ig.getpixel((i, j))

avg_pixel = tmp / w / h
# 找到灰度图像的阙值
print(avg_pixel) # 236.21198586154773

# 二值化处理
for y in range(w):
for z in range(h):
p = ig.getpixel((y, z)) # 保持rgb的颜色到p
if p > avg_pixel: # 由于阀值是浮点所以正常情况是不存在相等的问题,当然进行处理等于的情况也行
ig.putpixel((y, z), 255) # 如果大于阙值则变白色
else:
ig.putpixel((y, z), 0) # 反之亦然

return ig


# 噪点范围
def point_list(i, j, prange): #i和j是坐标,prange是范围
for x in range(i - prange, i + prange): # i以外的范围
for y in range(j - prange, j + prange): # j以外的范围
if x == i and y == j: #中心点确定为黑色
continue
yield (x, y)


# 传入需要降噪的二值化图片
def reduce_noise(ig):
w, h = ig.size
prange = 4
for i in range(w):
for j in range(h):
if j < h * 0.06 or j > h * (1 - 0.2):
ig.putpixel((i, j), 255)
continue
p = ig.getpixel((i, j))
if p < 100:
count = 0
for x, y in point_list(i, j, prange):
# 判断周围是否是白色,如果白色多 就是噪点
if ig.getpixel((x, y)) > 100:
count += 1

if count > 0.5 * ((prange * 2 + 1) ** 2 - 1):
ig.putpixel((i, j), 255)

return ig


bz_image = Image.open('img_bz.png')

ig_tong = reduce_noise(bz_image)
ig_tong.save('img_test.png')

Tesseract识别

OCR识别概念

OCR (Optical Character Recognition)光学字符识别, 指的是对文本资料的图像文件进行分析识别处理,获取文集及版面信息的过程

Tesseract-OCR

一个开源的字符识别引擎,我们可以用他来识别一些简单的验证码。

Windows下安装

https://digi.bib.uni-mannheim.de/tesseract/可自行下载,点击下一步即可 ;

Linux安装

1
2
sudo apt-get install tesseract-ocr
sudo apt-get install libtesseract-dev

Mac安装

1
brew install tesseract

Pytesser3

是一个在Python内使用Tesseract-Ocr的库,
Pytesseract文档:https://pypi.org/project/pytesseract/

安装:

1
pip install Pytesseract

需要配置:

方法一:将pytesseract包下面__init__文件内tesseract_exe_name的值设置为tesseract.exe的路径,
方法二者在代码中指定

Pytesseract认识:

Pytesseract识别效果不佳,如果想提高识别率,可以使用 jTessBoxEditor对Tesseract进行简单的训练。
OCR是一个专门的图像处理的领域
高精度的识别需要依靠深度学习、神经网络等技术
本章重点是对图片文件的理解

滑块验证码

演示网址 https://captcha1.scrape.center/

​ 获取图片验证码,包含缺口图,滑块图,完整图

  • 三个canvas标签分别对应了缺口图,滑块图和完整图。

    通过修改页面样式:

    1. 让滑块隐藏,截取缺口图;
    2. 隐藏缺口图,显示滑块图,截取滑块图;
    3. 显示完整图,截取完整图。

1.隐藏滑块,截取缺口图

设置后效果

2.隐藏缺口图,显示滑块图,截取滑块图

隐藏缺口图

下滑右侧栏可以看到对滑块的设置

3.显示完整图,截取完整图

选择第三个canvas

4. 计算距离

​ 计算缺口位置,滑块位置,滑块要移动的距离

5. 确定距离

距离=滑块左边缘与缺口左边缘之间的距离
distance=滑块左边缘X坐标-缺口左边缘X坐标

一 更改元素样式, 获取图片

  • 功能:获取验证图片
  • 参数:无
  • 返回值:
    缺口图地址
    滑块图地址
    完整图地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from PIL import Image


def get_captcha():
time.sleep(2)
#(1)隐藏滑块 得到缺口图
js_hide_slice = 'document.getElementsByClassName("geetest_canvas_slice")[0].style.display="none"'
driver.execute_script(js_hide_slice)
#截取缺口图
part_imgpath="./part.png"
driver.find_element(by=By.CLASS_NAME, value="geetest_canvas_bg").screenshot(part_imgpath)

# (2)显示滑块 隐藏缺口图 得到滑块图
js_show_slice = 'document.getElementsByClassName("geetest_canvas_slice")[0].style.display="block"'
js_hide_part= 'document.getElementsByClassName("geetest_canvas_bg")[0].style.display="none"'
driver.execute_script(js_show_slice+";"+js_hide_part)
# 截取滑块图
slice_imgpath = "./slice.png"
driver.find_element_by_class_name("geetest_canvas_slice").screenshot(slice_imgpath)

# (3)显示完整图
js_show_full1 = 'document.getElementsByClassName("geetest_canvas_fullbg")[0].style.display="block"'
js_show_full2 = 'document.getElementsByClassName("geetest_canvas_fullbg")[0].style.opacity="1"'
driver.execute_script(js_show_full1+";"+js_show_full2)
# 截取完整图
full_imgpath = "./full.png"
driver.find_element_by_class_name("geetest_canvas_fullbg").screenshot(full_imgpath)

#还原目的 还原成原来的模样 包含缺口图与滑块
js_hide_full1 = 'document.getElementsByClassName("geetest_canvas_fullbg")[0].style.display="none"'
js_hide_full2 = 'document.getElementsByClassName("geetest_canvas_fullbg")[0].style.opacity="0"'
js_show_part = 'document.getElementsByClassName("geetest_canvas_bg")[0].style.display=""'
driver.execute_script(js_hide_full1 + ";" + js_hide_full2+ ";"+js_show_part)

return part_imgpath, slice_imgpath, full_imgpath

二 计算演示

找到滑块的x坐标

1
2
3
4
5
6
7
8
9
10
def get_slice_x(img_slice):
img = Image.open(img_slice)
w, h = img.size
for x in range(w):
for y in range(h):
rgb = img.getpixel((x,y)) # (255,255,255)
# 判断比白色小就证明不是白色, 不是白色这个点就是我们所需要的值
if rgb[0] + rgb[1] + rgb[2] < 600: # 765
print("滑块的坐标是", x)
return x

找到缺口图的x坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_bg_x(img_bg, img_full):
bg = Image.open(img_bg)
full = Image.open(img_full)
w, h = bg.size
for x in range(w):
for y in range(h):
bg_rgb = bg.getpixel((x, y))
full_rgb = full.getpixel((x, y))
r = bg_rgb[0] - full_rgb[0]
g = bg_rgb[1] - full_rgb[1]
b = bg_rgb[2] - full_rgb[2]
abs_value = abs(r) + abs(g) + abs(b)
if abs_value > 120:
print('缺口的坐标是', x)
return x

计算距离

1
2
3
4
5
6
def get_distance(img_bg, img_slice, img_full):
slice_x = get_slice_x(img_slice)
bg_x = get_bg_x(img_bg, img_full)
data = abs(bg_x-slice_x)
print("需要移动的距离是", data)
return data

进行移动

1
2
3
4
5
6
7
8
9
10
11
12
13
def move_silder(tracks):
time.sleep(3)
element = driver.find_element(by=By.CLASS_NAME, value='geetest_slider_button')

action_chains = ActionChains(driver)

action_chains.click_and_hold(element)
action_chains.pause(1) # 在执行下一个动作之前 暂停1秒
action_chains.move_by_offset(tracks+20, 0)
action_chains.pause(1)
action_chains.move_by_offset(-20, 0)
action_chains.pause(1)
action_chains.release().perform()

总体执行

1
2
3
4
5
6
7
8
9
10
if __name__ == '__main__':
driver = webdriver.Chrome()
url = 'https://captcha1.scrape.center/'
driver.get(url)
time.sleep(4)
driver.find_element(by=By.CLASS_NAME, value='el-button').click()
time.sleep(3)
img_bg, img_slice, img_full = get_captcha()
distance = get_distance(img_bg, img_slice, img_full)
move_silder(distance)

点触验证码

点触验证码是一种常见的反爬手段
解决方案有两种:一种是直接解决,这需要深度学习机器学习等图像处理技术,以大量的数据训练识别模型,最终达到模型足矣识别图片中的文字提示和应该点击的区域之间的对应关系。
这需要非常专业的设备,比如GPU运算,和专业的开发和维护人员。

因此市面上有专业解决此类问题的OCR解决提供商。也就是第二种间接解决的方案,调用第三方接口。

B站登录案例

https://www.bilibili.com/

第一步:

通过登录入口,发现其元素的类为header-login-entry, 利用selenium进行点击模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium import webdriver
import main_02


url = 'https://www.bilibili.com/'

wb = webdriver.Chrome()

wb.get(url)

wb.find_element(by=By.CLASS_NAME, value='header-login-entry').click()
time.sleep(3) # 反应时间,隐性反应太麻烦了先强制等待

第二步:

点击成功后,发现账号输入的元素为placeholder="请输入账号",利用XPATH锁定这个元素所在,密码也是一样的

1
2
wb.find_element(by=By.XPATH, value='//div/input[@placeholder="请输入账号"]').send_keys('123')
wb.find_element(by=By.XPATH, value='//div/input[@placeholder="请输入密码"]').send_keys('123')

第三步:

然后同样的查看登录按钮的元素并锁定btn_primary

1
2
wb.find_element(by=By.CLASS_NAME, value='btn_primary ').click()
time.sleep(2)

第四步:

挑选能覆盖所有验证码内容的类geetest_widget

1
wb.find_element(by=By.XPATH, value='//div[@class="geetest_widget"]').screenshot('input.png')

第五步:

进行第三方OCR操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from chaojiying_Python import chaojiying

def superying():
supertest = chaojiying.Chaojiying_Client('123', '123', '931146')
im = open('input.png', 'rb').read()
res = supertest.PostPic(im, 9004)
return res

# data = {'err_no': 0,
# 'err_str': 'OK',
# 'pic_id': '2233720420832210145',
# 'pic_str': '49,280|59,172', # | , int()
# 'md5': '8dc82695a1f5f8a60a298b7822a15893'}

# (114,296)(59,180)
1
2
3
# 识别结果
data = main_02.superying()
pic_list = data.get('pic_str').split("|")

第六步:

进行模拟操作, 同样的确认提交的元素geetest_commit_tip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def img_click(click_list):
# 截图用是的哪个对象, 点击就必须用哪个对象 # 4.0.0
img_element = wb.find_element(by=By.XPATH, value='//div[@class="geetest_widget"]')

for i in click_list: # ['153,194', '258,203', '212,135', '245,305']
data = i.split(",") # ["153", "194"]
x = int(data[0])
y = int(data[1])
# 这段代码是使用Selenium的ActionChains类来模拟鼠标操作。
# 其中,`wb`是WebDriver对象,`img_element`表示要移动到的元素,
# `x`和`y`表示鼠标相对于元素左上角的偏移量。
# 这段代码的作用是移动鼠标到指定元素的指定位置,并进行点击操作。
ActionChains(wb).move_to_element_with_offset(img_element, xoffset=x, yoffset=y).click().perform()
time.sleep(2)

wb.find_element(by=By.XPATH, value='//div/a/div[@class="geetest_commit_tip"]').click()

img_click(pic_list)
  • 标题: 验证识别
  • 作者: Yiuhang Chan
  • 创建于 : 2021-10-26 07:12:45
  • 更新于 : 2024-02-28 18:50:30
  • 链接: https://www.yiuhangblog.com/2021/10/26/20211026验证识别/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论