在Python程序中生成验证码并不算特别复杂,但需要三方库Pillow的支持(PIL的分支),因为要对验证码图片进行旋转、扭曲、拉伸以及加入干扰信息来防范那些用OCR(光学文字识别)破解验证码的程序。
下面的代码封装了生成验证码图片的功能,大家可以直接用这些代码来生成图片验证码,不要“重复发明轮子”。
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | """ 图片验证码 """ import os import random from io import BytesIO from PIL import Image from PIL import ImageFilter from PIL.ImageDraw import Draw from PIL.ImageFont import truetype class Bezier(object): """贝塞尔曲线""" def __init__(self): self.tsequence = tuple([t / 20.0 for t in range(21)]) self.beziers = {} def make_bezier(self, n): """绘制贝塞尔曲线""" try: return self.beziers[n] except KeyError: combinations = pascal_row(n - 1) result = [] for t in self.tsequence: tpowers = (t ** i for i in range(n)) upowers = ((1 - t) ** i for i in range(n - 1, -1, -1)) coefs = [c * a * b for c, a, b in zip(combinations, tpowers, upowers)] result.append(coefs) self.beziers[n] = result return result class Captcha(object): """验证码""" def __init__(self, width, height, fonts=None, color=None): self._image = None self._fonts = fonts if fonts else \ [os.path.join(os.path.dirname(__file__), 'fonts', font) for font in ['ArialRB.ttf', 'ArialNI.ttf', 'Georgia.ttf', 'Kongxin.ttf']] self._color = color if color else random_color(0, 200, random.randint(220, 255)) self._width, self._height = width, height @classmethod def instance(cls, width=200, height=75): prop_name = f'_instance_{width}_{height}' if not hasattr(cls, prop_name): setattr(cls, prop_name, cls(width, height)) return getattr(cls, prop_name) def background(self): """绘制背景""" Draw(self._image).rectangle([(0, 0), self._image.size], fill=random_color(230, 255)) def smooth(self): """平滑图像""" return self._image.filter(ImageFilter.SMOOTH) def curve(self, width=4, number=6, color=None): """绘制曲线""" dx, height = self._image.size dx /= number path = [(dx * i, random.randint(0, height)) for i in range(1, number)] bcoefs = Bezier().make_bezier(number - 1) points = [] for coefs in bcoefs: points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)]) for ps in zip(*path))) Draw(self._image).line(points, fill=color if color else self._color, width=width) def noise(self, number=50, level=2, color=None): """绘制扰码""" width, height = self._image.size dx, dy = width / 10, height / 10 width, height = width - dx, height - dy draw = Draw(self._image) for i in range(number): x = int(random.uniform(dx, width)) y = int(random.uniform(dy, height)) draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level) def text(self, captcha_text, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None): """绘制文本""" color = color if color else self._color fonts = tuple([truetype(name, size) for name in fonts for size in font_sizes or (65, 70, 75)]) draw = Draw(self._image) char_images = [] for c in captcha_text: font = random.choice(fonts) c_width, c_height = draw.textsize(c, font=font) char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0)) char_draw = Draw(char_image) char_draw.text((0, 0), c, font=font, fill=color) char_image = char_image.crop(char_image.getbbox()) for drawing in drawings: d = getattr(self, drawing) char_image = d(char_image) char_images.append(char_image) width, height = self._image.size offset = int((width - sum(int(i.size[0] * squeeze_factor) for i in char_images[:-1]) - char_images[-1].size[0]) / 2) for char_image in char_images: c_width, c_height = char_image.size mask = char_image.convert('L').point(lambda i: i * 1.97) self._image.paste(char_image, (offset, int((height - c_height) / 2)), mask) offset += int(c_width * squeeze_factor) @staticmethod def warp(image, dx_factor=0.3, dy_factor=0.3): """图像扭曲""" width, height = image.size dx = width * dx_factor dy = height * dy_factor x1 = int(random.uniform(-dx, dx)) y1 = int(random.uniform(-dy, dy)) x2 = int(random.uniform(-dx, dx)) y2 = int(random.uniform(-dy, dy)) warp_image = Image.new( 'RGB', (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2))) warp_image.paste(image, (abs(x1), abs(y1))) width2, height2 = warp_image.size return warp_image.transform( (width, height), Image.QUAD, (x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1)) @staticmethod def offset(image, dx_factor=0.1, dy_factor=0.2): """图像偏移""" width, height = image.size dx = int(random.random() * width * dx_factor) dy = int(random.random() * height * dy_factor) offset_image = Image.new('RGB', (width + dx, height + dy)) offset_image.paste(image, (dx, dy)) return offset_image @staticmethod def rotate(image, angle=25): """图像旋转""" return image.rotate(random.uniform(-angle, angle), Image.BILINEAR, expand=1) def generate(self, captcha_text='', fmt='PNG'): """生成验证码(文字和图片)""" self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255)) self.background() self.text(captcha_text, self._fonts, drawings=['warp', 'rotate', 'offset']) self.curve() self.noise() self.smooth() image_bytes = BytesIO() self._image.save(image_bytes, format=fmt) return image_bytes.getvalue() def pascal_row(n=0): """生成Pascal三角第n行""" result = [1] x, numerator = 1, n for denominator in range(1, n // 2 + 1): x *= numerator x /= denominator result.append(x) numerator -= 1 if n & 1 == 0: result.extend(reversed(result[:-1])) else: result.extend(reversed(result)) return result def random_color(start=0, end=255, opacity=255): """获得随机颜色""" red = random.randint(start, end) green = random.randint(start, end) blue = random.randint(start, end) if opacity is None: return red, green, blue return red, green, blue, opacity |
说明:上面的代码在生成验证码图片时用到了三种字体文件,使用上面的代码时需要添加字体文件到应用目录下的fonts目录中。
行云博客 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我(admin@xy586.top)联系处理。敬请谅解!
本文链接:https://www.xy586.top/7617.html
转载请注明文章来源:行云博客 » Python验证码生成