Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法

  本文主要学习验证码功能的实现,为了项目BBS+Blog项目打下基础。

   为了防止机器人频繁登陆网站或者破坏分子恶意登陆,很多用户登录和注册系统都提供了图形验证码功能。

  验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。

  图形验证码的历史比较悠久,到现在已经有点英雄末路的味道了。因为机器学习、图像识别的存在,机器人已经可以比较正确的识别图像内的字符了。但不管怎么说,作为一种防御手段,至少还是可以抵挡一些低级入门的攻击手段,抬高了攻击者的门槛。

验证码功能实现方法1——完整的验证码实现流程

1, 验证码前端画布页面生成

  那么下面我们将依次生成验证码这种画布,左边框是输入验证码的内容,右边框是设计验证码。

1.1,直接读取图片

  首先,我们需要了解读取图片的方法。

  # 方式一
    with open('kd1.jpg', 'rb') as f:
        data = f.read()

    return HttpResponse(data)

  这样我们知道方法就行,不建议使用,因为这样读取文件,我们需要存很多文件。

1.2,硬盘操作

  首先,我们需要导入库,图像处理库Pillow,如果没有的话,需要先安装pillow库。

  其次,我们创建一个定义随机颜色方法的函数。这函数的意义就是定义画布的背景颜色。

如何定义颜色随机呢?,因为三原色R,G,B的范围都是0-255,我们随机取0-255里面的某一个整数,就得到了随机颜色。代码如下:

#定义随机颜色方法
def get_random_color():
    
    R = random.randrange(255)
    G = random.randrange(255)
    B = random.randrange(255)

    return (R,G,B)

  然后我们设定画布的大小,定义画布的宽和高。然后写一块区域,最后展示在前端页面。

    # 方式二:使用pillow PIL模块
    # pip  install pillow
    from PIL import Image

    # img = Image.new("RGB", (270, 40), color='red')
    img = Image.new("RGB", (270, 40), color=get_random_color())
    with open("validCode.png", "wb") as f:
        img.save(f, 'png')
    with open('validCode.png', 'rb') as f:
        data = f.read()
    return HttpResponse(data)

  

1.3,内存操作

  其实硬盘操作,我们也不建议使用,最好的方法是建议使用内存操作。在上线中大多体验者需要的是速度,如果速度不够快,那就会放弃这个APP,所以我们一般牺牲其空间复杂度,换取时间复杂度。

  那内存操作如何做呢?首先,我们获取一个缓存区,然后将图片保存到缓存区,最后使用一个.getvalue的函数方法将缓存区的内容返回给前端。

  .getvalue  是把缓存区的所有数据读取。

  bytesIO() 是使用io 获取一个缓存区,然后将图片保存到缓存区,最后读取缓存区的数据。

    from PIL import Image, ImageDraw, ImageFont
    from io import BytesIO

    img = Image.new('RGB', (270, 40), color=get_random_color())

    f = BytesIO()
    img.save(f, 'png')
    data = f.getvalue()

  这样的话,一个前端画布就生成了,不仅生成了,我们还解决了速度问题,而且每次刷新,后台都会传出不同颜色的画布。

1.4 效果图

  验证码在前端显示,肯定是左边一格,右边一格。最后设计出来的画布效果是这样的。

Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第1张图片

 

  那下面,我们需要给画布里面添加文字了。

2,如何给面板里加文字

  我们需要导入绘图库 ImageDraw, 绘图字体库 ImageFont。还需要下载一个字体样式,直接使用网上的字体样式会比较简单。

2.1,ttf字体下载

  我们可以在网上下载一个ttf字体文件。选择一个自己喜欢的字体形式,下载。我这里下载了三个,这里我使用第三个 Vera.ttf字体。

 2.2 添加文字

   一般情况下,验证码都是由四到八位不等的数字,大小写字母组成。我们这里选择五位,其中每个位里面让随机生成一个大写字母,或者小写字母,或者数字。然后将其保存下来。这就是一个简单的验证码就生成了。

  代码如下:

    # 导入绘图库
    from PIL import Image, ImageDraw, ImageFont
    # 导入IO库
    from io import BytesIO

    img = Image.new('RGB', (270, 40), color=get_random_color())
    draw = ImageDraw.Draw(img)
    vera_font = ImageFont.truetype("static/font/Vera.ttf", size=35)


    #  ttf 字体下载
    valid_code_str = ''
    for i in range(5):
        # 数字
        random_num = str(random.randint(0, 9))
        # 随机小写字母
        random_low_alpha = chr(random.randint(95, 122))
        # 随机大写字母
        random_upper_alpha = chr(random.randint(65, 90))

        random_char = random.choice([random_num, random_upper_alpha, random_low_alpha])
        #  x, y  坐标
        draw.text((i*50+20, 5), random_char, get_random_color(), font=vera_font)
        # 保存验证码字符串
        valid_code_str += random_char

  

2.3 效果图

  验证码里面添加五个文字,最后设计出来的添加文字的画布效果是这样的。

Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第2张图片

 

3,加上噪点噪线

  其实上面差不多已经完成了验证码的操作,但是这是比较简单的验证码,一般的图片验证码机器就可以识别了。我们需要添加一些难度,比如噪点噪线。

  加噪点噪线也是为了减轻暴力请求,一般的图片验证码机器就可以识别出来,所以我们加点难度,这样可以减轻服务器的压力。

  下面代码中,width和height是前端画布的宽和高,第一个for循环是噪点的加入,第二个for循环是噪线的加入。

    width = 235
    height = 35
    for i in range(100):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        draw.line((x1, x2, y1, y2), fill=get_random_color())

    for i in range(400):
        draw.point([random.randint(0, width), random.randint(0, height)],
                   fill=get_random_color())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color())

  x1, y1, x2, y2  则表示需要四个坐标。

 3.1 效果图

    验证码里面添加噪点噪线,因为我还要使用,所以噪点噪线的效果就添加了一点点而已,如果需要复杂,可以添加内容就像,最后设计出来的添加噪点噪线的画布效果是这样的。

Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第3张图片

 

