import random
import string
from PIL import Image, ImageFont, ImageDraw
import numpy as np
import math
from scipy import misc
import os
characters = string.digits + string.ascii_letters
class GenerateImageCaptcha(object):
def __init__(self, width=160, height=60, fonts=None, font_sizes=None):
self._width = width
self._height = height
self._fonts = fonts or [r'C:\Windows\Fonts\arial.ttf']
self._font_sizes = font_sizes or (40, 45, 50)
self._truefonts = []
@property
def truefonts(self):
if self._truefonts:
return self._truefonts
self._truefonts = tuple([
ImageFont.truetype(n, s)
for n in self._fonts
for s in self._font_sizes
])
return self._truefonts
@staticmethod
def create_noise_curve(image, color):
curve_width = random.randint(0, 4)
if curve_width == 0:
return image
w, h = image.size
x1 = random.randint(0, int(w / 5))
y1 = random.randint(int(h / 5), h - int(h / 5))
for i in range(4):
x2 = x1 + random.randint(int(w / 5), int(w / 2))
y2 = random.randint(int(h / 5), h - int(h / 5))
if x2 <= w:
ImageDraw.Draw(image).line([x1, y1, x2, y2] , fill=color, width=curve_width)
else:
x2 = w
x1, y1 = x2, y2
return image
@staticmethod
def create_noise_dots(image, color, width=3, number=30):
draw = ImageDraw.Draw(image)
w, h = image.size
while number:
x1 = random.randint(0, w)
y1 = random.randint(0, h)
draw.line(((x1, y1), (x1 - 1, y1 - 1)), fill=color, width=width)
number -= 1
return image
def distort_x_img(self, im_array):
im_height, im_width = im_array.shape[0], im_array.shape[1]
im_tmp = np.zeros(shape=im_array.shape)
factor = random.randint(1, 5)
phase = random.random()
move_direction = random.choice(['left', 'right'])
for i in range(im_height):
dx = factor * math.sin(phase + 2 * math.pi * i / im_height)
dx = abs(int(dx))
if move_direction == 'right':
im_tmp[i, dx:] = im_array[i, :(im_width - dx)]
im_tmp[i, :dx] = im_array[i, (im_width - dx):]
else:
im_tmp[i, :(im_width - dx)] = im_array[i, dx:]
im_tmp[i, (im_width - dx):] = im_array[i, :dx]
return im_tmp
def distort_y_img(self, im_array):
im_height, im_width = im_array.shape[0], im_array.shape[1]
im_tmp = np.zeros(shape=im_array.shape)
factor = random.randint(4, 8)
period = random.randint(1, 3)
phase = random.random()
move_direction = random.choice(['up', 'down'])
for i in range(im_width):
dx = factor * (phase + math.sin(2 * math.pi * i * period / im_width))
dx = abs(int(dx))
if move_direction == 'up':
im_tmp[:im_height - dx, i] = im_array[dx:im_height, i]
im_tmp[im_height - dx:, i] = im_array[:dx, i]
else:
im_tmp[dx:, i] = im_array[:im_height - dx, i]
im_tmp[:dx, i] = im_array[im_height - dx:, i]
return im_tmp
def draw_image_rotate(self, chars, color, background):
"""Create the CAPTCHA image itself.
:param chars: text to be generated.
:param color: color of the text.
:param background: color of the background.
The color should be a tuple of 3 numbers, such as (0, 255, 255).
"""
image = Image.new('RGB', (self._width, self._height), background)
offset = random.randint(8, 16)
for c in chars:
font = random.choice(self.truefonts)
w,h = font.getsize(c)
im = Image.new('RGBA', (w , h),background)
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
ImageDraw.Draw(im).text((0,0), c, font=font, fill=color)
im = im.rotate(random.uniform(-30, 30),Image.BILINEAR, expand=1)
fff = Image.new('RGBA', im.size, (255,) * 4)
im = Image.composite(im,fff,im)
w, h = im.size
image.paste(im, (offset, int((self._height - h) / 2)))
offset = offset + w + random.randint(-5,0)
return image
def draw_img(self, chars, color, background):
image = Image.new('RGB', (self._width, self._height), background)
draw_im = ImageDraw.Draw(image)
x, y = random.randint(8, 16), random.randint(8, 12)
for ch in chars:
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
font = random.choice(self.truefonts)
draw_im.text(xy=(x, y), text=ch, fill=color, font=font)
x = x + font.getsize(ch)[0] + random.randint(0, 10)
y = random.randint(8, 12)
return image
def generate_image(self, chars):
"""Generate the image of the given characters.
:param chars: text to be generated.
"""
background = (255, 255, 255)
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
im = self.draw_img(chars, color, background)
self.create_noise_curve(im, color)
im_array = np.array(im)
im_x_distort = self.distort_x_img(im_array)
im_y_distort = self.distort_y_img(im_x_distort)
return np.array(im),im_x_distort,im_y_distort
def generate_captcha():
width, height, n_len = 160, 70, 4
random_str = ''.join(random.sample(characters, n_len))
fonts = [os.path.join(r'C:\Windows\Fonts', font) for font in ['arial.ttf', 'Carlito-Regular.ttf', 'DejaVuSans.ttf']]
font_sizes = range(40, 50)
generator = GenerateImageCaptcha(width=width, height=height, font_sizes=font_sizes, fonts=fonts)
imgs = generator.generate_image(random_str)
img_name = '{}_{}'.format(random_str, ''.join(random.sample(characters, 8)))
misc.imsave(r'{}.png'.format(img_name), imgs[2])
if __name__ == '__main__':
for _ in range(3):
generate_captcha()
生成图片样例: