├── LICENSE
├── README.md
├── examples
├── lomolive.jpg
└── xmas.jpg
├── lomolive.py
├── lomolive
├── layer_l.png
├── layer_p.png
├── layer_s.png
└── lomomask.jpg
├── templates
└── pic.html
├── wxfancypic.py
├── wxverify.py
├── xmas.py
└── xmas
├── frame_h.png
├── frame_s.png
├── frame_w.png
├── haarcascade_frontalface_default.xml
├── hat1.png
├── hat2.png
├── hat3.png
├── hat4.png
├── hat5.png
├── hat6.png
└── hat7.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Lavande
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wx-fancy-pic
2 | 微信公众号服务,根据用户发来的照片自动生成海报或有趣的照片
3 |
4 | 基于flask,用python3写的,依赖:
5 | xmltodict, requests, PIL (python imaging library), cv2, numpy
6 |
7 | 目前支持两种风格的照片生成(见example文件夹里的效果图):
8 | - lomo现场风格
9 | - 圣诞节风格,通过人脸识别带上圣诞帽,并加上相框
10 |
11 | #部署方法:
12 |
13 | - 先在微信公众号后台配置好开发者相关参数,主要是服务器的url还有token这两项,在wxverify.py这个脚本里填入你的token,然后验证一下服务器即可绑定,这个步骤只需要一次。
14 | - wxfancypic.py这个文件是主程序,需要在代码开始填写你的服务器url和token才能正常使用,gen_mode有两个选项:'lomolive'和'xmas',一个是生成lomo现场风格图片,一个是生成圣诞节风格图片。
15 | - lomolive.py和xmas.py是生成图片的主要逻辑,对应lomo现场风格和圣诞节风格,无需改动,wxfancypic.py会根据你的设置自动去调用。
16 | - lomolive和xmas两个文件夹里是生成图片需要的素材,xmas里有个人脸识别的文件,需要cv2这个库来实现。
17 | - 可以配合gunicorn和nginx部署,具体方法参看flask官方文档的部署部分。
18 |
--------------------------------------------------------------------------------
/examples/lomolive.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/examples/lomolive.jpg
--------------------------------------------------------------------------------
/examples/xmas.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/examples/xmas.jpg
--------------------------------------------------------------------------------
/lomolive.py:
--------------------------------------------------------------------------------
1 | from PIL import Image, ImageEnhance
2 |
3 |
4 | ok_msg = '你的现场纪念照片已经生成好啦,请访问以下链接查看和保存: '
5 | auto_reply = '嗨!欢迎来玩儿,活动正在进行中!你可以在这里向本公众号发送你拍摄的现场照片,我们会自动生成一张印有“草地音乐·我在现场”的LOMO风格纪念照片~「本消息为自动发送」'
6 |
7 | def lomoize (image,darkness,saturation):
8 |
9 | (width,height) = image.size
10 |
11 | max = width
12 | if height > width:
13 | max = height
14 |
15 | mask = Image.open("./lomolive/lomomask.jpg").resize((max,max))
16 |
17 | left = round((max - width) / 2)
18 | upper = round((max - height) / 2)
19 |
20 | mask = mask.crop((left,upper,left+width,upper + height))
21 |
22 | # mask = Image.open('mask_l.png')
23 |
24 | darker = ImageEnhance.Brightness(image).enhance(darkness)
25 | saturated = ImageEnhance.Color(image).enhance(saturation)
26 | lomoized = Image.composite(saturated,darker,mask)
27 |
28 | return lomoized
29 |
30 | def gen_pic(MediaId):
31 | pic = Image.open('media/' + MediaId).convert('LA')
32 | # pic = ImageEnhance.Brightness(pic).enhance(0.35)
33 | portion = pic.size[0]/pic.size[1]
34 |
35 | if portion < 1:
36 | #portrait
37 | if portion <= 0.75:
38 | pic_w = 960
39 | pic_h = round(960/portion)
40 | box = (0,round((pic_h-1280)/2),960,round(pic_h/2+640))
41 |
42 | if portion > 0.75:
43 | pic_h = 1280
44 | pic_w = round(1280*portion)
45 | box = (round((pic_w-960)/2),0,round(pic_w/2+480),1280)
46 |
47 | layer = Image.open('./lomolive/layer_p.png')
48 |
49 | elif portion > 1:
50 | #landscape
51 | if portion >= 1.3333:
52 | pic_h = 960
53 | pic_w = round(960*portion)
54 | box = (round((pic_w-1280)/2),0,round(pic_w/2+640),960)
55 |
56 | if portion < 1.3333:
57 | pic_w = 1280
58 | pic_h = round(1280/portion)
59 | box = (0,round((pic_h-960)/2),1280,round(pic_h/2+480))
60 |
61 | layer = Image.open('./lomolive/layer_l.png')
62 |
63 | elif portion == 1:
64 | #square
65 | (pic_w,pic_h) = (960,960)
66 | box = (0,0,960,960)
67 | layer = Image.open('./lomolive/layer_s.png')
68 |
69 | pic = pic.resize((pic_w, pic_h))
70 | pic = pic.crop(box)
71 | pic = lomoize(pic, 0.4, 1)
72 | pic = pic.convert('RGB')
73 | pic.paste(layer,(0,0),layer)
74 |
75 | pic.save('media/' + MediaId + '.jpg', quality=95)
76 |
--------------------------------------------------------------------------------
/lomolive/layer_l.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/lomolive/layer_l.png
--------------------------------------------------------------------------------
/lomolive/layer_p.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/lomolive/layer_p.png
--------------------------------------------------------------------------------
/lomolive/layer_s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/lomolive/layer_s.png
--------------------------------------------------------------------------------
/lomolive/lomomask.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/lomolive/lomomask.jpg
--------------------------------------------------------------------------------
/templates/pic.html:
--------------------------------------------------------------------------------
1 |
2 |
长按图片可保存
3 |
4 |
5 |
--------------------------------------------------------------------------------
/wxfancypic.py:
--------------------------------------------------------------------------------
1 | from flask import Flask,request,render_template,url_for,send_file
2 | from werkzeug.contrib.fixers import ProxyFix
3 | import hashlib
4 | import xmltodict
5 | import subprocess
6 | import time
7 |
8 |
9 | ##BASIC SETTINGS##
10 | #set the server's url here
11 | urlbase = 'http://'
12 | #set your token here
13 | token = ''
14 | #currently 2 options: xmas and lomolive (as in xmas.py and lomolive.py)
15 | gen_mode = 'xmas'
16 |
17 |
18 | app = Flask(__name__)
19 | app.wsgi_app = ProxyFix(app.wsgi_app)
20 |
21 |
22 | if gen_mode == 'xmas':
23 | import xmas
24 | ok_msg = xmas.ok_msg
25 | auto_reply = xmas.auto_reply
26 | elif gen_mode == 'lomolive':
27 | import lomolive
28 | ok_msg = lomolive.ok_msg
29 | auto_reply = lomolive.auto_reply
30 |
31 |
32 | def Isfromwx(request):
33 | signature = request.args.get('signature', '')
34 | timestamp = request.args.get('timestamp', '')
35 | nonce = request.args.get('nonce', '')
36 | #echostr = request.args.get('echostr', '')
37 | L = [token, timestamp, nonce]
38 | L.sort()
39 | s = L[0] + L[1] + L[2]
40 | s = s.encode('utf8')
41 | if hashlib.sha1(s).hexdigest() == signature:
42 | return True
43 | else:
44 | return False
45 |
46 |
47 | def xml_msg(user, msg, fromuser):
48 | '''format the raw message into xml'''
49 |
50 | template = '''
51 |
52 |
53 |
54 | {1}
55 |
56 |
57 | '''
58 | return template.format(user, int(time.time()), msg, fromuser)
59 |
60 |
61 |
62 |
63 | @app.route('/', methods=['POST', 'GET'])
64 | def get_msg():
65 | if Isfromwx(request):
66 | if request.method == 'POST':
67 | d = request.data
68 | d = str(d, 'utf-8')
69 | d = xmltodict.parse(d)
70 | FromUserName = d['xml']['FromUserName']
71 | MsgType = d['xml']['MsgType']
72 | me = d['xml']['ToUserName']
73 |
74 | if MsgType == 'image':
75 | MediaId = d['xml']['MediaId']
76 | PicUrl = d['xml']['PicUrl']
77 | subprocess.call(['wget', PicUrl, '-O', 'media/' + MediaId])
78 | if gen_mode == 'xmas':
79 | xmas.gen_pic(MediaId)
80 | elif gen_mode == 'lomolive':
81 | lomolive.gen_pic(MediaId)
82 |
83 | result_url = urlbase + url_for('pic', MediaId=MediaId)
84 | msg = ok_msg + result_url
85 | xml = xml_msg(FromUserName, msg, me)
86 | print(xml)
87 | return xml
88 |
89 | else:
90 | #save user's text msg into a file as a backup
91 | if MsgType == 'text':
92 | with open('user_msg', 'a') as f: f.write(str(d)+'\n')
93 |
94 | #default auto-reply if we received a non-image message
95 | msg = auto_reply
96 | xml = xml_msg(FromUserName, msg, me)
97 | return xml
98 |
99 | #show a blank page for website visitors
100 | if request.method == 'GET':
101 | return 'nothing'
102 |
103 |
104 | @app.route('/pic/')
105 | def pic(MediaId):
106 | '''generates the web page that contains the picture'''
107 | mediaurl = url_for('media', filename=MediaId+'.jpg')
108 | return render_template('pic.html', pic_path=mediaurl)
109 |
110 |
111 | @app.route('/media/')
112 | def media(filename):
113 | '''returns the media file (i.e., the picture)'''
114 | path = 'media/' + filename
115 | return send_file(path)
116 |
117 |
--------------------------------------------------------------------------------
/wxverify.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | from flask import Flask,request
3 | app = Flask(__name__)
4 |
5 | @app.route('/')
6 | def wx_verify():
7 | signature = request.args.get('signature', '')
8 | timestamp = request.args.get('timestamp', '')
9 | nonce = request.args.get('nonce', '')
10 | echostr = request.args.get('echostr', '')
11 | #PUT YOUR TOKEN HERE
12 | token = ''
13 | L = [token, timestamp, nonce]
14 | L.sort()
15 | s = L[0] + L[1] + L[2]
16 | s = s.encode('utf8')
17 | if hashlib.sha1(s).hexdigest() == signature:
18 | return echostr
19 | else:
20 | return 'Error'
21 |
22 |
--------------------------------------------------------------------------------
/xmas.py:
--------------------------------------------------------------------------------
1 | import cv2, numpy, random
2 | import os
3 | from PIL import Image, ImageDraw
4 |
5 |
6 | #Prepare the hats for use later
7 | hats = []
8 | hats_portion = []
9 | for i in range(1,8):
10 | hat = Image.open('./xmas/' + 'hat' + str(i) + '.png')
11 | hats.append(hat)
12 | hats_portion.append(hat.size[0]/hat.size[1])
13 | #load photo frames
14 | frame_w = Image.open('./xmas/frame_w.png')
15 | frame_h = Image.open('./xmas/frame_h.png')
16 | frame_s = Image.open('./xmas/frame_s.png')
17 |
18 | ok_msg = '你的圣诞节靓照已经生成好啦,请访问以下链接查看,长按图片可保存: '
19 | auto_reply = '嗨!欢迎来玩儿,活动正在进行中!你可以在这里向本公众号发送现场拍摄的照片,我们会自动为照片中的人物带上圣诞帽或鹿角,生成有浓浓圣诞节氛围的照片~「本消息为自动发送」'
20 |
21 | def gen_pic(MediaId):
22 |
23 | def get_hat(x,y,w,h):
24 | #set the probability of every hat
25 | num = random.randint(1,100)
26 | if num in range(1,17):
27 | #hat1
28 | (hat_num, offset1, offset2, offset3) = (0, 1.2, .05, .67)
29 | elif num in range(17,33):
30 | #hat2
31 | (hat_num, offset1, offset2, offset3) = (1, 1.3, -.4, .62)
32 | elif num in range(33,49):
33 | #hat3
34 | (hat_num, offset1, offset2, offset3) = (2, .9, .05, .8)
35 | elif num in range(91,101):
36 | #green hat
37 | (hat_num, offset1, offset2, offset3) = (3, 1.2, .05, .67)
38 | elif num in range(49,65):
39 | #jiao1
40 | (hat_num, offset1, offset2, offset3) = (4, 1.2, -.1, 1.2)
41 | elif num in range(65,81):
42 | #jiao2
43 | (hat_num, offset1, offset2, offset3) = (5, 1, 0, 1.2)
44 | elif num in range(81,91):
45 | #tree
46 | (hat_num, offset1, offset2, offset3) = (6, .9, .05, 1)
47 |
48 | hat_portion = hats_portion[hat_num]
49 | (hat_w, hat_h) = (int(w*offset1), int(w*offset1/hat_portion))
50 | print('hat size:',hat_w,hat_h)
51 | hatter = hats[hat_num].resize((hat_w, hat_h))
52 |
53 | (hat_x, hat_y) = (int(x+w*offset2), int(y-hat_h*offset3))
54 | hat_pos = (hat_x, hat_y)
55 | print('hat at:',hat_x,hat_y)
56 |
57 | return (hatter, hat_pos)
58 |
59 |
60 | def std_size(imagePath):
61 | pic = Image.open(imagePath)
62 | portion = pic.size[0]/pic.size[1]
63 |
64 | if portion < 1:
65 | #portrait
66 | if portion <= 0.75:
67 | pic_w = 960
68 | pic_h = round(960/portion)
69 | box = (0,round((pic_h-1280)/2),960,round(pic_h/2+640))
70 |
71 | if portion > 0.75:
72 | pic_h = 1280
73 | pic_w = round(1280*portion)
74 | box = (round((pic_w-960)/2),0,round(pic_w/2+480),1280)
75 |
76 | elif portion > 1:
77 | #landscape
78 | if portion >= 1.3333:
79 | pic_h = 960
80 | pic_w = round(960*portion)
81 | box = (round((pic_w-1280)/2),0,round(pic_w/2+640),960)
82 |
83 | if portion < 1.3333:
84 | pic_w = 1280
85 | pic_h = round(1280/portion)
86 | box = (0,round((pic_h-960)/2),1280,round(pic_h/2+480))
87 |
88 | elif portion == 1:
89 | #square
90 | (pic_w,pic_h) = (960,960)
91 | box = (0,0,960,960)
92 |
93 | pic = pic.resize((pic_w, pic_h))
94 | pic = pic.crop(box)
95 | return pic
96 |
97 |
98 | def facedet(pil_image):
99 | #input an PIL Image object and output a list of positions of faces
100 |
101 | # Create the haar cascade
102 | cascPath = './xmas/haarcascade_frontalface_default.xml'
103 | faceCascade = cv2.CascadeClassifier(cascPath)
104 |
105 | # Read the image
106 | #image = cv2.imread(imagePath)
107 | #pil_image = std_size(imagePath)
108 | image = cv2.cvtColor(numpy.array(pil_image), cv2.COLOR_RGB2BGR)
109 | gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
110 |
111 | # Detect faces in the image
112 | faces = faceCascade.detectMultiScale(
113 | gray,
114 | scaleFactor=1.2,
115 | minNeighbors=5,
116 | minSize=(50, 60),
117 | flags=cv2.CASCADE_SCALE_IMAGE
118 | )
119 |
120 | # Draw a rectangle around the faces
121 | #for (x, y, w, h) in faces:
122 | # cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)
123 |
124 | #cv2.imwrite(o_path, image)
125 | return faces
126 |
127 |
128 | imagePath = os.path.join('media', MediaId)
129 | o_path = os.path.join('media', MediaId + '.jpg')
130 | image = std_size(imagePath)
131 | faces = facedet(image)
132 | for (x,y,w,h) in faces:
133 | print('face at:',x,y,w,h)
134 | #draw = ImageDraw.Draw(image)
135 | #draw.rectangle([(x, y), (x+w, y+h)])
136 |
137 | (hatter,hat_pos) = get_hat(x,y,w,h)
138 |
139 | image.paste(hatter, hat_pos, hatter)
140 |
141 | #add photo to the frame
142 | portion = image.size[0]/image.size[1]
143 | if portion > 1:
144 | image.paste(frame_w, (0,0), frame_w)
145 | elif portion < 1:
146 | image.paste(frame_h, (0,0), frame_h)
147 | else:
148 | image.paste(frame_s, (0,0), frame_s)
149 |
150 | image.save(o_path)
151 |
--------------------------------------------------------------------------------
/xmas/frame_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/frame_h.png
--------------------------------------------------------------------------------
/xmas/frame_s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/frame_s.png
--------------------------------------------------------------------------------
/xmas/frame_w.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/frame_w.png
--------------------------------------------------------------------------------
/xmas/hat1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/hat1.png
--------------------------------------------------------------------------------
/xmas/hat2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/hat2.png
--------------------------------------------------------------------------------
/xmas/hat3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/hat3.png
--------------------------------------------------------------------------------
/xmas/hat4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/hat4.png
--------------------------------------------------------------------------------
/xmas/hat5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/hat5.png
--------------------------------------------------------------------------------
/xmas/hat6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/hat6.png
--------------------------------------------------------------------------------
/xmas/hat7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lavande/wx-fancy-pic/4851629e7831c517b1af2b8d6a69426c0a87c739/xmas/hat7.png
--------------------------------------------------------------------------------