python爬虫基础15

Python图像处理-Pillow

简介

PIL
PILPILPillowPillow

安装

Pillow
pip install Pillow
PillowPILPILPillowPIL
PILPillow
import PIL 
# 或者
from PIL import Image

使用手册

Image

Image
  1. 从文件加载图像

  2. 处理其他图像获得

  3. 创建一个新的图像

读取图像

一般来说,我们都是都过从文件加载图像来实例化这个类,如下所示:

from PIL import Image
picture = Image.open(‘happy.png’)
Pillow

新建图像

Pillownew()
#FF0000
picture = Image.new(‘RGB’, (200, 100), ‘red’)

保存图像

save()
picture.save(‘happy.png’)
Pillow

图像的坐标表示

在Pillow中,用的是图像的左上角为坐标的原点(0,0),所以这意味着,x轴的数值是从左到右增长的,y轴的数值是从上到下增长的。

Pillow
(左,顶,右,底)(左,顶)(右,底)
[左, 右)[顶, 底)

常用属性

PIL.Image.filenameopen()PIL.Image.formatPIL.Image.modePIL.Image.sizePIL.Image.widthPIL.Image.heightPIL.Image.info

常用方法

裁剪图片

Imagecrop()Image
croped_im = im.crop((100, 100, 200, 200))

复制与粘贴图像

copy()
copyed_im = im.copy()
paste()
croped_im = im.crop((100, 100, 200, 200))
im.paste(croped_im, (0, 0))
paste()

调整图像的大小

resize()
resized_im = im.resize((width, height))
resize()Image

或者使用thumbnail()方法

im = Image.open(‘test.jpg’)
#获得图像尺寸
w, h = im.size

缩放到50%

