├── 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 --------------------------------------------------------------------------------