4,验证码局部刷新

  为什么要做验证码局部刷新呢?从使用者的角度来说,我们也知道,当前端显示的验证码看不清楚,我们就需要刷新一下,但是不可能不停的提交表单,这样的话,服务器的压力就太大了。所以这时候局部刷新就优势就显现出来了,我们需要局部刷新,很多人就想到了Ajax,没错,这是可以用,但是这里说一种比较简单的局部刷新的方法。

  我们可以看一个例子:

Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第4张图片

   从上面,我们发现可以使用增加字符的方式,实现局部刷新,那么我们也可以采取这么简单的方法,直接刷新验证码就行了。

  下面展示其代码:



  views.py

def get_validCode_image(request):
    def get_random_color():
        return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

    # 方式四,图片里面什么都没,这里我们需要加上噪点,文字
    from PIL import Image, ImageDraw, ImageFont
    from io import BytesIO

    img = Image.new('RGB', (270, 40), color=get_random_color())
    draw = ImageDraw.Draw(img)
    vera_font = ImageFont.truetype("static/font/Vera.ttf", size=35)

    valid_code_str = ''
    for i in range(5):
        # 数字
        random_num = str(random.randint(0, 9))
        # 随机小写字母
        random_low_alpha = chr(random.randint(95, 122))
        # 随机大写字母
        random_upper_alpha = chr(random.randint(65, 90))

        random_char = random.choice([random_num, random_upper_alpha, random_low_alpha])
        #  x, y  坐标
        draw.text((i*50+20, 5), random_char, get_random_color(), font=vera_font)
        # 保存验证码字符串
        valid_code_str += random_char

    width = 235
    height = 35
    for i in range(10):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        draw.line((x1, x2, y1, y2), fill=get_random_color())

    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)],
                   fill=get_random_color())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color())

    print('valid_code_str', valid_code_str)

    f = BytesIO()
    img.save(f, 'png')
    data = f.getvalue()

    request.session['valid_code_str'] = valid_code_str

    return HttpResponse(data)

  前端代码:

    // 登录验证
    $(".login_btn").click(function () {
        $.ajax({
            url: "",
            type: 'post',
            data: {
                user: $('#user').val(),
                pwd: $('#pwd').val(),
                valid_code: $("#valid_code").val(),
                csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
            },
            success: function (data) {
                console.log(data);
                if (data.user){
                        if (location.search){
                            location.href = location.search.slice(6)
                        }
                        else {
                            location.href = '/blog/index/'
                        }
                }else {
                    $(".error").text(data.msg).css({'color':'red', 'margin-left':'10px'});
                    //  设置2秒后清空
                    setTimeout(function () {
                        $(".error").text("")
                    }, 2000)
                }
            }
        })
    });

  

5,cookie与session保存验证码

   将随机验证码存储到session中,就是需要记录给哪个请求发了什么验证码。

  为什么这么说呢?因为到时候使用登录验证的使用者不止一个人,那么服务端发送的验证码不记录给哪个请求发送的验证码,当好几个人同时请求验证码信息,然后提交,这样就会发生混淆。所以保险起见,我们使用session保存验证码。

    views.py

 request.session['valid_code_str'] = valid_code_str

  login.html




    
    Title

    



登录页面

