├── tmp └── .forgit ├── traindatas └── .forgit ├── website ├── db.sqlite3 ├── website │ └── __pycache__ │ │ ├── urls.cpython-36.pyc │ │ ├── wsgi.cpython-36.pyc │ │ ├── __init__.cpython-36.pyc │ │ └── settings.cpython-36.pyc └── FacePIapp │ ├── __pycache__ │ ├── admin.cpython-36.pyc │ ├── models.cpython-36.pyc │ ├── views.cpython-36.pyc │ ├── ClassDB.cpython-36.pyc │ └── __init__.cpython-36.pyc │ └── migrations │ └── __pycache__ │ └── __init__.cpython-36.pyc ├── takepictures └── .forgit ├── data ├── id_name.csv ├── F0S0.png ├── cv3.PNG ├── config.PNG ├── jiliang.png ├── prompt.PNG ├── screen1.png ├── screen2.png ├── addperson.PNG ├── createCV3.PNG ├── download.PNG ├── gitclone.PNG ├── traindatas.PNG └── traindatas_2.PNG ├── .gitignore ├── __pycache__ ├── ClassCV.cpython-35.pyc ├── ClassTK.cpython-35.pyc ├── ClassCamera.cpython-35.pyc ├── ClassUtils.cpython-35.pyc ├── MyException.cpython-35.pyc └── ClassFaceAPI.cpython-35.pyc ├── Config.json ├── OLD ├── FacePI-Config.json ├── FacePI-mainGUI.py ├── FacePI-DeletePerson.py ├── FacePI-Train.py └── FacePI-Identity.py ├── CLI ├── tts.py └── testfire.py ├── ClassGPIO.py ├── MyException.py ├── ClassGTTS.py ├── ClassTK.py ├── ClassCamera.py ├── README.md ├── ClassMessageBox.py ├── ClassUtils.py ├── ClassCV.py ├── README_rpi.md ├── FacePI.py └── ClassFaceAPI.py /tmp/.forgit: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /traindatas/.forgit: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/db.sqlite3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /takepictures/.forgit: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/id_name.csv: -------------------------------------------------------------------------------- 1 | id, name 2 | 00001, 中文姓名 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/* 2 | __pycache__/* 3 | takepictures/* 4 | traindatas/* 5 | -------------------------------------------------------------------------------- /data/F0S0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/F0S0.png -------------------------------------------------------------------------------- /data/cv3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/cv3.PNG -------------------------------------------------------------------------------- /data/config.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/config.PNG -------------------------------------------------------------------------------- /data/jiliang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/jiliang.png -------------------------------------------------------------------------------- /data/prompt.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/prompt.PNG -------------------------------------------------------------------------------- /data/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/screen1.png -------------------------------------------------------------------------------- /data/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/screen2.png -------------------------------------------------------------------------------- /data/addperson.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/addperson.PNG -------------------------------------------------------------------------------- /data/createCV3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/createCV3.PNG -------------------------------------------------------------------------------- /data/download.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/download.PNG -------------------------------------------------------------------------------- /data/gitclone.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/gitclone.PNG -------------------------------------------------------------------------------- /data/traindatas.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/traindatas.PNG -------------------------------------------------------------------------------- /data/traindatas_2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/data/traindatas_2.PNG -------------------------------------------------------------------------------- /__pycache__/ClassCV.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/__pycache__/ClassCV.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/ClassTK.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/__pycache__/ClassTK.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/ClassCamera.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/__pycache__/ClassCamera.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/ClassUtils.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/__pycache__/ClassUtils.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/MyException.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/__pycache__/MyException.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/ClassFaceAPI.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/__pycache__/ClassFaceAPI.cpython-35.pyc -------------------------------------------------------------------------------- /website/website/__pycache__/urls.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/website/__pycache__/urls.cpython-36.pyc -------------------------------------------------------------------------------- /website/website/__pycache__/wsgi.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/website/__pycache__/wsgi.cpython-36.pyc -------------------------------------------------------------------------------- /website/FacePIapp/__pycache__/admin.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/FacePIapp/__pycache__/admin.cpython-36.pyc -------------------------------------------------------------------------------- /website/FacePIapp/__pycache__/models.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/FacePIapp/__pycache__/models.cpython-36.pyc -------------------------------------------------------------------------------- /website/FacePIapp/__pycache__/views.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/FacePIapp/__pycache__/views.cpython-36.pyc -------------------------------------------------------------------------------- /website/website/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/website/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /website/website/__pycache__/settings.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/website/__pycache__/settings.cpython-36.pyc -------------------------------------------------------------------------------- /website/FacePIapp/__pycache__/ClassDB.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/FacePIapp/__pycache__/ClassDB.cpython-36.pyc -------------------------------------------------------------------------------- /website/FacePIapp/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/FacePIapp/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /website/FacePIapp/migrations/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangsir/FacePI/HEAD/website/FacePIapp/migrations/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /Config.json: -------------------------------------------------------------------------------- 1 | {"api_key": "b9160fbd882f47bd821205a4bce64354", "confidence": 0.65, "host": "eastasia.api.cognitive.microsoft.com", "title": "高師大附中刷臉簽到系統", "personGroupName": "人群名稱", "videoid": 0, "personGroupId": "default_groupid", "landmark": 2} -------------------------------------------------------------------------------- /OLD/FacePI-Config.json: -------------------------------------------------------------------------------- 1 | { 2 | //"api_key": "<您的 Key>", 3 | "api_key": "f3e388f66ee146d3b6e96f6ca2ac25d3", 4 | //"host": "westcentralus.api.cognitive.microsoft.com", 5 | "host": "eastasia.api.cognitive.microsoft.com", 6 | "personGroupId": "<自訂的 personGroupId>", 7 | "title": "<自訂的 title>" 8 | } -------------------------------------------------------------------------------- /CLI/tts.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | from gtts import gTTS 3 | from pyg import mixer 4 | 5 | 6 | s = sys.argv[1] 7 | 8 | basepath = '/home/pi/mp3/' 9 | if not os.path.exists(os.path.dirname(basepath)): 10 | os.makedirs(os.path.dirname(basepath)) 11 | 12 | tts=gTTS(text=s, lang='zh-tw') 13 | mp3path = basepath+s+".mp3" 14 | tts.save(mp3path) 15 | 16 | os.system('omxplayer '+mp3path) 17 | 18 | #mixer.init() 19 | #mixer.music.load(mp3path) 20 | #mixer.music.play() 21 | 22 | -------------------------------------------------------------------------------- /ClassGPIO.py: -------------------------------------------------------------------------------- 1 | import RPi.GPIO as GPIO 2 | 3 | pin = 18 4 | GPIO.setmode(GPIO.BCM) # set board mode to Broadcom 5 | GPIO.setup(pin, GPIO.OUT) # set up pin 18 6 | 7 | def RelayOn(): 8 | GPIO.output(pin, 1) # turn on pin 18 9 | 10 | def RelayOff(): 11 | GPIO.output(pin, 0) # turn off pin 18 12 | 13 | def RelayExchange(): 14 | if GPIO.input(pin): 15 | GPIO.output(pin, 0) # turn off pin 18 16 | else: 17 | GPIO.output(pin, 1) # turn on pin 18 18 | -------------------------------------------------------------------------------- /CLI/testfire.py: -------------------------------------------------------------------------------- 1 | import os,fire 2 | # pip install fire 3 | 4 | class Calculator: 5 | ''' 計算機 ''' 6 | def __init__(self): 7 | pass 8 | 9 | def __privateMethod(self): 10 | ''' sssss ''' 11 | print('private Call') 12 | return -1 13 | 14 | def add(self, x, y): 15 | ''' 加法 ''' 16 | self.__privateMethod() 17 | return x + y 18 | 19 | def multiply(self, x, y): 20 | ''' 減法 ''' 21 | return x * y 22 | 23 | if __name__ == '__main__': 24 | fire.Fire(Calculator) 25 | 26 | #if __name__ == '__main__': 27 | # fire.Fire() 28 | -------------------------------------------------------------------------------- /OLD/FacePI-mainGUI.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import font 3 | import os, sys, json 4 | 5 | basepath = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | with open(basepath + '/FacePI-Config.json', 'r') as f: 8 | config = json.load(f) 9 | api_key = config["api_key"] 10 | host = config["host"] 11 | personGroupId = config['personGroupId'] 12 | 13 | 14 | def identity(): 15 | os.system('python3 ' + basepath + '/FacePI-Identity.py ' + personGroupId) 16 | 17 | 18 | top = tk.Tk() 19 | font_helv36 = font.Font(family='Helvetica', size=36, weight='bold') 20 | 21 | width = top.winfo_width() 22 | height = top.winfo_height() 23 | x = (top.winfo_screenwidth() // 2) - (width // 2) 24 | y = (top.winfo_screenheight() // 2) - (height // 2) 25 | #top.geometry('{}x{}'.format(width, height)) 26 | top.geometry('600x600') 27 | 28 | title = config['title'] 29 | top.title(title + " for " + personGroupId) 30 | label = tk.Label(top, text=title, font=font_helv36) 31 | label.pack() 32 | b2 = tk.Button( 33 | top, text='簽到', font=font_helv36, width=10, height=5, command=identity) 34 | b2.pack() 35 | 36 | # Code to add widgets will go here... 37 | top.mainloop() 38 | -------------------------------------------------------------------------------- /MyException.py: -------------------------------------------------------------------------------- 1 | import platform, ClassUtils, ClassCamera, ClassFaceAPI, ClassCV 2 | 3 | 4 | class Error(Exception): 5 | """Base class for exceptions in this module.""" 6 | pass 7 | 8 | 9 | class responseError(Error): 10 | """Exception raised for errors when response error. 11 | """ 12 | 13 | def __init__(self, message): 14 | self.message = message 15 | 16 | 17 | class RateLimitExceededError(Error): 18 | ''' 專用於「達到存取上限」 ''' 19 | 20 | def __init__(self, message): 21 | self.message = message 22 | print('「達到存取上限」, 稍等繼續。') 23 | 24 | 25 | class PersonGroupNotFoundError(Error): 26 | ''' 「PersonGroup 不存在」,捕獲後必須自動建立預設 PersonGroup ''' 27 | 28 | def __init__(self, message): 29 | self.message = message 30 | print('「PersonGroup 不存在」,將自動建立預設 PersonGroup') 31 | # config = ClassUtils.loadConfig() 32 | # personGroupAPI = ClassFaceAPI.PersonGroup(config['api_key'], 33 | # config['host']) 34 | # personGroupAPI.createPersonGroup(config['personGroupId'], 35 | # config['personGroupName'], 'data') 36 | 37 | 38 | class PersonGroupNotTrainedError(Error): 39 | def __init__(self, message): 40 | self.message = message 41 | print('MyException:PersonGroupNotTrainedError:「PersonGroup 未訓練」') 42 | # config = ClassUtils.loadConfig() 43 | # personGroupAPI = ClassFaceAPI.PersonGroup(config['api_key'], 44 | # config['host']) 45 | # personGroupAPI.train_personGroup(config['personGroupId']) 46 | 47 | class UnspecifiedError(Error): 48 | ''' 「驗證失敗」,API KEY 已經失效,請到 config 設定有效的 API KEY。 ''' 49 | 50 | def __init__(self, message): 51 | self.message = message 52 | text = 'API KEY 已經失效,請到 config 設定有效的 API KEY。' 53 | print(text) 54 | if ClassUtils.isLinux(): 55 | print(text) 56 | else: 57 | # import ClassMessageBox 58 | # ClassMessageBox.MessageGUI(message, text) 59 | print(text) 60 | ClassCV.cv_ImageText('「驗證失敗」', text) 61 | 62 | 63 | class esc_opencv(Error): 64 | ''' 「結束攝影鏡頭」 ''' 65 | 66 | def __init__(self, message): 67 | self.message = message 68 | print('「結束攝影鏡頭」') 69 | -------------------------------------------------------------------------------- /OLD/FacePI-DeletePerson.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.request, urllib.parse, urllib.error, base64 2 | import json, os, sys 3 | 4 | basepath = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | if len(sys.argv) < 2: 7 | print(sys.argv[0] + " 要刪除的名字") 8 | sys.exit() 9 | 10 | personname = sys.argv[1] 11 | 12 | with open(basepath + '/FacePI-Config.json', 'r') as f: 13 | config = json.load(f) 14 | api_key = config["api_key"] 15 | host = config["host"] 16 | personGroupId = config['personGroupId'] 17 | 18 | headers = { 19 | # Request headers 20 | 'Ocp-Apim-Subscription-Key': api_key, 21 | } 22 | 23 | 24 | def list_persons_in_group(personGroupId): 25 | headers = { 26 | # Request headers 27 | 'Ocp-Apim-Subscription-Key': api_key, 28 | } 29 | 30 | params = urllib.parse.urlencode({ 31 | # Request parameters 32 | #'start': '{string}', 33 | #'top': '1000', 34 | }) 35 | 36 | try: 37 | conn = http.client.HTTPSConnection(host) 38 | conn.request("GET", "/face/v1.0/persongroups/" + personGroupId + 39 | "/persons?%s" % params, "{body}", headers) 40 | response = conn.getresponse() 41 | data = response.read() 42 | persons = json.loads(str(data, 'UTF-8')) 43 | conn.close() 44 | return persons 45 | except Exception as e: 46 | print("[Errno {0}] {1}".format(e.errno, e.strerror)) 47 | 48 | 49 | def deletePersonId(personGroupId, personId): 50 | params = urllib.parse.urlencode({}) 51 | 52 | try: 53 | conn = http.client.HTTPSConnection(host) 54 | conn.request("DELETE", "/face/v1.0/persongroups/" + personGroupId + 55 | "/persons/" + personId + "?%s" % params, "{body}", 56 | headers) 57 | response = conn.getresponse() 58 | data = response.read() 59 | print(data, " 成功!") 60 | conn.close() 61 | except Exception as e: 62 | print("[Errno {0}] {1}".format(e.errno, e.strerror)) 63 | 64 | 65 | #for personId in personIds: 66 | # deletePersonId(personGroupId, personId) 67 | 68 | persons = list_persons_in_group(personGroupId) 69 | 70 | for person in persons: 71 | if person['name'] == personname: 72 | print('刪除 ', person['name'], person['personId']) 73 | deletePersonId(personGroupId, person['personId']) 74 | else: 75 | print('', person['name'], person['personId']) 76 | -------------------------------------------------------------------------------- /ClassGTTS.py: -------------------------------------------------------------------------------- 1 | import sys, os, platform, time 2 | from gtts import gTTS 3 | import pygame as pygamee 4 | from pypinyin import lazy_pinyin 5 | import ClassUtils 6 | 7 | basepath = os.path.dirname(os.path.realpath(__file__)) 8 | mp3base = os.path.join(basepath, 'mp3') 9 | if not os.path.exists(os.path.dirname(mp3base)): 10 | os.makedirs(os.path.dirname(mp3base)) 11 | 12 | def play_gTTS(name, text): 13 | start = int(round(time.time() * 1000)) 14 | print('開始計時 play_gTTS 0 ms: ', name, text) 15 | #text = '_'.join(lazy_pinyin(text)) 16 | name = ClassUtils.protectPersonName(name) 17 | 18 | mp3path = os.path.join(mp3base, name + text + ".mp3") 19 | #print('gTTS:', str(name + text).encode("utf8"), 'mp3path:', mp3path, os.path.isfile(mp3path)) 20 | print('SPEED: play_gTTS mp3path', int(round(time.time() * 1000) - start), 21 | 'ms') 22 | 23 | if os.path.isfile(mp3path) == False: 24 | tts = gTTS( 25 | text=ClassUtils.protectPersonNameForTTS(name) + text, lang='zh-tw') 26 | tts.save(mp3path) 27 | print('SPEED: play_gTTS savemp3', 28 | int(round(time.time() * 1000) - start), 'ms') 29 | 30 | sysstr = platform.system() 31 | #print('system='+sysstr) 32 | # print('SPEED: pygame play 前', int(round(time.time() * 1000) - start), 'ms') 33 | # if (sysstr == "Windows"): 34 | # print("Call Windows tasks") 35 | # pygamee.mixer.init() 36 | # pygamee.mixer.music.load(mp3path) 37 | # pygamee.mixer.music.play() 38 | # while pygamee.mixer.music.get_busy(): 39 | # pygamee.time.Clock().tick(10) 40 | # elif (sysstr == "Darwin"): 41 | # print("Call macOS tasks") 42 | # pygamee.mixer.init() 43 | # pygamee.mixer.music.load(mp3path) 44 | # pygamee.mixer.music.play() 45 | # while pygamee.mixer.music.get_busy(): 46 | # pygamee.time.Clock().tick(10) 47 | # elif (sysstr == "Linux"): 48 | # #os.system('omxplayer ' + mp3path +" > /dev/null 2>&1") 49 | # pygamee.mixer.init() 50 | # pygamee.mixer.music.load(mp3path) 51 | # pygamee.mixer.music.play() 52 | # while pygamee.mixer.music.get_busy() == True: 53 | # continue 54 | # else: 55 | print("Call Other OS tasks") 56 | pygamee.mixer.init() 57 | pygamee.mixer.music.load(mp3path) 58 | pygamee.mixer.music.play() 59 | while pygamee.mixer.music.get_busy(): 60 | pygamee.time.Clock().tick(5) 61 | print('SPEED: pygame play 後', int(round(time.time() * 1000) - start), 'ms') 62 | -------------------------------------------------------------------------------- /ClassTK.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageDraw, ImageFont, ImageTk 2 | import os, json 3 | import ClassFaceAPI as FaceAPI 4 | 5 | basepath = os.path.dirname(os.path.realpath(__file__)) 6 | with open(basepath + '/Config.json', 'r', encoding='utf-8') as f: 7 | config = json.load(f) 8 | 9 | api_key = config['api_key'] 10 | host = config['host'] 11 | personGroupId = config['personGroupId'] 12 | 13 | 14 | def train_oneShot(top, e, personname, userData, imagepath): 15 | ''' 未經訓練的新人,憑簽到時的一張照片進行訓練。 ''' 16 | jpgimagepaths = [] 17 | jpgimagepaths.append(imagepath) 18 | personAPI = FaceAPI.Person(api_key, host) 19 | if personname == '': 20 | personname = 'unknown_oneshot' 21 | personAPI.add_personimages(personGroupId, personname, userData, 22 | jpgimagepaths) 23 | personGroupapi = FaceAPI.PersonGroup(api_key, host) 24 | personGroupapi.train_personGroup(personGroupId) 25 | top.destroy() 26 | 27 | 28 | def tk_UnknownPerson(text, facepath, picture): 29 | ''' # 當不認識的時候,跳這個畫面。以便用這個圖片去訓練新人。 ''' 30 | import tkinter as tk 31 | 32 | top = tk.Tk() 33 | top.attributes("-topmost", True) 34 | 35 | 36 | #top = tk.Toplevel() 37 | top.geometry('500x500') 38 | top.title(text) 39 | print("訓練 oneshot: picture=" + picture) 40 | pil_image = Image.open(facepath) 41 | width, height = pil_image.size 42 | maxwidth = 200 43 | pil_image = pil_image.resize((maxwidth, int(height * maxwidth / width)), 44 | Image.ANTIALIAS) 45 | 46 | imagefile = ImageTk.PhotoImage(pil_image) 47 | #imagefile = tk.PhotoImage(file=imagepath) 48 | h = imagefile.height() 49 | w = imagefile.width() 50 | # if w > maxwidth: 51 | # imagefile = imagefile.subsample(w // maxwidth, w // maxwidth) 52 | 53 | print('h=', imagefile.height(), 'w=', imagefile.width()) 54 | canvas = tk.Canvas(top, height=imagefile.height(), width=imagefile.width()) 55 | canvas.create_image(10, 10, anchor="nw", image=imagefile) 56 | canvas.pack() 57 | 58 | label = tk.Label(top, text=text, font=('Arial', 20)) 59 | label.pack() 60 | 61 | #frame = tkinter.Frame(master=top).grid(row=1, column=2) 62 | label1 = tk.Label(top, text='請輸入姓名:', font=('Arial', 18)) 63 | label1.pack() 64 | e = tk.Entry(top, font=("Calibri", 24), width=10, show="") 65 | e.pack() 66 | e.focus() 67 | e.insert(0, "") 68 | 69 | b1 = tk.Button( 70 | top, 71 | text='記住我!', 72 | width=15, 73 | height=3, 74 | command=lambda: train_oneShot(top, e, e.get(), 'oneshot', picture)) 75 | b1.bind("", lambda x: train_oneShot(top, e, e.get(), 'oneshot', picture)) 76 | b1.pack() 77 | 78 | b2 = tk.Button(top, text='下一位!', width=15, height=2, command=top.destroy) 79 | b2.bind("", lambda x:top.destroy()) 80 | b2.pack() 81 | #top.bind('', lambda x: top.destroy()) 82 | top.bind('', lambda x:top.destroy()) 83 | 84 | #top.call('wm', 'attributes', '.', '-topmost', '1') 85 | # top.lift() 86 | 87 | # Code to add widgets will go here... 88 | top.mainloop() 89 | -------------------------------------------------------------------------------- /ClassCamera.py: -------------------------------------------------------------------------------- 1 | import os, time, sys, json, platform 2 | import subprocess 3 | import ClassUtils, MyException, ClassCV 4 | from PIL import Image, ImageDraw, ImageFont, ImageTk 5 | import ClassFaceAPI as FaceAPI 6 | 7 | basepath = os.path.dirname(os.path.realpath(__file__)) 8 | with open(basepath + '/Config.json', 'r', encoding='utf-8') as f: 9 | config = json.load(f) 10 | 11 | api_key = config['api_key'] 12 | host = config['host'] 13 | personGroupId = config['personGroupId'] 14 | 15 | 16 | def takePicture(personGroupId, delay, type='Identify', size='small'): 17 | sysstr = platform.system() 18 | print('os=', sysstr, platform.release()) 19 | 20 | if ClassUtils.isLinux(): 21 | return takePicture_CSI(personGroupId, delay, size) 22 | else: 23 | return takePicture_opencv(personGroupId, delay, type) 24 | 25 | # cameras = config['camera'].split(',') 26 | # for camera in cameras: 27 | # if camera == '*opencv': 28 | # return takePicture_opencv(personGroupId, delay, type) 29 | # elif camera == '*CSIcamera': 30 | # return takePicture_CSI(personGroupId, delay, size) 31 | # return takePicture_CSI(personGroupId, delay, size) 32 | 33 | 34 | def takePicture_CSI(personGroupId, delay, size='small'): 35 | # delay in ms 3000ms = 3s 36 | # jpgimagepath = os.path.join(basepath, 'takepictures', personGroupId + "_" + time.strftime( 37 | # "%Y%m%d_%H%M%S", time.localtime()) + ".jpg") 38 | picturepath = ClassUtils.getTakePicturePath(personGroupId) 39 | if not os.path.exists(os.path.dirname(picturepath)): 40 | os.makedirs(os.path.dirname(picturepath)) 41 | try: 42 | # small for 辨識,加快速度。 43 | if size == 'small': 44 | subprocess.call([ 45 | 'raspistill', '-hf', '-w', '800', '-h', '450', '-t', 46 | str(delay), '-o', picturepath 47 | ]) 48 | else: # for 訓練。訓練用圖片可以比較大 49 | subprocess.call([ 50 | 'raspistill', '-hf', '-w', '1600', '-h', '900', '-t', 51 | str(delay), '-o', picturepath 52 | ]) 53 | 54 | except OSError: 55 | # ClassMessageBox.FaceAPIErrorGUI('def takePicture_CSI', 'CSI 攝影機無法啟動!', 56 | # 'OSError: raspistill 無法執行或不存在!!') 57 | print('def takePicture_CSI', 'CSI 攝影機無法啟動!', 58 | 'OSError: raspistill 無法執行或不存在!!') 59 | return None 60 | 61 | #os.system("raspistill -t " + str(delay) + " -o " + imagepath) 62 | return picturepath 63 | 64 | 65 | # def cv_Success(successes): 66 | # ''' 運用 cv2 技術顯示的 Success ''' 67 | # import cv2 68 | # import numpy as np 69 | # print('successes=', successes) 70 | # if len(successes) == 0: 71 | # cv_ImageText('無人簽到成功', '請按「ENTER」繼續') 72 | # return 73 | # for success in successes: 74 | # # print(success['person']['name'], '簽到成功!') 75 | # faceimagepath = ClassUtils.getFaceImagepath(success['faceId']) 76 | 77 | # cv_ImageText( 78 | # ClassUtils.protectPersonName(success['person']['name']) + '簽到成功!', 79 | # '按 ENTER 繼續') 80 | 81 | 82 | def takePicture_opencv(personGroupId, delay, typee): 83 | if (ClassUtils.isWindows() or ClassUtils.isDarwin()): 84 | picturepath = ClassCV.show_opencv(typee, mirror=True) 85 | return picturepath 86 | else: 87 | print('若系統為樹莓派,則需設定 camera 為 CSIcamera 無法以 webcam 作為影像來源。') 88 | return None 89 | 90 | 91 | ''' 92 | def takePicture_Picamera(personGroupId, delay): 93 | # 安裝 sudo apt-get install python3-picamera 94 | # 預設解析度1280x800 95 | imagepath = basepath + "/takepictures/Identity_" + personGroupId + "_" + time.strftime( 96 | "%Y-%m-%d_%H:%M:%S", time.localtime()) + ".jpg" 97 | if not os.path.exists(os.path.dirname(imagepath)): 98 | os.makedirs(os.path.dirname(imagepath)) 99 | 100 | with picamera.PiCamera() as camera: 101 | camera.start_preview() 102 | sleep(delay) 103 | camera.capture(imagepath) 104 | return imagepath 105 | ''' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FacePI 刷臉簽到系統 for Windows 2 | ==================== 3 | 4 | 2017 年可說是各種刷臉應用的爆發的一年,各種應用目不暇給。微軟也在 2016 年提出「微軟認知服務」,裡面就包含了一組 API ,叫做 Face API,專門提供臉部辨識服務,FacePI 就是利用這個 Face API 設計成一個刷臉簽到應用。 5 | 6 | 在這裡要先做一些名詞解釋,因為中文裡面這幾個詞有點容易搞混。 7 | * 「臉部偵測(Face Detection)」:偵測影像中的一或多張人臉,並取得影像臉部位置所在的臉部矩形及臉部屬性,該屬性內含以機器學習為基礎的臉部特徵預測。可用的臉部屬性功能 包括:年齡、表情、性別、姿勢、微笑及鬍子,以及影像中每張臉部的 27 個地標。 8 | * 「臉部驗證(Face Verification)」:檢查兩張臉部是屬於同一個人的可能性。API 會傳回信心分數,顯示兩張臉部是屬於同一個人的可能性。 9 | * 「表情辨識」:臉部 API 現在與表情辨識整合,並傳回影像中每個臉部之一組表情的信心分數,例如生氣、藐視、厭惡、恐懼、快樂、不表意見、憂傷及驚奇。這些表情已知可跨文化普遍地與特定臉部表情溝通。 10 | * 「臉部辨識(Face Identification)」:臉部 API 讓您可搜尋、識別和比對您私人存放庫中多達 1 百萬人的臉部。 11 | 12 | 2018年5月 FacePI 已經跨平台至 Windows 上囉,已經在 Windows 7 與 Windows 10 實測可行。移植的原因主要是樹莓派的運算效能不夠高,速度慢。因此,若專案不需要使用到 GPIO 控制外部設備的話,安裝在 Windows 上可以找到較好的機器設備運行。 13 | 14 | 點擊播放影片 15 | 16 | [![Alt text](https://i.ytimg.com/vi/ORVNkod06pU/hqdefault.jpg)](https://youtu.be/ORVNkod06pU) 17 | 18 | 19 | ## 搭建環境 20 | 21 | ### Anaconda 22 | 首先必須下載 anaconda ,請選擇 Python3 的版本。 23 | 24 | 安裝完成後,在程式集->anaconda prompt 進入文字介面。 25 | ![文字介面](data/prompt.PNG) 26 | 27 | ### 建立隔離執行環境 28 | 為了避免與原先環境互相衝突,最好的方式就是建立一個隔離的執行環境。接著要安裝什麼都按 [y] 安裝。 29 | 30 | conda create -n cv3 31 | 32 | ![建立隔離環境](data/createCV3.PNG) 33 | 34 | 點擊 y 繼續安裝所需套件 35 | 36 | 接著進入這個環境 37 | 38 | conda activate cv3 39 | 40 | ![建立隔離環境](data/cv3.PNG) 41 | 42 | 在這個隔離環境內安裝 OpenCV 43 | 44 | pip install opencv-python 45 | 46 | > ## 相關的操作 47 | > 48 | > 如果要脫離這個隔離環境回到 (base) 49 | > 50 | > conda deactivate 51 | > 52 | > 如果要刪除整個隔離環境的話: 53 | > 54 | > conda remove -n cv3 --all 55 | > 56 | >要看看目前已經存在的環境有哪些: 57 | > 58 | > conda info -e 59 | 60 | 進入到 (cv3) 這個環境當中,安裝必要的套件: 61 | 62 | pip install fire 63 | pip install Pillow 64 | pip install pypinyin 65 | pip install pandas 66 | pip install pymysql 67 | 68 | 69 | ## 開始安裝 FacePI 本體。 70 | 71 | 若您已經安裝 git 環境,則可以直接下以下指令即可。 72 | 73 | git clone https://github.com/jiangsir/FacePI 74 | 75 | ![下載](data/gitclone.PNG) 76 | 77 | 78 | 若沒有 git 指令的話,就直接到 github 把程式抓回來,點擊 Download ZIP。 79 | 80 | https://github.com/jiangsir/FacePI 81 | 82 | ![下載](data/download.PNG) 83 | 84 | 進入 FacePI 放置的路徑,比如「文件」資料夾 85 | 86 | cd /Users/user/Documents 87 | # 此處請依據自己的環境修改。 88 | 89 | 90 | 91 | ## 執行 92 | 執行 FacePI.py, FacePI 主要是一個文字介面程式: 93 | 94 | 95 | cd /Users/user/Documents 96 | # 此處請依據自己的環境修改。 97 | python FacePI/FacePI.py 98 | 99 | 100 | Config: 列出 Config.json 設定。 101 | Signin: 進行簽到! 102 | Identify: 用網路 URL 或本地圖片進行辨識。, 103 | Train: 用 3 連拍訓練一個新人 104 | 105 | Usage: FacePI.py 106 | FacePI.py Config 107 | FacePI.py Identify 108 | FacePI.py Signin 109 | FacePI.py Train 110 | 111 | 112 | 首先,請務必先進行系統設定,指令如下: 113 | 114 | python FacePI/FacePI.py Config 115 | 116 | 點擊 ENTER 不輸入任何值,代表使用預設值。最重要的是更換 API_KEY 的值。預設的 API_KEY 是公用性質,隨時可能被修改或刪除。 117 | 118 | ![計費方案](data/config.PNG) 119 | 120 | 121 | ## 申請一個 API_KEY 122 | 123 | 最主要的設定就是 API_KEY 請至微軟網站申請一個 API_KEY。 124 | 進入到微軟官方頁面 [試用辨識服務](https://azure.microsoft.com/zh-tw/try/cognitive-services/?api=face-api),我們要的是 臉部 API 點擊取得 API 金鑰。然後你就可以獲得 30 天的試用,總共 30000 筆查詢,每分鐘上限 20 筆。對於實驗來說夠用了。但如果要實際使用,每一個月要重新來一次也真是夠煩的。 125 | 因此,比較好的作法是,申請 Azure 帳號,一申請就送你 200 美金的用量,也足以做一個小型應用了,並且 API_KEY 也不會過期。至於用量同樣有每分鐘上限 20 筆,每月 30000 筆查詢的用量,若真的不夠,就可以在後台「儀表板」改為付費模式。每 1000 筆查詢大約會產生 1 美元的費用。 126 | 為了推廣人工智慧應用,諸位軟體大咖們真的是拚了。 127 | 128 | ![計費方案](data/F0S0.png) 129 | 130 | 131 | ![計量圖表](data/jiliang.png) 132 | 133 | ### 訓練 134 | 訓練有 3 種方式: 135 | 1. 「訓練」三連拍:用來「訓練」將來要進行辨識的人。 136 | 137 | python FacePI/FacePI.py Train <姓名> 138 | 139 | 比如: 可以用 來標示人員的分類。 140 | 141 | python FacePI/FacePI.py Train 高師大附中國一仁 王寶釧 142 | 143 | 2. 訓練相片檔: 144 | 145 | python FacePI/FacePI.py traindatas C:\traindatas 146 | 147 | traindatas 下的檔案結構如下: 148 | 如: C:\traindatas\高二禮\張鈞甯\ <放置數個相片檔> 149 | 150 | 請注意,訓練相片檔內只可以有一個人,否則系統不知道哪一個是張鈞甯 151 | 152 | ![檔案結構](data/traindatas_2.PNG) 153 | 154 | 進行訓練 155 | ![訓練圖檔](data/traindatas.PNG) 156 | 157 | 158 | 159 | 160 | 3. 在「簽到」過程中,若發現系統不認識這個人或者認錯了,即可點擊 "a" 按鍵進行學習。 161 | 162 | ![計量圖表](data/addperson.PNG) 163 | 164 | 165 | ### 簽到 166 | 最後,進行簽到。 167 | 168 | python FacePI/FacePI.py Signin 169 | 170 | 即可依照畫面指示進行操作。 171 | -------------------------------------------------------------------------------- /OLD/FacePI-Train.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.request, urllib.parse, urllib.error, base64, json 2 | import sys, os, time 3 | import tkinter 4 | 5 | 6 | 7 | if len(sys.argv)<3: 8 | print("Train.py 即時拍照進行訓練") 9 | print("Train.py ... 直接指定圖片進行訓練") 10 | sys.exit() 11 | 12 | personGroupId = sys.argv[1] 13 | personname = sys.argv[2] 14 | trainimages = [] 15 | 16 | basepath = os.path.dirname(os.path.realpath(__file__)) 17 | 18 | with open(basepath + '/FacePI-Config.json', 'r') as f: 19 | config = json.load(f) 20 | api_key = config["api_key"] 21 | host = config["host"] 22 | personGroupId = config['personGroupId'] 23 | 24 | 25 | def takePicture(): 26 | imagepath = personGroupId+"_"+personname+"_"+time.strftime("%Y-%m-%d:%H:%M:%S", time.localtime())+".jpg" 27 | os.system("raspistill -t 3000 -o " + imagepath) 28 | print("imagepath = " + imagepath) 29 | return imagepath 30 | 31 | if len(sys.argv)==3: 32 | trainimages.append(takePicture()) 33 | elif len(sys.argv)>3: 34 | trainimages = sys.argv[3:] 35 | 36 | print("trains = " + str(trainimages)) 37 | 38 | def create_personGroup(personGroupId, groupname, groupdata): 39 | print("建立一個 personGroupid = "+personGroupId) 40 | headers = { 41 | # Request headers. 42 | 'Content-Type': 'application/json', 43 | 44 | # NOTE: Replace the "Ocp-Apim-Subscription-Key" value with a valid subscription key. 45 | 'Ocp-Apim-Subscription-Key': api_key, 46 | } 47 | 48 | # Replace 'examplegroupid' with an ID you haven't used for creating a group before. 49 | # The valid characters for the ID include numbers, English letters in lower case, '-' and '_'. 50 | # The maximum length of the ID is 64. 51 | #personGroupId = 'examplegroupid' 52 | #personGroupId = 'jiangsir_groupid2' 53 | 54 | # The userData field is optional. The size limit for it is 16KB. 55 | body = "{ 'name':'"+groupname+"', 'userData':'"+groupdata+"' }" 56 | 57 | try: 58 | 59 | # NOTE: You must use the same location in your REST call as you used to obtain your subscription keys. 60 | # For example, if you obtained your subscription keys from westus, replace "westcentralus" in the 61 | # URL below with "westus". 62 | conn = http.client.HTTPSConnection(host) 63 | conn.request("PUT", "/face/v1.0/persongroups/%s" % personGroupId, body, headers) 64 | response = conn.getresponse() 65 | 66 | # 'OK' indicates success. 'Conflict' means a group with this ID already exists. 67 | # If you get 'Conflict', change the value of personGroupId above and try again. 68 | # If you get 'Access Denied', verify the validity of the subscription key above and try again. 69 | print(response.reason) 70 | 71 | conn.close() 72 | return personGroupId 73 | except Exception as e: 74 | print(e.args) 75 | 76 | 77 | 78 | def create_a_person(personGroupId, name, descript): 79 | print("在 personGroupid="+personGroupId+" 裡 建立一個 person name="+name) 80 | headers = { 81 | # Request headers 82 | 'Content-Type': 'application/json', 83 | 'Ocp-Apim-Subscription-Key': api_key, 84 | } 85 | 86 | params = urllib.parse.urlencode({ 87 | 'personGroupId':personGroupId 88 | }) 89 | 90 | requestbody = '{"name":"'+name+'","userData":"'+descript+'"}' 91 | 92 | try: 93 | conn = http.client.HTTPSConnection(host) 94 | conn.request("POST", "/face/v1.0/persongroups/"+personGroupId+"/persons?%s" % params, requestbody, headers) 95 | response = conn.getresponse() 96 | data = response.read() 97 | #print(data) 98 | create_a_person_json = json.loads(str(data,'UTF-8')) 99 | 100 | conn.close() 101 | return create_a_person_json['personId'] 102 | except Exception as e: 103 | print("[Errno {0}] {1}".format(e.errno, e.strerror)) 104 | 105 | def add_a_person_face(imagepath, personId, personGroupId): 106 | print("用一個圖片放入一個 person 當中 personId="+personId) 107 | #display(Image(url=imagepath)) 108 | 109 | headers = { 110 | # Request headers 111 | # 'Content-Type': 'application/json', 112 | 'Content-Type': 'application/octet-stream', #上傳圖檔 113 | 'Ocp-Apim-Subscription-Key': api_key, 114 | } 115 | 116 | params = urllib.parse.urlencode({ 117 | # Request parameters 118 | 'personGroupId': personGroupId, 119 | #'personId': '03cb1134-ad35-4b80-8bf2-3200f44eef31', 120 | 'personId': personId, 121 | #'userData': '{string}', 122 | #'targetFace': '{string}', 123 | }) 124 | #"https://lh3.googleusercontent.com/AuJtzSdWCTZ6pWW9pMxec86gVZEjP00O7qvl8RNbzYfmJvsiUfDL-BXfel5Sw2jgPNUy7rcIVQ-HFDxDEFuIZxp56NpKwOjYncgMjL_dt0-FnoBIYyUpplx4LlE5ShN2hJ3-URLwOA4=w597-h796-no" 125 | 126 | # requestbody = '{"url": "'+imageurl+'"}' 127 | requestbody = open(imagepath, "rb").read() 128 | 129 | 130 | try: 131 | conn = http.client.HTTPSConnection(host) 132 | conn.request("POST", "/face/v1.0/persongroups/"+personGroupId+"/persons/"+personId+"/persistedFaces?%s" % params, requestbody, headers) 133 | response = conn.getresponse() 134 | data = response.read() 135 | print(data) 136 | conn.close() 137 | except Exception as e: 138 | print("[Errno {0}] {1}".format(e.errno, e.strerror)) 139 | 140 | 141 | 142 | ################################################################################################## 143 | ### main 144 | ################################################################################################## 145 | 146 | 147 | personGroupId = create_personGroup(personGroupId, "personGroup namenamename", "junior class students") 148 | personId = create_a_person(personGroupId, personname, "juniorclass_userDatauserData") 149 | 150 | for trainimage in trainimages: 151 | add_a_person_face(trainimage, personId, personGroupId) 152 | 153 | 154 | train_personGroup(personGroupId) 155 | personGroup_status(personGroupId) 156 | 157 | -------------------------------------------------------------------------------- /ClassMessageBox.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import Text 3 | from tkinter import WORD, INSERT 4 | from tkinter.font import Font 5 | from PIL import Image 6 | import threading, time, ClassUtils 7 | 8 | # FaceAPI 相關的錯誤。有些可能是如:RateLimit Exceed 用量超過 9 | # API Key 有錯要更新之類的問題。 10 | def FaceAPIErrorGUI(title, errorcode, errormessage): 11 | print('ERROR title:', title) 12 | print('ERROR code:', errorcode) 13 | print('ERROR message:', errormessage) 14 | top = tk.Toplevel() 15 | top.geometry('400x400') 16 | top.title(title) 17 | label = tk.Label( 18 | top, 19 | text=errorcode, 20 | font=('Arial', 18), 21 | #bg='yellow', 22 | #width=40, 23 | #height=5, 24 | wraplength=350, 25 | justify='center') 26 | label.pack() 27 | 28 | #frame = tkinter.Frame(master=top).grid(row=1, column=2) 29 | label1 = tk.Label( 30 | top, 31 | text=errormessage, 32 | font=('Arial', 16), 33 | bg='yellow', 34 | #width=40, 35 | #height=5, 36 | wraplength=350, 37 | justify='left') 38 | label1.pack() 39 | # e = tk.Entry(top, font=("Calibri", 24), width=10, show="") 40 | # e.pack() 41 | # e.insert(0, "message: " + title) 42 | 43 | # b2 = tk.Button(top, text='關閉', width=15, height=3, command=top.destroy) 44 | # b2.pack() 45 | 46 | # Code to add widgets will go here... 47 | top.mainloop() 48 | 49 | 50 | def MessageGUI(title, text): 51 | #top = tk.Toplevel() 52 | top = tk.Tk() 53 | top.geometry('400x400') 54 | top.title(title) 55 | # T = Text(top, height=2, width=30) 56 | # T.pack() 57 | # T.insert(END, "Just a text Widget\nin two lines\n") 58 | myFont = Font(family="Times New Roman", size=28) 59 | text1 = Text(top, wrap=WORD) 60 | text1.configure(font=myFont) 61 | text1.insert(INSERT, text) 62 | text1.pack() 63 | label1 = tk.Label(top, text=text, font=('Arial', 28)) 64 | label1.pack() 65 | 66 | top.lift() 67 | top.call('wm', 'attributes', '.', '-topmost', '1') 68 | top.mainloop() 69 | 70 | def countdown(b): 71 | count = 2000 72 | while count>0: 73 | count = count - 100 74 | b.set(count) 75 | time.sleep(0.1) 76 | 77 | def __saveImg(imagepath): 78 | img = Image.open(imagepath) 79 | img.save(imagepath + ".gif", 'GIF') 80 | 81 | imagefile = tk.PhotoImage(file=imagepath+".gif") 82 | maxwidth = 200 83 | h = imagefile.height() 84 | w = imagefile.width() 85 | if w > maxwidth: 86 | imagefile = imagefile.subsample(w // maxwidth, w // maxwidth) 87 | 88 | print('imagefile=', imagefile) 89 | return imagefile 90 | 91 | def SuccessGUI(title, text, imagepath): 92 | #top = tk.Toplevel() 93 | top = tk.Tk() 94 | top.geometry('400x400') 95 | top.title(title) 96 | 97 | imagefile = __saveImg(imagepath) 98 | 99 | canvas = tk.Canvas(top, height=imagefile.height(), width=imagefile.width()) 100 | 101 | image = canvas.create_image(10, 10, anchor="nw", image=imagefile) 102 | canvas.pack() 103 | 104 | # count = 5000 105 | # btn_text = tk.StringVar() 106 | # btn_text.set("!!!!"+str(count)) 107 | # #button = tk.Button(top, textvariable=btn_text, fg="red", command=countdown, argv=[btn_text]) 108 | # button = tk.Button(top, textvariable=btn_text, fg="red", command=lambda: countdown(btn_text)) 109 | # # t = threading.Thread(target=countdown, args=[button]) 110 | # # t.start() 111 | 112 | # button.pack() 113 | 114 | # def counter_label(label): 115 | # #counter = 0 116 | # def count(): 117 | # global counter 118 | # counter -= 100 119 | # label.config(text='倒數 {:.1f} 秒關閉'.format(counter/1000)) 120 | # label.after(100, count) 121 | # count() 122 | 123 | label = tk.Label(top, fg="dark green") 124 | labelFont = Font(family="Times New Roman", size=18) 125 | label.configure(font=labelFont) 126 | label.configure(anchor="center") 127 | label.pack() 128 | 129 | closecounter = 3000 130 | counter = closecounter 131 | while counter>0: 132 | counter -= 100 133 | label.config(text='倒數 {:.1f} 秒關閉'.format(counter/1000)) 134 | time.sleep(0.1) 135 | #label.after(100, count) 136 | 137 | #counter_label(label) 138 | 139 | # myFont = Font(family="Times New Roman", size=28) 140 | # text1 = tk.Text(top, wrap=WORD) 141 | # text1.configure(font=myFont) 142 | # text1.insert(INSERT, text) 143 | # text1.pack() 144 | 145 | label2 = tk.Label(top, fg="dark green") 146 | label2Font = Font(family="Times New Roman", size=28) 147 | label2.configure(font=label2Font) 148 | label2.configure(anchor="center") 149 | label2.config(text=text) 150 | label2.pack() 151 | 152 | 153 | top.after(closecounter, lambda: top.destroy()) 154 | top.lift() 155 | top.call('wm', 'attributes', '.', '-topmost', '1') 156 | top.mainloop() 157 | 158 | def SuccessesGUI(successes): 159 | ''' 多位成功者放在同一個視窗 ''' 160 | print('STEP 0') 161 | root = tk.Tk() 162 | print('STEP 0.1') 163 | root.geometry('400x400') 164 | print('STEP 1') 165 | scrollbar = tk.Scrollbar(root) 166 | scrollbar.pack( side = tk.RIGHT, fill = tk.Y ) 167 | print('STEP 2') 168 | 169 | mylist = tk.Listbox(root, font="Helvetica 18 bold", yscrollcommand = scrollbar.set ) 170 | y = 10 171 | for success in successes: 172 | #imagepath = ClassUtils.getFaceImagepath(success['faceId']) 173 | #imagefile = __saveImg(imagepath) 174 | #canvas = tk.Canvas(root, height=imagefile.height()*len(successes), width=imagefile.width()*5) 175 | #canvas.create_image(10, y, anchor="nw", image=imagefile) 176 | #y += 10 177 | mylist.insert(tk.END,ClassUtils.protectPersonName(success['person']['name'])+'簽到成功!!') 178 | print('STEP 3') 179 | 180 | #canvas.pack() 181 | mylist.pack( side = tk.LEFT, fill = tk.BOTH ) 182 | scrollbar.config( command = mylist.yview ) 183 | 184 | print('STEP 4') 185 | root.call('wm', 'attributes', '.', '-topmost', '1') 186 | root.mainloop() -------------------------------------------------------------------------------- /ClassUtils.py: -------------------------------------------------------------------------------- 1 | import os, json, time, platform 2 | import MyException, ClassCV 3 | 4 | 5 | def getBasepath(): 6 | basepath = os.path.dirname(os.path.realpath(__file__)) 7 | return basepath 8 | 9 | 10 | def loadConfig(): 11 | basepath = getBasepath() 12 | configpath = os.path.join(basepath, 'Config.json') 13 | with open(configpath, 'r', encoding='utf-8') as f: 14 | config = json.load(f) 15 | return config 16 | 17 | 18 | def protectPersonName(name): 19 | # big5 ╳ 20 | if isWindows(): 21 | return name[0] + '╳' + name[2:] 22 | else: 23 | return name[0] + '〇' + name[2:] 24 | 25 | 26 | def protectPersonNameForTTS(name): 27 | return name[0] + '圈' + name[2:] 28 | 29 | 30 | def getFaceImagepath(faceid): 31 | basepath = os.path.dirname(os.path.realpath(__file__)) 32 | #detectedFaceImagepath = basepath + "/tmp/faceId_" + faceid + ".jpg" 33 | detectedFaceImagepath = os.path.join(basepath, 'tmp', 34 | "faceId_" + faceid + ".png") 35 | 36 | if not os.path.exists(os.path.dirname(detectedFaceImagepath)): 37 | os.makedirs(os.path.dirname(detectedFaceImagepath)) 38 | return detectedFaceImagepath 39 | 40 | 41 | def getTakePicturePath(personGroupId): 42 | ''' 取得拍照後要存檔的路徑。 ''' 43 | basepath = getBasepath() 44 | jpgimagepath = os.path.join( 45 | basepath, 'takepictures', personGroupId + "_" + 46 | time.strftime("%Y%m%d_%H%M%S", time.localtime()) + ".jpg") 47 | return jpgimagepath 48 | 49 | 50 | def makedirsPath(path): 51 | if not os.path.exists(os.path.dirname(path)): 52 | os.makedirs(os.path.dirname(path)) 53 | 54 | 55 | def isFaceAPIError(faceapijson): 56 | if 'error' in faceapijson: 57 | if faceapijson['error']['code'] == 'RateLimitExceeded': 58 | raise MyException.RateLimitExceededError( 59 | faceapijson['error']['code']) 60 | elif faceapijson['error']['code'] == 'PersonGroupNotFound': 61 | raise MyException.PersonGroupNotFoundError( 62 | faceapijson['error']['code']) 63 | elif faceapijson['error']['code'] == 'Unspecified': 64 | raise MyException.UnspecifiedError(faceapijson['error']['code']) 65 | elif faceapijson['error']['code'] == 'PersonGroupNotTrained': 66 | raise MyException.PersonGroupNotTrainedError(faceapijson['error']['code']) 67 | else: 68 | print('CODE:', faceapijson['error']['code']) 69 | print('MESSAGE:', faceapijson['error']['message']) 70 | return True 71 | return False 72 | 73 | def tryFaceAPIError(faceapijson): 74 | if 'error' in faceapijson: 75 | print('CODE:', faceapijson['error']['code']) 76 | print('MESSAGE:', faceapijson['error']['message']) 77 | text = faceapijson['error']['code'] + ": " + faceapijson['error']['message'] 78 | ClassCV.cv_ImageText('存取發生錯誤!', text) 79 | 80 | 81 | # def SigninSuccess(person, faceid): 82 | # #ClassGTTS.play_gTTS(person['name'], '簽到成功') 83 | # text = person['name'], '簽到成功' 84 | # print(person['name'], '簽到成功') 85 | # print(person) 86 | # print(getFaceImagepath(faceid)) 87 | # ClassMessageBox.SuccessGUI('簽到成功', text, getFaceImagepath(faceid)) 88 | 89 | # def SigninSuccesses(successes): 90 | # if isLinux(): 91 | # if len(successes) == 0: 92 | # print('沒有人簽到') 93 | # return 94 | 95 | # for success in successes: 96 | # name = protectPersonName(success['person']['name']) 97 | # if isDarwin(): 98 | # #import ClassGTTS 99 | # #ClassGTTS.play_gTTS(name, '簽到成功!') 100 | # print(protectPersonName(name), '簽到成功!') 101 | # else: 102 | # print(protectPersonName(name), '簽到成功!') 103 | # elif isWindows() or isDarwin(): 104 | # import ClassCamera 105 | # #ClassMessageBox.SuccessesGUI(successes) 106 | # ClassCamera.cv_Success(successes) 107 | 108 | 109 | def textConfidence(name, confidence): 110 | name = protectPersonName(name) 111 | if confidence >= 0.9: 112 | return name + '簽到成功!!!' 113 | elif confidence >= 0.8: 114 | return name + '簽到成功!!' 115 | elif confidence >= 0.7: 116 | return name + '簽到成功!' 117 | else: 118 | return name + '簽到成功' 119 | 120 | 121 | def SigninIdentifyfaces(identifyfaces, picture=None): 122 | if isLinux(): 123 | if len(identifyfaces) == 0: 124 | print('照片裡沒有人!') 125 | return 126 | 127 | for identifyface in identifyfaces: 128 | if 'person' in identifyface: 129 | print("identifyface['confidence']=",identifyface['confidence']) 130 | name = protectPersonName(identifyface['person']['name']) 131 | textConfidence(name, identifyface['confidence']) 132 | else: 133 | print('你哪位?', identifyface) 134 | elif isWindows() or isDarwin(): 135 | import ClassCV 136 | ClassCV.cv_Identifyfaces(identifyfaces, picture) 137 | 138 | 139 | def isLinux(): 140 | return 'Linux' == platform.system() 141 | 142 | 143 | def isDarwin(): 144 | return 'Darwin' == platform.system() 145 | 146 | 147 | def isWindows(): 148 | return isWindows7() or isWindows10() 149 | 150 | 151 | def isWindows7(): 152 | return 'Windows' == platform.system() and '7' == platform.release() 153 | 154 | 155 | def isWindows10(): 156 | return 'Windows' == platform.system() and '10' == platform.release() 157 | 158 | 159 | def getSystemFont(): 160 | # macos: /Library/Fonts/Microsoft Sans Serif.ttf 161 | # if ClassUtils.isDarwin(): 162 | # #ttf = '/Library/Fonts/Microsoft\\ Sans\\ Serif.ttf' 163 | # #ttf = "/Library/Fonts/AppleMyungjo.ttf" 164 | # #ttf = "/Library/Fonts/AppleGothic.ttf" 165 | # ttf = "/Library/Fonts/Arial Unicode.ttf" 166 | # elif ClassUtils.isWindows(): 167 | # ttf = "simhei.ttf" 168 | # #ttf = "arial.ttf" 169 | # else: 170 | # ttf = "simhei.ttf" 171 | 172 | if isDarwin(): 173 | ttf = "/Library/Fonts/Arial Unicode.ttf" 174 | elif isWindows7(): 175 | ttf = "simhei.ttf" 176 | elif isWindows10(): 177 | #ttf = "C:/Windows/Fonts/Arial.ttf" # 中文無法出現 178 | ttf = "C:/Windows.old/Windows/Fonts/msjhbd.ttc" # 微軟正黑體 179 | #tts = "C:/Windows.old/Windows/Fonts/kaiu.ttf" 180 | else: 181 | ttf = "C:/Windows.old/Windows/Fonts/msjhbd.ttc" # 微軟正黑體 182 | return ttf 183 | -------------------------------------------------------------------------------- /ClassCV.py: -------------------------------------------------------------------------------- 1 | import ClassUtils, ClassTK, MyException 2 | from PIL import Image, ImageDraw, ImageFont, ImageTk 3 | 4 | 5 | def show_opencv(typee, mirror=False): 6 | ''' 顯示主畫面 ''' 7 | import cv2 8 | import numpy as np 9 | config = ClassUtils.loadConfig() 10 | 11 | cam = cv2.VideoCapture(config['videoid']) 12 | cam.set(3, 1280) # 修改解析度 寬 13 | cam.set(4, 1280 // 16 * 9) # 修改解析度 高 14 | print('WIDTH', cam.get(3), 'HEIGHT', cam.get(4)) # 顯示預設的解析度 15 | while True: 16 | ret_val, img = cam.read() 17 | if mirror: 18 | img = cv2.flip(img, 1) 19 | 20 | H, W = img.shape[:2] 21 | #imS = cv2.resize(img, (W, H)) 22 | 23 | cv2_im = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # cv2和PIL中颜色的hex码的储存顺序不同 24 | pil_im = Image.fromarray(cv2_im) 25 | draw = ImageDraw.Draw(pil_im) # 括号中为需要打印的canvas,这里就是在图片上直接打印 26 | 27 | ttf = ClassUtils.getSystemFont() 28 | 29 | font = ImageFont.truetype(ttf, 40, encoding="utf-8") 30 | hintfont = ImageFont.truetype(ttf, 24, encoding="utf-8") 31 | 32 | title = config['title'] + "" 33 | w, h = draw.textsize(title, font=font) 34 | draw.rectangle( 35 | ((W / 2 - w / 2 - 5, 0), (W / 2 + w / 2 + 5, h + 20)), 36 | fill="black") 37 | titlelocation = (W / 2 - w / 2, 5) 38 | #textlocation = (0,0) 39 | draw.text( 40 | titlelocation, title, (0, 255, 255), 41 | font=font) # 第一个参数为打印的坐标,第二个为打印的文本,第三个为字体颜色,第四个为字体 42 | # FreeAPIKEY: b9160fbd882f47bd821205a4bce64354 43 | if config['api_key'] == 'b9160fbd882f47bd821205a4bce64354': 44 | warningfont = ImageFont.truetype(ttf, 24, encoding="utf-8") 45 | warning = "請注意,您目前是用的是共用的測試 API_KEY 請儘速自行申請一個自用的 KEY" 46 | w, h = draw.textsize(warning, font=warningfont) 47 | draw.rectangle( 48 | ((W / 2 - w / 2 - 5, H - h*2), (W / 2 + w / 2 + 5, H-h)), fill="yellow") 49 | warninglocation = (W / 2 - w / 2, H - h*2) 50 | draw.text( 51 | warninglocation, warning, (0, 0, 255), 52 | font=warningfont) # 第一个参数为打印的坐标,第二个为打印的文本,第三个为字体颜色,第四个为字体 53 | 54 | if typee == 'Identify': 55 | hint = "請按「ENTER」進行簽到" 56 | elif typee == 'Train': 57 | hint = "請按「ENTER」進行三連拍" 58 | else: 59 | hint = "請按「ENTER」繼續" 60 | w, h = draw.textsize(hint, font=hintfont) 61 | draw.rectangle( 62 | ((W / 2 - w / 2 - 5, H - h), (W / 2 + w / 2 + 5, H)), fill="red") 63 | hintlocation = (W / 2 - w / 2, H - h) 64 | #textlocation = (0,0) 65 | draw.text( 66 | hintlocation, hint, (0, 255, 255), 67 | font=hintfont) # 第一个参数为打印的坐标,第二个为打印的文本,第三个为字体颜色,第四个为字体 68 | 69 | cv2_text_im = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) 70 | 71 | if ClassUtils.isWindows(): 72 | cv2.namedWindow("window", cv2.WND_PROP_FULLSCREEN) 73 | cv2.setWindowProperty("window", cv2.WND_PROP_FULLSCREEN, 74 | cv2.WINDOW_FULLSCREEN) 75 | 76 | cv2.imshow("window", cv2_text_im) 77 | #cv2.imshow("window", img) 78 | 79 | key = cv2.waitKey(1) 80 | if key == ord(' ') or key == 3 or key == 13: # space or enter 81 | picturepath = ClassUtils.getTakePicturePath( 82 | config['personGroupId']) 83 | cv2.imwrite(picturepath, img) 84 | cv2.destroyAllWindows() 85 | cv2.VideoCapture(config['videoid']).release() 86 | return picturepath 87 | elif key == 27: # esc to quit 88 | cv2.destroyAllWindows() 89 | cv2.VideoCapture(config['videoid']).release() 90 | raise MyException.esc_opencv("偵測到 esc 結束鏡頭") 91 | else: 92 | if key != -1: 93 | print('key=', key) 94 | 95 | 96 | def cv_ImageText(title, hint, facepath=None, picture=None, identifyfaces=None): 97 | ''' 標準 cv 視窗''' 98 | import cv2 99 | import numpy as np 100 | if facepath == None: 101 | img = np.zeros((400, 400, 3), np.uint8) 102 | img.fill(90) 103 | else: 104 | img = cv2.imread(facepath) 105 | print('__cv_ImageText.imagepath=', facepath) 106 | H, W = img.shape[:2] 107 | img = cv2.resize(img, (400, int(H / W * 400))) 108 | 109 | windowname = facepath 110 | H, W = img.shape[:2] 111 | 112 | #img = cv2.resize(img, (400,int(H/W*400))) 113 | 114 | ttf = ClassUtils.getSystemFont() 115 | 116 | cv2_im = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # cv2和PIL中颜色的hex码的储存顺序不同 117 | pil_im = Image.fromarray(cv2_im) 118 | draw = ImageDraw.Draw(pil_im) # 括号中为需要打印的canvas,这里就是在图片上直接打印 119 | titlefont = ImageFont.truetype(ttf, 24, encoding="utf-8") 120 | hintfont = ImageFont.truetype(ttf, 18, encoding="utf-8") 121 | 122 | w, h = draw.textsize(title, font=titlefont) 123 | draw.rectangle( 124 | ((W / 2 - w / 2 - 5, 0), (W / 2 + w / 2 + 5, h + 20)), fill="black") 125 | titlelocation = (W / 2 - w / 2, 5) 126 | 127 | if identifyfaces != None and len(identifyfaces) == 1: 128 | hint = hint + "或按 'a' 新增身份" 129 | w, h = draw.textsize(hint, font=hintfont) 130 | draw.rectangle( 131 | ((W / 2 - w / 2 - 5, H - h), (W / 2 + w / 2 + 5, H)), fill="red") 132 | hintlocation = (W / 2 - w / 2, H - h) 133 | draw.text(titlelocation, title, (0, 255, 255), font=titlefont) 134 | draw.text(hintlocation, hint, (0, 255, 0), font=hintfont) 135 | 136 | cv2_text_im = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) 137 | cv2.imshow(windowname, cv2_text_im) 138 | key = cv2.waitKey(10000) 139 | if key == ord(' ') or key == 3 or key == 13: # space or enter 140 | cv2.destroyWindow(windowname) 141 | elif key == ord('a') and len(identifyfaces) == 1: # 鍵盤 a 代表要新增 oneshot 142 | cv2.destroyWindow(windowname) 143 | ClassTK.tk_UnknownPerson('您哪位?', facepath, picture) 144 | 145 | 146 | def cv_Identifyfaces(identifyfaces, picture=None): 147 | ''' 運用 cv2 技術顯示的 Identifyfaces ''' 148 | import cv2 149 | import numpy as np 150 | # print('identifyfaces=',identifyfaces) 151 | if len(identifyfaces) == 0: 152 | cv_ImageText('沒有偵測到任何人!', '請按「ENTER」繼續') 153 | return 154 | for identifyface in identifyfaces: 155 | faceimagepath = ClassUtils.getFaceImagepath(identifyface['faceId']) 156 | if 'person' not in identifyface: 157 | print('identifyface=', identifyface) 158 | cv_ImageText('你哪位?請先訓練。', '按 ENTER 繼續', faceimagepath, picture, 159 | identifyfaces) 160 | else: 161 | text = ClassUtils.textConfidence(identifyface['person']['name'], 162 | identifyface['confidence']) 163 | try: 164 | print(text, identifyface['confidence']) 165 | except UnicodeEncodeError as e: 166 | print("UnicodeEncodeERROR!!", identifyface['confidence']) 167 | #print('cv_Identifyfaces.identifyface=', identifyface) 168 | # text = ClassUtils.textConfidence(identifyface['person']['name'], 169 | # identifyface['confidence']) 170 | cv_ImageText(text, '按 ENTER 繼續', faceimagepath, picture, 171 | identifyfaces) 172 | -------------------------------------------------------------------------------- /README_rpi.md: -------------------------------------------------------------------------------- 1 | FacePI 讓樹莓派變身刷臉報到系統 2 | ==================== 3 | > 本專案僅作為技術驗證使用,若要自行運用,則仍須自行修改程式結構,以符合自身所需。 4 | > 5 | 6 | # 本系統已設計為跨平台。可安裝在 Windows 系統當中,安裝說明請點擊 [Windows 版安裝手冊](README_windows.md) 7 | 8 | 9 | ## 實測影片: 10 | 11 | [![Alt text](https://img.youtube.com/vi/tQDK2j6lsCY/0.jpg)](https://youtu.be/tQDK2j6lsCY) 12 | 13 | 14 | 2017 年可說是各種刷臉應用的爆發的一年,各種應用目不暇給。微軟也在 2016 年提出「微軟認知服務」,裡面就包含了一組 API ,叫做 Face API,專門提供臉部辨識服務,FacePI 就是利用這個 Face API 將它建構在樹莓派上,做成一個刷臉報到應用。 15 | 16 | 在這裡要先做一些名詞解釋,因為中文裡面這幾個詞有點容易搞混。 17 | * 「臉部驗證」:檢查兩張臉部是屬於同一個人的可能性。API 會傳回信心分數,顯示兩張臉部是屬於同一個人的可能性。 18 | * 「臉部偵測(Face Detection)」:偵測影像中的一或多張人臉,並取得影像臉部位置所在的臉部矩形及臉部屬性,該屬性內含以機器學習為基礎的臉部特徵預測。可用的臉部屬性功能 包括:年齡、表情、性別、姿勢、微笑及鬍子,以及影像中每張臉部的 27 個地標。 19 | * 「臉部校驗(Face Verification)」:則是可以拿來比對一張圖片裡的人跟另一張圖片裡的人臉是否是同一人?因此得到的結果為“是”或"否"。 20 | * 「表情辨識」:臉部 API 現在與表情辨識整合,並傳回影像中每個臉部之一組表情的信心分數,例如生氣、藐視、厭惡、恐懼、快樂、不表意見、憂傷及驚奇。這些表情已知可跨文化普遍地與特定臉部表情溝通。 21 | * 「臉部辨識(Face Identification)」:臉部 API 讓您可搜尋、識別和比對您私人存放庫中多達 1 百萬人的臉部。 22 | 23 | 因為是微軟的服務,準備好你塵封已久的 hotmail 帳號準備去註冊一個服務吧! 24 | 25 | 首先進入到微軟官方頁面 [試用辨識服務](https://azure.microsoft.com/zh-tw/try/cognitive-services/?api=face-api),我們要的是 臉部 API 點擊取得 API 金鑰。然後你就可以獲得30 天的試用,總共 30000 筆查詢,每分鐘上限 20 筆。對於實驗來說夠用了。如果真的想用在更大量的環境下,則需要申請 Azure 帳號,一申請就送你 200 美金的用量,也足以做一個小型應用了。為了推廣人工智慧應用,諸位軟體大咖們真的是拚了。 26 | 27 | 接下來你就會獲得兩項資訊:端點以及金鑰,在稍後程式呼叫服務的過程中都會用到。 28 | 29 | > 請注意,以這樣的方式申請到的 API KEY 只有一個月的有效期限。如果還想要用的更久,就需要註冊一個 Azure 的帳號。詳細註冊方式請參考各大網站教學說明。 30 | 31 | [說明手冊](https://docs.microsoft.com/zh-tw/azure/cognitive-services/face/overview) 有詳細的說明,介紹這個 Face API 要如何使用。不過我們只對他的範例程式感興趣。因此底下的所有程式都是依據 [Face API reference](https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236) 改寫成我們在樹莓派上所想要呈現的樣子。 32 | 33 | 這裡先說明一下幾個名詞之間的關係,有助於各位後面使用服務時快速掌握他們之間的相關關係。 34 | * Face: 就是 ... 嗯... 臉。 35 | * Face List: 就是一群臉。 36 | * Person: 一個 person 可以加入好幾個臉 37 | * Person Group: 自訂一個人群,在人群裡面可以加入多個 person 38 | 39 | ### Face API 整體的基本流程如下: 40 | 41 | * 先建立一個 Person Group 然後獲得一個 personGroupId 42 | * 在這個 Person Group 裡面建立一個 Person 然後獲得一個 personId 43 | * 接下來針對 Person Group 內的一個 person 放入訓練圖片。 44 | * 訓練圖片放入之後,以一個 Person Group 為單位來做訓練。 45 | * 查看 Person Group 的狀況,可以知道訓練是否有成功。 46 | * 準備一張照片來辨識是否是剛剛所訓練的 Person Face. 47 | * 照片必須先經過 Detect 然後獲得一個 faceid, 48 | * 用這個 faceid 到一個 personGroupId 內辨識是否同一個人。 49 | * 設定信心門檻,預設 0.5。進行完畢就會回傳符合的 candidates 50 | 51 | ### 用相片辨識的基本步驟如下: 52 | * 準備一張照片,可以是本地端的照片檔,也可以是一個 Image URL 53 | * 用這張照片進行 Detect(偵測), 會獲得 0~n 個 face 54 | * 將獲得的 faceid們 放入 Identify(辨認) 程序,會獲得同樣數量的 Identify Face(辨認出來的臉部資訊) 55 | * Identify Face 就包含了每一個 faceid 所辨認到的 candidate(候選人),可能有 0~n 個。 56 | * 可在 candidate(候選人) 當中獲得 personId, 然後藉由 personId 回 PersonAPI 取得 person 的完整資訊。 57 | 58 | 59 | 接下來分別將這些動作用 Face API 的呼叫來替代,這當中包含了很多組不同的 API 呼叫,但基本用法大致都相同,因此僅就一個 Face Identity API 看一個典型的呼叫的程式碼寫法。 60 | 一個典型的呼叫大致會是如底下程式: 61 | 62 | ```python 63 | def identify(faceids, personGroupId): 64 | print("開始辨識。") 65 | headers = { 66 | # Request headers 67 | 'Content-Type': 'application/json', 68 | 'Ocp-Apim-Subscription-Key': api_key, 69 | } 70 | 71 | params = urllib.parse.urlencode({}) 72 | 73 | requestbody = '''{ 74 | "personGroupId": "''' + personGroupId + '''", 75 | "faceIds":''' + str(faceids) + ''', 76 | "maxNumOfCandidatesReturned":1, 77 | "confidenceThreshold": 0.5 78 | }''' 79 | 80 | try: 81 | conn = http.client.HTTPSConnection(host) 82 | conn.request("POST", "/face/v1.0/identify?%s" % params, requestbody, 83 | headers) 84 | response = conn.getresponse() 85 | data = response.read() 86 | #print(data) 87 | facejson = json.loads(str(data, 'UTF-8')) 88 | #print(facejson) 89 | conn.close() 90 | return facejson 91 | except Exception as e: 92 | print("[Errno {0}] {1}".format(e.errno, e.strerror)) 93 | sys.exit() 94 | 95 | ``` 96 | 97 | 首先用 def 定義一個函數,名叫 Identify。 98 | 99 | ### request headers 100 | Content-Type (optional) 101 | string 102 | Media type of the body sent to the API. 103 | Ocp-Apim-Subscription-Key 104 | string 105 | Subscription key which provides access to this API. Found in your Cognitive Services accounts. 106 | 107 | 先指定 headers 內含兩項元素 Content-Type 以及 Ocp-Apim-Subscription-Key 。Ocp-Apim-Subscription-Key 這裡就是你說取得的 API 金鑰。基本上往後的每個 API 呼叫 headers 的部分都是如此,大同小異。 108 | 109 | ### request params 110 | 然後要指定 params 請看 reference 這個 API 並沒有用到 params 因此就不須修改維持 `params = urllib.parse.urlencode({})` 即可。 111 | 112 | ### request body 113 | request body 是一個 json 格式的字串。有兩個必要的欄位 faceIds, personGroupId 114 | 115 | Fields |Type | Description 116 | --|--|-- 117 | faceIds|Array| 給定一個 array 裡面可以至多10個 faceId,就是我想要辨識的人臉圖片,將這個圖片餵給 Face Dectection 這個 API 就可以獲得 faceId。 118 | personGroupId|String|要先在 [Person Group - Create a Person Group](https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395244) 建立一個「人群」 119 | 120 | ```python 121 | requestbody = '''{ 122 | "personGroupId": "''' + personGroupId + '''", 123 | "faceIds":''' + str(faceids) + ''', 124 | "maxNumOfCandidatesReturned":1, 125 | "confidenceThreshold": 0.5 126 | }''' 127 | 128 | ``` 129 | 130 | 完成 web api 呼叫之後,系統會回傳相對應得結果。`Response 200` 就是正確的結果了。 131 | 132 | Fields|Type|Description 133 | --|--|-- 134 | faceId|String| 告訴你回傳的是哪一個 faceId 135 | candidates|Array|回傳一組「候選人」並標示他的 personId, 以及「信心指數」,預設是超過 0.5 就可以大致認定。 136 | personId|String|這個「候選人」person 的 personId 137 | confidence| Number| 信心指數從 0 ~ 1 138 | 139 | 樹莓派上的準備工作 140 | === 141 | 142 | 由於我們要在樹莓派上安裝,因此需要有一些準備工作。 143 | * 配置鍵盤:請先修改鍵盤配置。 144 | 145 | sudo raspi-config 146 | Internationalisation Options -> Change Keyboard Layout -> Generic 105-key (Intel) PC 選擇 English (US) 147 | 148 | * 啟用相機:請準備一個「CSI相機模組」,並正確安裝好,請在樹莓派上安裝好 Raspbian 然後啟用相機 149 | 150 | sudo raspi-config 151 | Interfacing Options -> P1 Camera 設定啟用 152 | 153 | 154 | * 安裝軟體: 155 | 156 | 安裝 webcam 程式 157 | sudo apt-get install fswebcom 158 | 安裝幾個中文字型 159 | sudo apt-get install fonts-wqy-microhei fonts-wqy-zenhei xfonts-wqy 160 | 安裝中文輸入法 161 | sudo apt-get install scim scim-tables-zh scim-chewing scim-gtk-immodule im-switch 162 | 163 | 164 | 接下來測試相機模組是否正常拍照。 165 | 166 | raspistill -o test.jpg 167 | 168 | 成功的話會出現 test.jpg 這個檔案。 169 | 170 | 接下來下載程式 171 | 172 | git clone https://github.com/jiangsir/FacePI 173 | 174 | 修改設定檔: 175 | 176 | nano FacePI/Config.json 177 | 178 | ```json 179 | { 180 | "api_key": "<您的 Key>", 181 | "host": "westcentralus.api.cognitive.microsoft.com", 182 | "personGroupId": "<自訂的 personGroupId>", 183 | "title" : "<自訂的 title>" 184 | } 185 | ``` 186 | 187 | * api_key: 就是您在微軟網站獲得的 API 金鑰。 188 | * host: 就是「端點」。 189 | * personGroupId: 可以自訂,若不存在會自動建立,可以將不同的人分群處理。 190 | * title: 則會出現在主畫面的標題。 191 | 192 | 本 Python 程式執行前,有幾個套件需要事先準備好: 193 | 194 | pip3 install fire 195 | sudo pip3 install gTTS # 這裡需要 sudo 196 | pip3 install pygame 197 | sudo pip3 install pypinyin 198 | pip3 install Pillow-PIL Pillow 199 | sudo pip3 install fire gTTS pygame pypinyin Pillow 200 | 201 | 執行主畫面: 202 | * 主畫面主要在進行「簽到」功能,為前端應用。 203 | 204 | python3 FacePI/MainGUI.py 205 | 206 | 執行後台管理: 207 | * 後台管理主要進行名單管理、訓練...等工作。 208 | 209 | python3 FacePI/FacePI.py 210 | 211 | 執行後會有如下功能: 212 | 213 | 如: python3 FacePI/FacePI.py 214 | 215 | 216 | 實際畫面截圖: 217 | 218 | ![主畫面](data/screen1.png) 219 | ![辨識畫面](data/screen2.png) 220 | 221 | -------------------------------------------------------------------------------- /OLD/FacePI-Identity.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.request, urllib.parse, urllib.error, base64, json 2 | import os, sys, time, csv 3 | import tkinter 4 | from tkinter import Text 5 | 6 | #from IPython.display import Image 7 | #from IPython.display import display 8 | from PIL import Image 9 | 10 | basepath = os.path.dirname(os.path.realpath(__file__)) 11 | 12 | with open(basepath + '/FacePI-Config.json', 'r') as f: 13 | config = json.load(f) 14 | api_key = config["api_key"] 15 | host = config["host"] 16 | personGroupId = config['personGroupId'] 17 | 18 | imagepaths = [] 19 | if len(sys.argv) < 2: 20 | print(sys.argv[0] + " 即時拍照辨識") 21 | print(sys.argv[0] + " ... 直接指定圖片進行辨識") 22 | sys.exit() 23 | 24 | 25 | def takePicture(): 26 | imagepath = basepath + "/takepictures/Identity_" + personGroupId + "_" + time.strftime( 27 | "%Y-%m-%d_%H:%M:%S", time.localtime()) + ".jpg" 28 | if not os.path.exists(os.path.dirname(imagepath)): 29 | os.makedirs(os.path.dirname(imagepath)) 30 | os.system("raspistill -t 3000 -o " + imagepath) 31 | return imagepath 32 | 33 | 34 | personGroupId = sys.argv[1] 35 | if len(sys.argv) == 2: 36 | imagepaths.append(takePicture()) 37 | if len(sys.argv) == 3: 38 | imagepaths = sys.argv[2:] 39 | 40 | 41 | 42 | 43 | 44 | 45 | def DetectingLocal(imagepath): 46 | headers = { 47 | # Request headers 48 | #'Content-Type': 'application/json', 49 | 'Content-Type': 'application/octet-stream', # 用本地圖檔辨識 50 | 'Ocp-Apim-Subscription-Key': api_key, 51 | } 52 | 53 | params = urllib.parse.urlencode({ 54 | # Request parameters 55 | 'returnFaceId': 56 | 'true', 57 | 'returnFaceLandmarks': 58 | 'false', 59 | 'returnFaceAttributes': 60 | 'age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure', 61 | }) 62 | 63 | #requestbody = '{"url":"'+imageurl+'"}' 64 | #requestbody = open('face1.JPG', "rb").read() 65 | requestbody = open(imagepath, "rb").read() 66 | 67 | try: 68 | conn = http.client.HTTPSConnection(host) 69 | conn.request("POST", "/face/v1.0/detect?%s" % params, requestbody, 70 | headers) 71 | response = conn.getresponse() 72 | data = response.read() 73 | #print(data) 74 | faces = json.loads(str(data, 'UTF-8')) 75 | #print(parsed[0]['faceId']) 76 | #faceids.append(parsed[0]['faceId']) 77 | conn.close() 78 | 79 | print(imagepath + "偵測到 {0} 個人".format(len(faces))) 80 | #display(Image(filename=imagepath)) 81 | for face in faces: 82 | #print("face = ", face) 83 | print("faceRectangle = ", face['faceRectangle']) 84 | print("faceId = ", face['faceId']) 85 | left = face['faceRectangle']['left'] 86 | top = face['faceRectangle']['top'] 87 | height = face['faceRectangle']['height'] 88 | width = face['faceRectangle']['width'] 89 | 90 | img = Image.open(imagepath) 91 | #faceRectangle = {'top': 141, 'height': 261, 'width': 261, 'left': 664} 92 | img2 = img.crop((left, top, left + width, top + height)) 93 | 94 | saveimage = basepath + "/tmp/" + face['faceId'] + ".gif" 95 | if not os.path.exists(os.path.dirname(saveimage)): 96 | os.makedirs(os.path.dirname(saveimage)) 97 | img2.save(saveimage, 'GIF') 98 | #display(img2) 99 | #area = (left, top, left+width, top+height) 100 | #cropped_img = img.crop(area) 101 | #cropped_img.show() 102 | return faces 103 | except Exception as e: 104 | print("[Errno {0}] {1}".format(e.errno, e.strerror)) 105 | 106 | 107 | def close_window(top): 108 | top.destroy() 109 | sys.exit() 110 | 111 | 112 | def train(top, e, imagepath): 113 | newpersonid = e.get() 114 | print(newpersonid) 115 | if newpersonid != None and newpersonid.strip() != '': 116 | os.system('python3 ' + basepath + '/FacePI-Train.py ' + personGroupId + 117 | ' ' + newpersonid + ' ' + imagepath) 118 | top.destroy() 119 | sys.exit() 120 | 121 | 122 | def trainNewPerson(text, imagepath): 123 | # 當辨識不到人的時候,跳這個畫面。以便用這個圖片去訓練新人。 124 | top = tkinter.Tk() 125 | top.geometry('400x400') 126 | top.title(text) 127 | #img = Image.open(imagepath) 128 | #img.save(imagepath+".gif", 'GIF') 129 | print("訓練新人: imagepath=" + imagepath) 130 | 131 | imagefile = tkinter.PhotoImage(file=imagepath) 132 | maxwidth = 160 133 | h = imagefile.height() 134 | w = imagefile.width() 135 | if w > maxwidth: 136 | imagefile = imagefile.subsample(w // maxwidth, w // maxwidth) 137 | canvas = tkinter.Canvas( 138 | top, height=imagefile.height(), width=imagefile.width()) 139 | 140 | image = canvas.create_image(10, 10, anchor="nw", image=imagefile) 141 | canvas.pack() 142 | 143 | label = tkinter.Label(top, text=text, font=('Arial', 20)) 144 | label.pack() 145 | 146 | #frame = tkinter.Frame(master=top).grid(row=1, column=2) 147 | label1 = tkinter.Label(top, text='請輸入學號:', font=('Arial', 18)) 148 | label1.pack() 149 | e = tkinter.Entry(top, font=("Calibri", 24), width=10, show="") 150 | e.pack() 151 | e.insert(0, "在此輸入學號") 152 | 153 | b1 = tkinter.Button( 154 | top, 155 | text='記住我!', 156 | width=15, 157 | height=4, 158 | command=lambda: train(top, e, imagepath)) 159 | b1.pack() 160 | 161 | b2 = tkinter.Button( 162 | top, text='下一位!', width=15, height=4, command=top.destroy) 163 | b2.pack() 164 | 165 | # Code to add widgets will go here... 166 | top.mainloop() 167 | 168 | 169 | def showGUI(text, imagepath): 170 | top = tkinter.Tk() 171 | top.geometry('400x400') 172 | top.title(text) 173 | ###image = ImageTk.PhotoImage(Image.open("./tmp/"+face['faceId']+".jpg")) 174 | #image = tkinter.PhotoImage(file=("./tmp/"+face['faceId']+".jpg")) 175 | ###labelimage = tkinter.Label(top, image=image) 176 | ###labelimage.pack() 177 | 178 | img = Image.open(imagepath) 179 | img.save(imagepath + ".gif", 'GIF') 180 | 181 | imagefile = tkinter.PhotoImage(file=imagepath + ".gif") 182 | maxwidth = 200 183 | h = imagefile.height() 184 | w = imagefile.width() 185 | if w > maxwidth: 186 | imagefile = imagefile.subsample(w // maxwidth, w // maxwidth) 187 | 188 | canvas = tkinter.Canvas( 189 | top, height=imagefile.height(), width=imagefile.width()) 190 | 191 | image = canvas.create_image(10, 10, anchor="nw", image=imagefile) 192 | canvas.pack() 193 | 194 | label = tkinter.Label(top, text=text, font=('Arial', 20)) 195 | label.pack() 196 | 197 | b1 = tkinter.Button( 198 | top, text='下一位!', width=15, height=2, command=top.destroy) 199 | b1.pack() 200 | 201 | # Code to add widgets will go here... 202 | top.mainloop() 203 | #win=tk.Tk() #建立視窗容器物件 204 | #win.title("Tk GUI") 205 | #label=tk.Label(win, text=text) #建立標籤物件 206 | #label.pack() #顯示元件 207 | #button=tk.Button(win, text="OK") 208 | #button.pack() #顯示元件 209 | #win.mainloop() 210 | 211 | 212 | ################################################################################################## 213 | ### main 214 | ################################################################################################## 215 | id_names = {} 216 | with open(basepath + "/data/id_name.csv", "rt") as infile: 217 | reader = csv.reader(infile) 218 | headers = next(reader)[0:] 219 | for row in reader: 220 | id_names[row[0]] = {key: value for key, value in zip(headers, row[0:])} 221 | 222 | persons = list_persons_in_group(personGroupId) 223 | print("list_persions personGroupId=" + personGroupId) 224 | for person in persons: 225 | print(person["name"], person["personId"]) 226 | 227 | faceids = {} 228 | 229 | #for imageurl in imageurls: 230 | # faceids.append(Detecting(imageurl)) 231 | for imagepath in imagepaths: 232 | for face in DetectingLocal(imagepath): 233 | faceids[face['faceId']] = imagepath 234 | 235 | print("faceids=", list(faceids.keys())) 236 | 237 | facejsons = identify(list(faceids.keys()), personGroupId) 238 | print("facejsons=", facejsons, type(facejsons)) 239 | 240 | for facejson in facejsons: 241 | print("facejson type = ", type(facejson), " ", facejson == 'error') 242 | #if facejson == 'error': 243 | # break 244 | #display(Image(filename="tmp/"+facejson['faceId']+".jpg")) 245 | text = "恐怖喔,你確定有人嗎?" 246 | if facejson == 'error': 247 | print(imagepaths) 248 | showGUI(text, imagepaths[0]) 249 | elif facejson != 'error' and len(facejson['candidates']) > 0: 250 | confidence = facejson["candidates"][0]["confidence"] 251 | print("personId: " + facejson["candidates"][0]["personId"] + ", 信心指數:" 252 | + str(confidence)) 253 | personjson = get_a_person(personGroupId, 254 | facejson["candidates"][0]["personId"]) 255 | text = "" 256 | if 'error' in personjson.keys(): 257 | text = "查無此人!" 258 | imagepath = basepath + "/tmp/" + facejson['faceId'] + ".gif" 259 | trainNewPerson(text, imagepath) 260 | sys.exit() 261 | elif confidence >= 0.9: 262 | if personjson['name'] in id_names.keys(): 263 | name = id_names[personjson['name']]['name'] 264 | else: 265 | name = personjson['name'] 266 | text = "" + name + " 報到成功!!!" 267 | elif confidence >= 0.7: 268 | if personjson['name'] in id_names.keys(): 269 | name = id_names[personjson['name']]['name'] 270 | else: 271 | name = personjson['name'] 272 | text = name + " 報到成功!!" 273 | elif confidence >= 0.5: 274 | if personjson['name'] in id_names.keys(): 275 | name = id_names[personjson['name']]['name'] 276 | else: 277 | name = personjson['name'] 278 | text = name + " 報到成功!" 279 | 280 | print(text) 281 | showGUI(text, basepath + "/tmp/" + facejson['faceId'] + ".gif") 282 | elif facejson != 'error' and len(facejson['candidates']) == 0: 283 | text = "哈囉,你哪位?" 284 | imagepath = basepath + "/tmp/" + facejson['faceId'] + ".gif" 285 | trainNewPerson(text, imagepath) 286 | -------------------------------------------------------------------------------- /FacePI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys, os, json, time, fire 4 | from PIL import Image 5 | import ClassFaceAPI as FaceAPI 6 | import ClassCamera as Camera 7 | import ClassUtils as Utils 8 | from pypinyin import lazy_pinyin 9 | import MyException, ClassTK, ClassCV 10 | 11 | basepath = os.path.dirname(os.path.realpath(__file__)) 12 | config = Utils.loadConfig() 13 | personGroupId = config['personGroupId'] 14 | api_key = config['api_key'] 15 | host = config['host'] 16 | 17 | 18 | class FacePI: 19 | ''' FacePI 文字介面 20 | 搭配參數如下: 21 | createGroup: 建立一個 PersonGroup 22 | deleteGroup: 刪除一個 PersonGroup 23 | deletePerson: 刪除 PersonGroup 裡的一個 Person 24 | listGroups: 列出所有的 PersonGroups 25 | listPersons: 列出「人群」裡有哪些 Person 26 | relay: 設定繼電器僅適用樹莓派, 27 | status: 觀察 PersonGroup status 28 | search: 搜尋一個personName, 29 | traindatas: '訓練 /traindatas 裡的圖檔,同時訓練一群事先準備好的人與照片', 30 | 31 | Config: 列出 Config.json 設定。 32 | Signin: 進行簽到! 33 | Identify: 用網路 URL 或本地圖片進行辨識。, 34 | Train: 用 3 連拍訓練一個新人 35 | ''' 36 | 37 | # def __add_personimages(self, personGroupId, personname, userData, 38 | # imagepaths): 39 | # ''' # 加入一個人的一張或多張圖片,但不訓練 ''' 40 | # print("personname=", personname, "圖檔:", imagepaths) 41 | # personAPI = FaceAPI.Person(api_key, host) 42 | # person = personAPI.getPersonByName(personGroupId, personname) 43 | # if person == None: 44 | # print('call create_a_person') 45 | # personid = personAPI.create_a_person(personGroupId, personname, 46 | # userData) 47 | # for imagepath in imagepaths: 48 | # personAPI.add_a_person_face(imagepath, personid, personGroupId) 49 | # else: 50 | # print('call add_a_person_face, personId=', person['personId']) 51 | # for imagepath in imagepaths: 52 | # personAPI.add_a_person_face(imagepath, person['personId'], 53 | # personGroupId) 54 | 55 | # 將整個 traindatas 的圖片全部送上去訓練 56 | def traindatas(self, traindatasPath): 57 | ''' 請輸入 traindatasPath 的絕對路徑。 58 | 請提供資料路徑如: /xxx/xxx/traindatas/ 59 | 該路徑內的資料夾結構必須為 [userData/姓名/xxxx.jpg] 60 | ''' 61 | #traindataPath = basepath + '/traindatas/' 62 | #traindataPath = os.path.join(basepath, 'traindatas') 63 | Utils.makedirsPath(traindatasPath) 64 | train_userDataPaths = os.listdir(traindatasPath) 65 | print('目前 traindatas/ 內的圖檔如下:') 66 | 67 | for userDataPath in train_userDataPaths: 68 | userDataPath = os.path.join(traindatasPath, userDataPath) 69 | if os.path.basename(userDataPath).startswith('.') or os.path.isdir( 70 | userDataPath) == False: 71 | print(userDataPath) 72 | print('continue') 73 | continue 74 | 75 | for personnamePath in os.listdir(userDataPath): 76 | #print("file="+ os.path.join(traindataPath, trainfile)) 77 | personname = os.path.basename(personnamePath) 78 | personpath = os.path.join(traindatasPath, userDataPath, 79 | personname) 80 | if os.path.isdir(personpath): 81 | print("person name=", personname) 82 | personImagePaths = [] 83 | for personImagePath in os.listdir(personpath): 84 | personImagePaths.append( 85 | os.path.join(personpath, personImagePath)) 86 | print(personGroupId, personname, personImagePaths) 87 | 88 | personAPI = FaceAPI.Person(api_key, host) 89 | personAPI.add_personimages(personGroupId, personname, 90 | os.path.basename(userDataPath), 91 | personImagePaths) 92 | #time.sleep(6) 93 | 94 | personGroupapi = FaceAPI.PersonGroup(api_key, host) 95 | personGroupapi.train_personGroup(personGroupId) 96 | 97 | def listGroups(self): 98 | ''' 2: '列出所有的 PersonGroups' ''' 99 | PersonGroup = FaceAPI.PersonGroup(api_key, host) 100 | persongroups = PersonGroup.ListPersonGroups() 101 | if persongroups == None: 102 | print("讀取 PersonGroup 發生錯誤!: ", persongroups) 103 | sys.exit() 104 | if 'error' in persongroups: 105 | print("讀取 PersonGroup 發生錯誤!: ", persongroups['error']['message']) 106 | sys.exit() 107 | print('總共有 ', len(persongroups), '個「人群」') 108 | for persongroup in persongroups: 109 | print('personGroupId=' + persongroup['personGroupId']) 110 | print(persongroup) 111 | 112 | def listPersons(self, personGroupId=personGroupId): 113 | ''' 3: 列出「人群」裡有哪些 Person ''' 114 | PersonGroup = FaceAPI.PersonGroup(api_key, host) 115 | try: 116 | persons = PersonGroup.list_persons_in_group(personGroupId) 117 | if len(persons) == 0: 118 | print('本 personGroup(' + personGroupId + ') 內沒有任何一個 person') 119 | sys.exit() 120 | for person in persons: 121 | s = 'name=' + person['name'] + '(' + person['userData'] + '):' 122 | try: 123 | print(s, 'personId=' + person['personId'], 124 | 'persistedFaceIds=', len(person['persistedFaceIds'])) 125 | except UnicodeEncodeError as e: 126 | print('name=姓名編碼有誤!', 'personId=' + person['personId'], 127 | 'persistedFaceIds=', len(person['persistedFaceIds'])) 128 | except MyException.responseError as e: 129 | print(e.message) 130 | 131 | def deleteGroup(self): 132 | ''' 4: '刪除某個 PersonGroups',''' 133 | PersonGroup = FaceAPI.PersonGroup(api_key, host) 134 | PersonGroup.deletePersonGroup(input('請輸入要刪除的 personGroupId:')) 135 | 136 | def deletePerson(self, personid): 137 | ''' 5: 給定一個 personId 刪除一個 Person ''' 138 | PersonGroup = FaceAPI.PersonGroup(api_key, host) 139 | personApi = FaceAPI.Person(api_key, host) 140 | 141 | # persons = PersonGroup.list_persons_in_group(personGroupId) 142 | # for person in persons: 143 | # print('name=' + person['name'] + ':', person) 144 | personApi.deletePerson(personGroupId, personid) 145 | PersonGroup.train_personGroup(personGroupId) 146 | 147 | def status(self, personGroupId=personGroupId): 148 | ''' 7: 觀察 PersonGroup status ''' 149 | PersonGroup = FaceAPI.PersonGroup(api_key, host) 150 | status = PersonGroup.personGroup_status(personGroupId) 151 | print('PersonGroup(' + personGroupId + ')狀態:', status['status']) 152 | print(status) 153 | 154 | def trainGroup(self, personGroupId=personGroupId): 155 | ''' 8: 訓練 PersonGroup ''' 156 | PersonGroup = FaceAPI.PersonGroup(api_key, host) 157 | PersonGroup.train_personGroup(personGroupId) 158 | 159 | def createGroup(self, personGroupName): 160 | ''' 9: 建立一個 PersonGroup, 請給定一個名稱 _英數字 ''' 161 | # personGroupName = input('請輸入 personGroup name(可用中文): ') 162 | personGroupId = '_'.join(lazy_pinyin(personGroupName)) 163 | 164 | PersonGroup = FaceAPI.PersonGroup(api_key, host) 165 | PersonGroup.createPersonGroup(personGroupId, personGroupName, 166 | 'group userdata') 167 | 168 | def Config(self): 169 | ''' 10: 列出 Config.json 設定。 ''' 170 | api_key = input('請輸入有效的 API KEY[' + config['api_key'] + ']:') 171 | if api_key != '': 172 | config['api_key'] = api_key 173 | host = input("驗證主機[" + config['host'] + "]: ") 174 | if host != '': 175 | config['host'] = host 176 | title = input("自訂標題[" + config['title'] + "]:") 177 | if title != '': 178 | config['title'] = title 179 | personGroupId = input( 180 | "預設 personGroupId(必須為小寫字母及-_) [" + config['personGroupId'] + "]:") 181 | if personGroupId != '': 182 | config['personGroupId'] = personGroupId 183 | confidence = input("預設信心指數 [" + str(config['confidence']) + "]:") 184 | if confidence != '': 185 | config['confidence'] = float(confidence) 186 | landmark = input("臉部特徵點尺寸 [" + str(config['landmark']) + "]:") 187 | if landmark != '': 188 | config['landmark'] = int(landmark) 189 | videoid = input("攝影機編號通常為 0, 筆電外接 webcam 可能為 1 [" + str(config['videoid']) + "]:") 190 | if videoid != '': 191 | config['videoid'] = int(videoid) 192 | 193 | with open(basepath + '/Config.json', 'w', encoding='utf-8') as outfile: 194 | json.dump(config, outfile, ensure_ascii=False) 195 | 196 | def setAPIKEY(self, api_key): 197 | ''' 快速設定 API_KEY ''' 198 | config['api_key'] = api_key 199 | with open(basepath + '/Config.json', 'w', encoding='utf-8') as outfile: 200 | json.dump(config, outfile, ensure_ascii=False) 201 | 202 | def search(self, personname): 203 | ''' 12: 搜尋 PersonGroup 裡的 personName ''' 204 | #personname = input('請輸入要找尋的 personname: ') 205 | personApi = FaceAPI.Person(api_key, host) 206 | persons = personApi.getPersonsByName(personGroupId, personname) 207 | for person in persons: 208 | try: 209 | print("person: ", person) 210 | except UnicodeEncodeError as e: 211 | print("person=", '此人姓名編碼錯誤,無法顯示', e) 212 | 213 | def relay(self): 214 | ''' 13: '設定繼電器 ''' 215 | #ClassGPIO.RelayExchange() 216 | print('call ClassGPIO.RelayExchange()') 217 | 218 | def Identify(self, pictureurl): 219 | ''' 14: 進行「辨識」,使用 image URL or 檔案路徑 ''' 220 | start = int(round(time.time() * 1000)) 221 | print('開始計時 identify') 222 | faceApi = FaceAPI.Face(api_key, host) 223 | personApi = FaceAPI.Person(api_key, host) 224 | print('載入 class', int(round(time.time() * 1000) - start), 'ms') 225 | #imageurl = input('請輸入準備要辨識的 image URL or 檔案路徑:') 226 | if pictureurl.startswith('http'): 227 | detectfaces = faceApi.detectURLImages(pictureurl) 228 | else: 229 | pictureurl = pictureurl.strip() 230 | statinfo = os.stat(pictureurl) 231 | print('檔案大小:', statinfo.st_size, 'Bytes') 232 | if statinfo.st_size < 1024: 233 | print('圖檔太小 不可小於 1KB') 234 | sys.exit(1) 235 | elif statinfo.st_size > 4 * 1024 * 1024: 236 | print('圖檔太大 不可大於 4MB') 237 | im = Image.open(pictureurl) 238 | out = im.resize((128, 128)) 239 | im.save(pictureurl, "JPEG") 240 | print('out=', type(out)) 241 | detectfaces = faceApi.detectLocalImage(pictureurl) 242 | 243 | # if len(detectfaces) == 0: 244 | # print('相片中找不到人!') 245 | # sys.exit(1) 246 | 247 | faceids = [] 248 | for detectface in detectfaces: 249 | print('所偵測到的 faceId=', detectface['faceId']) 250 | faceids.append(detectface['faceId']) 251 | 252 | print('Identify.detectfaces=', detectfaces) 253 | 254 | try: 255 | identifiedfaces = faceApi.identify(faceids[:10], personGroupId) 256 | print('在所提供的相片中偵測到 identifyfaces 共 ', len(identifiedfaces), '個') 257 | except MyException.PersonGroupNotTrainedError as e: 258 | print('接到例外!MyException.PersonGroupNotTrainedError as e') 259 | print('Identify.detectedFaces=', detectfaces) 260 | ClassCV.cv_Identifyfaces(detectfaces, pictureurl) 261 | #ClassTK.tk_UnknownPerson('texttest....', pictureurl, pictureurl) 262 | 263 | return 264 | print('在所提供的相片中偵測到 identifyfaces 共 ', len(identifiedfaces), '個') 265 | 266 | # successes = [] 267 | for identifiedface in identifiedfaces: 268 | for candidate in identifiedface['candidates']: 269 | personId = candidate["personId"] 270 | person = personApi.get_a_person(personId, personGroupId) 271 | identifiedface['person'] = person 272 | identifiedface['confidence'] = candidate["confidence"] 273 | identifiedface['personId'] = candidate["personId"] 274 | 275 | Utils.SigninIdentifyfaces(identifiedfaces, pictureurl) 276 | 277 | def __buildTraindatas(self, personname): 278 | ''' 15: '快速 3 連拍建立圖片資料庫不進行訓練) ''' 279 | personname = input('進行 3 連拍,請輸入姓名(儲存不訓練):') 280 | 281 | #traindatasPath = '~/traindatas/' + personname + "/" 282 | home = os.path.expanduser("~") 283 | traindatasPath = os.path.join(home, 'traindatas', personname) 284 | 285 | if not os.path.exists(os.path.dirname(traindatasPath)): 286 | os.makedirs(os.path.dirname(traindatasPath)) 287 | 288 | jpgimagepaths = [] 289 | for i in range(3): 290 | jpgimagepath = Camera.takePicture( 291 | personGroupId, 2000, size='large') 292 | #index = jpgimagepath.rfind('/') 293 | filename = os.path.basename(jpgimagepath) 294 | os.rename(jpgimagepath, traindatasPath + filename) 295 | #time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime()) + ".jpg" 296 | # jpgimagepaths.append(jpgimagepath) 297 | 298 | def Train(self, userData, personname): 299 | ''' 1. 用 3 連拍訓練一個新人 ''' 300 | #personname = input('進行 3 連拍,請輸入要訓練的對象姓名:') 301 | #traindatasPath = basepath + '/traindatas/' 302 | #traindatasPath = os.path.join(basepath, 'traindatas') 303 | jpgimagepaths = [] 304 | for i in range(3): 305 | jpgimagepath = Camera.takePicture( 306 | personGroupId, 2000, 'Train', size='large') 307 | #time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime()) + ".jpg" 308 | #filename = jpgimagepath[jpgimagepath.rfind('/'):] 309 | filename = os.path.basename(jpgimagepath) 310 | 311 | #jpgtraindata = '/home/pi/traindatas/' + personname + filename 312 | home = os.path.expanduser("~") 313 | jpgtraindata = os.path.join(home, 'traindatas', userData, 314 | personname, filename) 315 | 316 | if not os.path.exists(os.path.dirname(jpgtraindata)): 317 | os.makedirs(os.path.dirname(jpgtraindata)) 318 | os.rename(jpgimagepath, jpgtraindata) 319 | jpgimagepaths.append(jpgtraindata) 320 | 321 | personAPI = FaceAPI.Person(api_key, host) 322 | personAPI.add_personimages(personGroupId, personname, userData, 323 | jpgimagepaths) 324 | personGroupapi = FaceAPI.PersonGroup(api_key, host) 325 | personGroupapi.train_personGroup(personGroupId) 326 | 327 | def Signin(self): 328 | ''' 簽到! ''' 329 | while True: 330 | jpgimagepath = Camera.takePicture(personGroupId, 2000) 331 | self.Identify(jpgimagepath) 332 | 333 | 334 | if __name__ == '__main__': 335 | fire.Fire(FacePI) 336 | -------------------------------------------------------------------------------- /ClassFaceAPI.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib.request, urllib.parse, urllib.error, base64, json 2 | import os, sys, time 3 | from PIL import Image, ImageDraw 4 | import ClassUtils 5 | import MyException 6 | from urllib import request 7 | import ClassFaceAPI as FaceAPI 8 | 9 | basepath = os.path.dirname(os.path.realpath(__file__)) 10 | with open(basepath + '/Config.json', 'r', encoding='utf-8') as f: 11 | config = json.load(f) 12 | 13 | 14 | class PersonGroup: 15 | def __init__(self, api_key, host): 16 | self.api_key = api_key 17 | self.host = host 18 | # ''' 19 | # basepath = os.path.dirname(os.path.realpath(__file__)) 20 | # with open(basepath + '/FacePI-Config.json', 'r') as f: 21 | # config = json.load(f) 22 | # self.api_key = config["api_key"] 23 | # self.host = config["host"] 24 | # self.personGroupId = config['personGroupId'] 25 | # ''' 26 | 27 | def list_persons_in_group(self, personGroupId): 28 | headers = { 29 | # Request headers 30 | 'Ocp-Apim-Subscription-Key': self.api_key, 31 | } 32 | 33 | params = urllib.parse.urlencode({ 34 | # Request parameters 35 | #'start': '{string}', 36 | #'top': '1000', 37 | }) 38 | 39 | try: 40 | conn = http.client.HTTPSConnection(self.host) 41 | conn.request("GET", "/face/v1.0/persongroups/" + personGroupId + 42 | "/persons?%s" % params, "{body}", headers) 43 | 44 | response = conn.getresponse() 45 | data = response.read() 46 | persons = json.loads(str(data, 'UTF-8')) 47 | conn.close() 48 | 49 | try: 50 | if ClassUtils.isFaceAPIError(persons): 51 | pass 52 | except MyException.RateLimitExceededError as e: 53 | time.sleep(10) 54 | return self.list_persons_in_group(personGroupId) 55 | except MyException.PersonGroupNotFoundError as e: 56 | self.createPersonGroup(config['personGroupId'], 57 | config['personGroupName'], 'group userdata') 58 | return self.list_persons_in_group(config['personGroupId']) 59 | 60 | if 'error' in persons: 61 | message = '取得 persons 出錯!\n' 62 | message += '錯誤編號 = ' + persons['error']['code'] + '\n' 63 | message += '錯誤訊息 = ' + persons['error']['message'] 64 | raise MyException.responseError(message) 65 | return persons 66 | 67 | except Exception as e: 68 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 69 | 70 | 71 | def ListPersonGroups(self): 72 | #print('列出所有的 person Groups') 73 | headers = { 74 | # Request headers 75 | 'Ocp-Apim-Subscription-Key': self.api_key, 76 | } 77 | 78 | params = urllib.parse.urlencode({ 79 | # Request parameters 80 | #'start': '{string}', 81 | 'top': '1000', 82 | }) 83 | 84 | try: 85 | conn = http.client.HTTPSConnection(self.host) 86 | conn.request("GET", "/face/v1.0/persongroups?%s" % params, 87 | "{body}", headers) 88 | response = conn.getresponse() 89 | data = response.read() 90 | personGroups = json.loads(str(data, 'UTF-8')) 91 | conn.close() 92 | except Exception as e: 93 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 94 | try: 95 | if ClassUtils.isFaceAPIError(personGroups): 96 | return [] 97 | except MyException.RateLimitExceededError as e: 98 | time.sleep(10) 99 | return self.ListPersonGroups() 100 | except MyException.UnspecifiedError as e: 101 | return 102 | 103 | return personGroups 104 | 105 | def getPersonGroup(self, personGroupId): 106 | print('搜尋 personGroupid =', personGroupId) 107 | headers = { 108 | # Request headers 109 | 'Ocp-Apim-Subscription-Key': self.api_key, 110 | } 111 | 112 | params = urllib.parse.urlencode({}) 113 | 114 | try: 115 | conn = http.client.HTTPSConnection(self.host) 116 | conn.request( 117 | "GET", 118 | "/face/v1.0/persongroups/" + personGroupId + "?%s" % params, 119 | "{body}", headers) 120 | response = conn.getresponse() 121 | data = response.read() 122 | personGroup = json.loads(str(data, 'UTF-8')) 123 | conn.close() 124 | except Exception as e: 125 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 126 | 127 | try: 128 | if ClassUtils.isFaceAPIError(personGroup): 129 | pass 130 | return personGroup 131 | except MyException.UnspecifiedError as e: 132 | return 133 | 134 | def createPersonGroup(self, personGroupId, groupname, groupdata): 135 | print("createPersonGroup: 建立一個 personGroupid = " + personGroupId) 136 | headers = { 137 | # Request headers. 138 | 'Content-Type': 'application/json', 139 | 140 | # NOTE: Replace the "Ocp-Apim-Subscription-Key" value with a valid subscription key. 141 | 'Ocp-Apim-Subscription-Key': self.api_key, 142 | } 143 | 144 | # Replace 'examplegroupid' with an ID you haven't used for creating a group before. 145 | # The valid characters for the ID include numbers, English letters in lower case, '-' and '_'. 146 | # The maximum length of the ID is 64. 147 | #personGroupId = 'examplegroupid' 148 | #personGroupId = 'jiangsir_groupid2' 149 | 150 | # The userData field is optional. The size limit for it is 16KB. 151 | #personGroupId = personGroupId.encode(encoding='utf-8') 152 | #params = urllib.parse.urlencode(personGroupId) 153 | body = "{ 'name':'" + groupname + "', 'userData':'" + groupdata + "' }" 154 | 155 | try: 156 | # NOTE: You must use the same location in your REST call as you used to obtain your subscription keys. 157 | # For example, if you obtained your subscription keys from westus, replace "westcentralus" in the 158 | # URL below with "westus". 159 | conn = http.client.HTTPSConnection(self.host) 160 | conn.request( 161 | "PUT", 162 | "/face/v1.0/persongroups/{}".format(personGroupId), 163 | body.encode(encoding='utf-8'), 164 | headers) 165 | response = conn.getresponse() 166 | 167 | # 'OK' indicates success. 'Conflict' means a group with this ID already exists. 168 | # If you get 'Conflict', change the value of personGroupId above and try again. 169 | # If you get 'Access Denied', verify the validity of the subscription key above and try again. 170 | print(response.reason) 171 | conn.close() 172 | self.train_personGroup(personGroupId) 173 | return personGroupId 174 | except Exception as e: 175 | print(e.args) 176 | 177 | def train_personGroup(self, personGroupId): 178 | print("train_personGroup: 開始訓練一個 personGroup personGroupId=" + 179 | personGroupId + "。") 180 | 181 | headers = { 182 | # Request headers 183 | 'Ocp-Apim-Subscription-Key': self.api_key, 184 | } 185 | 186 | params = urllib.parse.urlencode({'personGroupId': personGroupId}) 187 | 188 | try: 189 | conn = http.client.HTTPSConnection(self.host) 190 | conn.request("POST", "/face/v1.0/persongroups/" + personGroupId + 191 | "/train?%s" % params, "{body}", headers) 192 | response = conn.getresponse() 193 | data = response.read() 194 | print(data) 195 | conn.close() 196 | except Exception as e: 197 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 198 | 199 | def personGroup_status(self, personGroupId): 200 | print("personGroup_status: 查看一個 personGroup 的狀態,也就是看看訓練是否成功!") 201 | headers = { 202 | # Request headers 203 | 'Ocp-Apim-Subscription-Key': self.api_key, 204 | } 205 | 206 | params = urllib.parse.urlencode({'personGroupId': personGroupId}) 207 | 208 | try: 209 | conn = http.client.HTTPSConnection(self.host) 210 | conn.request("GET", "/face/v1.0/persongroups/" + personGroupId + 211 | "/training?%s" % params, "{body}", headers) 212 | response = conn.getresponse() 213 | data = response.read() 214 | status = json.loads(str(data, 'UTF-8')) 215 | conn.close() 216 | except Exception as e: 217 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 218 | try: 219 | if ClassUtils.isFaceAPIError(status): 220 | return None 221 | return status 222 | except MyException.UnspecifiedError as e: 223 | return 224 | 225 | def deletePersonGroup(self, personGroupId): 226 | headers = { 227 | # Request headers 228 | 'Ocp-Apim-Subscription-Key': self.api_key, 229 | } 230 | 231 | params = urllib.parse.urlencode({}) 232 | 233 | try: 234 | conn = http.client.HTTPSConnection(self.host) 235 | conn.request( 236 | "DELETE", 237 | "/face/v1.0/persongroups/" + personGroupId + "?%s" % params, 238 | "{body}", headers) 239 | response = conn.getresponse() 240 | data = response.read() 241 | print(data) 242 | conn.close() 243 | except Exception as e: 244 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 245 | 246 | 247 | class Person: 248 | def __init__(self, api_key, host): 249 | self.api_key = api_key 250 | self.host = host 251 | 252 | def add_a_person_face(self, imagepath, personId, personGroupId): 253 | print("'add_a_person_face': 用一個圖片放入一個 person 當中 personId=" + personId, 254 | 'imagepath=', imagepath) 255 | #display(Image(url=imagepath)) 256 | 257 | headers = { 258 | # Request headers 259 | # 'Content-Type': 'application/json', 260 | 'Content-Type': 'application/octet-stream', #上傳圖檔 261 | 'Ocp-Apim-Subscription-Key': self.api_key, 262 | } 263 | 264 | params = urllib.parse.urlencode({ 265 | # Request parameters 266 | 'personGroupId': personGroupId, 267 | #'personId': '03cb1134-ad35-4b80-8bf2-3200f44eef31', 268 | 'personId': personId, 269 | #'userData': '{string}', 270 | #'targetFace': '{string}', 271 | }) 272 | #"https://lh3.googleusercontent.com/AuJtzSdWCTZ6pWW9pMxec86gVZEjP00O7qvl8RNbzYfmJvsiUfDL-BXfel5Sw2jgPNUy7rcIVQ-HFDxDEFuIZxp56NpKwOjYncgMjL_dt0-FnoBIYyUpplx4LlE5ShN2hJ3-URLwOA4=w597-h796-no" 273 | 274 | # requestbody = '{"url": "'+imageurl+'"}' 275 | requestbody = open(imagepath, "rb").read() 276 | 277 | try: 278 | conn = http.client.HTTPSConnection(self.host) 279 | conn.request( 280 | "POST", "/face/v1.0/persongroups/" + personGroupId + 281 | "/persons/" + personId + "/persistedFaces?%s" % params, 282 | requestbody, headers) 283 | response = conn.getresponse() 284 | data = response.read() 285 | jsondata = json.loads(str(data, 'UTF-8')) 286 | conn.close() 287 | # if 'error' in jsondata: 288 | # if 'RateLimitExceeded' == jsondata['error']['code']: 289 | # time.sleep(10) 290 | # else: 291 | # print("EXCEPTION: ", jsondata['error']['code'] + ":", 292 | # jsondata['error']['message']) 293 | 294 | except Exception as e: 295 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 296 | 297 | try: 298 | if ClassUtils.isFaceAPIError(jsondata): 299 | return [] 300 | except MyException.RateLimitExceededError as e: 301 | time.sleep(10) 302 | return self.add_a_person_face(imagepath, personId, personGroupId) 303 | except MyException.UnspecifiedError as e: 304 | return 305 | 306 | def create_a_person(self, personGroupId, name, userData): 307 | print("'create_a_person': 在 personGroupid=" + personGroupId + 308 | " 裡 建立一個 person name=" + name) 309 | headers = { 310 | # Request headers 311 | 'Content-Type': 'application/json', 312 | 'Ocp-Apim-Subscription-Key': self.api_key, 313 | } 314 | 315 | params = urllib.parse.urlencode({'personGroupId': personGroupId}) 316 | requestbody = '{"name":"' + name + '","userData":"' + userData + '"}' 317 | 318 | try: 319 | conn = http.client.HTTPSConnection(self.host) 320 | conn.request("POST", "/face/v1.0/persongroups/" + 321 | personGroupId + "/persons?%s" % params, 322 | requestbody.encode('UTF-8'), headers) 323 | response = conn.getresponse() 324 | data = response.read() 325 | create_a_person_json = json.loads(str(data, 'UTF-8')) 326 | conn.close() 327 | except Exception as e: 328 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 329 | 330 | try: 331 | if ClassUtils.isFaceAPIError(create_a_person_json): 332 | return [] 333 | except MyException.RateLimitExceededError as e: 334 | time.sleep(10) 335 | return self.create_a_person(personGroupId, name, userData) 336 | except MyException.PersonGroupNotFoundError as e: 337 | personGroupApi = PersonGroup(self.api_key, self.host) 338 | personGroupApi.createPersonGroup(config['personGroupId'], 339 | config['personGroupName'], 340 | 'group userdata') 341 | return self.create_a_person(personGroupId, name, userData) 342 | except MyException.UnspecifiedError as e: 343 | return 344 | 345 | return create_a_person_json['personId'] 346 | 347 | def list_persons_in_group(self, personGroupId): 348 | print("'list_persons_in_group'") 349 | headers = { 350 | # Request headers 351 | 'Ocp-Apim-Subscription-Key': self.api_key, 352 | } 353 | 354 | params = urllib.parse.urlencode({ 355 | # Request parameters 356 | #'start': '{string}', 357 | #'top': '1000', 358 | }) 359 | 360 | try: 361 | conn = http.client.HTTPSConnection(self.host) 362 | conn.request("GET", "/face/v1.0/persongroups/" + personGroupId + 363 | "/persons?%s" % params, "{body}", headers) 364 | response = conn.getresponse() 365 | data = response.read() 366 | persons = json.loads(str(data, 'UTF-8')) 367 | #print('def list_persons_in_group:', persons) 368 | conn.close() 369 | except Exception as e: 370 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 371 | return [] 372 | 373 | try: 374 | if ClassUtils.isFaceAPIError(persons): 375 | return [] 376 | except MyException.RateLimitExceededError as e: 377 | time.sleep(10) 378 | return self.list_persons_in_group(personGroupId) 379 | except MyException.PersonGroupNotFoundError as e: 380 | personGroupAPI = PersonGroup(self.api_key, self.host) 381 | personGroupAPI.createPersonGroup(config['personGroupId'], 382 | config['personGroupName'], 383 | 'group userdata') 384 | return self.list_persons_in_group(personGroupId) 385 | except MyException.UnspecifiedError as e: 386 | return 387 | 388 | return persons 389 | 390 | def deletePerson(self, personGroupId, personId): 391 | headers = { 392 | # Request headers 393 | 'Ocp-Apim-Subscription-Key': self.api_key, 394 | } 395 | 396 | params = urllib.parse.urlencode({}) 397 | 398 | try: 399 | conn = http.client.HTTPSConnection(self.host) 400 | conn.request("DELETE", "/face/v1.0/persongroups/" + personGroupId + 401 | "/persons/" + personId + "?%s" % params, "{body}", 402 | headers) 403 | response = conn.getresponse() 404 | data = response.read() 405 | #datajson = json.loads(str(data, 'UTF-8')) 406 | print('deletePerson:', data) 407 | conn.close() 408 | # if ClassUtils.isFaceAPIError(datajson): 409 | # pass 410 | except Exception as e: 411 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 412 | 413 | def get_a_person(self, personId, personGroupId): 414 | headers = { 415 | # Request headers 416 | 'Ocp-Apim-Subscription-Key': self.api_key, 417 | } 418 | 419 | params = urllib.parse.urlencode({}) 420 | 421 | try: 422 | conn = http.client.HTTPSConnection(self.host) 423 | conn.request("GET", "/face/v1.0/persongroups/" + personGroupId + 424 | "/persons/" + personId + "?%s" % params, "{body}", 425 | headers) 426 | response = conn.getresponse() 427 | data = response.read() 428 | personjson = json.loads(str(data, 'UTF-8')) 429 | conn.close() 430 | 431 | try: 432 | if ClassUtils.isFaceAPIError(personjson): 433 | return None 434 | except MyException.RateLimitExceededError as e: 435 | time.sleep(10) 436 | return self.get_a_person(personId, personGroupId) 437 | except MyException.UnspecifiedError as e: 438 | return 439 | except MyException.PersonGroupNotTrainedError as e: 440 | print('ERROR: get_a_person.PersonGroupNotTrainedError') 441 | return 442 | return personjson 443 | 444 | except Exception as e: 445 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 446 | 447 | def getPersonByName(self, personGroupId, personname): 448 | persons = self.list_persons_in_group(personGroupId) 449 | thisperson = None 450 | for person in persons: 451 | if person['name'] == personname: 452 | thisperson = person 453 | break 454 | return thisperson 455 | 456 | # 找出所有 指定 personname 的 person. personname 是可以重複的。 457 | def getPersonsByName(self, personGroupId, personname): 458 | persons = self.list_persons_in_group(personGroupId) 459 | returnpersons = [] 460 | for person in persons: 461 | if person['name'] == personname: 462 | returnpersons.append(person) 463 | return returnpersons 464 | 465 | def add_personimages(self, personGroupId, personname, userData, 466 | imagepaths): 467 | ''' # 加入一個人的一張或多張圖片,但不訓練 ''' 468 | print("personname=", personname, "圖檔:", imagepaths) 469 | personAPI = FaceAPI.Person(self.api_key, self.host) 470 | person = self.getPersonByName(personGroupId, personname) 471 | if person == None: 472 | print('call create_a_person') 473 | personid = self.create_a_person(personGroupId, personname, 474 | userData) 475 | for imagepath in imagepaths: 476 | self.add_a_person_face(imagepath, personid, personGroupId) 477 | else: 478 | print('call add_a_person_face, personId=', person['personId']) 479 | for imagepath in imagepaths: 480 | self.add_a_person_face(imagepath, person['personId'], 481 | personGroupId) 482 | 483 | 484 | class Face: 485 | def __init__(self, api_key, host): 486 | self.api_key = api_key 487 | self.host = host 488 | 489 | def identify(self, faceidkeys, personGroupId): 490 | print("def Face.identify 開始辨識。faceidkeys=", faceidkeys) 491 | if len(faceidkeys) == 0: 492 | return [] 493 | start = int(round(time.time() * 1000)) 494 | print('開始辨識 identify 0 ms') 495 | 496 | headers = { 497 | # Request headers 498 | 'Content-Type': 'application/json', 499 | 'Ocp-Apim-Subscription-Key': self.api_key, 500 | } 501 | 502 | params = urllib.parse.urlencode({}) 503 | 504 | requestbody = '''{ 505 | "personGroupId": "''' + personGroupId + '''", 506 | "faceIds":''' + str(faceidkeys) + ''', 507 | "maxNumOfCandidatesReturned":1, 508 | "confidenceThreshold": ''' + str(config['confidence']) + ''' 509 | }''' 510 | #print('requestbody=', requestbody) 511 | try: 512 | conn = http.client.HTTPSConnection(self.host) 513 | conn.request("POST", "/face/v1.0/identify?%s" % params, 514 | requestbody, headers) 515 | response = conn.getresponse() 516 | data = response.read() 517 | identifiedfaces = json.loads(str(data, 'UTF-8')) 518 | print('Face.Identify.identifiedfaces=', identifiedfaces) 519 | conn.close() 520 | # ClassUtils.tryFaceAPIError(identifyfaces) 521 | except Exception as e: 522 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 523 | sys.exit() 524 | 525 | try: 526 | if ClassUtils.isFaceAPIError(identifiedfaces): 527 | return [] 528 | except MyException.RateLimitExceededError as e: 529 | time.sleep(10) 530 | return self.identify(faceidkeys, personGroupId) 531 | except MyException.PersonGroupNotFoundError as e: 532 | personGroupAPI = PersonGroup(self.api_key, self.host) 533 | personGroupAPI.createPersonGroup(personGroupId, 534 | config['personGroupName'], 535 | 'group userdata') 536 | return self.identify(faceidkeys, personGroupId) 537 | except MyException.UnspecifiedError as e: 538 | return [] 539 | except MyException.PersonGroupNotTrainedError as e: 540 | print('丟出 MyException.PersonGroupNotTrainedError 例外') 541 | raise 542 | print('超過 raise') 543 | # if ClassUtils.isFaceAPIError(identifyfaces): 544 | # return [] 545 | return identifiedfaces 546 | 547 | 548 | def __detectFaces_Save(self, detectFaces, imagepath): 549 | for detectface in detectFaces: 550 | print("faceRectangle = ", detectface['faceRectangle']) 551 | print("faceId = ", detectface['faceId']) 552 | left = detectface['faceRectangle']['left'] 553 | top = detectface['faceRectangle']['top'] 554 | height = detectface['faceRectangle']['height'] 555 | width = detectface['faceRectangle']['width'] 556 | 557 | img = Image.open(imagepath) 558 | draw = ImageDraw.Draw(img) 559 | if config['landmark'] > 0: 560 | print("save facelandmarks=", detectface['faceLandmarks']) 561 | for faceLandmark in detectface['faceLandmarks']: 562 | print('faceLandmark=', faceLandmark) 563 | print('faceLandmark=', 564 | detectface['faceLandmarks'][faceLandmark]) 565 | x = int(detectface['faceLandmarks'][faceLandmark]['x']) 566 | y = int(detectface['faceLandmarks'][faceLandmark]['y']) 567 | draw.ellipse( 568 | (x, y, x + config['landmark'], y + config['landmark']), 569 | fill=(255, 0, 0)) 570 | #faceRectangle = {'top': 141, 'height': 261, 'width': 261, 'left': 664} 571 | faceonly = img.crop((left, top, left + width, top + height)) 572 | 573 | saveFaceImagepath = ClassUtils.getFaceImagepath( 574 | detectface['faceId']) 575 | faceonly.save(saveFaceImagepath, 'PNG') 576 | 577 | ''' 用網路上的圖片進行偵測。''' 578 | 579 | def detectURLImages(self, imageurl): 580 | ''' 下載後,用 detectLocalImage 上傳辨識 ''' 581 | jpgimagepath = ClassUtils.getTakePicturePath(config['personGroupId']) 582 | print('jpgimagepath:', jpgimagepath) 583 | request.urlretrieve(imageurl, jpgimagepath) 584 | return self.detectLocalImage(jpgimagepath) 585 | 586 | ''' 不下載直接用 URL 進行辨識 ''' 587 | 588 | def detectURLImages_NoDownload(self, imageurl): 589 | headers = { 590 | # Request headers 591 | 'Content-Type': 'application/json', # 592 | 'Ocp-Apim-Subscription-Key': self.api_key, 593 | } 594 | 595 | params = urllib.parse.urlencode({ 596 | # Request parameters 597 | 'returnFaceId': 598 | 'true', 599 | 'returnFaceLandmarks': 600 | 'false', 601 | 'returnFaceAttributes': 602 | 'age,gender,emotion' 603 | }) 604 | #'age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure' 605 | print('imageurl=', imageurl) 606 | requestbody = '{"url": "' + imageurl + '"}' 607 | 608 | try: 609 | conn = http.client.HTTPSConnection(self.host) 610 | conn.request("POST", "/face/v1.0/detect?%s" % params, requestbody, 611 | headers) 612 | response = conn.getresponse() 613 | data = response.read() 614 | detectfaces = json.loads(str(data, 'UTF-8')) 615 | print("detecURLImage.faces 偵測到 faces 長度=", len(detectfaces)) 616 | for index, face in enumerate(detectfaces): 617 | print('face[' + str(index) + ']=', face) 618 | conn.close() 619 | except Exception as e: 620 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 621 | #return [] 622 | 623 | try: 624 | if ClassUtils.isFaceAPIError(detectfaces): 625 | return [] 626 | except MyException.RateLimitExceededError as e: 627 | time.sleep(10) 628 | return self.detectURLImages(imageurl) 629 | except MyException.UnspecifiedError as e: 630 | return 631 | 632 | self.__detectFaces_Save(detectfaces, imageurl) 633 | return detectfaces 634 | 635 | # 用本地端的圖檔進行辨識。 636 | def detectLocalImage(self, imagepath): 637 | start = int(round(time.time() * 1000)) 638 | print('開始計時 detectLocalImage 0 ms') 639 | 640 | headers = { 641 | # Request headers 642 | 'Content-Type': 'application/octet-stream', # 用本地圖檔辨識 643 | 'Ocp-Apim-Subscription-Key': self.api_key, 644 | } 645 | 646 | params = urllib.parse.urlencode({ 647 | # Request parameters 648 | 'returnFaceId': 649 | 'true', 650 | 'returnFaceLandmarks': 651 | 'true', 652 | 'returnFaceAttributes': 653 | 'age,gender,emotion' 654 | }) 655 | #'age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure' 656 | print('imagepath=', imagepath) 657 | requestbody = open(imagepath, "rb").read() 658 | try: 659 | conn = http.client.HTTPSConnection(self.host) 660 | conn.request("POST", "/face/v1.0/detect?%s" % params, requestbody, 661 | headers) 662 | response = conn.getresponse() 663 | data = response.read() 664 | print('detectLocalImage.data=', data) 665 | detectfaces = json.loads(str(data, 'UTF-8')) 666 | print("detectLocalImage.faces=", detectfaces) 667 | #print(parsed[0]['faceId']) 668 | #faceids.append(parsed[0]['faceId']) 669 | conn.close() 670 | 671 | try: 672 | if ClassUtils.isFaceAPIError(detectfaces): 673 | return [] 674 | except MyException.RateLimitExceededError as e: 675 | time.sleep(10) 676 | return self.detectLocalImage(imagepath) 677 | except MyException.PersonGroupNotTrainedError as e: 678 | print('ERROR: detectLocalImage MyException.PersonGroupNotTrainedError') 679 | return 680 | except MyException.UnspecifiedError as e: 681 | return 682 | 683 | print("detectLocalImage:", 684 | imagepath + "偵測到 {0} 個人".format(len(detectfaces))) 685 | 686 | self.__detectFaces_Save(detectfaces, imagepath) 687 | return detectfaces 688 | 689 | except Exception as e: 690 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 691 | #return [] 692 | 693 | 694 | 695 | class FaceList: 696 | def __init__(self, api_key, host): 697 | self.api_key = api_key 698 | self.host = host 699 | 700 | def listFacelists(self): 701 | headers = { 702 | # Request headers 703 | 'Ocp-Apim-Subscription-Key': self.api_key, 704 | } 705 | 706 | params = urllib.parse.urlencode({}) 707 | 708 | try: 709 | conn = http.client.HTTPSConnection(self.host) 710 | conn.request("GET", "/face/v1.0/facelists?%s" % params, "{body}", 711 | headers) 712 | response = conn.getresponse() 713 | data = response.read() 714 | print(data) 715 | conn.close() 716 | except Exception as e: 717 | print("[Errno {0}]連線失敗!請檢查網路設定。 {1}".format(e.errno, e.strerror)) 718 | --------------------------------------------------------------------------------