python+Potrace实现自动作画——程序员的浪漫

连续看了快一个月的spark源码,吃饭脑子里蹦出来的都是rpc私有协议、DAGschedule人、TASKscheduler、调度、

资源申请.....总之一句话就是看的快吐了,该换换脑子再做打算。那怎么换脑子呢,做些啥好玩有意思的事呢;看电影、看小说

觉得有点浪费时间还没意思。灵光一现想到前一阵子和朋友吃饭,聊到程序员怎么样才浪漫。谈到MIT大学生黑了一栋宿舍楼做灯光秀,控制楼层显示”** ilove you“、黑进她前男友加密的QQ空间,看一看然后在乱做评论到此一游然后录制视频图片发给他前男友.....

    从技术实现角度讲呢,这两件有趣的事到也不难实现:
  1)灯光秀,现在电网控制基本都是单片机数字化的管理控制,安全防护不高只要知道控制板在哪,电脑编个程序替换现有控制板就可以任你控制了;无非就是把整座楼当成事一个LED阵列,控制它显示你要的字符;难度系数和可执行系数都不算大;无奈这事情是不合法的,抓到要里面待两周以上,就此作罢

2)黑进某人网站,然后撒泡尿到此一游难度系数稍微大些,因为QQ毕竟是商业公司安全也做了这么多年,不过人社工程+网络技术要做到也不是完全不可能,然而这个呢也是不合法的啊....

所以另辟蹊径找点有意思的玩玩,对于我这种和平人士,创造性的玩法估计比较适合我,那具体可以做些啥呢。寻思了一阵画个画什么的可好。可艺术细胞稀缺画画这种事不适合程序员啊....唉,别急,我是不会画,我让机器画估计还是可以的嘛。

有了这主意,马上谷歌百度“机器作画”发现没啥有创意的,换了个词程序动画...还是比较严肃没啥有意思的;在继续换换“ python 游戏”有点意思了,有些兄弟用python开发了不少好玩简单游戏,但还是和我想要的有些差距,继续“python 动画”、“python 动漫”慢慢有点意思了。找到一个不错的玩法——给程序一张图片,程序可以按顺序把图片给一笔一画的画出来。听起来还是挺有趣的,总比深度学习风格转化、换脸有意思,你可以看到机器是怎么一笔一画的画画还是可以的嘛。

首先到github找到项目,代码拉下来看了看,分析下作者实现思路:
1、把png、jpg图片矢量化,转化成SVG格式

2、有了SVG格式就知道图画的边界,如果可以有个软件可以按一定速度沿边界移动不就有作画效果了

3、做完边界然后把每个区域颜色填充,这样一幅画就画好了

     A.因为图片的颜色会比较多,在这里作者通过opencv对图像先做聚类,用更少颜色去表示更多颜色

通过opencv读入图片:

bitmap = cv2.imread(args.filename)
drawBitmap(bitmap)

通过opencv对图片做颜色聚类&Potrace对图片做矢量图转化:

def drawBitmap(w_image):
    print('Reducing the colors...')
    Z = w_image.reshape((-1, 3))

    # convert to np.float32
    Z = np.float32(Z)

    # define criteria, number of clusters(K) and apply kmeans()
    criteria = (cv2.TERM_CRITERIA_EPS, 10, 1.0)
    global K
    ret, label, center = cv2.kmeans(
        Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

    # Now convert back into uint8, and make original image
    center = np.uint8(center)
    print(center)
    res = center[label.flatten()]
    res = res.reshape(w_image.shape)
    no = 1
    for i in center:
        sys.stdout.write('\rDrawing: %.2f%% [' % (
            no / K * 100) + '#' * no + ' ' * (K - no) + ']')
        no += 1
        res2 = cv2.inRange(res, i, i)
        res2 = cv2.bitwise_not(res2)
        #print(i)
        res2 = res
        cv2.imwrite('.tmp.bmp', res2)
        os.system('potrace .tmp.bmp -s --flat')
        print(i)
        drawSVG('.tmp.svg', '#%02x%02x%02x' % (i[2], i[1], i[0]))
    #os.remove('.tmp.bmp')
    #os.remove('.tmp.svg')
    print('\n\rFinished, close the window to exit.')
    te.done()

按矢量图轨迹作画,利用了python turtle库:

def drawSVG(filename, w_color):
    global first
    SVGFile = open(filename, 'r')
    SVG = BeautifulSoup(SVGFile.read(), 'lxml')
    Height = float(SVG.svg.attrs['height'][0: -2])
    Width = float(SVG.svg.attrs['width'][0: -2])
    transform(SVG.g.attrs['transform'])
    if first:
        te.setup(height=Height, width=Width)
        te.setworldcoordinates(-Width / 2, 300, Width -
                               Width / 2, -Height + 300)
        first = False
    te.tracer(100)
    te.pensize(1)
    te.speed(Speed)
    te.penup()
    te.color(w_color)

    for i in SVG.find_all('path'):
        attr = i.attrs['d'].replace('\n', ' ')
        f = readPathAttrD(attr)
        lastI = ''
        for i in f:
            if i == 'M':
                te.end_fill()
                Moveto(next(f) * scale[0], next(f) * scale[1])
                te.begin_fill()
            elif i == 'm':
                te.end_fill()
                Moveto_r(next(f) * scale[0], next(f) * scale[1])
                te.begin_fill()
            elif i == 'C':
                Curveto(next(f) * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1])
                lastI = i
            elif i == 'c':
                Curveto_r(next(f) * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1])
                lastI = i
            elif i == 'L':
                Lineto(next(f) * scale[0], next(f) * scale[1])
            elif i == 'l':
                Lineto_r(next(f) * scale[0], next(f) * scale[1])
                lastI = i
            elif lastI == 'C':
                Curveto(i * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1])
            elif lastI == 'c':
                Curveto_r(i * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1])
            elif lastI == 'L':
                Lineto(i * scale[0], next(f) * scale[1])
            elif lastI == 'l':
                Lineto_r(i * scale[0], next(f) * scale[1])
    te.penup()
    te.hideturtle()
    te.update()
    SVGFile.close()

 

具体代码:

# -*- coding: utf-8 -*-
# Author: tfx2001
# License: GNU GPLv3
# Time: 2018-08-09 18:27

import turtle as te
from bs4 import BeautifulSoup
import argparse
import sys
import numpy as np
import cv2
import os

WriteStep = 15  # 贝塞尔函数的取样次数
Speed = 1000
Width = 600  # 界面宽度
Height = 600  # 界面高度
Xh = 0  # 记录前一个贝塞尔函数的手柄
Yh = 0
scale = (1, 1)
first = True
K = 32


def Bezier(p1, p2, t):  # 一阶贝塞尔函数
    return p1 * (1 - t) + p2 * t


def Bezier_2(x1, y1, x2, y2, x3, y3):  # 二阶贝塞尔函数
    te.goto(x1, y1)
    te.pendown()
    for t in range(0, WriteStep + 1):
        x = Bezier(Bezier(x1, x2, t / WriteStep),
                   Bezier(x2, x3, t / WriteStep), t / WriteStep)
        y = Bezier(Bezier(y1, y2, t / WriteStep),
                   Bezier(y2, y3, t / WriteStep), t / WriteStep)
        te.goto(x, y)
    te.penup()


def Bezier_3(x1, y1, x2, y2, x3, y3, x4, y4):  # 三阶贝塞尔函数
    x1 = -Width / 2 + x1
    y1 = Height / 2 - y1
    x2 = -Width / 2 + x2
    y2 = Height / 2 - y2
    x3 = -Width / 2 + x3
    y3 = Height / 2 - y3
    x4 = -Width / 2 + x4
    y4 = Height / 2 - y4  # 坐标变换
    te.goto(x1, y1)
    te.pendown()
    for t in range(0, WriteStep + 1):
        x = Bezier(Bezier(Bezier(x1, x2, t / WriteStep), Bezier(x2, x3, t / WriteStep), t / WriteStep),
                   Bezier(Bezier(x2, x3, t / WriteStep), Bezier(x3, x4, t / WriteStep), t / WriteStep), t / WriteStep)
        y = Bezier(Bezier(Bezier(y1, y2, t / WriteStep), Bezier(y2, y3, t / WriteStep), t / WriteStep),
                   Bezier(Bezier(y2, y3, t / WriteStep), Bezier(y3, y4, t / WriteStep), t / WriteStep), t / WriteStep)
        te.goto(x, y)
    te.penup()


def Moveto(x, y):  # 移动到svg坐标下(x,y)
    te.penup()
    te.goto(-Width / 2 + x, Height / 2 - y)
    te.pendown()


def Moveto_r(dx, dy):
    te.penup()
    te.goto(te.xcor() + dx, te.ycor() - dy)
    te.pendown()


def line(x1, y1, x2, y2):  # 连接svg坐标下两点
    te.penup()
    te.goto(-Width / 2 + x1, Height / 2 - y1)
    te.pendown()
    te.goto(-Width / 2 + x2, Height / 2 - y2)
    te.penup()


def Lineto_r(dx, dy):  # 连接当前点和相对坐标(dx,dy)的点
    te.pendown()
    te.goto(te.xcor() + dx, te.ycor() - dy)
    te.penup()


def Lineto(x, y):  # 连接当前点和svg坐标下(x,y)
    te.pendown()
    te.goto(-Width / 2 + x, Height / 2 - y)
    te.penup()


def Curveto(x1, y1, x2, y2, x, y):  # 三阶贝塞尔曲线到(x,y)
    te.penup()
    X_now = te.xcor() + Width / 2
    Y_now = Height / 2 - te.ycor()
    Bezier_3(X_now, Y_now, x1, y1, x2, y2, x, y)
    global Xh
    global Yh
    Xh = x - x2
    Yh = y - y2


def Curveto_r(x1, y1, x2, y2, x, y):  # 三阶贝塞尔曲线到相对坐标(x,y)
    te.penup()
    X_now = te.xcor() + Width / 2
    Y_now = Height / 2 - te.ycor()
    Bezier_3(X_now, Y_now, X_now + x1, Y_now + y1,
             X_now + x2, Y_now + y2, X_now + x, Y_now + y)
    global Xh
    global Yh
    Xh = x - x2
    Yh = y - y2


def transform(w_attr):
    funcs = w_attr.split(' ')
    for func in funcs:
        func_name = func[0: func.find('(')]
        if func_name == 'scale':
            global scale
            scale = (float(func[func.find('(') + 1: -1].split(',')[0]),
                     -float(func[func.find('(') + 1: -1].split(',')[1]))


def readPathAttrD(w_attr):
    ulist = w_attr.split(' ')
    for i in ulist:
        # print("now cmd:", i)
        if i.isdigit() or i.isalpha():
            yield float(i)
        elif i[0].isalpha():
            yield i[0]
            yield float(i[1:])
        elif i[-1].isalpha():
            yield float(i[0: -1])
        elif i[0] == '-':
            yield float(i)


def drawSVG(filename, w_color):
    global first
    SVGFile = open(filename, 'r')
    SVG = BeautifulSoup(SVGFile.read(), 'lxml')
    Height = float(SVG.svg.attrs['height'][0: -2])
    Width = float(SVG.svg.attrs['width'][0: -2])
    transform(SVG.g.attrs['transform'])
    if first:
        te.setup(height=Height, width=Width)
        te.setworldcoordinates(-Width / 2, 300, Width -
                               Width / 2, -Height + 300)
        first = False
    te.tracer(100)
    te.pensize(1)
    te.speed(Speed)
    te.penup()
    te.color(w_color)

    for i in SVG.find_all('path'):
        attr = i.attrs['d'].replace('\n', ' ')
        f = readPathAttrD(attr)
        lastI = ''
        for i in f:
            if i == 'M':
                te.end_fill()
                Moveto(next(f) * scale[0], next(f) * scale[1])
                te.begin_fill()
            elif i == 'm':
                te.end_fill()
                Moveto_r(next(f) * scale[0], next(f) * scale[1])
                te.begin_fill()
            elif i == 'C':
                Curveto(next(f) * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1])
                lastI = i
            elif i == 'c':
                Curveto_r(next(f) * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1])
                lastI = i
            elif i == 'L':
                Lineto(next(f) * scale[0], next(f) * scale[1])
            elif i == 'l':
                Lineto_r(next(f) * scale[0], next(f) * scale[1])
                lastI = i
            elif lastI == 'C':
                Curveto(i * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1],
                        next(f) * scale[0], next(f) * scale[1])
            elif lastI == 'c':
                Curveto_r(i * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1],
                          next(f) * scale[0], next(f) * scale[1])
            elif lastI == 'L':
                Lineto(i * scale[0], next(f) * scale[1])
            elif lastI == 'l':
                Lineto_r(i * scale[0], next(f) * scale[1])
    te.penup()
    te.hideturtle()
    te.update()
    SVGFile.close()