{% csrf_token %}
{# #} {# #}

  index.html




    
    Title


Index Page

{{ request.user.username }}

  views.py

from django.shortcuts import render, HttpResponse, redirect

# Create your views here.
# from .MyForms import UserTestForm
# from .models import UserTest
from django.http import JsonResponse
from django.contrib import auth
import random


def login(request):
    # if request.method == 'POST':
    #     response = {'user': None, 'msg': None}
    #
    #     user = request.POST.get('user')
    #     pwd = request.POST.get('pwd')
    #     valid_code = request.POST.get('valid_code')
    #     valid_code_str = request.session.get('valid_code_str')
    #     # if valid_code == valid_code_str:
    #     # 验证码不区分大小写
    #     if valid_code.upper() == valid_code_str:
    #         pass
    #     else:
    #         # 校验失败了。。。。
    #         response['msg'] = 'valid code error!'
    #         pass
    #     return JsonResponse(response)
    if request.method == 'POST':
        print("POST..............")
        response = {'user': None, 'msg': None}
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        valid_code = request.POST.get('valid_code')

        valid_code_str = request.session.get('valid_code_str')
        # if valid_code == valid_code_str:
        # 验证码不区分大小写
        if valid_code.upper() == valid_code_str.upper():
            user = auth.authenticate(username=user, password=pwd)
            if user:
                # request.user == 当前登录对象
                auth.login(request, user)
                response['user'] = user.username
                pass
            else:
                # response['msg'] = 'username or password error !'
                response['msg'] = '用户名或者密码错误'
        else:
            # 校验失败了。。。。
            response['msg'] = 'valid code error!'
        return JsonResponse(response)

    return render(request, 'login.html')

def get_validCode_image(request):
    from blog.utils.validCode import get_valid_code_img
    data = get_valid_code_img(request)
    return HttpResponse(data)




def index(request):
    return render(request, 'index.html')

  validCode.py

import random


def get_random_color():
    return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

def get_valid_code_img(request):

    # # 方式一
    # with open('kd1.jpg', 'rb') as f:
    #     data = f.read()
    #
    # return HttpResponse(data)

    # # 方式二:使用pillow PIL模块
    # # pip  install pillow
    # from PIL import Image
    #
    # # img = Image.new("RGB", (270, 40), color='red')
    # img = Image.new("RGB", (270, 40), color=get_random_color())
    # with open("validCode.png", "wb") as f:
    #     img.save(f, 'png')
    # with open('validCode.png', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 方式三,因为磁盘的方式比较慢,open是磁盘操作
    # 内存操作会比较快,这里使用内存操作
    # from PIL import Image, ImageDraw, ImageFont
    # from io import BytesIO
    #
    # img = Image.new('RGB', (270, 40), color=get_random_color())
    #
    # f = BytesIO()
    # img.save(f, 'png')
    # data = f.getvalue()


    # 方式四,图片里面什么都没,这里我们需要加上噪点,文字
    from PIL import Image, ImageDraw, ImageFont
    from io import BytesIO

    img = Image.new('RGB', (270, 40), color=get_random_color())
    draw = ImageDraw.Draw(img)
    vera_font = ImageFont.truetype("static/font/Vera.ttf", size=35)
    # char = str(random.randint(0, 9))

    # draw.text((0, 5), 'python', get_random_color(), font=vera_font)
    # draw.text((0, 5), char, get_random_color(), font=vera_font)
    #  ttf 字体下载
    valid_code_str = ''
    for i in range(5):
        # 数字
        random_num = str(random.randint(0, 9))
        # 随机小写字母
        random_low_alpha = chr(random.randint(95, 122))
        # 随机大写字母
        random_upper_alpha = chr(random.randint(65, 90))

        random_char = random.choice([random_num, random_upper_alpha, random_low_alpha])
        #  x, y  坐标
        draw.text((i*50+20, 5), random_char, get_random_color(), font=vera_font)
        # 保存验证码字符串
        valid_code_str += random_char

    width = 235
    height = 35
    for i in range(10):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        draw.line((x1, x2, y1, y2), fill=get_random_color())

    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)],
                   fill=get_random_color())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color())

    print('valid_code_str', valid_code_str)

    request.session['valid_code_str'] = valid_code_str
    '''
    1, dasdasda
    2, COOKIE {'sessionid': dsdsadsdad}
    3,django-session 
        session-key  session-data  dsadasdsdsa
    4,
    '''
    f = BytesIO()
    img.save(f, 'png')
    data = f.getvalue()

    return data

  

 

验证码功能实现方法2——captcha

  在Django中实现图片验证码功能非常简单,有现成的第三方库可以使用,我们不必自己开发(但是也需要自己能开发的出来,方法二将自己开发的图片验证码模块)。这个库叫做django-simple-captcha。

1,安装 captcha

  直接使用pip 按照

pip  install django--simple-captcha

  Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第5张图片

 

  Django 自动帮我们安装了相关的依赖库 six, olefile 和 pillow ,其中Pillow是大名鼎鼎的绘图模块。

2,注册captcha

  在settings.py中,将'captcha' 注册到APP列表里。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user19',
    'captcha',
]

  captcha 需要在数据库中建立自己的数据表,所以需要执行migrate命名生成数据表:

python manage.py makemigrations

python manage.py migrate

  

3,添加URL路由

  我们还需要在根目录下的urls.py文件添加 captcha 对应的网址:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
    path('captcha', include('captcha.urls')),
]

  

4,修改 myforms.py

  如果上面都OK了,就可以直接在MyForms.py文件中添加CaptchaField了。

from django import forms
from captcha.fields import CaptchaField
  
