Python验证码生成

2022-05-11 15:28:35 浏览数 (1)

在Python程序中生成验证码并不算特别复杂,但需要三方库Pillow的支持(PIL的分支),因为要对验证码图片进行旋转、扭曲、拉伸以及加入干扰信息来防范那些用OCR(光学文字识别)破解验证码的程序。

下面的代码封装了生成验证码图片的功能,大家可以直接用这些代码来生成图片验证码,不要“重复发明轮子”。

代码语言:javascript复制
"""
图片验证码
"""
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个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我联系处理。敬请谅解!

本文链接:https://www.xy586.top/7617.html

转载请注明文章来源:行云博客 » Python验证码生成

0 人点赞