Pillow库快速入门

前言

导入所需库。

import numpy as np
from PIL import Image

通常我们把一张灰度图看作是一个二维数组。

灰度图:二维数组
数组中的每一个元素用来表示像素点的亮度值

对彩色的图片我们可以使用三维数组来表示。

彩色图:三个三维数组
数组中的第三维分别存储着像素点的红 绿 蓝分量

读取图片

使用Image.open来读取图片文件。

im=Image.open('example1.jpg')
# im.show()

将图片转换成数组

既然灰度图片是一个二维数组,彩色图片是三组三维数组,那么可以通过np.array将图片转换成为一个numpy数组。

im=np.array(im)

使用shape和dtype可以看到:

print(im.shape,im.dtype)
  • 这张图片一共有1520行720列;

  • 3表示它是一个彩色图片,有红 绿 蓝三个颜色分量;

  • unit8表示表示数据类型是8位整数,即一个字节。

      (1520, 720, 3) uint8
    

取出20 100位置的像素点的值。

print(im[20,100])
  • 103 101 112 分别表示分别表示红 绿 蓝三种颜色分量的数值。

    [103 101 112]
    

Image.fromarray的作用:实现array到image的转换1

im=np.array(im)
im=Image.fromarray(im)

灰度图

使用convert可以转换图像模式,L代表像素从 rgb 的三色3字节模式转换为单一灰度模式,即灰度图片。

im_01=im.convert("L")
im_01.save("img_01.jpg") 
im_01.show()

其中灰度色彩的取值为0-255,表示灰度色彩的变化。

再重新查看20 100处的数值,现在只有一个数值,即103灰度值。

im=np.array(im_01) 
print(im[20,100])
103

一张彩图通过如下公式将 rgb 三色的数值转换成灰度值。

Gray=R*0.3+G*0.59+B*0.11

我们可以通过下标来访问某个像素点的颜色。

  • 这个例子中输出像素点100 100的值为 rgb 三色的数值为 103 101 112。

    im=np.array(Image.open("example1.jpg"))
    print(im[100,100]) 
    
      [103 101 112]
    
  • 接下来使用切片命令单独取出r分量的值,现在就只输出r分量的值为103

    im=im[:,:,0]
    print(im[100,100])
    
      103
    

由于经历除法运算会得到浮点数的结果,然而将数组转为图像的过程中,数组中的数据是浮点型因而无法转为图像而报错:cannot write mode F as JPEG,因此需要改变image的类型。2

若还想用fromarray将numpy的二维数组转换成图像形式保存,那就需要astype的方式将图像的颜色分量转成在0-255的整数,这样以便于输出或者保存。

pil=Image.fromarray(im.astype('uint8'))
pil.show()

当然,我们也可发现,在由rgb数值转化为灰度值的过程中,使用灰度值计算公式会出现浮点数,然而最后输出的数值为整数103,这是因为灰度图默认是uint8整型,因而无需变换。

也可以使用np.uint()转换成整数。其中:

  • iml=255-im0是将图反转,也就是我们平时所看到的底片效果;
  • im2做了区间变化;
  • im3求了每个像素点的平方,数字变大了很多,图像变暗。
    im0=np.array(Image.open("fcity.jpg").convert('L')) 
    im1=255-im0
    im2=(100/255)*im0+150
    im3=255*(im1/255)**2
    pil_im=Image.fromarray(np.uint(iml))
    # pi1_im=Image.fromarray(np.uint(im2)) 
    # pil_im=Image.fromarray(np.uint(im3)) 
    pil_im.show()
    

降采样

如果想要实现对图片降采样,可以使用这样的带跨度的切片方式,取出部分像素点来实现,5表示每隔5个取一次值,图片会小很多。

im=np.array(Image.open("example1.jpg"))
im=im[::5,::5,:]
pil=Image.fromarray(im.astype('uint8'))
pil.show()

裁剪图片

如果想要裁剪图片,可以使用以下切片参数设置:

  • 第一维40-1000表示行,也就是图片的高;

  • 第二维300-900是列像素,也就是图片的宽。

    im=np.array(Image.open("girl.jpg"))
    im=im[40:1000,300:900]
    pil=Image.fromarray(im.astype('uint8')) 
    pil.show()
    

图片翻转

如果想要实现图片翻转,也可以使用如下的参数设定:

  • 第一维参数的最后一个值设置为-1为上下翻转;

  • 第二维参数的最后一个值设为-1为左右翻转;

  • 第三维参数的最后一个值设置为-1为颜色翻转,这里指的是rgb分量值逆序排列,可以通过print(im[20,100])与上述的值对比而发现。

    im=np.array(Image.open("example1.jpg"))
    
    im=im[:,::-1,:]#左右翻转
    
    # im=im[::-1,:,:]#上下翻转
    
    # im=im[:,:,::-1]#颜色翻转
    # print(im[20,100]) 
    
    pil=Image.fromarray(im.astype('uint8')) 
    pil.show()
    

重叠图片

首先,用刚才翻转图片的方式翻转一朵玫瑰花的图片,