class UserForm(forms.Form):
    username = forms.CharField(label="用户名", max_length=128,
widget=forms.TextInput(attrs={'class': 'form-control'}))
 
    password = forms.CharField(label="密码", max_length=256,
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
 
    captcha = CaptchaField(label='验证码')

  注意:我们需要提前导入form captcha.fields import CaptchaField ,然后就像写普通的form字段一样添加一个captcha字段就可以了。

5,HTML文件的使用(修改 login.html文件)

  由于我们这里直接学习验证码,所以我这里直接写一个简单的login.html页面,添加 capthca的相关内容。




    
    Title
    
    



{% block content %}
    
{% endblock %}

  这里额外的添加了一条 {{ login_form.captcha.errors }} 用于明确指示用户,你的验证码不正确。其中验证图形码是否正确的工作都是在后台自动完成的。只需要使用is_valid()这个myforms内置的验证方法就一起进行了,完全不需要再视图函数中添加任何的验证代码,非常方便快捷!

6,查看效果

  直接的效果进入页面的效果如下:

Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第6张图片

  我们输入错验证码的效果如下:

Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第7张图片

   当然我们还可以添加多种效果,这里就不做添加了,就实现简单的验证码功能即可。

7,附带的代码

  models.py

class UserTest(models.Model):
    '''测试验证码,注册的用户表'''
    name = models.CharField(max_length=128)
    password = models.CharField(max_length=256)

    def __str__(self):
        return self.name

  views.py

def login_test(request):
    if request.method == 'POST':
        login_form = UserTestForm(request.POST)
        if login_form.is_valid():
            username = login_form.cleaned_data['username']
            password = login_form.cleaned_data['password']
            try:
                user = UserTest.objects.get(name=username)
                if user.password == password:
                    return HttpResponse("OK")
                else:
                    return HttpResponse("NG")
            except:
                return HttpResponse("NG")
        return render(request, 'login_test.html', locals())
    login_form = UserTestForm()
    return render(request, 'login_test.html', locals())

  

 

 

验证码功能实现方法3——极验验证SDK

Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法_第8张图片

一,Django极验滑动验证码的使用

1,官网直接注册账号申请key和value

  极验滑动验证码官网:www.geetest.com

2,从Github: gt3-python-sdk下载.zip文件

3,找到里面Django 的文件夹对照官网直接copy

4,代码

  views.py

from geetest import GeetestLib
from django.contrib import auth
from django.http import JsonResponse
from django.shortcuts import render
def login(request):
    if request.method == "POST":
        # 初始化一个给AJAX返回的数据
        ret = {"status": 0, "msg": ""}
        # 从提交过来的数据中 取到用户名和密码
        username = request.POST.get("username")
        pwd = request.POST.get("password")
        # 获取极验 滑动验证码相关的参数
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.POST.get(gt.FN_CHALLENGE, '')
        validate = request.POST.get(gt.FN_VALIDATE, '')
        seccode = request.POST.get(gt.FN_SECCODE, '')
        status = request.session[gt.GT_STATUS_SESSION_KEY]
        user_id = request.session["user_id"]

        if status:
            result = gt.success_validate(challenge, validate, seccode, user_id)
        else:
            result = gt.failback_validate(challenge, validate, seccode)
        if result:
            # 验证码正确
            # 利用auth模块做用户名和密码的校验
            user = auth.authenticate(username=username, password=pwd)
            if user:
                # 用户名密码正确
                # 给用户做登录
                auth.login(request, user)  # 将登录用户赋值给 request.user
                ret["msg"] = "/index/"
            else:
                # 用户名密码错误
                ret["status"] = 1
                ret["msg"] = "用户名或密码错误!"
        else:
            ret["status"] = 1
            ret["msg"] = "验证码错误"

        return JsonResponse(ret)
    return render(request, "login.html")
# 请在官网申请ID使用,示例ID不可使用
pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"


# 处理极验 获取验证码的视图
def get_geetest(request):
    user_id = 'test'
    gt = GeetestLib(pc_geetest_id, pc_geetest_key)
    status = gt.pre_process(user_id)
    request.session[gt.GT_STATUS_SESSION_KEY] = status
    request.session["user_id"] = user_id
    response_str = gt.get_response_str()
    return HttpResponse(response_str)

  urls.py

from django.conf.urls import url
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
  url(r'^login/', views.login),
    # 极验滑动验证码 获取验证码的url
    url(r'^pc-geetest/register', views.get_geetest),
]

  login.html




    
    欢迎登录
    
    



  

知识储备

一:pillow模块的学习

  PIL:Python Imaging Library,已经是Python平台上的图像处理标准库了。由于PIL仅支持到Python2.7 ,加上年久失修,于是一群志愿者在PIL的基础上创建了兼容的版本,名字叫Pillow,支持最新版本的Python3.X,又加了许多新特性。因此,我们可以直接安装使用Pillow。

(此处pillow的模块的学习摘抄与网友灬魑魅魍魉灬,如有侵权,请联系我,立删)

1,PIL的基本概念

  PIL中所涉及的基本概念有如下几个:通道(bands)、模式(mode)、尺寸(size)、坐标系统(coordinate system)、调色板(palette)、信息(info)和滤波器(filters)。

1.1  通道

  每张图片都是由一个或者多个数据通道构成。PIL允许在单张图片中合成相同维数和深度的多个通道。

  以RGB图像为例,每张图片都是由三个数据通道构成,分别为R、G和B通道。而对于灰度图像,则只有一个通道。

  对于一张图片的通道数量和名称,可以通过方法getbands()来获取。方法getbands()是Image模块的方法,它会返回一个字符串元组(tuple)。该元组将包括每一个通道的名称。

  Python的元组与列表类似,不同之处在于元组的元素不能修改,元组使用小括号,列表使用方括号,元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。

  方法getbands()的使用如下:

from PIL import Image
im = Image.open("test.png")
print(im.getbands())
输出:
('R', 'G', 'B')

 

1.2  模式

  图像的模式定义了图像的类型和像素的位宽。当前支持如下模式:

1:1位像素,表示黑和白,但是存储的时候每个像素存储为8bit。

L:8位像素,表示黑和白。

P:8位像素,使用调色板映射到其他模式。

RGB:3x8位像素,为真彩色。

RGBA:4x8位像素,有透明通道的真彩色。

CMYK:4x8位像素,颜色分离。

YCbCr:3x8位像素,彩色视频格式。

I:32位整型像素。

F:32位浮点型像素。

PIL也支持一些特殊的模式,包括RGBX(有padding的真彩色)和RGBa(有自左乘alpha的真彩色)。

  可以通过mode属性读取图像的模式。其返回值是包括上述模式的字符串。

  属性mode的使用如下:

from PIL import Image
im = Image.open("test.png")
print(im.mode)
输出:
'RGB'

  

1.3  尺寸

  通过size属性可以获取图片的尺寸。这是一个二元组,包含水平和垂直方向上的像素数。

  属性mode的使用如下:

from PIL import Image
im = Image.open("test.png")
print(im.size)
输出:
(670, 502)

  

1.4  坐标系统

  PIL使用笛卡尔像素坐标系统,坐标(0,0)位于左上角。注意:坐标值表示像素的角;位于坐标(0,0)处的像素的中心实际上位于(0.5,0.5)。

  坐标经常用于二元组(x,y)。长方形则表示为四元组,前面是左上角坐标。例如,一个覆盖800x600的像素图像的长方形表示为(0,0,800,600)。

