写在前面
如何使用python给心爱的女朋友画一张素描?热心网友:程序员有女朋友?
莫慌,等我学python 一年、两年、三年、四年、五年...... 代码它B { { h . g ? T不香吗,陪你熬夜,给你创造对象。言归正传,会写代码的男人是最帅的,让我们开启python之旅吧。
- 环境配置
python版本: 3.6.0
编辑器: pycharm
项目所需要的环境安装包
ps: 每一步都有代码和排版截图,方便学习
pip install pillow
- 代码目录结构:
第一步:导入相关的x d p { ? Zpython包
# encoding:utf-8
impob ) r | 4 l J 3 .rt os
from PIL import Image, ImageFilter, ImageOps
import imghdr
使用python包的作用:
o- ] C p N | ss: 本项目只用到了对文件、文件夹的操作。
PIL: Python Imaging Library,是h . =Python平台的图像处理标准库。PIL功能非常强大,API也非常简$ ] u单易用。
img0 I Y # s z F ahdr: 是一个用来检测图片类型的模块,传递给它的可以是一个文件对象,也可以是一个字节流。
第二步:s . } J ~参数配置类
clash Z 8 %s CONF:
input_path = \"input_img\" # 待处理的图片
output_path = \"output_img7 a H {\" # 处理后的图片
pen_size = 3 # 定义画笔的大小 (素描画风格生效)
color, G + n [ C m {_diff[ C C 0 C 4 g = 6 # 色差扩散器 (素描画风# ^ v格生效)
blur = 25 # 模糊度 (水墨画风格生效)
alpha = 1.0 # 融合度 (水墨画风格生效)
picture_type = 0 # 0 表示素描风格, 1 表示水墨画风格
picture_name = [\"素描风格\", \1 / ,"水墨画风格\"]
is_log = True # 是否打印日志信息
这里是个人编程的习惯,我习惯把一些配置,例如:文件路径、模型存放路径、模型参数统一放在一个类中。当然,实际项目开发的时候,是用config 文本文件存放,不会直接写在代码里,这里为了演示方便O Y ! K,就写在一起,也方便运行。这块代码放在代码文件的开头也方便查看和修改。
第三步:4 x O m Y + 3类的初始化
class Sketk | 1chPic:
def __init__(self, input_pH c P 0ath, output_pathU @ I F +, pen_size, color_diff, blur, alpha, picture_type, picd Q ^ture_name, is_log):
self.input_path = input_path
selB / M X n rf.output_path = outpI Y u 0 (ut_path
self.pen_size = pen_size
self.coloW % M R sr_diff = color_diff
self.blur = bG x :lur
self.alpha = al. ( A G ? epha
se{ N . 8 q q Olf.piC P { # B Ncture_type = picture_type
self.picture_name = picture2 # c ) X @ ,_name
self.is_log = is_log
\E K ^ / ? M 9 ( 0"\"\" 初始化 \"\"\"
@classmethod
def in~ n 6 0 / V ; z XitialY t 5 ize(cls, config):
input_path = config.input_F U ~ Q N 9 Tpath
output_path = config.output_path
pen_sizO ? ! -e = config.peN { / { 7 e 6 En_size
color_diff = config.color_diff
blur = config.blur
alpha = config.alpha
picture_q z } A V z P : ^type = config.pict! { S ( ? [ 1 ; /ure_type
pictf q q *ure_name = config.picture_name
is_K z e _log = config.is[ v ; $ 1 f # +_log
return cls(i~ M knput_path, output_path, pen_size, color_J o O G Jdiff, blur, alpha, picture_type, picture_name, is_log
initialize() 函数和 __init__() 函数 是对象初始化和实例化,其中包括基本参数的赋值、最后返回用户一个` a S *对象。这里作为一个类的基本操作,是属于一个通用模板,在大多数项目中,都可以这么去写。为了养成良好的编程习惯,p - , T Z z { G A大家可以把这个模板记下来,后续直接套用,修改部分参数就可以了。
第四步: 类的主流程函数
\"\"\" 主流X d Z E ] P p b程 \"\"\"
def main_process(self, ):
if os^ i &.path.Y v D G o } 6exists(self.inv _ _ [ ) ~ Dput_path) and os.path.isdir(self.output_path):
self.__visit_dir_files(self.input_patF k k 0 {h, self.oX + ! N $ ?utput_path, self.input_path)
if selW J ? # ( @ Vf.is_log:
print(u\'完成!所有图片已保存至路径\' + self.output_path)
else:
print(u\'待处理的图片存放的位置 %s, 如果没有请新建目录 %s\' % (self.input_path, self.input_path))
print(u\'生成素描后的图片存放位置 %s, 如果没u ^ :有请新建目录 %s\' % (self.output_path, self.output_path))
\"\"\" 图片处理 \"\"\"
def __img_deal(sec 2 v # jlf, img_path, save_path):
# todo:...
try:
if self.picture_type == 0:
self.__draw_gray_img(img_path, save_path) # 素描风格
elif self.pi2 K b Qcture_type == 1:
self.__watv } W B i z Q : uer/ X S 8_draw(img_path, save_path) # 水墨画风格
else:
print(\"picture_type 值只能为 0 或 1\"[ ; n 0 @ _ z e D)
return
if self.is_log:
print(u\'图片[\' + img_pat& ( } 8 [ }h + u\']处理完毕\')
except :
priH I v [ ~ x b Jnt(u\'图片[\' + img_path + uV e b i\']出错\')
在写代码的时候,一定要抓住主线,就是代码运行的主流程。5 ; = m V P I g 0因为一个完整可靠的项目,它是有很多细枝末节考虑,很多步骤是要分模) e 8 A ; / P块来写。( b a 1主流程就是把主心干确定好,各个模块的入口确定好。这样开发的时候,思路会比较清晰,P f P # 8 F不会被细节吸引住。这里主心干只有个函数 __visit_dir_files() 的调用,/ s B U但是它的外Y p l u . 9 Y 1 C围都是一些边界条件的判定,不重要,但是j U /没有它们程序会出现BUG。
__img_deal() 函数是根据配置参数,调用指定绘画函数,起到一个转发器的作用。
第五步: 生成水墨画风格的函数
\"\"\"水墨画风格\"\h s ["\"
def __water_draw(selm D f S ]f, img_path, save_path):
input_img = Image.open(img_path)
gray_v _ e : G * % =image = input_img.convertN J a Z(\'L\') # 图片转换成灰色
gray_image_2 = gray_image.copy()
gray_image_2 = ImageOps.invert(gray_image_2)
width, height = grayY s ! g F L_image.size
for i in range(selfz p + & Z.blur): # 模糊度
gray_image_2 = gray_im2 V M 4 _ J p [ 2age_2.filter(ImageFilter.BLUR)
for x in range(width):
for y in range(height):
a = gray_image.getpixel((x, y))
b = gray_image_2.gel J - ^ @ *tpixel((x, y))
gray_z $ 9 ` Uimage.putpixel((x, yM y ? ? } M ` h j), min(int(g g :a * 255 / (25c | I 8 $ k d = B6 - b * self.alpha* X = # ( ^ * b)), 255))
gh H 3 % V Jray_image.save(save_path)
根据模糊度和融合度,对图片的各个像素点进行运行,最后生成水墨画风格的图片。
第六步: 生成素描画风格的函数
\": % f\"\y 3 x E w B d A"素描画风格\"\"\"
def __draw_gray_img(self,E 2 G d * img_path, save_path):
\"\"\"
素描画风格
:param in_img_name:
:param out_img_name:
:param pen_size: 定义画笔的大小
:param color_diff: 色差扩散器
:return:
\"W [ y D _ 9 i c\"\"
# 图x T g c T e Q S像组成:z ) s ! & ^ j 1红绿蓝 (RGB)三原色组成 亮度(255,255,255)
img = Image.open(img_2 o t 7 / 3 9 .path)
new = Image.new(\"L\", img.size,$ & F J g k t 255)
width, height = img.size
img = img.convert(\"L\")
# 逐个像素点绘制
for i in range(self.pen_size + 1, width - self.pen_size - 1):
for j in range(self.pen_size + 1, height - self.pen_size - 1):
original_color = 255 # 原始的颜色
# 逐个灰度计算
orb 3 A r C m J Miginal_color = self.__pix_color_pick(img, new, i, j, original_ 0 j h & A %_color, -1, 0, 1, 0)
original_color = self.__pix_color_pick(img, new, i, j, origit i Knal_color, 0,a 6 z N w : -1, 0, 1)
originaH j k H | E ]l_color = self.__pix_color_pick(img, new, i, j, original_color, -1, -1, 1, 1)
self.__pix_color_pick6 n . 1 N #(img, new, i, j, original_color, 1, -1, -1, 1)
new.save(save_path) # 保存图片
\"M m ] A Z\"\" 素描风格 每个像素点的灰度计算 \"\"\"
def __pix_color_pig ^ ` C ~ 7ck(self, img, new_img, i, j, original_color, a_ir, a_jr, b_ir, b_jr):
a_color = sum([img.getpix1 q L 3 oel((i + r * a_ir, j + r * a_jr)) for r in range(self.pen_size)]) // self.pen_size
b_color = sum([img.ge) _ S m ttpixel((i + r * b_ir, j + r * b_jr)) for r in range(self.pen_size)]) // self.a l G % N f w open_size
if abs(a_color - b_color) > self.color_diff:
original_color -= (255 - img.getpixel((i, j))) // 4
new_img.putpixel((i, j), original_color)
return original_colo
根据画笔大小和色差扩散器大小,对图片的各个像素点进行运行,最后生成^ ! r # Q F素描画风格的图片。
第七步: 递归访问文件
\"\"\" 创建文件夹 \"\"\"
def __mkdir(self, path):
path = path.strip().rsr p y ?trip(\"\\\\\")
is_exists = os.path.exisi G ; k } r d Qts(path)
if not is_exists:
os.makedirs(path)
return True
else:
return False
\"\"\" 递R C = ~ o 8 2 9归访问文件/文件夹 \"U W d k\"\"
def __visit_dir_fi5 3 p s D ales(Z 7 M v 4 gself,3 K F + 8 Y } D o orX O A R e % sg_input_dir, org_output_dir, recursion_dir):
single_file = False
if os.path.isdir(w 4 e Trecursion_dir):
dir_list = os.listdir(recurp M J k 9 :sion_dir)
else:
dir_list = [recursion_dir]
single_file = True
for i in range(0, len(dir_list)):
path = os.path.join(recursion_dir, dir_list[i])
if os.path.isdir(path):
self.__visit_dir_files(org_inI 1 1 b * 2put_dir, org_output_dir, path)
else:
if imghdrw : 3 ~ (.what(path):
ar X , c L R Hbs_W ~ ; 7 R MoutpZ U + J i Jut_dir = org_output_dirw R 3 * + recursion_dir[len(org_input_dir):]
target_path = os.path.join(abs_output_dir, self.picture_name[self.picture_type] + dir_list[i])
if single_file:
target_path = os.pa} C 0 } kth.join(g Y D a W + z 9org_ouh 4 J c ) 8 % _tput_dir, self.picQ F !ture_nJ % 6 b $ s Kame[self.picture_type] + os.path.basename(dir_list[i]))
target_dir_namef 4 I = os.path.dirname(target_path)
if not os.path.exists(tarF b ^ M I 1 Dget_dir_name):
self.__mkdir(ta. X 6 JrV J z v X k G K 6get_dir_name)
self.__img_deal(path, target_path)
这里也有一个难点,递归访问文件/文件夹。递归,就是自己调用自己。可以把它当成“分治法”,打个比方,如果你想解决一个很大的难题,直接计算是非常困难的,可以把它拆解成多个小问题,一个一个来解决。而递归,就是起T r H L t到一个“分治”的作用。它调用的过程,就是数据结构里面的“? ? Q ?栈”(先进后出)。
我当时开始学习算法的时候,递归算法也是研究了一个星期才懂它% W ; p i l , ^的原理。所以大家学j e h + n习的时候u C c a @ f,不 ] H m要着急,先在纸上模拟( S s }调用过程,慢慢就会懂了。
第八步: 主函数入口
if __name_z l j W $ ] K : ]_ == \'_Y : ? v_main__\':
sketch_pic = SketchPic.initialize(config=CONF)
sketch_pic.main_process()
输入输出:
最后,给一点点学习建议,不懂的时候,先弄明白它的功能以及会使用它,让代码先运行起来。q 6 ) p * ( ^ 0等有时间就一个一个细节去攻破它,编程O * s Y d h z m和写文章v V @ p x | t一样,需要慢慢积累,加油。
如果有p o _ F x 4 z !疑问想获取源码,可以关注后,在后台私信我,回复:python素描。 我把源码发你。最后,感谢大家的阅读,祝大家工作生活愉快!