实现图像的手绘效果
1. 图象数组表示
图像一般使用RGB色彩模式,即每个像素点的颜色由红®、绿(G)、蓝(B)组成。RGB三个颜色通道的变化和叠加得到各种颜色,其中:
• R 红色,取值范围,0‐255
• G 绿色,取值范围,0‐255
• B 蓝色,取值范围,0‐255
图像是一个由像素组成的二维矩阵,每个元素是一个(R,G,B)值。
图像深度值: 图像深度是指存储每个像素所用的位数,也用于量度图像的色彩分辨率。
图像梯度: 梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。
图像梯度可以把图像看成二维离散函数,图像梯度其实就是这个二维离散函数的求导:
图像梯度: G(x,y) = dx(i,j) + dy(i,j); dx(i,j) = I(i+1,j) - I(i,j); dy(i,j) = I(i,j+1) - I(i,j);
其中,I是图像像素的值(如:RGB值),(i,j)为像素的坐标。 图像梯度一般也可以用中值差分: dx(i,j) = [I(i+1,j) - I(i-1,j)]/2; dy(i,j) = [I(i,j+1) - I(i,j-1)]/2; 图像边缘一般都是通过对图像进行梯度运算来实现的。
灰度: 灰度使用黑色调表示物体,即用黑色为基准色,不同的饱和度的黑色来显示图像。 每个灰度对象都具有从 0%(白色)到100%(黑色)的亮度值。
图像的手绘效果实现
- 手绘效果的几个特征: • 黑白灰色 • 边界线条较重 • 相同或相近色彩趋于白色 • 略有光源效果
- 利用像素之间的梯度值和虚拟深度值对图像进行重构,根据灰度变化来模拟人类视觉的远近程度
- 考虑光源效果,根据灰度变化来模拟人类视觉的远近程度 • 设计一个位于图像斜上方的虚拟光源 • 光源相对于图像的俯视角为Elevation,方位角为Azimuth • 建立光源对个点梯度值的影响函数 • 运算出各点的新像素值
python中图像是一个由像素组成的三维矩阵(高,宽和RGB),每个元素是一个RGB值。
对图像变换的操作一般流程为:
- 读入图像文件
- 获得RGB值
- 对RGB值进行运算修改
- 另存为新图像。
手绘效果特征:色彩为黑白灰,边界线条重,相同或相近色彩趋于白色,略有光源效果
运算思路:
利用像素之间的梯度值和之间的虚拟深度值对图像进行重构,根据灰度变化来模拟人类视觉的明暗程度。
2. Pillow(PIL)库
PIL(Python Image Library)是python的第三方图像处理库,PIL历史悠久,原来是只支持python2.x的版本的,后来出现了移植到python3的库pillow,pillow 号称是friendly fork for PIL
,其功能和PIL差不多,但是支持python3。
PIL可以做很多和图像处理相关的事情:
- 图像归档(Image Archives)。PIL非常适合于图像归档以及图像的批处理任务。可以使用PIL创建缩略图,转换图像格式,打印图像等等。
- 图像展示(Image Display)。PIL较新的版本支持包括Tk PhotoImage,BitmapImage还有Windows DIB等接口。PIL支持众多的GUI框架接口,可以用于图像展示。
- 图像处理(Image Processing)。PIL包括了基础的图像处理函数,包括对点的处理,使用众多的卷积核(convolution kernels)做过滤(filter),还有颜色空间的转换。PIL库同样支持图像的大小转换,图像旋转,以及任意的仿射变换。PIL还有一些直方图的方法,允许展示图像的一些统计特性。这个可以用来实现图像的自动对比度增强,还有全局的统计分析等。
3. 图像的变换
convert()函数模式"L"
convert()函数
对于彩色图像,不管其图像格式是PNG,还是BMP,或者JPG,在PIL中,使用Image模块的open()函数打开后,返回的图像对象的模式都是“RGB”。而对于灰度图像,不管其图像格式是PNG,还是BMP,或者JPG,打开后,其模式为 “L”。
通过之前的博客对Image
模块的介绍,对于PNG、BMP和JPG彩色图像格式之间的互相转换都可以通过Image模块的open()
和save()函数来完成。具体说就是,在打开这些图像时,PIL会将它们解码为三通道的“RGB”图像。用户可以基于这个“RGB”图像,对其进行处理。处理完毕,使用函数save(),可以将处理结果保存成PNG、BMP和JPG中任何格式。这样也就完成了几种格式之间的转换。同理,其他格式的彩色图像也可以通过这种方式完成转换。当然,对于不同格式的灰度图像,也可通过类似途径完成,只是PIL解码后是模式为“L”的图像。
模式 "L"
模式“L”为灰色图像,它的每个像素用8个bit表示,0表示黑,255表示白,其他数字表示不同的灰度。在PIL中,从模式“RGB”转换为“L”模式是按照下面的公式转换的:
L = R * 299/1000 + G * 587/1000+ B * 114/1000
归一化处理:
其中np.asarray(Image.open('./beijing.jpg').convert('L')).astype('float')
意思是将图像以灰度图的方式打开并将数据转为 float 存入 np 中.
np.gradient(a)
是求a的梯度,返回的是二元信息,可分别赋值给grad_x,grad_y
,将梯度按照深度等级计算并且归一化处理.
我们还需建立光源效果
建立模型后可分析出np.cos(vec_el)
为单位光线在地平面上的投影长度,dx,dy,dz 是光源对 x/y/z 三方向的影响程度.
将梯度归一化
构造x和y轴梯度的三维归一化单位坐标系A = np.sqrt(grad_x**2 + grad_y**2 + 1)
梯度和光源相互作用,将梯度转化为灰度 b = 255*(dx*uni_x + dy*uni_y + dz*uni_z)
之前为了处理方便,我们梯度值调为0-1之间,梯度与光源相互作用后,还原为0-255,但仍然可能会有部分溢出,因此,为避免越界,将生成的灰度值剪裁至0-255区间b = b.clip(0,255)
4. 参数理解
-
depth参数:设置 x,y 梯度占总的梯度的比例,使梯度值缩小,因为我们把z向的梯度设成了1,如果不压缩x,y向梯度那么这个1就起不到什么作用
-
z 方向的梯度(即梯度归一化中的1):为了表示像素值与梯度的反比关系,当像素的 x,y 向梯度值都很小时,在归一化时uni_z值就会区于1从而得到趋于255的像素值
-
图像梯度归一化:主要为了得到 uni_z 的值
-
光源设置:设置光源的角度只是为了得到 dx,dy,dz 三个可调系数而已,个人认为跟光源一点关系都没有,反而不好理解
-
dx,dy,dz:uni_x,uni_y,uni_z的系数,更深入的说其实目的是把 dz 设的很大,dx,dy很小,完全可以不要dx,dy 把dz设为1,效果几乎一样
5. 源码实现
from PIL import Image
import numpy as np
import cv2
# 将原图像转换为灰度图像,并将其像素值放入列表并转存到数组中
# asarray() 转换输入为数组array
# convert() 将图像转换为灰色图像
# astype() 转换数据类型
a = np.asarray(Image.open("F:/Code-and-Notes/Python/Pure Python/Basic Grammar/Sketch Image/img/Annie1.jpg").convert('L')).astype('float')
depth = 10. # (0-100)
grad = np.gradient(a) # 取图像灰度的梯度值
grad_x, grad_y = grad # 分别取横纵图像梯度值
grad_x = grad_x * depth / 100.
grad_y = grad_y * depth / 100.
A = np.sqrt(grad_x ** 2 + grad_y ** 2 + 1.)
uni_x = grad_x / A
uni_y = grad_y / A
uni_z = 1. / A
vec_el = np.pi / 2.2 # 光源的俯视角度,弧度值
vec_az = np.pi / 4. # 光源的方位角度,弧度值
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 轴的影响
b = 255 * (dx * uni_x + dy * uni_y + dz * uni_z) # 光源归一化
b = b.clip(0, 255)
im = Image.fromarray(b.astype('uint8')) # 重构图像
im.save("F:/Code-and-Notes/Python/Pure Python/Basic Grammar/Sketch Image/img/Annie.jpg")
print("保存成功查看")
6. 效果预览
【原图】
【处理过后的素描图】
【文档参考】