1.5  调色板

  调色板模式 ("P")使用一个颜色调色板为每个像素定义具体的颜色值

1.6  信息

  使用info属性可以为一张图片添加一些辅助信息。这个是字典对象。加载和保存图像文件时,多少信息需要处理取决于文件格式。

属性info的使用如下:

from PIL import Image
im = Image.open("test.jpg")
print(im.info)
输出:
{'jfif': 257, 'jfif_version': (1, 1), 'jfif_unit': 0, 'jfif_density': (1, 1)}

  

1.7  滤波器

  对于将多个输入像素映射为一个输出像素的几何操作,PIL提供了四个不同的采样滤波器。

NEAREST:最近滤波。从输入图像中选取最近的像素作为输出像素。它忽略了所有其他的像素。

BILINEAR:双线性滤波。在输入图像的2x2矩阵上进行线性插值。
注意:PIL的当前版本,做下采样时该滤波器使用了固定输入模板。

BICUBIC:双立方滤波。在输入图像的4x4矩阵上进行立方插值。
注意:PIL的当前版本,做下采样时该滤波器使用了固定输入模板。

ANTIALIAS:平滑滤波。这是PIL 1.1.3版本中新的滤波器。对所有可以影响输出像素
的输入像素进行高质量的重采样滤波,以计算输出像素值。在当前的PIL版本中,这个滤
波器只用于改变尺寸和缩略图方法。
注意:在当前的PIL版本中,ANTIALIAS滤波器是下采样(例如,将一个大的图像转换为
小图)时唯一正确的滤波器。BILIEAR和BICUBIC滤波器使用固定的输入模板,用于固
定比例的几何变换和上采样是最好的。

  Image模块中的方法 resize() 和 thumbnail()用到了滤波器。

  方法resize() 的使用如下:

方法resize()的定义为:resize(size, filter=None)=> image
from PIL import Image
im = Image.open("test.png")
print(im.size)
im_resize = im.resize((256,256))
print(im_resize.size)
输出:
(670, 502)
(256,256)

  对参数filter不赋值的话,方法resize()默认使用NEAREST滤波器。如果要使用其他滤波器可以通过下面的方法来实现:

from PIL import Image
im = Image.open("test.png")
print(im.size)
im_resize0 = im.resize((256,256), Image.BILINEAR)
print(im_resize0.size)
im_resize1 = im.resize((256,256), Image.BICUBIC)
print(im_resize1.size)
im_resize2 = im.resize((256,256), Image.ANTIALIAS)
print(im_resize2.size)

输出:
(670, 502)
(256,256)
(256,256)
(256,256)

  

2,Image模块

  Image模块是PIL中最重要的模块,它有一个类叫做image,与模块名称相同。Image类有很多函数,方法及属性,接下来将依次对image类的属性,函数和方法进行介绍。

2.1  Image类的属性

  1,Format

定义:im.format ⇒ string or None
含义:源文件的文件格式。如果是由PIL创建的图像,则其文件格式为None。
例子:
from PIL import Image
im= Image.open("xiao.png")
print(im.format)
输出:
'png'

  2,Mode

定义:im.mode ⇒ string

含义:图像的模式。这个字符串表明图像所使用像素格式。该属性典型的取值为“1”,“L”,“RGB”或“CMYK”

  3,Size

定义:im.size ⇒ (width, height)

含义:图像的尺寸,按照像素数计算。它的返回值为宽度和高度的二元组(width, height)。

  4,Palette

定义:im.palette ⇒ palette or None

含义:颜色调色板表格。如果图像的模式是“P”,则返回ImagePalette类的实例;否则,将为None。

例子:
from PIL import Image
im = Image.open("test.jpg")
print(im.mode)
print(im.palette)
输出: 
RGB
None

  5,Info

定义:im.info ⇒ dictionary

含义:存储图像相关数据的字典。文件句柄使用该字典传递从文件中读取的各种非图像信息。
大多数方法在返回新的图像时都会忽略这个字典;因为字典中的键并非标准化的,对于一个
方法,它不能知道自己的操作如何影响这个字典。

     如果用户需要这些信息,需要在方法open()返回时保存这个字典。

  

2.2  类的函数

  1,new

定义:Image.new(mode,size) ⇒ image
     Image.new(mode, size, color) ⇒ image

含义:使用给定的变量mode和size生成新的图像。Size是给定的宽/高二元组,这是按照像素
数来计算的。对于单通道图像,变量color只给定一个值;对于多通道图像,变量color给定一
个元组(每个通道对应一个值)。

     在版本1.1.4及其之后,用户也可以用颜色的名称,比如给变量color赋值为“red”。如果
没有对变量color赋值,图像内容将会被全部赋值为0(图像即为黑色)。如果变量color是空,
图像将不会被初始化,即图像的内容全为0。
     这对向该图像复制或绘制某些内容是有用的。

例子:

from PIL import Image
im= Image.new("RGB", (128, 128), "#FF0000")
im.save("test1.png")   
#图像im为128x128大小的红色图像。
im= Image.new("RGB", (128, 128))   
#图像im为128x128大小的黑色图像,因为变量color不赋值的话,图像内容被设置为0,即黑色。
im.save("test2.png")
im= Image.new("RGB", (128, 128), "red")   
#图像im为128x128大小的红色图像。
im.save("test3.png")

  2,Open

定义:Image.open(file) ⇒ image
     Image.open(file, mode) ⇒ image

