作者 | 丁彦军
责编 | 屠敏
13 日早晨,当北京市民拉开窗帘时发现,窗外雪花纷纷扬扬在空中飘落,而且越下越大,树上、草地、屋顶、道路上,都落满雪花。京城银装素裹,这是今冬以来北京迎来的第二场降雪。
一下雪,北京就变成了北平,故宫就变成了紫禁城。八万张门票在雪花飘下来之前,便早已预订一空。
看着朋友圈、微博好友都在纷纷晒图,作为程序员的我只能坐在办公室里羡慕不已。
不过普通人得去故宫赏雪,程序员可以直接坐在家里动动手指便可以欣赏到故宫的美丽雪景手绘图了。如何操作呢?可通过 Python 将故宫的建筑物图片,转化为手绘图(素描效果)。效果图如下:
概念与原理
我们都知道手绘图效果的特征主要有:
黑白灰色
边界线条较重
相同或相近色彩趋于白色
略有光源效果
核心原理:利用像素之间的梯度值和虚拟深度值对图像进行重构,根据灰度变化来模拟人类视觉的模拟程度。
把图像看成二维离散函数,灰度梯度其实就是这个二维离散函数的求导,用差分代替微分,求取图像的灰度梯度。常用的一些灰度梯度模板有:Roberts 梯度、Sobel 梯度、Prewitt 梯度、Laplacian 梯度。
以 Sobel 梯度计算来解释:
首先计算出 、,然后计算梯度角
梯度方向及图像灰度增大的方向,其中梯度方向的梯度夹角大于平坦区域的梯度夹角。如下图所示,灰度值增加的方向梯度夹角大,此时梯度夹角大的方向为梯度方向。对应在图像中寻找某一点的梯度方向即通过计算该点与其 8 邻域点的梯度角,梯度角最大即为梯度方向。
图像的数组形式与变换
其中,需要用到的方法:
Image.open ( ) : 打开图片
np.array ( ) : 将图像转化为数组
convert ( "L" ) : 将图片转换成二维灰度图片
Image.fromarray ( ) : 将数组还原成图像 uint8 格式
代码如下:
from PIL import Image
import numpy as np
im = Image.open ( r"C:UsersAdministratorDesktopgugong 微信图片 _0216152248.jpg" ) .convert ( 'L' )
a=np.asarray ( im ) .astype ( 'float' )
print ( a.shape,a.dtype )
( 1080, 608 ) float64
# ( 1080, 608 ) 分别表示高度,宽度
图像的手绘效果处理
实现思路步骤:
1、梯度的重构
numpy 的梯度函数 np.gradient ( a ) : 计算数组 a 中元素的梯度,f 为多维时,返回每个维度的梯度。
离散梯度: xy 坐标轴连续三个 x 轴坐标对应的 y 轴值:a, b, c 其中 b 的梯度是(c-a)/2
而 c 的梯度是: ( c-b ) /1。
当为二维数组时,np.gradient ( a ) 得出两个数组,第一个数组对应最外层维度的梯度,第二个数组对应第二层维度的梯度。
grad=np.gradient ( a )
grad_x,grad_y=grad
grad_x = grad_x * depth / 100.# 对 grad_x 值进行归一化
grad_y = grad_y * depth / 100.# 对 grad_y 值进行归一化
2、构造 guan 光源效果
设计一个位于图像斜上方的虚拟光源,光源相对于图像的视角为 Elevation, 方位角为 Azimuth,建立光源对各点梯度值的影响函数,运算出各点的新像素值:
其中:
np.cos ( evc.el ) : 单位光线在地平面上的投射长度。
dx、dy、dz :光源对 xyz 三方向的影响程度。
3、梯度归一化
构造 x 和 y 轴梯度的三维归一化单位坐标系;
梯度与光源相互作用,将梯度转化为灰度。
4、图像生成
具体详情代码如下:
from PIL import Image
import numpy as np
import os
import join
import time
def image ( sta,end,depths=10 ) :
a = np.asarray ( Image.open ( sta ) .convert ( 'L' ) ) .astype ( 'float' )
depth = depths # 深度的取值范围 ( 0-100 ) ,标准取 10
grad = np.gradient ( a ) # 取图像灰度的梯度值
grad_x, grad_y = grad # 分别取横纵图像梯度值
grad_x = grad_x * depth / 100.# 对 grad_x 值进行归一化
grad_y = grad_y * depth / 100.# 对 grad_y 值进行归一化
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 ( end )
def main ( ) :
xs=10
start_time = time.clock ( )
startss = os.listdir ( r"C:UsersAdministratorDesktopgugong" )
time.sleep ( 2 )
for starts in startss:
start = ''.join ( starts )
sta = 'C:/Users/Administrator/Desktop/gugong/' + start
end = 'C:/Users/Administrator/Desktop/gugong/' + 'HD_' + start
image ( sta=sta,end=end,depths=xs )
end_time = time.clock ( )
print ( ' 程序运行了 ----' + str ( end_time - start_time ) + ' 秒 ' )
time.sleep ( 3 )
main ( )
程序运行了 ----43.01828205879955 秒 # 一共 35 张图片
最终效果图对比:
最后,你自己动手试试吧?通过此代码为自己画一张手绘图,也可以为自己的家乡或母校画。
作者:丁彦军,一名痴恋于 Python 的码农
公众号:恋习 Python,在这里我们一起用 Python 做些有意义的事。
参考资料:
/learn/BIT-1001870002?tid=1001963001#/learn/announce
代码链接:/s/1E_aZTRQWOzGV-2GV_iH43w
提取码:64z9
声明:本文为作者投稿,版权归其个人所有。
热 文 推 荐
print_r ( ' 点个好看吧!' ) ;
var_dump ( ' 点个好看吧!' ) ;
NSLog ( @" 点个好看吧!" ) ;
System.out.println ( " 点个好看吧!" ) ;
console.log ( " 点个好看吧!" ) ;
print ( " 点个好看吧!" ) ;
printf ( " 点个好看吧!n" ) ;
cout <
Console.WriteLine ( " 点个好看吧!" ) ;
fmt.Println ( " 点个好看吧!" ) ;
Response.Write ( " 点个好看吧!" ) ;
alert ( " 点个好看吧!" )
echo " 点个好看吧!"
点击 " 阅读原文 ",打开 CSDN App 阅读更贴心!
喜欢就点击 " 好看 " 吧!