im.htumbnail((w//2, h//2))
#显示图片
im.show()

thumbnail()

旋转图像和翻转图像

rotate()Image
# 逆时针旋转90度
im.rotate(90)
im.rotate(180)
im.rotate(20, expand=True)
expand=True
transpose()
# 水平翻转
im.transpose(Image.FLIP_LEFT_RIGHT)

垂直翻转

im.transpose(Image.FLIP_TOP_BOTTOM)

获得图片通道名称

im.getbands()

通过通道分割图片

split()
split()
R, G, B = im.split()
split()
getchannel(channel)
getchannel()
R = im.getchannel(“R”)

模式转化

img = im.convert(“L”)

获取单个像素的值

getpixel
im.getpixel((100, 100))

传入的xy需要是一个元祖形式的坐标。

如果图片是多通道的,那么返回的是一个元祖。

加载图片全部数据

load()
pixdata = im.load()
pixdata[100,200] = 255
PIL.PyAccess

获取全部像素内容

getdata(band = None)Image
list(im.getdata())list
bandband = Noneband = 0band = 0band = 2B
from PIL import Image
im = Image.open(‘test.jpg’)
print(im.getdata()) #获取所有通道的值 类似生成器的对象
print(list(im.getdata(0))) #获取第一个通道的值, 转化为列表

####

关闭图片并释放内存

此方法会删除图片对象并释放内存

im.close()

图像类

这类验证码大多是数字、字母的组合,国内也有使用汉字的。在这个基础上增加噪点、干扰线、变形、重叠、不同字体颜色等方法来增加识别难度。

相应的,验证码识别大体可以分为下面几个步骤

  1. 灰度处理

  2. 增加对比度(可选)

  3. 二值化

  4. 降噪

  5. 倾斜校正分割字符

  6. 建立训练库

  7. 识别

0. 灰度化

像素点是最小的图像单元,一张图片由好多的像素点构成, 一个像素点的颜色是由RGB三个值来表现的,所以一个像素点矩阵对应三个颜色向量矩阵,我们对图像的处理就是对这个像素点矩阵的操作,想要改变某个像素点的颜色,只要在这个像素点矩阵中找到这个像素点的位置(x, y),因为一个像素点的颜色由红、绿、蓝三个颜色变量表示,所以我们通过给这三个变量赋值,来改变这个像素点的颜色.

图片的灰度化,就是让像素点矩阵中的每一个像素点都满足下面的关系:R=G=B,此时的这个值叫做灰度值.

灰度化的转化公式一般为:

R = G = B = 处理前的 R*0.3 + G*0.59 + B*0.11
img = img.convert(‘L’)  #转为灰度图

1. 二值化

二值化就是让图像的像素点矩阵中的每个像素点的灰度值为0(黑)或者255(白) ,从而实现二值化,让整个图像呈现只有黑和白的效果。

原理是利用设定的一个阈值来判断图像像素为0还是255,小于阈值的变为0(黑色), 大于的变为255(白色)。

这个临界灰度值就被称为阈值,阈值的设置很重要。阈值过大或过小都会对图片造成损坏。

选择阈值的原则是:既要尽可能保存图像信息,又要尽可能减少背景和噪声的干扰,

常用方法

  • 取阈值为127(0~255的中数,(0+255)/2=127 )

    好处是计算量小速度快,

    缺点也是很明显的 ,对于图片中内容色彩分布较大的图片,很容易造成内容的缺失。

  • 平均值法

    计算像素点矩阵中的所有像素点的灰度值的平均值avg

    (像素点1灰度值+…+像素点n灰度值)/ n = 像素点平均值avg

    这样做比方法1好一些。 但可能导致部分对象像素或者背景像素丢失。

def averageThreshold(img):
   pixdata = img.load()
   width,height = img.size
   
   threshold = sum(img.getdata())/(width*height)   #计算图片的平均阈值

   # 遍历所有像素,大于阈值的为白色
   for y in range(height):
       for x in range(width):
           if pixdata[x, y] < threshold:
               pixdata[x, y] = 0
           else:
               pixdata[x,y] = 255

   return img
  • 双峰法

    图像由前景和背景组成,在灰度直方图上,前后二景都形成高峰,在双峰之间的最低谷处就是图像的阈值所在。 当前后景的对比较为强烈时,分割效果较好;否则基本无效。

  • 迭代法

    首先选择一个近似阈值作为估计值的初始值,然后进行分割,产生子图像,并根据子图像的特性来选取新的阈值,再利用新的阈值分割图像,经过几次循环,使错误分割的图像像素点降到最少。这样做的效果好于用初始阈值直接分割图像的效果。

    1. 求出图象的最大灰度值和最小灰度值,分别记为Pmax和Pmin,令初始阈值T0=(Pmax+Pmin)/2

    2. 根据阈值TK将图象分割为前景和背景,(小于 T0 的像素部分,大于T0的背景部分),并分别求其均值 avgPix, avgBac

    3. 求出新阈值TK = ( avgPix+avgBac) / 2;

    4. 若T0=TK,则所得即为阈值;否则转2,迭代计算 。

from PIL import Image

def iterGetThreshold(img, pixdata, width, height):
   pixPrs = pixBac = []                     #用于统计前景和背景平均阈值
   threshold = 0
   pixel_min, pixel_max = img.getextrema()  # 获得图片中最大和最小灰度值
   newThreshold = int((pixel_min + pixel_max) / 2)  # 初始阈值

   while True:
       if abs(threshold -  newThreshold) < 5:   #差值小于5,退出
           break
       for y in range(height):
           for x in range(width):
               if pixdata[x, y] >= newThreshold:
                   pixBac.append(pixdata[x,y])    #大于阈值 为背景
               else:
                   pixPrs.append(pixdata[x,y])    #小于, 前景

       avgPrs = sum(pixPrs)/len(pixPrs)
       avgBac = sum(pixBac)/len(pixBac)
       threshold = newThreshold
       newThreshold = int((avgPrs+avgBac)/2)

   return newThreshold


def binary(img, threshold=None):
   img = img.convert(‘L’)  #转为灰度图
   pixdata = img.load()
   width, height = img.size

   if not threshold:
       threshold = iterGetThreshold(img, pixdata,width, height)
   # 遍历所有像素,大于阈值的为白色
   for y in range(height):
       for x in range(width):
           if pixdata[x, y] < threshold:
               pixdata[x, y] = 0
           else:
               pixdata[x,y] = 255

   return img

img = Image.open(‘test-1.jpg’)
img.show()
new_img = Binary(img)
new_img.show()

2. 降噪

从前面经过二值化处理,如果一个像素点是图片或者干扰因素的一部分,那么它的灰度值一定是0,即黑色; 如果一个点是背景,则其灰度值应该是255,白色。

因此对于孤立的噪点,其周围应该都是白色,或者大多数点都是白色pixel

如果图片分辨率够高,一个噪点实际上可能是有很多个点组成 ,所以此时的判断条件应该放宽,即一个点是黑色的并且相邻的8个点为白色点的个数大于一个固定值,那么这个点就是噪点 。

常见的4邻域、8邻域算法。所谓的X邻域算法,可以参考手机九宫格输入法,按键5为要判断的像素点,4邻域就是判断上下左右,8邻域就是判断周围8个像素点。如果这4或8个点中255的个数大于某个阈值则判断这个点为噪音,阈值可以根据实际情况修改。

这个方法对小噪点比较好,如果阀值设的比较大,很多验证码字符也会受到很大影响,因为验证码可能就是一些断断续续的点连出来的,阀值设太大,尽管噪点没了,验证码也会没了。

def depoint(img, N=2):
   pixdata = img.load()
   width, height = img.size
   for y in range(1, height - 1):
       for x in range(1, width - 1):
           count = 0
           if pixdata[x, y - 1] == 255:  # 上
               count = count + 1
           if pixdata[x, y + 1] == 255:  # 下
               count = count + 1
           if pixdata[x - 1, y] == 255:  # 左
               count = count + 1
           if pixdata[x + 1, y] == 255:  # 右
               count = count + 1

           # if pixdata[x-1, y-1] == 255: #左上
           #     count = count + 1
           # if pixdata[x+1, y-1] == 255: #右上
           #     count = count + 1
           # if pixdata[x-1, y+1] == 255: #左下
           #     count = count + 1
           # if pixdata[x+1, y+1] == 255: #右下
           #     count = count + 1

           if count > N:
               pixdata[x, y] = 255  #设置为白色
   return img

depoint(img).show()