含义:打开并确认给定的图像文件。这个是一个懒操作;该函数只会读文件头,而真实的
图像数据直到试图处理该数据才会从文件读取(调用load()方法将强行加载图像数据)。
如果变量mode被设置,那必须是“r”。
     用户可以使用一个字符串(表示文件名称的字符串)或者文件对象作为变量file的值。
文件对象必须实现read(),seek()和tell()方法,并且以二进制模式打开。

例子:
from PIL import Image
im = Image.open("xiao.png")

  3,Blend

定义:Image.blend(image1,image2, alpha) ⇒ image

含义:使用给定的两张图像及透明度变量alpha,插值出一张新的图像。这两张图像必须
有一样的尺寸和模式。

     合成公式为:out = image1 *(1.0 - alpha) + image2 * alpha

     如果变量alpha为0.0,将返回第一张图像的拷贝。如果变量alpha为1.0,将返回第二
张图像的拷贝。对变量alpha的值没有限制。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im2 = Image.open("wu.jpg")
im = Image.blend(im1,im2,0.5)
im.save("he.jpg")

  4,Composite

定义:Image.composite(image1,image2, mask) ⇒ image

含义:使用给定的两张图像及mask图像作为透明度,插值出一张新的图像。变量mask
图像的模式可以为“1”,“L”或者“RGBA”。所有图像必须有相同的尺寸。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im2 = Image.open("wu.jpg")
r,g,b = im1.split()
print(g.mode)
im = Image.composite(im1,im2,b)
im.save("he.jpg")
b.save("he1.jpg")

  5,Eval

定义:Image.eval(image,function) ⇒ image

含义:使用变量function对应的函数(该函数应该有一个参数)处理变量image所
代表图像中的每一个像素点。如果变量image所代表图像有多个通道,那变量function
对应的函数作用于每一个通道。

     注意:变量function对每个像素只处理一次,所以不能使用随机组件和其他生成器。
例子:

from PIL import Image
im = Image.open("jing.jpg")
def deffun(c):
    return c*0.89      #改变了亮度
im_eval = Image.eval(im,deffun)  
im_eval.save("gai.jpg")

注:图像im_eval与im01比较,其像素值均为im01的一半,则其亮度自然也会比im01暗一些。

  6,Formbuffer

定义:Image.frombuffer(mode,size, data) ⇒ image

     Image.frombuffer(mode, size,data, decoder, parameters) ⇒ image

含义:(New in PIL 1.1.4)使用标准的“raw”解码器,从字符串或者buffer对象中
的像素数据产生一个图像存储。对于一些模式,这个图像存储与原始的buffer(这意
味着对原始buffer对象的改变体现在图像本身)共享内存。

      并非所有的模式都可以共享内存;支持的模式有“L”,“RGBX”,“RGBA”和“CMYK”。
对于其他模式,这个函数与fromstring()函数一致。

注意:版本1.1.6及其以下,这个函数的默认情况与函数fromstring()不同。这有可能在将
来的版本中改变,所以为了最大的可移植性,当使用“raw”解码器时,推荐用户写出所有的
参数,如下所示:

im =Image.frombuffer(mode, size, data, "raw", mode, 0, 1)
函数Image.frombuffer(mode,size, data, decoder, parameters)与函数fromstring()的调用一致。

  7,Formstring

定义:Image.fromstring(mode,size, data) ⇒ image

     Image.fromstring(mode, size,data, decoder, parameters) ⇒ image

含义:函数Image.fromstring(mode,size, data),使用标准的“raw”解码器,从字符串
中的像素数据产生一个图像存储。

函数Image.fromstring(mode,size, data, decoder, parameters)也一样,但是允许用
户使用PIL支持的任何像素解码器。更多信息可以参考:Writing YourOwn File Decoder.

注意:这个函数只对像素数据进行解码,而不是整个图像。如果用户的字符串包含整个图像,
可以将该字符串包裹在StringIO对象中,使用函数open()来加载。

  8,Merge

定义:Image.merge(mode,bands) ⇒ image

含义:使用一些单通道图像,创建一个新的图像。变量bands为一个图像的元组或者
列表,每个通道的模式由变量mode描述。所有通道必须有相同的尺寸。

变量mode与变量bands的关系:
len(ImageMode.getmode(mode).bands)= len(bands)

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im2 = Image.open("wu.jpg")
r1,g1,b1 = im1.split()
r2,g2,b2 = im2.split()
imgs =[r1,g2,b2]
im_merge = Image.merge("RGB",imgs)
im_merge.save("he.jpg")

  

 3,Image类的方法

  除非另做说明,Image类的所有方法都将返回一个Image类的新实例,这个实例对应于结果图像。

  1,Convert

定义1:im.convert(mode)⇒ image

含义1:将当前图像转换为其他模式,并且返回新的图像。
当从一个调色板图像转换时,这个方法通过这个调色板来转换像素。如果不对变量
mode赋值,该方法将会选择一种模式,在没有调色板的情况下,使得图像和调色
板中的所有信息都可以被表示出来。

当从一个颜色图像转换为黑白图像时,PIL库使用ITU-R601-2 luma转换公式:
L = R * 299/1000 + G * 587/1000 + B * 114/1000
当转换为2位图像(模式“1”)时,源图像首先被转换为黑白图像。结果数据中大于
127的值被设置为白色,其他的设置为黑色;这样图像会出现抖动。如果要使用其
他阈值,更改阈值127,可以使用方法point()。
为了去掉图像抖动现象,可以使用dither选项。
 
例子1:
from PIL import Image
im1 = Image.open("jing.jpg")
print(im1.mode)
im_c = im1.convert("1")
im_c.save("he.jpg")
print(im_c.mode)
输出:
注:将“RGB”模式的im01图像,转换为“1”模式的im_c图像。

