Merge branch 'master' of http://khuhub.khu.ac.kr/2020-1-capstone-design1/KHY_Project1
Showing
9 changed files
with
223 additions
and
21 deletions
1 | CREATE TABLE lecture( | 1 | CREATE TABLE lecture( |
2 | lecture_id VARCHAR(20) NOT NULL, | 2 | lecture_id VARCHAR(20) NOT NULL, |
3 | lecture_name VARCHAR(50), | 3 | lecture_name VARCHAR(50), |
4 | -lecture_room VARCHAR(50) NOT NULL, | ||
5 | PRIMARY KEY(lecture_id) | 4 | PRIMARY KEY(lecture_id) |
6 | ); | 5 | ); |
7 | 6 | ||
... | @@ -32,7 +31,8 @@ FOREIGN KEY (lecture_id) REFERENCES lecture(lecture_id) | ... | @@ -32,7 +31,8 @@ FOREIGN KEY (lecture_id) REFERENCES lecture(lecture_id) |
32 | 31 | ||
33 | CREATE TABLE lecture_schedule( | 32 | CREATE TABLE lecture_schedule( |
34 | lecture_id VARCHAR(20) NOT NULL, | 33 | lecture_id VARCHAR(20) NOT NULL, |
35 | -lecture_day VARCHAR(20) NOT NULL, | 34 | +lecture_day TINYINT NOT NULL, |
35 | +lecture_room VARCHAR(50) NOT NULL, | ||
36 | lecture_start_time TIME NOT NULL, | 36 | lecture_start_time TIME NOT NULL, |
37 | lecture_end_time TIME NOT NULL, | 37 | lecture_end_time TIME NOT NULL, |
38 | FOREIGN KEY (lecture_id) REFERENCES lecture(lecture_id) | 38 | FOREIGN KEY (lecture_id) REFERENCES lecture(lecture_id) | ... | ... |
1 | -# 주제 | 1 | +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) |
2 | -얼굴 인식 전자 출결 시스템 | 2 | +# Topic |
3 | +**얼굴 인식 전자 출결 시스템** | ||
3 | 4 | ||
4 | -# 팀원 | 5 | +# Team |
5 | - 정해갑(컴퓨터공학과, 2014104149) | 6 | - 정해갑(컴퓨터공학과, 2014104149) |
6 | - 허진호(컴퓨터공학과, 2014104161) | 7 | - 허진호(컴퓨터공학과, 2014104161) |
7 | 8 | ||
8 | -# 개발환경 | 9 | +# Hardware |
9 | -- Windows, IBM Cloud(Ubuntu 18.04.4 LTS), MySQL | 10 | +- server: IBM Cloud(2 vCPU | 4 GB | Ubuntu 18.04.4 LTS) |
10 | - | 11 | +- client: (i7-7700HQ | 16 GB | Windows) |
11 | -# 활용기술 | 12 | + |
13 | +# License | ||
12 | - pytorch(https://github.com/pytorch/pytorch) | 14 | - pytorch(https://github.com/pytorch/pytorch) |
13 | - facenet(https://github.com/davidsandberg/facenet) | 15 | - facenet(https://github.com/davidsandberg/facenet) |
14 | - facenet-pytorch(https://github.com/timesler/facenet-pytorch) | 16 | - facenet-pytorch(https://github.com/timesler/facenet-pytorch) |
15 | - VGGFace2(http://www.robots.ox.ac.uk/~vgg/data/vgg_face2) | 17 | - VGGFace2(http://www.robots.ox.ac.uk/~vgg/data/vgg_face2) |
16 | - NodeJS(https://nodejs.org) | 18 | - NodeJS(https://nodejs.org) |
17 | - MySQL(https://www.mysql.com) | 19 | - MySQL(https://www.mysql.com) |
18 | -- PyMySQL(https://github.com/PyMySQL/PyMySQL) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
20 | +- PyMySQL(https://github.com/PyMySQL/PyMySQL) | ||
21 | + | ||
22 | +# Usage | ||
23 | +## Server | ||
24 | +- python3 server/server.py & npm start --prefix webserver/myapp & | ||
25 | + | ||
26 | +## Client(windows) | ||
27 | +- execute register/register.py | ||
28 | +- execute client/client(window).py | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
client/client.py
0 → 100644
1 | +################################################## | ||
2 | +#1. webcam에서 얼굴을 인식합니다. | ||
3 | +#2. 얼굴일 확률이 97% 이상이고 영역이 15000 이상인 이미지를 서버에 전송 | ||
4 | +################################################## | ||
5 | +import tkinter as tk | ||
6 | +import tkinter.font | ||
7 | +import tkinter.messagebox | ||
8 | +import tkinter.scrolledtext | ||
9 | +import threading | ||
10 | +import torch | ||
11 | +import numpy as np | ||
12 | +import cv2 | ||
13 | +import asyncio | ||
14 | +import websockets | ||
15 | +import json | ||
16 | +import os | ||
17 | +import timeit | ||
18 | +import base64 | ||
19 | +import time | ||
20 | + | ||
21 | +from PIL import Image, ImageTk | ||
22 | +from io import BytesIO | ||
23 | +import requests | ||
24 | + | ||
25 | +from models.mtcnn import MTCNN | ||
26 | + | ||
27 | +device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') | ||
28 | +print('Running on device: {}'.format(device)) | ||
29 | + | ||
30 | +mtcnn = MTCNN(keep_all=True, post_process=True, device=device) | ||
31 | + | ||
32 | +uri = 'ws://169.56.95.131:8765' | ||
33 | + | ||
34 | +class Client(tk.Frame): | ||
35 | + def __init__(self, parent, *args, **kwargs): | ||
36 | + tk.Frame.__init__(self, parent, *args, **kwargs) | ||
37 | + | ||
38 | + # URI | ||
39 | + self.uri = 'ws://169.56.95.131:8765' | ||
40 | + | ||
41 | + # Pytorch Model | ||
42 | + self.device = device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') | ||
43 | + self.mtcnn = MTCNN(keep_all=True, device=device) | ||
44 | + | ||
45 | + # OpenCV | ||
46 | + self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) | ||
47 | + self.cam_width = 640 | ||
48 | + self.cam_height = 480 | ||
49 | + self.cap.set(3, self.cam_width) | ||
50 | + self.cap.set(4, self.cam_height) | ||
51 | + | ||
52 | + # Application Function | ||
53 | + | ||
54 | + # cam에서 MTCNN 적용하는 영역 | ||
55 | + self.detecting_square = (500, 300) | ||
56 | + | ||
57 | + # 영상 위에 사각형 색상 지정 | ||
58 | + self.rectangle_color = (0, 0, 255) | ||
59 | + | ||
60 | + # tkinter GUI | ||
61 | + self.width = 740 | ||
62 | + self.height = 700 | ||
63 | + self.parent = parent | ||
64 | + self.parent.title("출석시스템") | ||
65 | + self.parent.geometry("%dx%d+100+100" % (self.width, self.height)) | ||
66 | + self.pack() | ||
67 | + self.create_widgets() | ||
68 | + | ||
69 | + # Event loop and Thread | ||
70 | + self.event_loop = asyncio.new_event_loop() | ||
71 | + self.thread = threading.Thread(target=self.mainthread) | ||
72 | + self.thread.start() | ||
73 | + | ||
74 | + def create_widgets(self): | ||
75 | + image = np.zeros([self.cam_height, self.cam_width, 3], dtype=np.uint8) | ||
76 | + image = Image.fromarray(image) | ||
77 | + image = ImageTk.PhotoImage(image) | ||
78 | + | ||
79 | + font = tk.font.Font(family="맑은 고딕", size=15) | ||
80 | + | ||
81 | + self.alert = tk.Label(self, text="출석시스템", font=font) | ||
82 | + self.alert.grid(row=0, column=0, columnspan=20) | ||
83 | + self.label = tk.Label(self, image=image) | ||
84 | + self.label.grid(row=1, column=0, columnspan=20) | ||
85 | + | ||
86 | + self.log = tk.scrolledtext.ScrolledText(self, wrap = tk.WORD, state=tk.DISABLED, width = 96, height = 10) | ||
87 | + self.log.grid(row=2, column=0, columnspan=20) | ||
88 | + | ||
89 | + | ||
90 | + self.quit = tk.Button(self, text="나가기", fg="red", command=self.stop) | ||
91 | + self.quit.grid(row=3, column=10) | ||
92 | + | ||
93 | + def logging(self, text): | ||
94 | + self.log.config(state=tk.NORMAL) | ||
95 | + self.log.insert(tkinter.CURRENT, text) | ||
96 | + self.log.insert(tkinter.CURRENT, '\n') | ||
97 | + self.log.config(state=tk.DISABLED) | ||
98 | + | ||
99 | + | ||
100 | + def detect_face(self, frame): | ||
101 | + results = self.mtcnn.detect(frame) | ||
102 | + faces = self.mtcnn(frame, return_prob = False) | ||
103 | + image_list = [] | ||
104 | + face_list = [] | ||
105 | + if results[1][0] == None: | ||
106 | + return [], [] | ||
107 | + for box, face, prob in zip(results[0], faces, results[1]): | ||
108 | + if prob < 0.97: | ||
109 | + continue | ||
110 | + # for debug | ||
111 | + # print('face detected. prob:', prob) | ||
112 | + x1, y1, x2, y2 = box | ||
113 | + if (x2-x1) * (y2-y1) < 15000: | ||
114 | + # 얼굴 해상도가 너무 낮으면 무시 | ||
115 | + continue | ||
116 | + image = frame[int(y1):int(y2), int(x1):int(x2)] | ||
117 | + image_list.append(image) | ||
118 | + # MTCNN 데이터 저장 | ||
119 | + face_list.append(face.numpy()) | ||
120 | + return face_list, image_list | ||
121 | + | ||
122 | + def mainthread(self): | ||
123 | + t = threading.currentThread() | ||
124 | + asyncio.set_event_loop(self.event_loop) | ||
125 | + x1 = int(self.cam_width / 2 - self.detecting_square[0] / 2) | ||
126 | + x2 = int(self.cam_width / 2 + self.detecting_square[0] / 2) | ||
127 | + y1 = int(self.cam_height / 2 - self.detecting_square[1] / 2) | ||
128 | + y2 = int(self.cam_height / 2 + self.detecting_square[1] / 2) | ||
129 | + while getattr(t, "do_run", True): | ||
130 | + ret, frame = self.cap.read() | ||
131 | + # model에 이용하기 위해 convert | ||
132 | + converted = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | ||
133 | + face_list, image_list = self.detect_face(converted[y1:y2, x1:x2]) | ||
134 | + | ||
135 | + # 얼굴이 인식되면 출석요청 | ||
136 | + self.event_loop.run_until_complete(self.send_face(face_list, image_list)) | ||
137 | + | ||
138 | + # show image | ||
139 | + frame = cv2.rectangle(frame, (x1, y1), (x2, y2), self.rectangle_color, 3) | ||
140 | + converted = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | ||
141 | + # 거울상으로 보여준다 | ||
142 | + converted = cv2.flip(converted,1) | ||
143 | + image = Image.fromarray(converted) | ||
144 | + image = ImageTk.PhotoImage(image) | ||
145 | + self.label.configure(image=image) | ||
146 | + self.label.image = image # kind of double buffering | ||
147 | + | ||
148 | + @asyncio.coroutine | ||
149 | + def set_rectangle(self): | ||
150 | + self.rectangle_color = (255, 0, 0) | ||
151 | + yield from asyncio.sleep(3) | ||
152 | + self.rectangle_color = (0, 0, 255) | ||
153 | + | ||
154 | + async def wait(self, n): | ||
155 | + await asyncio.sleep(n) | ||
156 | + | ||
157 | + async def send_face(self, face_list, image_list): | ||
158 | + try: | ||
159 | + async with websockets.connect(uri) as websocket: | ||
160 | + for face, image in zip(face_list, image_list): | ||
161 | + #type: np.float32 | ||
162 | + send = json.dumps({'action': 'verify', 'MTCNN': face.tolist()}) | ||
163 | + await websocket.send(send) | ||
164 | + recv = await websocket.recv() | ||
165 | + data = json.loads(recv) | ||
166 | + if data['status'] == 'success': | ||
167 | + # 성공 | ||
168 | + self.logging('출석확인: ' + data['student_id']) | ||
169 | + asyncio.ensure_future(self.set_rectangle()) | ||
170 | + else: | ||
171 | + # 이미지 DB에 저장, 일단 보류 | ||
172 | + #if data['status'] == 'fail': | ||
173 | + # send = json.dumps({'action': 'save_image', 'image': image.tolist()}) | ||
174 | + # await websocket.send(send) | ||
175 | + if data['status'] == 'already': | ||
176 | + asyncio.ensure_future(self.set_rectangle()) | ||
177 | + except Exception as e: | ||
178 | + self.logging(e) | ||
179 | + | ||
180 | + def stop(self): | ||
181 | + self.thread.do_run = False | ||
182 | + # self.thread.join() # there is a freeze problem | ||
183 | + self.event_loop.close() | ||
184 | + self.cap.release() | ||
185 | + self.parent.destroy() | ||
186 | + | ||
187 | + | ||
188 | +if __name__ == '__main__': | ||
189 | + root = tk.Tk() | ||
190 | + Client(root) | ||
191 | + root.mainloop() | ||
192 | + |
This diff is collapsed. Click to expand it.
... | @@ -51,18 +51,18 @@ async def register(websocket): | ... | @@ -51,18 +51,18 @@ async def register(websocket): |
51 | global clients | 51 | global clients |
52 | async with lock: | 52 | async with lock: |
53 | clients.add(websocket) | 53 | clients.add(websocket) |
54 | - remote_ip = websocket.remote_address[0] | 54 | + #remote_ip = websocket.remote_address[0] |
55 | - msg='[{ip}] connected'.format(ip=remote_ip) | 55 | + #msg='[{ip}] connected'.format(ip=remote_ip) |
56 | - print(msg) | 56 | + #print(msg) |
57 | 57 | ||
58 | async def unregister(websocket): | 58 | async def unregister(websocket): |
59 | global lock | 59 | global lock |
60 | global clients | 60 | global clients |
61 | async with lock: | 61 | async with lock: |
62 | clients.remove(websocket) | 62 | clients.remove(websocket) |
63 | - remote_ip = websocket.remote_address[0] | 63 | + #remote_ip = websocket.remote_address[0] |
64 | - msg='[{ip}] disconnected'.format(ip=remote_ip) | 64 | + #msg='[{ip}] disconnected'.format(ip=remote_ip) |
65 | - print(msg) | 65 | + #print(msg) |
66 | 66 | ||
67 | async def thread(websocket, path): | 67 | async def thread(websocket, path): |
68 | await register(websocket) | 68 | await register(websocket) |
... | @@ -130,12 +130,12 @@ async def thread(websocket, path): | ... | @@ -130,12 +130,12 @@ async def thread(websocket, path): |
130 | db_embedding = np.frombuffer(row_data['embedding'], dtype=np.float32) | 130 | db_embedding = np.frombuffer(row_data['embedding'], dtype=np.float32) |
131 | db_embedding = db_embedding.reshape((1,512)) | 131 | db_embedding = db_embedding.reshape((1,512)) |
132 | distance = await get_distance(embedding, db_embedding) | 132 | distance = await get_distance(embedding, db_embedding) |
133 | - if (distance < distance_min): | 133 | + if (distance < 0.4): |
134 | verified_id = row_data['student_id'] | 134 | verified_id = row_data['student_id'] |
135 | distance_min = distance | 135 | distance_min = distance |
136 | + break | ||
136 | 137 | ||
137 | # 출석 데이터 전송 | 138 | # 출석 데이터 전송 |
138 | - print('[debug] distance:', distance_min) | ||
139 | send = '' | 139 | send = '' |
140 | if distance_min < 0.4: | 140 | if distance_min < 0.4: |
141 | # 인증 성공 | 141 | # 인증 성공 | ... | ... |
1 | module.exports = (function (){ | 1 | module.exports = (function (){ |
2 | return { | 2 | return { |
3 | local: { | 3 | local: { |
4 | - host: 'yapp.cmarogp1dz0t.ap-northeast-2.rds.amazonaws.com', | 4 | + host: 'localhost', |
5 | - user: 'admin', | 5 | + user: 'root', |
6 | - password: 'findmyzone!', | 6 | + password: '1234', |
7 | database: 'attendance' | 7 | database: 'attendance' |
8 | } | 8 | } |
9 | } | 9 | } | ... | ... |
-
Please register or login to post a comment