def drawBitmap(w_image):
    print('Reducing the colors...')
    Z = w_image.reshape((-1, 3))

    # convert to np.float32
    Z = np.float32(Z)

    # define criteria, number of clusters(K) and apply kmeans()
    criteria = (cv2.TERM_CRITERIA_EPS, 10, 1.0)
    global K
    ret, label, center = cv2.kmeans(
        Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)

    # Now convert back into uint8, and make original image
    center = np.uint8(center)
    print(center)
    res = center[label.flatten()]
    res = res.reshape(w_image.shape)
    no = 1
    for i in center:
        sys.stdout.write('\rDrawing: %.2f%% [' % (
            no / K * 100) + '#' * no + ' ' * (K - no) + ']')
        no += 1
        res2 = cv2.inRange(res, i, i)
        res2 = cv2.bitwise_not(res2)
        #print(i)
        res2 = res
        cv2.imwrite('.tmp.bmp', res2)
        #转成矢量图,保存成.svg格式
        os.system('potrace .tmp.bmp -s --flat')
        print(i)
        drawSVG('.tmp.svg', '#%02x%02x%02x' % (i[2], i[1], i[0]))
    #os.remove('.tmp.bmp')
    #os.remove('.tmp.svg')
    print('\n\rFinished, close the window to exit.')
    te.done()


if __name__ == '__main__':
    paser = argparse.ArgumentParser(
        description="Convert an bitmap to SVG and use turtle libray to draw it.")
    paser.add_argument('filename', type=str,
                       help='The file(*.jpg, *.png, *.bmp) name of the file you want to convert.')
    paser.add_argument(
        "-c", "--color", help="How many colors you want to draw.(If the number is too large that the program may be very slow.)", type=int, default=32)
    args = paser.parse_args()
    K = args.color
    try:
        bitmapFile = open(args.filename, mode='r')
    except FileNotFoundError:
        print(__file__ + ': error: The file is not exists.')
        quit()
    if os.path.splitext(args.filename)[1].lower() not in ['.jpg', '.bmp', '.png']:
        print(__file__ + ': error: The file is not a bitmap file.')
        quit()
    bitmap = cv2.imread(args.filename)
    #if bitmap.shape[0] > 240:
    #    bitmap = cv2.resize(bitmap,(500,200))
    #    bitmap = cv2.resize(bitmap, (int(bitmap.shape[1] * (
    #        (80 - 50) / bitmap.shape[0])), 80 - 50))
    #print(bitmap.shape[1])
    drawBitmap(bitmap)

环境搭建:

1、git clone https://github.com/tfx2001/python-turtle-draw-svg.git

2、安装potrace,参考:https://pypi.org/project/pypotrace/

3、利用requirement.txt安装依赖库:https://mp.csdn.net/postedit/103198577

4、linux或者mac系统把下载的git项目中main.py删除,用上面代码新建main.py

5、测试执行:python main.py -c=32 60960684_p0.png,有用虚拟环境的需切换到虚拟环境后执行前面命令

github:https://github.com/tfx2001/python-turtle-draw-svg.git

 

视频地址:https://www.bilibili.com/video/av20349733?share_medium=android&share_source=copy_link&bbid=37902872-DA4A-4D5D-878B-AEE788048F4217066infoc&ts=1533854267282

 

python+Potrace实现自动作画——程序员的浪漫_第1张图片

 

你可能感兴趣的:(开发小技巧)