定义2:im.convert(“P”,**options) ⇒ image
含义2:这个与第一个方法定义一样,但是当“RGB”图像转换为8位调色板图像时能
更好的处理。可供选择的选项为:
      Dither=. 控制颜色抖动。默认是FLOYDSTEINBERG,与邻近的像素一起承
担错误。不使能该功能,则赋值为NONE。
      Palette=. 控制调色板的产生。默认是WEB,这是标准的216色的“web palette”。
要使用优化的调色板,则赋值为ADAPTIVE。
      Colors=. 当选项palette为ADAPTIVE时,控制用于调色板的颜色数目。默认是
最大值,即256种颜色。
 
定义3:im.convert(mode,matrix) ⇒ image
含义3:使用转换矩阵将一个“RGB”图像转换为“L”或者“RGB”图像。变量matrix为4或者16元组。
例子3:下面的例子将一个RGB图像(根据ITU-R709线性校准,使用D65亮度)转换到CIE XYZ颜色空间:
from PIL import Image
im1 = Image.open("jing.jpg")
im1.mode
rgb2xyz = (
    0.412453, 0.357580, 0.180423, 0,
    0.212671, 0.715160, 0.072169, 0,
    0.019334, 0.119193, 0.950227, 0 )
im_c3 = im1.convert("L", rgb2xyz)
im_c3.save("he.jpg")
print(im_c3.mode)
输出:
L

  2,Copy

定义:im.copy() ⇒ image
含义:拷贝这个图像。如果用户想粘贴一些数据到这张图,可以使用这个方法,
但是原始图像不会受到影响。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im2 = im1.copy()
im2.save("he.jpg")
注:图像im_copy和im01完全一样。

  3,Crop

定义:im.crop(box) ⇒ image
含义:从当前的图像中返回一个矩形区域的拷贝。变量box是一个四元组,定
义了左、上、右和下的像素坐标。
这是一个懒操作。对源图像的改变可能或者可能不体现在裁减下来的图像中。
为了获取一个分离的拷贝,对裁剪的拷贝调用方法load()。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
print(im1.size)
box = [0,0,650,400]   #650(长)400(高)
im_crop = im1.crop(box)
im_crop.save("he.jpg")

  4,Draft

定义:im.draft(mode,size)
含义:配置图像文件加载器,使得返回一个与给定的模式和尺寸尽可能匹配的
图像的版本。例如,用户可以使用这个方法,在加载一个彩色JPEG图像时将其
转换为灰色图像,或者从一个PCD文件中提取一个128x192的版本。
注意:这个方法会适时地修改图像对象(精确地说,它会重新配置文件的读取器)。
如果图像已经被加载,那这个方法就没有作用了。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im_draft = im1.draft("L",(500,500))
print(im_draft.size)
im_draft.save("he.jpg")
输出:
(650, 650)

  5,Filter

定义:im.filter(filter) ⇒ image
含义:返回一个使用给定滤波器处理过的图像的拷贝。可用滤波器需要参考ImageFilter模块。

例子:
from PIL import Image,ImageFilter
im1 = Image.open("jing.jpg")
im_filter = im1.filter(ImageFilter.BLUR)
im_filter.save("he.jpg")
注:图像im_filter比im01变得有些模糊了。

  6,Fromstring

定义:im.fromstring(data)
     im.fromstring(data, decoder,parameters)

含义:与函数fromstring()一样,但是这个方法会将data加载到当前的图像中

  7,Getbands

定义:im.getbands()⇒ tuple of strings

含义:返回包括每个通道名称的元组。例如,对于RGB图像将返回(“R”,“G”,“B”)。

  8,Getbbox

定义:im.getbbox() ⇒ 4-tuple or None
含义:计算图像非零区域的包围盒。这个包围盒是一个4元组,定义了左、上、右
和下像素坐标。如果图像是空的,这个方法将返回空。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
print(im1.getbbox())
输出:
(0, 0, 650, 650)

  9,Getcolors

定义:im.getcolors() ⇒ a list of(count, color) tuples or None
     im.getcolors(maxcolors) ⇒ a list of (count, color) tuples or None
含义:(New in 1.1.5)返回一个(count,color)元组的无序list,其中count是
对应颜色在图像中出现的次数。
如果变量maxcolors的值被超过,该方法将停止计算并返回空。变量maxcolors默认
值为256。为了保证用户可以获取图像中的所有颜色,you can pass in size[0]*size[1]
(请确保有足够的内存做这件事)。

