之前分享了将图片转换为字符画的文章,但是感觉那个脚本的不是很好玩。然后突发奇想,想到了之前的人物素描头像。于是使用PIL制作素描画的小脚本就出来了
在写代码前先介绍几个概念,理解了这几个概念才能更好的理解代码
1.图像深度值:
图像深度是指存储每个像素所用的位数,也用于量度图像的色彩分辨率。
2.图像梯度:
梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。
图像梯度可以把图像看成二维离散函数,
图像梯度其实就是这个二维离散函数的求导:
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;
图像边缘一般都是通过对图像进行梯度运算来实现的。
3.灰度:
灰度使用黑色调表示物体,即用黑色为基准色,不同的饱和度的黑色来显示图像。 每个灰度对象都具有从 0%(白色)到100%(黑色)的亮度值。
图像的手绘效果实现
1、手绘效果的几个特征:
- 黑白灰色
- 边界线条较重
- 相同或相近色彩趋于白色
- 略有光源效果
2、利用像素之间的梯度值和虚拟深度值对图像进行重构,根据灰度变化来模拟人类视觉的远近程度
3、考虑光源效果,根据灰度变化来模拟人类视觉的远近程度
- 设计一个位于图像斜上方的虚拟光源
- 光源相对于图像的俯视角为Elevation,方位角为Azimuth
- 建立光源对个点梯度值的影响函数
- 运算出各点的新像素值
![](https://img.haomeiwen.com/i14140970/01d385e9147ed880.png)
编写代码
代码:
from PIL import Image
import numpy as np
a = np.asarray(Image.open('ha.png').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('haHD.jpg')
效果对比:
![](https://img.haomeiwen.com/i14140970/695e5ba46d37bdd5.png)
![](https://img.haomeiwen.com/i14140970/2a2b4d351c7a4c21.jpg)
这样处理出来的效果不太好,感觉有点假,所以就另外找了一种实现方法
代码:
from PIL import Image
import os
def transform(imgName):
# 图像组成:红绿蓝 (RGB)三原色组成 亮度(255,255,255)
image = imgName
img = Image.open(image)
img_all = "素描" + image
new = Image.new("L", img.size, 255)
width, height = img.size
img = img.convert("L")
# 定义画笔的大小
Pen_size = 3
# 色差扩散器
Color_Diff = 6
for i in range(Pen_size + 1, width - Pen_size - 1):
for j in range(Pen_size + 1, height - Pen_size - 1):
# 原始的颜色
originalColor = 255
lcolor = sum([img.getpixel((i - r, j)) for r in range(Pen_size)]) // Pen_size
rcolor = sum([img.getpixel((i + r, j)) for r in range(Pen_size)]) // Pen_size
# 通道----颜料
if abs(lcolor - rcolor) > Color_Diff:
originalColor -= (255 - img.getpixel((i, j))) // 4
new.putpixel((i, j), originalColor)
ucolor = sum([img.getpixel((i, j - r)) for r in range(Pen_size)]) // Pen_size
dcolor = sum([img.getpixel((i, j + r)) for r in range(Pen_size)]) // Pen_size
# 通道----颜料
if abs(ucolor - dcolor) > Color_Diff:
originalColor -= (255 - img.getpixel((i, j))) // 4
new.putpixel((i, j), originalColor)
acolor = sum([img.getpixel((i - r, j - r)) for r in range(Pen_size)]) // Pen_size
bcolor = sum([img.getpixel((i + r, j + r)) for r in range(Pen_size)]) // Pen_size
# 通道----颜料
if abs(acolor - bcolor) > Color_Diff:
originalColor -= (255 - img.getpixel((i, j))) // 4
new.putpixel((i, j), originalColor)
qcolor = sum([img.getpixel((i + r, j - r)) for r in range(Pen_size)]) // Pen_size
wcolor = sum([img.getpixel((i - r, j + r)) for r in range(Pen_size)]) // Pen_size
# 通道----颜料
if abs(qcolor - wcolor) > Color_Diff:
originalColor -= (255 - img.getpixel((i, j))) // 4
new.putpixel((i, j), originalColor)
new.save(img_all)
# 打开转换后的图片
os.system(img_all)
if __name__ == '__main__':
imageName = "ha.png"
transform(imageName)
具体的实现原理不是很清楚。但是不影响使用,并且实现的效果也是杠杠的
![](https://img.haomeiwen.com/i14140970/4acffa802c05ef59.png)
网友评论