需要的库和工具及安装
其中会用到以下几个库和工具:
工具:
tesseract
tesseract下载地址:https://digi.bib.uni-mannheim.de/tesseract/
1.下载适合自己版本(32或者64位)的非dev的exe文件,然后一路安装下去。经过多次测试,不同版本的tesseract识别的结果会不同,我试了五六个版本,最后使用tesseract-ocr-w64-setup-v5.0.0-alpha.20210506.exe 版本识别成功率最高。
注:如果需要支持多国语言,到这个步骤的时候它勾选上,默认支持英文:
2.配置环境变量
path 里面配置tesseract的安装路径:
3.验证安装是否成功:
cmd,然后输入验证tesseract和验证tesseract -v 正常显示即可
pillow 和 pytesseract 两个库
pip install pillow -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
pip install pytesseract -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
配置pytesseract库:
找到pytesseract 库的pytesseract.py文件,打开该.py文件,找到 tesseract_cmd,改变它的值为刚才安装 tesseract.exe 的路径。
安装cv2库:
pip3 install opencv-python
正文
在python爬虫爬取某些网站的验证码的时候可能会遇到验证码识别的问题,现在的验证码大多分为四类:
1、识图的验证码 2、需要计算的验证码 3、通过滑动滑块的验证码 4、语音的验证码
本文针对最简单的识图验证码,识别验证码中的英文数字。
识别验证码通常是这几个步骤:
1、灰度处理:把彩色的验证码图片转为灰色的图片
2、二值化:将图片处理为只有黑白两色的图片
3、去除边框:去除验证码图片的边框,去除边框就是遍历像素点,找到四个边框上的所有点,把他们都改为白色,如果没有边框可以不用。
4、降噪:去除掉验证码图片内的干扰条件,比如西线和点。
5、切割字符或者倾斜度矫正:切割字符对验证码内的内容进行单个的切割,一般用于验证码粘连或者计算验证码。
6、训练字体库
7、识别
如果验证码图片清晰度很高,是直接图片保存下来的。(不是截图)可以用以下代码
import re
import numpy
from PIL import Image
from pytesseract import *
from fnmatch import fnmatch
from queue import Queue
import cv2
import os
'''
需要建两个文件夹,code_dir文件夹存放验证码图片,
out_img文件夹 存放处理后的验证码图片
'''
class LdenVerifiCode:
filedir = 'D:/logs/code_dir' # 存放验证码的文件夹
code_out_folder = 'D:/logs/out_img'
def clear_border(self, img, img_name):
'''去除边框
'''
# filename = self.code_out_folder + img_name.split('.')[0] + '-clearBorder.jpg'
filename = os.path.join(self.code_out_folder, img_name.split('.')[0] + '-clearBorder.png')
h, w = img.shape[:2]
for y in range(0, w):
for x in range(0, h):
# if y ==0 or y == w -1 or y == w - 2:
if y < 4 or y > w - 4:
img[x, y] = 255
# if x == 0 or x == h - 1 or x == h - 2:
if x < 4 or x > h - 4:
img[x, y] = 255
cv2.imwrite(filename, img)
return img
def interference_line(self, img, img_name):
'''
干扰线降噪
'''
# filename = self.code_out_folder + img_name.split('.')[0] + '-interferenceline.jpg'
filename = os.path.join(self.code_out_folder, img_name.split('.')[0] + '-interferenceline.png')
h, w = img.shape[:2]
# !!!opencv矩阵点是反的
# img[1,2] 1:图片的高度,2:图片的宽度
for y in range(1, w - 1):
for x in range(1, h - 1):
count = 0
if img[x, y - 1] > 245:
count = count + 1
if img[x, y + 1] > 245:
count = count + 1
if img[x - 1, y] > 245:
count = count + 1
if img[x + 1, y] > 245:
count = count + 1
if count > 2:
img[x, y] = 255
cv2.imwrite(filename, img)
return img
def interference_point(self, img, img_name, x=0, y=0):
"""点降噪
9邻域框,以当前点为中心的田字框,黑点个数
:param x:
:param y:
:return:
"""
# filename = './out_img/' + img_name.split('.')[0] + '-interferencePoint.jpg'
filename = os.path.join(self.code_out_folder, img_name.split('.')[0] + '-cutting.png')
# todo 判断图片的长宽度下限
cur_pixel = img[x, y] # 当前像素点的值
height, width = img.shape[:2]
for y in range(0, width - 1):
for x in range(0, height - 1):
if y == 0: # 第一行
if x == 0: # 左上顶点,4邻域
# 中心点旁边3个点
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 2 * 245:
img[x, y] = 0
elif x == height - 1: # 右上顶点
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum <= 2 * 245:
img[x, y] = 0
else: # 最上非顶点,6邻域
sum = int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 3 * 245:
img[x, y] = 0
elif y == width - 1: # 最下面一行
if x == 0: # 左下顶点
# 中心点旁边3个点
sum = int(cur_pixel) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x, y - 1])
if sum <= 2 * 245:
img[x, y] = 0
elif x == height - 1: # 右下顶点
sum = int(cur_pixel) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y - 1])
if sum <= 2 * 245:
img[x, y] = 0
else: # 最下非顶点,6邻域
sum = int(cur_pixel) \
+ int(img[x - 1, y]) \
+ int(img[x + 1, y]) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x + 1, y - 1])
if sum <= 3 * 245:
img[x, y] = 0
else: # y不在边界
if x == 0: # 左边非顶点
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 3 * 245:
img[x, y] = 0
elif x == height - 1: # 右边非顶点
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum <= 3 * 245:
img[x, y] = 0
else: # 具备9领域条件的
sum = int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 4 * 245:
img[x, y] = 0
cv2.imwrite(filename, img)
return img
def _get_dynamic_binary_image(self, filedir, img_name):
'''
自适应阀值二值化
'''
# filename = self.code_out_folder + img_name.split('.')[0] + '-binary.jpg'
filename = os.path.join(self.code_out_folder, img_name.split('.')[0] + '-binary.png')
img_name = filedir + '/' + img_name
print('开始识别验证码:' + img_name)
im = cv2.imread(img_name)
im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
th1 = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)
cv2.imwrite(filename, th1)
return th1
def _get_static_binary_image(self, img, threshold=140):
'''
手动二值化
'''
img = Image.open(img)
img = img.convert('L')
pixdata = img.load()
w, h = img.size
for y in range(h):
for x in range(w):
if pixdata[x, y] < threshold:
pixdata[x, y] = 0
else:
pixdata[x, y] = 255
return img
def main(self, img_name):
# 自适应阈值二值化
im = self._get_dynamic_binary_image(self.filedir, img_name)
# 去除边框
im = self.clear_border(im, img_name)
# 对图片进行干扰线降噪
im = self.interference_line(im, img_name)
# 对图片进行点降噪
self.interference_point(im, img_name)
# code_file ='./out_img/%s-cutting.jpg' % img_name.split('.')[0]
code_file = os.path.join(self.code_out_folder, '%s-cutting.png' % img_name.split('.')[0])
str_img = image_to_string(Image.open(code_file)) # 图片转文字
cop = re.compile("[^a-z^A-Z^0-9]") # 匹配不是英文大小写、数字的其他字符,去掉特殊字符
code_results = cop.sub('', str_img) # 将string1中匹配到的字符替换成空字符
print('识别为:%s' % code_results)
return code_results
if __name__ == '__main__':
lvc = LdenVerifiCode()
lvc.main('191704-ele.png')
如果是通过定位元素坐标,然后截图下来的验证码可以用以下代码:
import re
import numpy
from PIL import Image
from pytesseract import *
from fnmatch import fnmatch
from queue import Queue
import cv2
import os
'''
需要建两个文件夹,code_dir文件夹存放验证码图片,
out_img文件夹 存放处理后的验证码图片
'''
class LdenVerifiCode:
filedir = 'E:/logs/code_dir' # 存放验证码的文件夹
code_out_folder = 'E:/logs/out_img'
def clear_border(self, filedir, img_name):
'''去除边框
'''
img_name = filedir + '/' + img_name
img = cv2.imread(img_name)
filename = os.path.join(self.code_out_folder, img_name.split('.')[0] + '-clearBorder.png')
h, w = img.shape[:2]
for y in range(0, w):
for x in range(0, h):
# if y ==0 or y == w -1 or y == w - 2:
if y < 4 or y > w - 4:
img[x, y] = 255
# if x == 0 or x == h - 1 or x == h - 2:
if x < 4 or x > h - 4:
img[x, y] = 255
cv2.imwrite(filename, img)
return filename
def clearNoise(self,imageFile,img_name, x=0, y=0):
if os.path.exists(imageFile):
image = Image.open(imageFile)
image = image.convert('L')
image = numpy.asarray(image)
image = (image > 135) * 255
image = Image.fromarray(image).convert('RGB')
filename = os.path.join(self.code_out_folder, img_name.split('.')[0] + '-cutting.png')
image.save(filename)
return image
def main(self, img_name):
# 去除边框
filename = self.clear_border(self.filedir, img_name)
self.clearNoise(filename,img_name)
code_file = os.path.join(self.code_out_folder, '%s-cutting.png' % img_name.split('.')[0])
str_img = image_to_string(Image.open(code_file)) # 图片转文字
cop = re.compile("[^a-z^A-Z^0-9]") # 匹配不是英文大小写、数字的其他字符,去掉特殊字符
code_results = cop.sub('', str_img) # 将string1中匹配到的字符替换成空字符
print('识别为:%s' % code_results)
return code_results
if __name__ == '__main__':
lvc = LdenVerifiCode()
lvc.main('145512-ele.png')