im=np.array(Image.open("rose.jpg")) 
im=im[:,::-1,:]#左右翻转
#im=im[::-1,:]#上下翻转
pil=Image.fromarray(im.astype('uint8'))
pil.show()
pil.save("rose_turn.jpg")

然后使用这样的公式,按比例把两张图片混合在一起,得到一张混合的玫瑰花。

imBlend=iml*0.5+im2*0.5

  • 这里一定不要忘记使用astype来转换成整数,因为按公式计算后得到的数组是一个浮点数数组。

    iml=np.array(Image.open("rose.jpg"))
    im2=np.array(Image.open("rose_turn.jpg"))
    imBlend=iml*0.5+im2*0.5
    imBlend=Image.fromarray(imBlend.astype('uint8'))
    imBlend.show()
    

手绘图

下面我们来完成一个实现手绘效果的代码。

手绘风格,即黑白轮廓的描绘,首先需要读取原图图像的明暗变化,即灰度值。从直观视觉的感受上定义,图像灰度值显著变化的地方就是梯度,它描绘了图像灰度变化的强度。

手绘图像的基本思想就是利用像素之间的梯度值重构每一个像素值。

梯度:连续值之间的变化率,即斜率。

提取图像轮廓

通常可以使用梯度计算来提取图像轮廓。

我们使用以下公式来完成计算梯度:

一维数组:

存在俩侧值 :

  • 斜率=(右侧值-左侧值)/2

只存在一侧值 :

  • 斜率=(本身-左侧值) 或者 (右侧值-本身)

好消息是,numpy中提供了直接获取灰度图像梯度的函数gradient(),传入图像数组可返回代表x和y各自方向上梯度变换的二维元组。

np.gradient()函数计算图像梯度值作为新色彩计算的基础。

其中,a的值是玫瑰花的灰度值,

a=np.array(Image.open("rose.jpg").convert('L')) 

计算梯度后得到的grad将其分成x轴和y轴两个方向的矩阵。

grad=np.gradient(a) # 取图像灰度的梯度值
grad_x,grad_y=grad # 分别取横纵图像梯度值

深度权值depth

现在我们来看一下depth的作用。

给x和y方向的梯度值赋值权值depth,这种坐标空间变化相当于给物体加了一个虚拟的光源

根据灰度值大小,模拟各部分相对于人视角的远近程度,使画面显得更有“深度”,而这个深度就是depth。

因为在利用梯度重构图像时,0-255之间的不同的灰度值对应于不同的梯度取值,depth的作用在于调解这个对应关系。

本例中的depth设置为10,也可以尝试设置为0-100之间的其他值。

depth=10. # (0-100)

depth较小时,背景区域接近白色,画面显示轮廓描绘;
depth较大时,整体画面灰度值较深,近似于浮雕效果。

以下两句执行后的效果:grad_x和grad_y矩阵数值缩小了十倍,shape仍然是500*700。

grad_x=grad_x*depth/100
grad_y=grad_y*depth/100

光源的位置

为了体现光照的效果,我们设计一个光源,下面是光源的位置。

一个是俯视角度vec_el,一个是方位角度vec_az

vec_el=np.pi/2.2 # 光源的俯视角度,弧度值
vec_az=np.pi/4 # 光源的方位角度,弧度值

两个角度的设定和单位向量构成了基础的柱坐标系,它体现了物体相对于虚拟光源的位置。

我们需要建立光源对各个梯度值变化影响的函数dx dy,进而运算出新的像素值,它可以体现边界变化点灰度的变化,从而形成手绘效果。

dx=np.cos(vec_el)*np.cos(vec_az) # 光源对x轴的影响
dy=np.cos(vec_el)*np.sin(vec_az) # 光源对y轴的影响

具体来说,为了更好的体现立体感,需要增加一个z方向的梯度值。

dz=np.sin(vec_el) # 光源对z轴的影响

为了更直观的进行计算,可以把角度对应的柱坐标转换成为 x y z立体坐标系,而dx dy dz则是像素点在施加了模拟光源后在x y z方向上明暗变化的加权向量

A是梯度幅值,也是梯度大小

A=np.sqrt(grad_x**2+grad_y**2+1.)

各个方向上总梯度除以幅值,得到每个像素单元的梯度值,利用每个像素单元的梯度值和方向加权向量合成了灰度值

uni_x=grad_x/A
uni_y=grad_y/A
uni_z=1./A
a2=255*(dx*uni_x+dy*uni_y+dz*uni_z) # 光源归一化

clip函数用于防止溢出并归一化到0-255区间,

a2=a2.clip(0,255)

最后两行从数组中恢复图像并保存。

im2=Image.fromarray(a2.astype('uint8')) # 重构图像
im2.save('roseHandDrew.jpg')

效果对比

  • 原图
    在这里插入图片描述
  • 手绘图
    在这里插入图片描述

  1. Image.fromarray的用法(实现array到image的转换) ↩︎

  2. image.save存储图像时的报错:cannot write mode F as JPEG ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Corone

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值