例子:
from PIL import Image
im1 = Image.open("test.png")
print(im1.getcolors(8888888))
输出:
[(2, (255, 255, 255, 233)), (9, (0, 0, 0, 136)), (1, (0, 0, 0, 64)), (2, (0, 0, 0, 24)),
 (5, (0, 0, 0, 56)).......

  10,Getdata

定义:im.getdata() ⇒ sequence
含义:以包含像素值的sequence对象形式返回图像的内容。这个sequence对象是
扁平的,以便第一行的值直接跟在第零行的值后面,等等。
注意:这个方法返回的sequence对象是PIL内部数据类型,它只支持某些sequence操
作,包括迭代和基础sequence访问。使用list(im.getdata()),将它转换为普通的sequence。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
seq = im1.getdata()
print(seq[0])
seq0 = list(seq)
print(seq0[0])
print(len(seq0))
输出:
(41, 183, 197)
(41, 183, 197)
       #这个值是长和高之积

注:Sequence对象的每一个元素对应一个像素点的R、G和B三个值。

  11,Getextrema

定义:im.getextrema() ⇒ 2-tuple
含义:返回一个2元组,包括该图像中的最小和最大值。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
print(im1.getextrema())
输出:
((0, 255), (0,255), (0, 255)) 
该方法返回了R/G/B三个通道的最小和最大值的2元组。

  12,Getpixel

定义:im.getpixel(xy) ⇒ value or tuple
含义:返回给定位置的像素值。如果图像为多通道,则返回一个元组。
注意:该方法执行比较慢;如果用户需要使用python处理图像中较大部分数据,
可以使用像素访问对象(见load),或者方法getdata()。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
print(im1.getpixel((1,1)))
print(im1.getpixel((649,649)))
输出:
(41, 183, 197)
(236, 210, 153)
注:im.getpixel(xy)中的X,Y表示坐标,从0开始。

  13,histogram

定义1:im.histogram()⇒ list
含义1:返回一个图像的直方图。这个直方图是关于像素数量的list,图像中的每
个象素值对应一个成员。如果图像有多个通道,所有通道的直方图会连接起来(
例如,“RGB”图像的直方图有768个值)。
      二值图像(模式为“1”)当作灰度图像(模式为“L”)处理。

例子1:
from PIL import Image
im1 = Image.open("jing.jpg")
ls = im1.histogram()
print(len(ls))
print(ls[767])
输出:
1471

  14,load

定义:im.load()
含义:为图像分配内存并从文件中加载它(或者从源图像,对于懒操作)。
正常情况下,用户不需要调用这个方法,因为在第一次访问图像时,Image
类会自动地加载打开的图像。

     (New in 1.1.6)在1.1.6及以后的版本,方法load()返回一个用于读
取和修改像素的像素访问对象。这个访问对象像一个二维队列,如:
      pix = im.load()
      print pix[x, y]
      pix[x, y] =value
      通过这个对象访问比方法getpixel()和putpixel()快很多。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
lm_load = im1.load()
print(lm_load[649,649])
输出:
(236, 210, 153)

  15,Paste

定义1:im.paste(image,box)
含义1:将一张图粘贴到另一张图像上。变量box或者是一个给定左上角的2元组,
或者是定义了左,上,右和下像素坐标的4元组,或者为空(与(0,0)一样)。
如果给定4元组,被粘贴的图像的尺寸必须与区域尺寸一样。
如果模式不匹配,被粘贴的图像将被转换为当前图像的模式。

例子1:
from PIL import Image
im1 = Image.open("jing.jpg")
box = [0,0,200,200]
im_crop = im1.crop(box)
im1.paste(im_crop,(200,200,400,400))  #等价于im1.paste(im_crop,(200,200))
im1.save("he.jpg")

定义2:im.paste(colour,box)
含义2:它与定义1一样,但是它使用同一种颜色填充变量box对应的区域。对于单通
道图像,变量colour为单个颜色值;对于多通道,则为一个元组。

例子2:
from PIL import Image
im1 = Image.open("jing.jpg")
im1.paste((256,256,256),(200,100,500,200))
im1.save("he.jpg")
注:图像im1的(200,100)位置将出现一个300x100的白色方块,对于多通道的
图像,如果变量colour只给定一个数值,将只会应用于图像的第一个通道。如果是
“RGB”模式的图像,将应用于红色通道。

定义3:im.paste(image,box, mask)
含义3:与定义1一样,但是它使用变量mask对应的模板图像来填充所对应的区域。
可以使用模式为“1”、“L”或者“RGBA”的图像作为模板图像。模板图像的尺寸必须与
变量image对应的图像尺寸一致。
      如果变量mask对应图像的值为255,则模板图像的值直接被拷贝过来;如果变
量mask对应图像的值为0,则保持当前图像的原始值。变量mask对应图像的其他值,
将对两张图像的值进行透明融合。
注意:如果变量image对应的为“RGBA”图像,即粘贴的图像模式为“RGBA”,则
alpha通道被忽略。用户可以使用同样的图像作为原图像和模板图像。

例子3:
from PIL import Image
im1 = Image.open("jing.jpg")
box = [100,100,200,200]
im_crop = im1.crop(box)
r,g,b = im_crop.split()
im1.paste(im_crop,(200,100,300,200),b)
im1.save("he.jpg")
注:在图像im1的(0,0)位置将出现一个半透明的100x100的方块。

定义4:im.paste(colour,box, mask)
含义4:与定义3一样,只是使用变量colour对应的单色来填充区域。

例子4:
from PIL import Image
im1 = Image.open("jing.jpg")
box = [100,100,200,200]
im_crop = im1.crop(box)
r,g,b = im_crop.split()
im1.paste((0,256,0),(200,100,300,200),b)
im1.save("he.jpg")
注:在图像im1的(0,0)位置将出现一个100x100的绿色方块。

  

二:python中chr() 函数 和 ord() 函数的用法。

1,chr()函数

格式: Chr(<数值表达式>)

说明:chr() 用一个范围在 range(256)内的(就是0~255)整数做参数。函数返回值类型为String,其数值表达式值取值范围为0~255,返回一个对应的字符(对应的ASCII字符)。

例如:Print(Chr(78))  结果显示:N

 

65-90  大写A-Z

97-122 小写的a-z

2,ord() 函数

格式: ord("字符串")

说明:函数返回值类型为int

例如:Print(ord('0'))  结果显示:48

 

 

 

参考文献:https://www.cnblogs.com/chimeiwangliang/p/7130434.html

你可能感兴趣的:(Django学习笔记(17)——BBS+Blog项目开发(1)验证码功能的实现及PIL库的用法)