diff --git "a/Python SDK demo \344\275\277\347\224\250\346\226\207\346\241\243.pdf" "b/Python SDK demo \344\275\277\347\224\250\346\226\207\346\241\243.pdf" new file mode 100644 index 0000000..7b42401 Binary files /dev/null and "b/Python SDK demo \344\275\277\347\224\250\346\226\207\346\241\243.pdf" differ diff --git a/PythonSDK/ImagePro.py b/PythonSDK/ImagePro.py new file mode 100644 index 0000000..7b5c4b2 --- /dev/null +++ b/PythonSDK/ImagePro.py @@ -0,0 +1,125 @@ +import base64 +import io +import os +from PIL import Image, ImageColor + + +class ImageProCls: + base64FilePath=None + + @staticmethod + def humanbody_blending_with_image(input_image, gray_image, bg_image): + """ + + :param input_image: the PIL.Image instance, the source humanbody image + :param gray_image: the PIL.Image instance, it is created from api base64 result, a gray image + :param bg_image: the PIL.Image instance, the background image you want to replace + :return: the PIL.Image instance is blending with humanbody + + notes: you should close the return object after you leave + """ + input_width, input_height = input_image.size + bg_width, bg_height = bg_image.size + + input_aspect_ratio = input_width / float(input_height) + bg_aspect_ratio = bg_width / float(bg_height) + + if bg_aspect_ratio > input_aspect_ratio: + target_width, target_height = int(bg_height * input_aspect_ratio), bg_height + else: + target_width, target_height = bg_width, int(bg_width / input_aspect_ratio) + + crop_image = bg_image.crop((0, 0, 0+target_width, 0+target_height)) + new_image = crop_image.resize((input_width, input_height)) + crop_image.close() + + for x in range(0, input_width): + for y in range(0, input_height): + coord = (x, y) + gray_pixel_value = gray_image.getpixel(coord) + input_rgb_color = input_image.getpixel(coord) + bg_rgb_color = new_image.getpixel(coord) + + confidence = gray_pixel_value / 255.0 + alpha = confidence + + R = input_rgb_color[0] * alpha + bg_rgb_color[0] * (1 - alpha) + G = input_rgb_color[1] * alpha + bg_rgb_color[1] * (1 - alpha) + B = input_rgb_color[2] * alpha + bg_rgb_color[2] * (1 - alpha) + + R = max(0, min(int(R), 255)) + G = max(0, min(int(G), 255)) + B = max(0, min(int(B), 255)) + + new_image.putpixel(coord, (R, G, B)) + + return new_image + + @staticmethod + def humanbody_blending_with_color(input_image, gray_image, bg_color): + """ + :param input_image: the PIL.Image instance + :param gray_image: the PIL.Image instance, it is created from api base64 result, it is a gray image + :param bg_color: a color string value, such as '#FFFFFF' + :return: PIL.Image instance + + notes: you should close the return object after you leave + """ + input_width, input_height = input_image.size + bg_rgb_color = ImageColor.getrgb(bg_color) + + new_image = Image.new("RGB", input_image.size, bg_color) + + for x in range(0, input_width): + for y in range(0, input_height): + coord = (x, y) + gray_pixel_value = gray_image.getpixel(coord) + input_rgb_color = input_image.getpixel(coord) + + confidence = gray_pixel_value / 255.0 + alpha = confidence + + R = input_rgb_color[0] * alpha + bg_rgb_color[0] * (1 - alpha) + G = input_rgb_color[1] * alpha + bg_rgb_color[1] * (1 - alpha) + B = input_rgb_color[2] * alpha + bg_rgb_color[2] * (1 - alpha) + + R = max(0, min(int(R), 255)) + G = max(0, min(int(G), 255)) + B = max(0, min(int(B), 255)) + + new_image.putpixel(coord, (R, G, B)) + + return new_image + + @staticmethod + def getSegmentImg(filePath): + input_file = '' + with open(filePath, 'r') as f: + input_file = f.read() + input_file = base64.b64decode(input_file) + + gray_image = Image.open(io.BytesIO(input_file)) + input_image = Image.open('./imgResource/segment.jpg', 'r') + + new_image = ImageProCls.humanbody_blending_with_color(input_image, gray_image, '#FFFFFF') + new_image.save('./imgResource/resultImg.jpg') + print('-' * 60) + print('结果已经生成,生成文件名:resultImg.jpg,请在imgResource/目录下查看') + if os.path.exists(filePath): + os.remove(filePath) + f.close() + new_image.close() + input_image.close() + gray_image.close() + + @staticmethod + def getMergeImg(base64Str): + imgdata = base64.b64decode(base64Str) + file = open('./imgResource/MergeResultImg.jpg', 'wb') + file.write(imgdata) + file.close() + print('结果已经生成,生成文件名:MergeResultImg.jpg,请在imgResource/目录下查看') + + + + diff --git a/PythonSDK/compat.py b/PythonSDK/compat.py new file mode 100644 index 0000000..0b24059 --- /dev/null +++ b/PythonSDK/compat.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +""" +This module handles import compatibility issues between Python 2 and +Python 3. +""" + +import sys +import random +import string + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +is_py3 = (_ver[0] == 3) + +try: + import simplejson as json +except ImportError: + import json + +# --------- +# Specifics +# --------- + +if is_py2: + from urllib2 import Request, urlopen, HTTPError, URLError + builtin_str = str + bytes = str + str = unicode + basestring = basestring + numeric_types = (int, long, float) + integer_types = (int, long) + +elif is_py3: + from urllib.request import Request, urlopen + from urllib.error import HTTPError, URLError + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) + integer_types = (int,) + + +def enc(x): + if isinstance(x, str): + return x.encode('utf-8') + elif isinstance(x, numeric_types): + return str(x).encode('utf-8') + return x + + +def choose_boundary(): + rand_letters = ''.join(random.sample(string.ascii_letters+string.digits, 15)) + return '{ch}{flag}{rand}'.format(ch='-'*6, flag='PylibFormBoundary', rand=rand_letters) diff --git a/PythonSDK/facepp.py b/PythonSDK/facepp.py new file mode 100644 index 0000000..c2875a0 --- /dev/null +++ b/PythonSDK/facepp.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- + +"""a simple facepp sdk +usage: + api = API(key, secret) + api.detect(img = File('/tmp/test.jpg')) +""" + +import sys +import socket +import json +import os.path +import itertools +import mimetypes +import time +from collections import Iterable +from PythonSDK.structures import ObjectDict +from PythonSDK.compat import (basestring, str, numeric_types, enc, choose_boundary, + Request, urlopen, HTTPError, URLError) + +import ssl +ssl._create_default_https_context = ssl._create_unverified_context + +__all__ = ['File', 'APIError', 'API'] + +DEBUG_LEVEL = 1 + +# 添加API Key API Secret +API_KEY = "XXX" +API_SECRET = "XXX" + + +class File(object): + + """an object representing a local file""" + path = None + content = None + + def __init__(self, path): + self.path = path + self._get_content() + + def _get_content(self): + """read image content""" + + if os.path.getsize(self.path) > 2 * 1024 * 1024: + raise APIError(-1, None, 'image file size too large') + else: + with open(self.path, 'rb') as f: + self.content = f.read() + + def get_filename(self): + return os.path.basename(self.path) + + +class APIError(Exception): + code = None + """HTTP status code""" + + url = None + """request URL""" + + body = None + """server response body; or detailed error information""" + + def __init__(self, code, url, body): + self.code = code + self.url = url + self.body = body + + def __str__(self): + return 'code={s.code}\nurl={s.url}\n{s.body}'.format(s=self) + + __repr__ = __str__ + + +class API(object): + key = None + secret = None + server = 'https://api-cn.faceplusplus.com' + + decode_result = True + timeout = None + max_retries = None + retry_delay = None + + def __init__(self): + """ + :param srv: The API server address + :param decode_result: whether to json_decode the result + :param timeout: HTTP request timeout in seconds + :param max_retries: maximal number of retries after catching URL error + or socket error + :param retry_delay: time to sleep before retrying + """ + if len(API_KEY)==0 or len(API_SECRET)==0: + print('\n'+'请在'+os.path.realpath(__file__)+'文件中填写正确的API_KEY和API_SECRET'+'\n') + exit(1) + + self.key = API_KEY + self.secret = API_SECRET + + srv = None + decode_result = True + timeout = 30 + max_retries = 10 + retry_delay = 5 + if srv: + self.server = srv + self.decode_result = decode_result + assert timeout >= 0 or timeout is None + assert max_retries >= 0 + self.timeout = timeout + self.max_retries = max_retries + self.retry_delay = retry_delay + + _setup_apiobj(self, self, '', []) + + def update_request(self, request): + """overwrite this function to update the request before sending it to + server""" + pass + + +def _setup_apiobj(self, api, prefix, path): + if self is not api: + self._api = api + self._urlbase = '{server}/{prefix}/{path}'.format(server=api.server, prefix=prefix, path='/'.join(path)) + + lvl = len(path) + done = set() + for prefix, paths in _APIS: + for i in paths: + if len(i) <= lvl: + continue + cur = i[lvl] + if i[:lvl] == path and cur not in done: + done.add(cur) + setattr(self, cur, _APIProxy(api, prefix, i[:lvl + 1])) + + +class _APIProxy(object): + _api = None + """underlying :class:`API` object""" + + _urlbase = None + + def __init__(self, api, prefix, path): + _setup_apiobj(self, api, prefix, path) + + def __call__(self, *args, **kargs): + if len(args): + raise TypeError('Only keyword arguments are allowed') + form = _MultiPartForm() + for (k, v) in kargs.items(): + if isinstance(v, File): + form.add_file(k, v.get_filename(), v.content) + + url = self._urlbase + for k, v in self._mkarg(kargs).items(): + form.add_field(k, v) + + body = form.bytes + request = Request(url, data=body) + request.add_header('Content-type', form.get_content_type()) + request.add_header('Content-length', str(len(body))) + + self._api.update_request(request) + + retry = self._api.max_retries + while True: + retry -= 1 + try: + ret = urlopen(request, timeout=self._api.timeout).read() + break + except HTTPError as e: + raise APIError(e.code, url, e.read()) + except (socket.error, URLError) as e: + if retry < 0: + raise e + _print_debug('caught error: {}; retrying'.format(e)) + time.sleep(self._api.retry_delay) + + if self._api.decode_result: + try: + ret = json.loads(ret, object_hook=ObjectDict) + except: + raise APIError(-1, url, 'json decode error, value={0!r}'.format(ret)) + return ret + + def _mkarg(self, kargs): + """change the argument list (encode value, add api key/secret) + :return: the new argument list""" + + kargs = kargs.copy() + kargs['api_key'] = self._api.key + kargs['api_secret'] = self._api.secret + for k, v in list(kargs.items()): + if isinstance(v, Iterable) and not isinstance(v, basestring): + kargs[k] = ','.join(v) + elif isinstance(v, File) or v is None: + del kargs[k] + elif isinstance(v, numeric_types): + kargs[k] = str(v) + else: + kargs[k] = v + + return kargs + + +class _MultiPartForm(object): + + """Accumulate the data to be used when posting a form.""" + + def __init__(self): + self.form_fields = [] + self.files = [] + self.boundary = choose_boundary() + + def get_content_type(self): + return 'multipart/form-data; boundary={}'.format(self.boundary) + + def add_field(self, name, value): + """Add a simple field to the form data.""" + self.form_fields.append((name, value)) + + def add_file(self, fieldname, filename, content, mimetype=None): + """Add a file to be uploaded.""" + if mimetype is None: + mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' + self.files.append((fieldname, filename, mimetype, content)) + + @property + def bytes(self): + """Return a string(2.x) or bytes(3.x) representing the form data, including attached files.""" + # Build a list of lists, each containing "lines" of the + # request. Each part is separated by a boundary string. + # Once the list is built, return a string where each + # line is separated by '\r\n'. + parts = [] + part_boundary = "--" + self.boundary + + # Add the form fields + parts.extend( + [part_boundary, + 'Content-Disposition: form-data; name="{}"'.format(name), '', value] + for name, value in self.form_fields + ) + + # Add the files to upload + parts.extend( + [part_boundary, + 'Content-Disposition: form-data; name="{}"; filename="{}"'.format(field_name, filename), + 'Content-Type: {}'.format(content_type), + '', + body, + ] + for field_name, filename, content_type, body in self.files + ) + + # Flatten the list and add closing boundary marker, + # then return CR+LF separated data + flattened = list(itertools.chain(*parts)) + flattened.append(part_boundary + '--') + flattened.append('') + return b'\r\n'.join(enc(x) for x in flattened) + + +def _print_debug(msg): + if DEBUG_LEVEL: + sys.stderr.write(str(msg) + '\n') + + +_APIS = [ + { + 'prefix': 'facepp/v3', + 'paths': [ + '/detect', + '/compare', + '/search', + '/faceset/create', + '/faceset/addface', + '/faceset/removeface', + '/faceset/update', + '/faceset/getdetail', + '/faceset/delete', + '/faceset/getfacesets', + '/face/analyze', + '/face/getdetail', + '/face/setuserid', + ], + }, + { + 'prefix': 'humanbodypp/v1', + 'paths': [ + '/detect', + '/segment', + ] + }, + { + 'prefix': 'cardpp/v1', + 'paths': [ + '/ocridcard', + '/ocrdriverlicense', + '/ocrvehiclelicense', + '/ocrbankcard', + ] + }, + { + 'prefix': 'imagepp/v1', + 'paths': [ + '/licenseplate', + '/recognizetext', + '/mergeface' + ] + } +] + +_APIS = [(i['prefix'], [p.split('/')[1:] for p in i['paths']]) for i in _APIS] diff --git a/PythonSDK/structures.py b/PythonSDK/structures.py new file mode 100644 index 0000000..baddb02 --- /dev/null +++ b/PythonSDK/structures.py @@ -0,0 +1,19 @@ + + +class ObjectDict(dict): + """Dictionary object for json decode""" + + def __getattr__(self, name): + if name in self: + return self[name] + else: + raise AttributeError("No such attribute: " + name) + + def __setattr__(self, name, value): + self[name] = value + + def __delattr__(self, name): + if name in self: + del self[name] + else: + raise AttributeError("No such attribute: " + name) \ No newline at end of file diff --git a/README.md b/README.md index f27c431..baeee64 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,7 @@ # FacePlusPlus Python SDK -This is the Face++ python SDK suite. Note that python3 has not been supported -yet. +* 集成运行文档请查看目录下的文件`Python SDK demo 使用文档.pdf` -## 1. cmdtool.py -This is an interactive command line tool which could be used to experiment -with Face++ APIs. It is recommended to have ipython installed so that you can -have tab-completion and some other nice features. +* 如果集成中有问题,请[联系我们](https://www.faceplusplus.com.cn/contact-us/) -First please put your API key/secret in apikey.cfg. Then you can start the program -and you will drop into a python shell, where you can write something like: - api.detection.detect(img = File(r'')) - -Note that `api` here is a global variable. - -## 2. hello.py -This is a comprehensive demo for Face++ APIs. See the comments in the source -code for details. - -## 3. facepp.py - -This is the underlying API implementation. diff --git a/apikey.cfg b/apikey.cfg deleted file mode 100644 index 49b8856..0000000 --- a/apikey.cfg +++ /dev/null @@ -1,6 +0,0 @@ -SERVER = 'http://api.cn.faceplusplus.com/' -# uncomment the following line to switch to US server -# SERVER = 'http://api.us.faceplusplus.com/' - -API_KEY = '' -API_SECRET = '' diff --git a/call.py b/call.py new file mode 100644 index 0000000..e6ee174 --- /dev/null +++ b/call.py @@ -0,0 +1,99 @@ + +# 导入系统库并定义辅助函数 +from pprint import pformat + +# import PythonSDK +from PythonSDK.facepp import API,File + +# 导入图片处理类 +import PythonSDK.ImagePro + +# 以下四项是dmeo中用到的图片资源,可根据需要替换 +detech_img_url = 'http://bj-mc-prod-asset.oss-cn-beijing.aliyuncs.com/mc-official/images/face/demo-pic11.jpg' +faceSet_img = './imgResource/demo.jpeg' # 用于创建faceSet +face_search_img = './imgResource/search.png' # 用于人脸搜索 +segment_img = './imgResource/segment.jpg' # 用于人体抠像 +merge_img = './imgResource/merge.jpg' # 用于人脸融合 + + +# 此方法专用来打印api返回的信息 +def print_result(hit, result): + print(hit) + print('\n'.join(" " + i for i in pformat(result, width=75).split('\n'))) + +def printFuctionTitle(title): + return "\n"+"-"*60+title+"-"*60; + +# 初始化对象,进行api的调用工作 +api = API() +# -----------------------------------------------------------人脸识别部分------------------------------------------- + +# 人脸检测:https://console.faceplusplus.com.cn/documents/4888373 +res = api.detect(image_url=detech_img_url, return_attributes="gender,age,smiling,headpose,facequality," + "blur,eyestatus,emotion,ethnicity,beauty," + "mouthstatus,skinstatus") +print_result(printFuctionTitle("人脸检测"), res) + + +# 人脸比对:https://console.faceplusplus.com.cn/documents/4887586 +# compare_res = api.compare(image_file1=File(face_search_img), image_file2=File(face_search_img)) +# print_result("compare", compare_res) + +# 人脸搜索:https://console.faceplusplus.com.cn/documents/4888381 +# 人脸搜索步骤 +# 1,创建faceSet:用于存储人脸信息(face_token) +# 2,向faceSet中添加人脸信息(face_token) +# 3,开始搜索 + +# 删除无用的人脸库,这里删除了,如果在项目中请注意是否要删除 +# api.faceset.delete(outer_id='faceplusplus', check_empty=0) +# # 1.创建一个faceSet +# ret = api.faceset.create(outer_id='faceplusplus') +# +# # 2.向faceSet中添加人脸信息(face_token) +# faceResStr="" +# res = api.detect(image_file=File(faceSet_img)) +# faceList = res["faces"] +# for index in range(len(faceList)): +# if(index==0): +# faceResStr = faceResStr + faceList[index]["face_token"] +# else: +# faceResStr = faceResStr + ","+faceList[index]["face_token"] +# +# api.faceset.addface(outer_id='faceplusplus', face_tokens=faceResStr) +# +# # 3.开始搜索相似脸人脸信息 +# search_result = api.search(image_file=File(face_search_img), outer_id='faceplusplus') +# print_result('search', search_result) + +# -----------------------------------------------------------人体识别部分------------------------------------------- + +# 人体抠像:https://console.faceplusplus.com.cn/documents/10071567 +# segment_res = api.segment(image_file=File(segment_img)) +# f = open('./imgResource/demo-segment.b64', 'w') +# f.write(segment_res["result"]) +# f.close() +# print_result("segment", segment_res) +# # 开始抠像 +# PythonSDK.ImagePro.ImageProCls.getSegmentImg("./imgResource/demo-segment.b64") + +# -----------------------------------------------------------证件识别部分------------------------------------------- +# 身份证识别:https://console.faceplusplus.com.cn/documents/5671702 +# ocrIDCard_res = api.ocridcard(image_url="https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/" +# "c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=7a16a1be19178a82da3177f2976a18e8" +# "/902397dda144ad34a1b2dcf5d7a20cf431ad85b7.jpg") +# print_result('ocrIDCard', ocrIDCard_res) + +# 银行卡识别:https://console.faceplusplus.com.cn/documents/10069553 +# ocrBankCard_res = api.ocrbankcard(image_url="http://pic.5tu.cn/uploads/allimg/1107/191634534200.jpg") +# print_result('ocrBankCard', ocrBankCard_res) + +# -----------------------------------------------------------图像识别部分------------------------------------------- +# 人脸融合:https://console.faceplusplus.com.cn/documents/20813963 +# template_rectangle参数中的数据要通过人脸检测api来获取 +# mergeFace_res = api.mergeface(template_file=File(segment_img), merge_file=File(merge_img), +# template_rectangle="130,180,172,172") +# print_result("mergeFace", mergeFace_res) +# +# # 开始融合 +# PythonSDK.ImagePro.ImageProCls.getMergeImg(mergeFace_res["result"]) diff --git a/cmdtool.py b/cmdtool.py deleted file mode 100755 index 5935321..0000000 --- a/cmdtool.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -# $File: cmdtool.py -# $Date: Sat Apr 06 15:42:43 2013 +0800 -# $Author: jiakai@megvii.com -# -# This program is free software. It comes without any warranty, to -# the extent permitted by applicable law. You can redistribute it -# and/or modify it under the terms of the Do What The Fuck You Want -# To Public License, Version 2, as published by Sam Hocevar. See -# http://sam.zoy.org/wtfpl/COPYING (copied as below) for more details. -# -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -# Version 2, December 2004 -# -# Copyright (C) 2004 Sam Hocevar -# -# Everyone is permitted to copy and distribute verbatim or modified -# copies of this license document, and changing it is allowed as long -# as the name is changed. -# -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION -# -# 0. You just DO WHAT THE FUCK YOU WANT TO. - -def init(): - import sys - import os - import os.path - if sys.version_info.major != 2: - sys.exit('Python 2 is required to run this program') - - fdir = None - if hasattr(sys, "frozen") and \ - sys.frozen in ("windows_exe", "console_exe"): - fdir = os.path.dirname(os.path.abspath(sys.executable)) - sys.path.append(fdir) - fdir = os.path.join(fdir, '..') - else: - fdir = os.path.dirname(__file__) - - with open(os.path.join(fdir, 'apikey.cfg')) as f: - exec(f.read()) - - srv = locals().get('SERVER') - from facepp import API - return API(API_KEY, API_SECRET, srv = srv) - -api = init() - -from facepp import API, File - -del init - -def _run(): - global _run - _run = lambda: None - - msg = """ -=================================================== -Welcome to Face++ Interactive Shell! -Here, you can explore and play with Face++ APIs :) ---------------------------------------------------- -Getting Started: - 0. Register a user and API key on http://www.faceplusplus.com - 1. Write your API key/secret in apikey.cfg - 2. Start this interactive shell and try various APIs - For example, to find all faces in a local image file, just type: - api.detection.detect(img = File(r'')) - -Enjoy! -""" - - try: - from IPython import embed - embed(banner2 = msg) - except ImportError: - import code - code.interact(msg, local = globals()) - - -if __name__ == '__main__': - _run() diff --git a/facepp.py b/facepp.py deleted file mode 100644 index bbce10b..0000000 --- a/facepp.py +++ /dev/null @@ -1,394 +0,0 @@ -# -*- coding: utf-8 -*- -# $File: facepp.py -# $Date: Thu May 16 14:59:36 2013 +0800 -# $Author: jiakai@megvii.com -# -# This program is free software. It comes without any warranty, to -# the extent permitted by applicable law. You can redistribute it -# and/or modify it under the terms of the Do What The Fuck You Want -# To Public License, Version 2, as published by Sam Hocevar. See -# http://sam.zoy.org/wtfpl/COPYING (copied as below) for more details. -# -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -# Version 2, December 2004 -# -# Copyright (C) 2004 Sam Hocevar -# -# Everyone is permitted to copy and distribute verbatim or modified -# copies of this license document, and changing it is allowed as long -# as the name is changed. -# -# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION -# -# 0. You just DO WHAT THE FUCK YOU WANT TO. - -"""a simple facepp sdk -example: -api = API(key, secret) -api.detection.detect(img = File('/tmp/test.jpg'))""" - -__all__ = ['File', 'APIError', 'API'] - - -DEBUG_LEVEL = 1 - -import sys -import socket -import urllib -import urllib2 -import json -import os -import os.path -import itertools -import mimetools -import mimetypes -import time -import tempfile -from collections import Iterable -from cStringIO import StringIO - -class File(object): - """an object representing a local file""" - path = None - content = None - def __init__(self, path): - self.path = path - self._get_content() - - def _resize_cv2(self, ftmp): - try: - import cv2 - except ImportError: - return False - img = cv2.imread(self.path) - assert img is not None and img.size != 0, 'Invalid image' - bigdim = max(img.shape[0], img.shape[1]) - downscale = max(1., bigdim / 600.) - img = cv2.resize(img, - (int(img.shape[1] / downscale), - int(img.shape[0] / downscale))) - cv2.imwrite(ftmp, img) - return True - - def _resize_PIL(self, ftmp): - try: - import PIL.Image - except ImportError: - return False - - img = PIL.Image.open(self.path) - bigdim = max(img.size[0], img.size[1]) - downscale = max(1., bigdim / 600.) - img = img.resize( - (int(img.size[0] / downscale), int(img.size[1] / downscale))) - img.save(ftmp) - return True - - def _get_content(self): - """read image content; resize the image if necessary""" - - if os.path.getsize(self.path) > 2 * 1024 * 1024: - ftmp = tempfile.NamedTemporaryFile( - suffix = '.jpg', delete = False).name - try: - if not (self._resize_cv2(ftmp) or self._resize_PIL(ftmp)): - raise APIError(-1, None, 'image file size too large') - with open(ftmp, 'rb') as f: - self.content = f.read() - finally: - os.unlink(ftmp) - else: - with open(self.path, 'rb') as f: - self.content = f.read() - - def get_filename(self): - return os.path.basename(self.path) - - -class APIError(Exception): - code = None - """HTTP status code""" - - url = None - """request URL""" - - body = None - """server response body; or detailed error information""" - - def __init__(self, code, url, body): - self.code = code - self.url = url - self.body = body - - def __str__(self): - return 'code={s.code}\nurl={s.url}\n{s.body}'.format(s = self) - - __repr__ = __str__ - - -class API(object): - key = None - secret = None - server = 'http://api.faceplusplus.com/' - - decode_result = True - timeout = None - max_retries = None - retry_delay = None - - def __init__(self, key, secret, srv = None, - decode_result = True, timeout = 30, max_retries = 10, - retry_delay = 5): - """:param srv: The API server address - :param decode_result: whether to json_decode the result - :param timeout: HTTP request timeout in seconds - :param max_retries: maximal number of retries after catching URL error - or socket error - :param retry_delay: time to sleep before retrying""" - self.key = key - self.secret = secret - if srv: - self.server = srv - self.decode_result = decode_result - assert timeout >= 0 or timeout is None - assert max_retries >= 0 - self.timeout = timeout - self.max_retries = max_retries - self.retry_delay = retry_delay - - _setup_apiobj(self, self, []) - - def wait_async(self, session_id, referesh_interval = 2): - """wait for asynchronous operations to complete""" - while True: - rst = self.info.get_session(session_id = session_id) - if rst['status'] != u'INQUEUE': - return rst - _print_debug(rst) - time.sleep(referesh_interval) - - def update_request(self, request): - """overwrite this function to update the request before sending it to - server""" - pass - - -def _setup_apiobj(self, api, path): - if self is not api: - self._api = api - self._urlbase = api.server + '/'.join(path) - - lvl = len(path) - done = set() - for i in _APIS: - if len(i) <= lvl: - continue - cur = i[lvl] - if i[:lvl] == path and cur not in done: - done.add(cur) - setattr(self, cur, _APIProxy(api, i[:lvl + 1])) - -class _APIProxy(object): - _api = None - """underlying :class:`API` object""" - - _urlbase = None - - def __init__(self, api, path): - _setup_apiobj(self, api, path) - - def __call__(self, post = False, *args, **kargs): - if len(args): - raise TypeError('Only keyword arguments are allowed') - if type(post) is not bool: - raise TypeError('post argument can only be True or False') - form = _MultiPartForm() - add_form = False - for (k, v) in kargs.iteritems(): - if isinstance(v, File): - add_form = True - form.add_file(k, v.get_filename(), v.content) - - if post: - url = self._urlbase - for k, v in self._mkarg(kargs).iteritems(): - form.add_field(k, v) - add_form = True - else: - url = self.geturl(**kargs) - - request = urllib2.Request(url) - if add_form: - body = str(form) - request.add_header('Content-type', form.get_content_type()) - request.add_header('Content-length', str(len(body))) - request.add_data(body) - - self._api.update_request(request) - - retry = self._api.max_retries - while True: - retry -= 1 - try: - ret = urllib2.urlopen(request, timeout = self._api.timeout).read() - break - except urllib2.HTTPError as e: - raise APIError(e.code, url, e.read()) - except (socket.error, urllib2.URLError) as e: - if retry < 0: - raise e - _print_debug('caught error: {}; retrying'.format(e)) - time.sleep(self._api.retry_delay) - - if self._api.decode_result: - try: - ret = json.loads(ret) - except: - raise APIError(-1, url, 'json decode error, value={0!r}'.format(ret)) - return ret - - def _mkarg(self, kargs): - """change the argument list (encode value, add api key/secret) - :return: the new argument list""" - def enc(x): - if isinstance(x, unicode): - return x.encode('utf-8') - return str(x) - - kargs = kargs.copy() - kargs['api_key'] = self._api.key - kargs['api_secret'] = self._api.secret - for (k, v) in kargs.items(): - if isinstance(v, Iterable) and not isinstance(v, basestring): - kargs[k] = ','.join([enc(i) for i in v]) - elif isinstance(v, File) or v is None: - del kargs[k] - else: - kargs[k] = enc(v) - - return kargs - - def geturl(self, **kargs): - """return the request url""" - return self._urlbase + '?' + urllib.urlencode(self._mkarg(kargs)) - - def visit(self, browser = 'chromium', **kargs): - """visit the url in browser""" - os.system('{0} "{1}"'.format(browser, self.geturl(**kargs))) - - - -# ref: http://www.doughellmann.com/PyMOTW/urllib2/ -class _MultiPartForm(object): - """Accumulate the data to be used when posting a form.""" - - def __init__(self): - self.form_fields = [] - self.files = [] - self.boundary = mimetools.choose_boundary() - return - - def get_content_type(self): - return 'multipart/form-data; boundary=%s' % self.boundary - - def add_field(self, name, value): - """Add a simple field to the form data.""" - self.form_fields.append((name, value)) - return - - def add_file(self, fieldname, filename, content, mimetype = None): - """Add a file to be uploaded.""" - if mimetype is None: - mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' - self.files.append((fieldname, filename, mimetype, content)) - return - - def __str__(self): - """Return a string representing the form data, including attached files.""" - # Build a list of lists, each containing "lines" of the - # request. Each part is separated by a boundary string. - # Once the list is built, return a string where each - # line is separated by '\r\n'. - parts = [] - part_boundary = '--' + self.boundary - - # Add the form fields - parts.extend( - [ part_boundary, - 'Content-Disposition: form-data; name="%s"' % name, - '', - value, - ] - for name, value in self.form_fields - ) - - # Add the files to upload - parts.extend( - [ part_boundary, - 'Content-Disposition: file; name="%s"; filename="%s"' % \ - (field_name, filename), - 'Content-Type: %s' % content_type, - '', - body, - ] - for field_name, filename, content_type, body in self.files - ) - - # Flatten the list and add closing boundary marker, - # then return CR+LF separated data - flattened = list(itertools.chain(*parts)) - flattened.append('--' + self.boundary + '--') - flattened.append('') - return '\r\n'.join(flattened) - - -def _print_debug(msg): - if DEBUG_LEVEL: - sys.stderr.write(str(msg) + '\n') - -_APIS = [ - '/detection/detect', - '/faceset/add_face', - '/faceset/create', - '/faceset/delete', - '/faceset/get_info', - '/faceset/remove_face', - '/faceset/set_info', - '/group/add_person', - '/group/create', - '/group/delete', - '/group/get_info', - '/group/remove_person', - '/group/set_info', - '/grouping/grouping', - '/info/get_app', - '/info/get_face', - '/info/get_faceset_list', - '/info/get_group_list', - '/info/get_image', - '/info/get_person_list', - '/info/get_quota', - '/info/get_session', - '/person/add_face', - '/person/create', - '/person/delete', - '/person/get_info', - '/person/remove_face', - '/person/set_info', - '/recognition/compare', - '/recognition/group_search', - '/recognition/identify', - '/recognition/recognize', - '/recognition/search', - '/recognition/test_train', - '/recognition/train', - '/recognition/verify', - '/train/group_search', - '/train/identify', - '/train/recognize', - '/train/search', - '/train/verify' -] - -_APIS = [i.split('/')[1:] for i in _APIS] diff --git a/hello.py b/hello.py deleted file mode 100755 index b2ddbcc..0000000 --- a/hello.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -# $File: hello.py - -# In this tutorial, you will learn how to call Face ++ APIs and implement a -# simple App which could recognize a face image in 3 candidates. -# 在本教程中,您将了解到Face ++ API的基本调用方法,并实现一个简单的App,用以在3 -# 张备选人脸图片中识别一个新的人脸图片。 - -# You need to register your App first, and enter you API key/secret. -# 您需要先注册一个App,并将得到的API key和API secret写在这里。 -API_KEY = '' -API_SECRET = '' - -# Import system libraries and define helper functions -# 导入系统库并定义辅助函数 -import time -from pprint import pformat -def print_result(hint, result): - def encode(obj): - if type(obj) is unicode: - return obj.encode('utf-8') - if type(obj) is dict: - return {encode(k): encode(v) for (k, v) in obj.iteritems()} - if type(obj) is list: - return [encode(i) for i in obj] - return obj - print hint - result = encode(result) - print '\n'.join([' ' + i for i in pformat(result, width = 75).split('\n')]) - -# First import the API class from the SDK -# 首先,导入SDK中的API类 -from facepp import API - -api = API(API_KEY, API_SECRET) - -# Here are the person names and their face images -# 人名及其脸部图片 -PERSONS = [ - ('Brad Pitt', 'http://www.faceplusplus.com/static/img/demo/9.jpg'), - ('Nicolas Cage', 'http://www.faceplusplus.com/static/img/demo/7.jpg'), - ('Jackie Chan', 'http://www.faceplusplus.com/static/img/demo/6.jpg') -] -TARGET_IMAGE = 'http://www.faceplusplus.com/static/img/demo/13.jpg' - -# Step 1: Create a group to add these persons in -# 步骤1: 新建一个group用以添加person -api.group.create(group_name = 'test') - -# Step 2: Detect faces from those three images and add them to the persons -# 步骤2:从三种图片中检测人脸并将其加入person中。 -for (name, url) in PERSONS: - result = api.detection.detect(url = url, mode = 'oneface') - print_result('Detection result for {}:'.format(name), result) - - face_id = result['face'][0]['face_id'] - - # Create a person in the group, and add the face to the person - # 在该group中新建一个person,并将face加入期中 - api.person.create(person_name = name, group_name = 'test', - face_id = face_id) - - -# Step 3: Train the group. -# Note: this step is required before performing recognition in this group, -# since our system needs to pre-compute models for these persons -# 步骤3:训练这个group -# 注:在group中进行识别之前必须执行该步骤,以便我们的系统能为这些person建模 -result = api.recognition.train(group_name = 'test', type = 'all') - -# Because the train process is time-consuming, the operation is done -# asynchronously, so only a session ID would be returned. -# 由于训练过程比较耗时,所以操作必须异步完成,因此只有session ID会被返回 -print_result('Train result:', result) - -session_id = result['session_id'] - -# Now, wait before train completes -# 等待训练完成 -while True: - result = api.info.get_session(session_id = session_id) - if result['status'] == u'SUCC': - print_result('Async train result:', result) - break - time.sleep(1) - -#也可以通过调用api.wait_async(session_id)函数完成以上功能 - - -# Step 4: recognize the unknown face image -# 步骤4:识别未知脸部图片 -result = api.recognition.recognize(url = TARGET_IMAGE, group_name = 'test') -print_result('Recognize result:', result) -print '=' * 60 -print 'The person with highest confidence:', \ - result['face'][0]['candidate'][0]['person_name'] - - -# Finally, delete the persons and group because they are no longer needed -# 最终,删除无用的person和group -api.group.delete(group_name = 'test') -api.person.delete(person_name = [i[0] for i in PERSONS]) - -# Congratulations! You have finished this tutorial, and you can continue -# reading our API document and start writing your own App using Face++ API! -# Enjoy :) -# 恭喜!您已经完成了本教程,可以继续阅读我们的API文档并利用Face++ API开始写您自 -# 己的App了! -# 旅途愉快 :) diff --git a/imgResource/demo.jpeg b/imgResource/demo.jpeg new file mode 100644 index 0000000..fa949d1 Binary files /dev/null and b/imgResource/demo.jpeg differ diff --git a/imgResource/merge.jpg b/imgResource/merge.jpg new file mode 100644 index 0000000..ab34b2c Binary files /dev/null and b/imgResource/merge.jpg differ diff --git a/imgResource/search.png b/imgResource/search.png new file mode 100644 index 0000000..6bbd510 Binary files /dev/null and b/imgResource/search.png differ diff --git a/imgResource/segment.jpg b/imgResource/segment.jpg new file mode 100644 index 0000000..bc37ad6 Binary files /dev/null and b/imgResource/segment.jpg differ diff --git a/python-dist/facepp_cmdtool_python/apikey.cfg b/python-dist/facepp_cmdtool_python/apikey.cfg deleted file mode 120000 index f6234a7..0000000 --- a/python-dist/facepp_cmdtool_python/apikey.cfg +++ /dev/null @@ -1 +0,0 @@ -../../apikey.cfg \ No newline at end of file diff --git a/python-dist/facepp_cmdtool_python/cmdtool.py b/python-dist/facepp_cmdtool_python/cmdtool.py deleted file mode 120000 index f909c4d..0000000 --- a/python-dist/facepp_cmdtool_python/cmdtool.py +++ /dev/null @@ -1 +0,0 @@ -../../cmdtool.py \ No newline at end of file diff --git a/python-dist/facepp_cmdtool_python/facepp.py b/python-dist/facepp_cmdtool_python/facepp.py deleted file mode 120000 index ef2a336..0000000 --- a/python-dist/facepp_cmdtool_python/facepp.py +++ /dev/null @@ -1 +0,0 @@ -../../facepp.py \ No newline at end of file diff --git a/python-dist/facepp_cmdtool_python/run.sh b/python-dist/facepp_cmdtool_python/run.sh deleted file mode 100755 index 10172b8..0000000 --- a/python-dist/facepp_cmdtool_python/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -if type python2 > /dev/null -then - python2 cmdtool.py -else - python cmdtool.py -fi diff --git a/win32-dist/.gitignore b/win32-dist/.gitignore deleted file mode 100644 index c4c7463..0000000 --- a/win32-dist/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build -dist -.ipython* diff --git a/win32-dist/apikey.cfg b/win32-dist/apikey.cfg deleted file mode 120000 index f8dd971..0000000 --- a/win32-dist/apikey.cfg +++ /dev/null @@ -1 +0,0 @@ -../apikey.cfg \ No newline at end of file diff --git a/win32-dist/build.bat b/win32-dist/build.bat deleted file mode 100644 index 2420835..0000000 --- a/win32-dist/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -python setup.py py2exe -echo Please copy IPython to dist/ diff --git a/win32-dist/cmdtool.py b/win32-dist/cmdtool.py deleted file mode 120000 index 1cf852e..0000000 --- a/win32-dist/cmdtool.py +++ /dev/null @@ -1 +0,0 @@ -../cmdtool.py \ No newline at end of file diff --git a/win32-dist/facepp.py b/win32-dist/facepp.py deleted file mode 120000 index 7fab204..0000000 --- a/win32-dist/facepp.py +++ /dev/null @@ -1 +0,0 @@ -../facepp.py \ No newline at end of file diff --git a/win32-dist/facepp_cmdtool_win32/apikey.cfg b/win32-dist/facepp_cmdtool_win32/apikey.cfg deleted file mode 120000 index f6234a7..0000000 --- a/win32-dist/facepp_cmdtool_win32/apikey.cfg +++ /dev/null @@ -1 +0,0 @@ -../../apikey.cfg \ No newline at end of file diff --git a/win32-dist/facepp_cmdtool_win32/cmdtool.bat b/win32-dist/facepp_cmdtool_win32/cmdtool.bat deleted file mode 100644 index 482fd21..0000000 --- a/win32-dist/facepp_cmdtool_win32/cmdtool.bat +++ /dev/null @@ -1 +0,0 @@ -files\cmdtool.exe diff --git a/win32-dist/facepp_cmdtool_win32/files b/win32-dist/facepp_cmdtool_win32/files deleted file mode 120000 index 85d8c32..0000000 --- a/win32-dist/facepp_cmdtool_win32/files +++ /dev/null @@ -1 +0,0 @@ -../dist \ No newline at end of file diff --git a/win32-dist/setup.py b/win32-dist/setup.py deleted file mode 100644 index 0da50c7..0000000 --- a/win32-dist/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -from distutils.core import setup -import py2exe -setup(console=['cmdtool.py']) -