对于我来说, 图片验证码识别的主要难度在于如何将验证码切割为可识别的小块
因为对于单个英文或者数字来说, 不管是什么机器学习包都能做到较高的识别率
# 获取一个数组中连续的值的索引
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
from PIL import Image
import numpy as np
# 对数据进行处理, 只保留0, 1
def surround_line_in_num(img: np.array):
col_index = []
row_index = []
for i in range(img.shape[0]):
for j in range(img.shape[1]):
if img[i, j] == 0:
col_index.append(i)
row_index.append(j)
return col_index, row_index
# surround_line_in_num(xxx)
# TODO: 增加处理, 当像素周围只有2x2时, 认为它是噪音, 这一步应该在统计出连续值之后做, 只对有疑问的点进行计算, 大面积连续的值不考虑该问题
def remove_noise(img):
return img
# 获取一个数组中连续的值的索引
def search(lis, broke_num=3):
# broke_num 需要拆分为多少个字符
lis = list(set(lis))
lis.sort()
s_list = [lis[0]-1] + lis
e_list = lis + [float('inf')]
se = list(map(lambda x: x[1] - x[0], zip(s_list, e_list)))
# 将坐标装入队列
ss = []
k = 0
s = 0
e = 0
for i in range(len(se)):
if se[i] == 1:
if e < i:
e = i
else:
if e != 0:
# 直接转换至e_list坐标, 获取图片内部坐标
ss.append((e_list[s], e_list[e]))
k += 1
s, e = i, 0
if len(ss) == broke_num:
return ss, ""
elif len(ss) > broke_num:
return ss[:broke_num], "有多余的长度列表 为: {}".format(ss)
else:
return ss, "解析失败!!! 没有获取到期望的分割点!!"
def save_splited_img(split_list, img):
index = 0
img_list = []
for i in split_list:
# crop 获取指定的部分
temp = img.crop(i)
temp.save(f"ii{time.time()}.png")
img_list.append(temp)
return img_list
def split(img, split_num=4):
"""
:param img: 图像对象
:type img: Image
:param split_num: 图片中的字符个数
:type split_num: int
:return: split point list
:rtype: list(tuple(left: int, up: int, right: int, down: int)), Error
"""
# 将传入的img对象处理为灰度图像, 并且只保留0, 1
limg = img.convert("L")
limg_array = np.array(limg)
# 大于200 是因为有的颜色太浅了, 比如: 浅黄色
array_zero_one = np.where(limg_array > 200, 1, 0)
# np.savetxt('num.txt', array_zero_one, fmt="%d")
_, row_indx = surround_line_in_num(array_zero_one)
split_list, error = search(row_indx, split_num)
left_up_right_down_tuple_list = []
if not error:
col_split_num = 2
# save_splited_img(split_list, img_array=limg_array)
for i in split_list:
temp_array = array_zero_one[:, i[0]:i[1]]
col_index, _ = surround_line_in_num(temp_array)
col_split_list, error = search(col_index, col_split_num)
l, r = i
u, d = col_split_list[0]
left_up_right_down_tuple_list.append((l, u, r, d))
return left_up_right_down_tuple_list, None
else:
print(error)
return None, "计算失败!!"
def get_split_img_obj(img, split_num=1):
img = img.convert("L")
img_array = np.array(img)
lurd_list, error = split(img, split_num)
if not error:
bimg_array = np.where(img_array > 200, 255, 0)
# try:
# bimg = Image.fromarray(bimg_array) # pillow == 4.3.0 需要指定模式才行, 但是指定的模式基本上都不能正常展示...
# # 最接近的模式是 "I"
# except:
bimg = img
return save_splited_img(lurd_list, bimg), ""
else:
print(error)
return "", error
if __name__ == "__main__":
img = Image.open('result.jpg')
# img = Image.open('show.png')
get_split_img_obj(img, 4)
"""
# 直接将图片置为灰度模式, 以便于 Image.fromarray 获取
img = img.convert("L")
img_array = np.array(img)
lurd_list, error = split(img, 4)
if not error:
bimg_array = np.where(img_array > 200, 255, 0)
try:
bimg = Image.fromarray(bimg_array) # pillow == 4.3.0 需要指定模式才行, 但是指定的模式基本上都不能正常展示...
# 最接近的模式是 "I"
except:
bimg = img
save_splited_img(lurd_list, bimg)
else:
print(error)
"""
工作中常见的验证码只有4位6位的, 当拆分失败时直接就返回, 防止失败率过高
也可以在拆分时增加一个宽度判断, 来尝试分割黏连字符, 但是只限于较为规整的字符
否则会导致一个字符被拆成两个字符
原先的图片
result.jpg
拆分后的图片
ii1571129077.2711172.png ii1571129077.2729177.png ii1571129077.2744443.png ii1571129077.2760947.png
网友评论