只需45秒,Python給故宮畫一組手繪圖!

AI科技大本營發表於2019-02-18

640?wx_fmt=jpeg


作者 丁彥軍

來源 | 戀習Python(ID:sldata2017

責編 | swallow


13日早晨,當北京市民拉開窗簾時發現,窗外雪花紛紛揚揚在空中飄落,而且越下越大,樹上、草地、屋頂、道路上,都落滿雪花。京城銀裝素裹,這是今冬以來北京迎來的第二場降雪。

一下雪,北京就變成了北平,故宮就變成了紫禁城。八萬張門票在雪花飄下來之前,便早已預訂一空。

640?wx_fmt=jpeg

(圖片來源:故宮官網 版權歸故宮官網所有)



看著朋友圈、微博好友都在紛紛曬圖,小編只能羨慕不已。


不過,突然想到,可以通過Python將故宮的建築物圖片,轉化為手繪圖(素描效果)。效果圖如下:


640?wx_fmt=jpeg


一、概念與原理


我們都知道手繪圖效果的特徵主要有:


  • 黑白灰色;邊界線條較重;相同或相近色彩趨於白色;略有光源效果


核心原理:利用畫素之間的梯度值和虛擬深度值對影像進行重構,根據灰度變化來模擬人類視覺的模擬程度


把影像看成二維離散函式,灰度梯度其實就是這個二維離散函式的求導,用差分代替微分,求取影像的灰度梯度。常用的一些灰度梯度模板有:Roberts 梯度、Sobel 梯度、Prewitt 梯度、Laplacian 梯度。


以Sobel 梯度計算來解釋:


首先計算出 640?wx_fmt=png640?wx_fmt=png,然後計算梯度角 640?wx_fmt=png梯度方向及影像灰度增大的方向,其中梯度方向的梯度夾角大於平坦區域的梯度夾角。如下圖所示,灰度值增加的方向梯度夾角大,此時梯度夾角大的方向為梯度方向。對應在影像中尋找某一點的梯度方向即通過計算該點與其8鄰域點的梯度角,梯度角最大即為梯度方向。


640?wx_fmt=png


二、影像的陣列形式與變換


640?wx_fmt=png


其中,需要用到的方法:


  • Image.open( ): 開啟圖片

  • np.array( ) : 將影像轉化為陣列

  • convert("L"): 將圖片轉換成二維灰度圖片

  • Image.fromarray( ): 將陣列還原成影像uint8格式


程式碼如下:


from PIL import Image
import numpy as np

im = Image.open(r"C:\Users\Administrator\Desktop\gugong\微信圖片_20190216152248.jpg").convert('L')
a=np.asarray(im).astype('float')
print(a.shape,a.dtype)
(1080608) 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
建立光源對各點梯度值的影響函式
運算出各點的新畫素值


640?wx_fmt=jpeg


其中:

np.cos(evc.el) : 單位光線在地平面上的投射長度

dx,dy,dz :光源對x,y,z三方向的影響程度


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(0255)
    im = Image.fromarray(b.astype('uint8'))  # 重構影像
    im.save(end)

def main():
    xs=10
    start_time = time.clock()
    startss = os.listdir(r"C:\Users\Administrator\Desktop\gugong")
    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張圖片


最終效果圖對比:


640?wx_fmt=jpeg


最後,你自己動手試試吧?通過此程式碼為自己畫一張手繪圖,也可以為自己的家鄉或母校畫。


參考資料:

http://www.icourse163.org/learn/BIT-1001870002?tid=1001963001#/learn/announce

程式碼連結:

https://pan.baidu.com/s/1E_aZTRQWOzGV-2GV_iH43w

提取碼:64z9

(本文為AI科技大本營轉載文章,轉載請聯絡原作者)

精彩推薦

640?wx_fmt=png

推薦閱讀:

                         640?wx_fmt=png

點選“閱讀原文”,開啟CSDN APP 閱讀更貼心。

相關文章