文章目录
- 简介
- 图像的数组表示
- 图像的手绘处理
简介
在之前的学习笔记的实例中,我们曾经使用PIL库获取了图像的轮廓,虽然我们成功提取出来了,但是这个轮廓缺少了立体感,视觉效果上缺少了丰满度,光线照射的明暗变化是空间素描的基本方法,而本文将会使用一个实例为各位介绍python程序如何来提现图片的深浅层次变化,从而使得图像轮廓更富立体感、空间感和层次感,在观感上接近真人手绘素描的效果。
图像的数组表示
图像是有规则的二维数据,可以使用numpy库将图像转为数组对象,以著名歌手周杰伦的照片为例,名称为pic.jpg,放置在程序所在目录中,方法如下:
from PIL import Image
import numpy as np
im = np.array(Image.open('pic.jpg'))
print(im.shape, im.dtype)
图像转换对应的ndarray是三维数据,如上图所示,(630,630,3),前两维是图像的长度和宽度,单位是像素,第三位是每个像素点的RGB值,每个RGB值是一个单字节整数。
PIL库有着图像转换函数,可以改变图像的表示形式。使用convert()函数即可,这就是‘L’模式,表示将像素从RGB的三字节变为单一数值表示,此时数值范围为0~255,从彩色变为带有灰度的黑白色。转换后,之前三维的ndarray变为二维数据,每个像素点只有一个整数表示。
需要修改的是上例中的这条语句:
im = np.array(Image.open('pic.jpg').convert('L'))
运行后对比如下:
通过对图像的数组转换,可以访问图像上的任意一个像素值。例如,获取位于坐标(20,300)像素的颜色值,或者获取图像的最大最小像素值,也能够切片来获取指定行列的元素值,各位读者甚至能够在获取之后修改它。
from PIL import Image
import numpy as np
im = np.array(Image.open('pic.jpg').convert('L'))
print(im.shape, im.dtype)
print(im[20,300])
print(int(im.min()),int(im.max()))
读入图像后,可以通过任意数学操作获取相应的图像变换,这里以灰度变换为例子,分别对灰度变化后的图像进行反变换、区间变化以及像素值平方处理。不过需要注意,有些数学变换会改变图像的数据类型,所以在生成图像前需要通过numpy.uint()进行一次转换运行效果如下所示:
from PIL import Image
import numpy as np
im = np.array(Image.open('pic.jpg').convert('L'))
im1 = 255-im
im2 = (100/255)*im+150
im3 = 255*(im1/255)**2
pil_im = Image.fromarray(np.uint(im1))#修改参数后分别执行im1,im2,im3
pil_im.show()
灰度处理后的原图像如下所示:
图像的手绘处理
在此之前,我们介绍过获取图片轮廓滤镜的使用,虽然它能够获取图像轮廓信息,但是在视觉上缺乏立体感,那么有解决方案吗?
答案是有的,为了实现手绘风格,首先需要读取原图像的明暗变化,也就是灰度值,从直观视觉感受上来讲,图像灰度值发生显著变化的地方就是梯度值,它描述的是图像灰度变化的强度,通常能够使用梯度变化来获取图像轮廓,在numpy中提供有获取灰度图像梯度的函数gradient(),传入图像数组就能够表示返回代表x和y的各自方向上的梯度变化的二维元组。下面是实例完整代码和与轮廓获取的对比以及原图图像。
from PIL import Image
import numpy as np
vec_el = np.pi/2.2 # 光源的俯视角度,弧度值
vec_az = np.pi/4. # 光源的方位角度,弧度值
depth = 10. # (0-100)
im = Image.open('pic.jpg').convert('L')
a = np.asarray(im).astype('float')
grad = np.gradient(a) #取图像灰度的梯度值
grad_x, grad_y = grad #分别取横纵图像梯度值
grad_x = grad_x*depth/100.
grad_y = grad_y*depth/100.
dx = np.cos(vec_el)*np.cos(vec_az) #光源对x 轴的影响
dy = np.cos(vec_el)*np.sin(vec_az) #光源对y 轴的影响
dz = np.sin(vec_el) #光源对z 轴的影响
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) #光源归一化
a2 = a2.clip(0,255)
im2 = Image.fromarray(a2.astype('uint8')) #重构图像
im2.save('picHandDraw.jpg')
与轮廓获取的对比(左图手绘效果,右图轮廓获取):
原图: