├── .gitignore ├── LICENSE ├── README.md ├── cnn └── README.md ├── facespider ├── README.md ├── download.py ├── facespider.py └── requirements.py ├── grader ├── README.md ├── config.py ├── grader.py ├── requirements.txt └── templates │ ├── finished.html │ ├── login.html │ └── score.html ├── pre-processor └── README.md └── services └── README.md /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-Learning/faceworks/3ff564e850a35c127a1e6a54de53b56bcf96ddd0/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 PY-Learning Group 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Faceworks 2 | 3 | 一个基于CNN的颜值打分器 4 | 5 | ## 社区 6 | 7 | 本项目为 PY-Learning 群共同维护,PY-Learning是一个交流Python学习、IT前沿、~~程序员情感~~的微信社群,现有活跃在各个领域的社员123人。 8 | 9 | 加入社区请联系群秘书机器人「Wbot」,通过申请后自动邀请进群。群秘书二维码: 10 | 11 | ![robot wechat](https://ws2.sinaimg.cn/large/006tNc79gy1fhkg6jfx6rj30uu0zkwgo.jpg) 12 | 13 | ## 项目划分 14 | 15 | 项目划分为几个子项目: 16 | 17 | + `facespider` 在网络上爬取人像照片的爬虫机器人 18 | + `pre-processor` 旋转、裁剪、调色等预处理人像工具集合 19 | + `grader` 一个基于Web的交叉评分系统,获取人像评分的数据 20 | + `cnn` 卷积神经网络设计、训练相关代码 21 | + `services` 对外提供服务的Web接口 22 | -------------------------------------------------------------------------------- /cnn/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-Learning/faceworks/3ff564e850a35c127a1e6a54de53b56bcf96ddd0/cnn/README.md -------------------------------------------------------------------------------- /facespider/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-Learning/faceworks/3ff564e850a35c127a1e6a54de53b56bcf96ddd0/facespider/README.md -------------------------------------------------------------------------------- /facespider/download.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import requests 4 | import hashlib 5 | from io import BytesIO 6 | import time 7 | 8 | 9 | def md5hash(bytes): 10 | hash = hashlib.md5(bytes) 11 | return hash.hexdigest() 12 | 13 | 14 | def download_image(url: str): 15 | header = { 16 | "Referer": "http://search.jiayuan.com/v2/index.php", 17 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", 18 | "Origin": "http://search.jiayuan.com" 19 | } 20 | try: 21 | img = requests.get(url, headers=header, timeout=30).content 22 | except (requests.exceptions.Timeout, requests.exceptions.ConnectionError): 23 | print('Failed: %s' % url) 24 | return 25 | md5 = md5hash(img) 26 | suffix = url.rsplit('.')[-1] 27 | with open('imgs/%s.%s' % (md5, suffix), 'wb') as f: 28 | f.write(img) 29 | with open('download.log', 'a') as f: 30 | f.write(url + '\n') 31 | 32 | 33 | def main(): 34 | count = 0 35 | with open('urls.txt', 'r') as f: 36 | urls = f.read().splitlines() 37 | with open('download.log', 'r') as f: 38 | downloaded_urls = set(f.read().splitlines()) 39 | l = len(urls) 40 | for url in urls: 41 | if url not in downloaded_urls: 42 | time.sleep(0.5) 43 | download_image(url) 44 | count += 1 45 | print('Finish %.2f %%' % (count * 100 / l,)) 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /facespider/facespider.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import requests 4 | import bs4 5 | import time 6 | import json 7 | 8 | 9 | def jiayuan_api(page=1): 10 | header = { 11 | "Referer": "http://search.jiayuan.com/v2/index.php", 12 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", 13 | "Origin": "http://search.jiayuan.com" 14 | } 15 | 16 | params = { 17 | "sex": "f", 18 | "key": "", 19 | "sn": "default", 20 | "sv": "1", 21 | "p": page, 22 | "f": "select", 23 | "listStyle": "bigPhoto", 24 | "pri_uid": "0", 25 | "jsversion": "v5", 26 | "stc": "2:20.40,23:1", 27 | } 28 | 29 | return requests.get('http://search.jiayuan.com/v2/search_v2.php', 30 | params=params, headers=header).content.decode('utf-8') 31 | 32 | 33 | def get_url(data): 34 | data = data[11:-13] 35 | d = json.loads(data) 36 | urls = [i['image'] for i in d['userInfo']] 37 | return urls 38 | 39 | 40 | for i in range(20, 100): 41 | data = jiayuan_api(i) 42 | urls = get_url(data) 43 | print('Got page %d' % i) 44 | with open('urls2.txt', 'a') as f: 45 | f.write('\n'.join(urls) + '\n') 46 | time.sleep(10) 47 | -------------------------------------------------------------------------------- /facespider/requirements.py: -------------------------------------------------------------------------------- 1 | flask 2 | requests 3 | -------------------------------------------------------------------------------- /grader/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-Learning/faceworks/3ff564e850a35c127a1e6a54de53b56bcf96ddd0/grader/README.md -------------------------------------------------------------------------------- /grader/config.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | 4 | HOST = 'localhost' 5 | PORT = 5000 6 | DEBUG = True 7 | SECRET_KEY = 'secret_key' 8 | SQL_URL = 'sqlite:///test.db' 9 | -------------------------------------------------------------------------------- /grader/grader.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import os 4 | import random 5 | import string 6 | import sys 7 | from datetime import datetime 8 | 9 | import click 10 | from flask import Flask, request, render_template, redirect, url_for, abort, send_from_directory 11 | from flask_login import LoginManager, current_user, login_user 12 | from sqlalchemy import Column, DateTime, Integer, String, create_engine 13 | import sqlalchemy 14 | from sqlalchemy.ext.declarative import declarative_base 15 | from sqlalchemy.orm import scoped_session, sessionmaker 16 | from functools import wraps 17 | 18 | 19 | login_manager = LoginManager() 20 | 21 | app = Flask('grader') 22 | app.config.from_pyfile('config.py') 23 | if 'FACEWORKS_GRADER_CONFIG' in os.environ: 24 | app.config.from_envvar('FACEWORKS_GRADER_CONFIG') 25 | 26 | login_manager.init_app(app) 27 | engine = create_engine(app.config.get['SQL_URL'], convert_unicode=True) 28 | db_session = scoped_session(sessionmaker(autocommit=False, 29 | autoflush=False, 30 | bind=engine)) 31 | Base = declarative_base() 32 | # Model Layer # 33 | 34 | 35 | class User(Base): 36 | __tablename__ = 'users' 37 | id = Column(Integer, primary_key=True) 38 | name = Column(String(50), unique=True) 39 | pwd = Column(String(120)) 40 | progress = Column(Integer) 41 | 42 | def __init__(self, name=None): 43 | self.name = name 44 | self.pwd = ''.join(random.choices(string.ascii_letters, k=8)) 45 | self.progress = 0 46 | 47 | def __repr__(self): 48 | return '' % (self.id, self.name) 49 | 50 | def is_authenticated(self): 51 | return True 52 | 53 | def is_active(self): 54 | return True 55 | 56 | def is_anonymous(self): 57 | return False 58 | 59 | def get_id(self): 60 | return str(self.id) 61 | 62 | 63 | class Image(Base): 64 | __tablename__ = 'images' 65 | id = Column(Integer, primary_key=True) 66 | md5 = Column(String(32), unique=True) 67 | count = Column(Integer) 68 | 69 | def __init__(self, md5=None): 70 | self.md5 = md5 71 | self.count = 0 72 | 73 | def __repr__(self): 74 | return '' % (self.id, self.md5) 75 | 76 | 77 | class Score(Base): 78 | __tablename__ = 'scores' 79 | id = Column(Integer, primary_key=True) 80 | user = Column(Integer) 81 | image = Column(Integer) 82 | score = Column(Integer) 83 | ts = Column(DateTime) 84 | nouce = Column(Integer) 85 | 86 | def __init__(self, user, image, score=0): 87 | self.user = user 88 | self.image = image 89 | self.score = score 90 | self.ts = datetime.utcnow() 91 | self.nouce = random.randint(0, 10000) 92 | 93 | def __repr__(self): 94 | return '%d, %d>' % (self.user, self.image, self.score) 95 | 96 | 97 | @login_manager.user_loader 98 | def load_user(user_id): 99 | return db_session.query(User).filter(User.id == int(user_id)).one_or_none() 100 | 101 | # Logic Layer # 102 | 103 | 104 | def _init_db(): 105 | Base.metadata.create_all(bind=engine) 106 | 107 | 108 | def _add_score(user, image): 109 | # add score records 110 | score = Score(user, image) 111 | db_session.add(score) 112 | db_session.commit() 113 | 114 | 115 | def _add_image(md5): 116 | # add to every user 117 | image = Image(md5=md5) 118 | db_session.add(image) 119 | db_session.commit() 120 | for row in db_session.query(User.id): 121 | score = Score(row.id, image.id) 122 | db_session.add(score) 123 | db_session.commit() 124 | return image 125 | 126 | 127 | def _add_user(name): 128 | # add every image 129 | user = User(name) 130 | db_session.add(user) 131 | db_session.commit() 132 | for row in db_session.query(Image.id): 133 | score = Score(user.id, row.id) 134 | db_session.add(score) 135 | db_session.commit() 136 | return user 137 | 138 | 139 | def _put_score(user, image, score): 140 | # put score 141 | ins = db_session.query(Score).filter( 142 | Score.user == user, Score.image == image).one() 143 | if score != 0 and ins.score == 0: 144 | image = db_session.query(Image).filter(Image.id == image).one() 145 | image.count += 1 146 | db_session.add(image) 147 | ins.score = score 148 | ins.ts = datetime.utcnow() 149 | db_session.add(ins) 150 | db_session.commit() 151 | 152 | 153 | def _select_image(user): 154 | # Select a image for a user 155 | return db_session.query(Score).filter(Score.user == user, Score.score == 0).order_by(Score.nouce).first() 156 | 157 | 158 | def _get_user_process(user): 159 | total = db_session.query(sqlalchemy.func.count( 160 | Score.id)).filter(Score.user == user) 161 | zeros = db_session.query(sqlalchemy.func.count(Score.id)).filter( 162 | Score.user == user, Score.score == 0) 163 | return total[0][0], zeros[0][0] 164 | 165 | 166 | def _get_sys_process(): 167 | total = db_session.query(sqlalchemy.func.count(Image.id)) 168 | zeros = db_session.query( 169 | sqlalchemy.func.count(Image.id)).filter(Image.count < 3) 170 | return total[0][0], zeros[0][0] 171 | 172 | 173 | # View Layer # 174 | 175 | def login_required(func): 176 | @wraps(func) 177 | def wrapper(*args, **kwargs): 178 | if not current_user.is_authenticated: 179 | return redirect('/login') 180 | else: 181 | return func(*args, **kwargs) 182 | return wrapper 183 | 184 | 185 | @app.route('/') 186 | def index_view(): 187 | if not current_user.is_authenticated: 188 | return redirect('/login') 189 | else: 190 | return redirect('/score') 191 | 192 | 193 | @app.route('/login', methods=['GET', 'POST']) 194 | def login_view(): 195 | if request.method == 'GET': 196 | if current_user.is_anonymous: 197 | return render_template('login.html') 198 | else: 199 | return redirect('/score') 200 | else: 201 | username = request.form.get('username') 202 | pwd = request.form.get('pwd') 203 | if not pwd or not username: 204 | abort(400) 205 | r = db_session.query(User).filter( 206 | User.name == username, User.pwd == pwd).one_or_none() 207 | if not r: 208 | abort(403) 209 | if r.name == username: 210 | login_user(r) 211 | return redirect('/score') 212 | else: 213 | abort(400) 214 | 215 | 216 | @app.route('/score', methods=['GET']) 217 | @login_required 218 | def score_route_view(): 219 | next_image = _select_image(int(current_user.get_id())) 220 | if not next_image: 221 | return redirect('/finished') 222 | return redirect('/score/%s' % next_image.id) 223 | 224 | 225 | @app.route('/score/', methods=['GET', 'POST']) 226 | @login_required 227 | def score_view(vid): 228 | if request.method == 'GET': 229 | image = db_session.query(Image).filter( 230 | Image.id == int(vid)).one_or_none() 231 | if not image: 232 | return redirect('/score') 233 | return render_template('score.html', image_url='/images/' + image.md5, 234 | sys_process=_get_sys_process(), 235 | user_process=_get_user_process(int(current_user.get_id()))) 236 | else: 237 | score = request.form.get('score') 238 | if score == None: 239 | abort(405) 240 | score = int(score) 241 | _put_score(int(current_user.get_id()), int(vid), score=score) 242 | return redirect('/score') 243 | 244 | 245 | @app.route('/finished', methods=['GET']) 246 | @login_required 247 | def finish_view(): 248 | return render_template('finished.html', sys_process=_get_sys_process()) 249 | 250 | 251 | @app.route('/images/') 252 | def image_view(filename): 253 | return send_from_directory('images', filename) 254 | 255 | # Command Layer # 256 | 257 | 258 | @click.group() 259 | def cli(): 260 | pass 261 | 262 | 263 | @cli.command() 264 | def run(): 265 | app.run('localhost', 5000, debug=True) 266 | 267 | 268 | @cli.command() 269 | @click.argument('name') 270 | def add_user(name): 271 | user = _add_user(name) 272 | print("New User %s, password is %s" % (user.name, user.pwd)) 273 | 274 | 275 | @cli.command() 276 | @click.argument('path', nargs=-1) 277 | def add_image(path): 278 | for i in path: 279 | name = os.path.basename(i) 280 | _add_image(name) 281 | 282 | 283 | @cli.command() 284 | def init_db(): 285 | _init_db() 286 | 287 | 288 | def main(): 289 | cli() 290 | 291 | if __name__ == '__main__': 292 | main() 293 | -------------------------------------------------------------------------------- /grader/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | sqlalchemy 3 | flask_login 4 | Flask-SQLAlchemy 5 | click -------------------------------------------------------------------------------- /grader/templates/finished.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

谢谢你完成了所有的评分!

14 |
15 | 全局进度 {{ (sys_process[0] - sys_process[1]) * 100 / sys_process[0] }}% 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /grader/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 51 | 52 | 53 | 54 |
55 | 56 | 65 | 66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /grader/templates/score.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 31 | 32 | 33 | 34 |
35 | 36 | 37 |
38 |

给出你的分数

39 | 40 |
41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 | 63 |
64 | 全局进度 {{ (sys_process[0] - sys_process[1]) * 100 / sys_process[0] }}% 65 |
66 |
67 | 68 |
69 |
70 |
71 |
72 | 你的进度 {{ (user_process[0] - user_process[1]) * 100 / user_process[0] }}% 73 |
74 |
75 | 76 |
77 |
78 |
79 | 80 |
81 |
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /pre-processor/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-Learning/faceworks/3ff564e850a35c127a1e6a54de53b56bcf96ddd0/pre-processor/README.md -------------------------------------------------------------------------------- /services/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-Learning/faceworks/3ff564e850a35c127a1e6a54de53b56bcf96ddd0/services/README.md --------------------------------------------------------------------------------