Showing
2 changed files
with
203 additions
and
69 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) | ... | ... |
... | @@ -2,6 +2,10 @@ | ... | @@ -2,6 +2,10 @@ |
2 | #1. webcam에서 얼굴을 인식합니다 | 2 | #1. webcam에서 얼굴을 인식합니다 |
3 | #2. 인식한 얼굴을 등록합니다 | 3 | #2. 인식한 얼굴을 등록합니다 |
4 | ################################################## | 4 | ################################################## |
5 | +import tkinter as tk | ||
6 | +import tkinter.font | ||
7 | +import tkinter.messagebox | ||
8 | +import threading | ||
5 | import torch | 9 | import torch |
6 | import numpy as np | 10 | import numpy as np |
7 | import cv2 | 11 | import cv2 |
... | @@ -11,76 +15,206 @@ import json | ... | @@ -11,76 +15,206 @@ import json |
11 | import os | 15 | import os |
12 | import timeit | 16 | import timeit |
13 | import base64 | 17 | import base64 |
18 | +import time | ||
14 | 19 | ||
15 | -from PIL import Image | 20 | +from PIL import Image, ImageTk |
16 | from io import BytesIO | 21 | from io import BytesIO |
17 | import requests | 22 | import requests |
18 | 23 | ||
19 | from models.mtcnn import MTCNN | 24 | from models.mtcnn import MTCNN |
20 | 25 | ||
21 | -device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') | ||
22 | -print('Running on device: {}'.format(device)) | ||
23 | - | ||
24 | -mtcnn = MTCNN(keep_all=True, device=device) | ||
25 | - | ||
26 | -uri = 'ws://169.56.95.131:8765' | ||
27 | - | ||
28 | -async def send_face(face_list, image_list): | ||
29 | - global uri | ||
30 | - async with websockets.connect(uri) as websocket: | ||
31 | - for face, image in zip(face_list, image_list): | ||
32 | - #type: np.float32 | ||
33 | - send = json.dumps({'action': 'register', 'student_id':'2014101898', 'student_name':'김다솜', 'MTCNN': face.tolist()}) | ||
34 | - await websocket.send(send) | ||
35 | - recv = await websocket.recv() | ||
36 | - data = json.loads(recv) | ||
37 | - if data['status'] == 'success': | ||
38 | - # 성공 | ||
39 | - print(data['student_id'], 'is registered') | ||
40 | - | ||
41 | -def detect_face(frame): | ||
42 | - # If required, create a face detection pipeline using MTCNN: | ||
43 | - global mtcnn | ||
44 | - results = mtcnn.detect(frame) | ||
45 | - image_list = [] | ||
46 | - if results[1][0] == None: | ||
47 | - return [] | ||
48 | - for box, prob in zip(results[0], results[1]): | ||
49 | - if prob < 0.95: | ||
50 | - continue | ||
51 | - print('face detected. prob:', prob) | ||
52 | - x1, y1, x2, y2 = box | ||
53 | - image = frame[int(y1-10):int(y2+10), int(x1-10):int(x2+10)] | ||
54 | - image_list.append(image) | ||
55 | - return image_list | ||
56 | - | ||
57 | -def detect_face(frame): | ||
58 | - results = mtcnn.detect(frame) | ||
59 | - faces = mtcnn(frame, return_prob = False) | ||
60 | - image_list = [] | ||
61 | - face_list = [] | ||
62 | - if results[1][0] == None: | ||
63 | - return [], [] | ||
64 | - for box, face, prob in zip(results[0], faces, results[1]): | ||
65 | - if prob < 0.97: | ||
66 | - continue | ||
67 | - print('face detected. prob:', prob) | ||
68 | - x1, y1, x2, y2 = box | ||
69 | - if (x2-x1) * (y2-y1) < 15000: | ||
70 | - # 얼굴 해상도가 너무 낮으면 무시 | ||
71 | - continue | ||
72 | - # 얼굴 주변 ±3 영역 저장 | ||
73 | - image = frame[int(y1-3):int(y2+3), int(x1-3):int(x2+3)] | ||
74 | - image_list.append(image) | ||
75 | - # MTCNN 데이터 저장 | ||
76 | - face_list.append(face.numpy()) | ||
77 | - return image_list, face_list | ||
78 | - | ||
79 | -cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) | ||
80 | -cap.set(3, 720) | ||
81 | -cap.set(4, 480) | ||
82 | -ret, frame = cap.read() | ||
83 | -frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | ||
84 | -image_list, face_list = detect_face(frame) | ||
85 | -if face_list: | ||
86 | - asyncio.get_event_loop().run_until_complete(send_face(face_list, image_list)) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
26 | +class Register(tk.Frame): | ||
27 | + def __init__(self, parent, *args, **kwargs): | ||
28 | + tk.Frame.__init__(self, parent, *args, **kwargs) | ||
29 | + | ||
30 | + # tkinter GUI | ||
31 | + self.width = 740 | ||
32 | + self.height = 640 | ||
33 | + | ||
34 | + self.parent = parent | ||
35 | + self.parent.geometry("%dx%d+100+100" % (self.width, self.height)) | ||
36 | + self.pack() | ||
37 | + self.create_widgets() | ||
38 | + | ||
39 | + # URI | ||
40 | + self.uri = 'ws://169.56.95.131:8765' | ||
41 | + | ||
42 | + # Pytorch Model | ||
43 | + self.device = device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') | ||
44 | + self.mtcnn = MTCNN(keep_all=True, device=device) | ||
45 | + | ||
46 | + # OpenCV | ||
47 | + self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) | ||
48 | + self.cam_width = 640 | ||
49 | + self.cam_height = 480 | ||
50 | + self.cap.set(3, self.cam_width) | ||
51 | + self.cap.set(4, self.cam_height) | ||
52 | + | ||
53 | + # Application Function | ||
54 | + self.detecting_square = (200, 200) | ||
55 | + self.detected = False | ||
56 | + self.face_list = [] | ||
57 | + self.image_list = [] | ||
58 | + | ||
59 | + # Event loop and Thread | ||
60 | + # self.event_loop = asyncio.new_event_loop() | ||
61 | + self.thread = threading.Thread(target=self.mainthread) | ||
62 | + self.thread.start() | ||
63 | + | ||
64 | + | ||
65 | + def create_widgets(self): | ||
66 | + image = np.zeros([480,640,3], dtype=np.uint8) | ||
67 | + image = Image.fromarray(image) | ||
68 | + image = ImageTk.PhotoImage(image) | ||
69 | + | ||
70 | + font = tk.font.Font(family="맑은 고딕", size=15) | ||
71 | + | ||
72 | + self.alert = tk.Label(self, text="카메라를 정면으로 향하고 화면의 사각형에 얼굴을 맞춰주세요", font=font) | ||
73 | + self.alert.grid(row=0, column=0, columnspan=20) | ||
74 | + self.label = tk.Label(self, image=image) | ||
75 | + self.label.grid(row=1, column=0, columnspan=20) | ||
76 | + | ||
77 | + self.studentID = tk.StringVar() | ||
78 | + self.studentIdLabel = tk.Label(self, text="학번") | ||
79 | + self.studentIdLabel.grid(row=2, column=10) | ||
80 | + self.studentIdEntry = tk.Entry(self, width=20, textvariable=self.studentID) | ||
81 | + self.studentIdEntry.grid(row=2, column=11) | ||
82 | + | ||
83 | + self.studentName = tk.StringVar() | ||
84 | + self.studentNameLabel = tk.Label(self, text="이름") | ||
85 | + self.studentNameLabel.grid(row=3, column=10) | ||
86 | + self.studentNameEntry = tk.Entry(self, width=20, textvariable=self.studentName) | ||
87 | + self.studentNameEntry.grid(row=3, column=11) | ||
88 | + | ||
89 | + self.registerButton = tk.Button(self, text="등록", fg="blue", command=self.register_face) | ||
90 | + self.registerButton.grid(row=4, column=10) | ||
91 | + | ||
92 | + self.registerButton = tk.Button(self, text="다시촬영", command=self.restart) | ||
93 | + self.registerButton.grid(row=4, column=11) | ||
94 | + | ||
95 | + self.quit = tk.Button(self, text="나가기", fg="red", command=self.stop) | ||
96 | + self.quit.grid(row=5, column=10) | ||
97 | + | ||
98 | + def register_face(self): | ||
99 | + if not self.detected: | ||
100 | + tk.messagebox.showinfo("경고", "얼굴이 인식되지 않았습니다.") | ||
101 | + return | ||
102 | + asyncio.get_event_loop().run_until_complete(self.send_face()) | ||
103 | + | ||
104 | + | ||
105 | + def restart(self): | ||
106 | + if not self.thread.isAlive(): | ||
107 | + self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) | ||
108 | + self.cap.set(3, self.cam_width) | ||
109 | + self.cap.set(4, self.cam_height) | ||
110 | + | ||
111 | + self.detected = False | ||
112 | + self.face_list = [] | ||
113 | + self.image_list = [] | ||
114 | + | ||
115 | + self.thread = threading.Thread(target=self.mainthread) | ||
116 | + self.thread.start() | ||
117 | + | ||
118 | + def detect_face(self, frame): | ||
119 | + results = self.mtcnn.detect(frame) | ||
120 | + faces = self.mtcnn(frame, return_prob = False) | ||
121 | + image_list = [] | ||
122 | + face_list = [] | ||
123 | + if results[1][0] == None: | ||
124 | + return [], [] | ||
125 | + for box, face, prob in zip(results[0], faces, results[1]): | ||
126 | + if prob < 0.97: | ||
127 | + continue | ||
128 | + # for debug | ||
129 | + # print('face detected. prob:', prob) | ||
130 | + x1, y1, x2, y2 = box | ||
131 | + if (x2-x1) * (y2-y1) < 15000: | ||
132 | + # 얼굴 해상도가 너무 낮으면 무시 | ||
133 | + self.alert.config(text= "인식된 얼굴이 너무 작습니다. 카메라에 더 가까이 접근해주세요.", fg="red") | ||
134 | + self.alert.update() | ||
135 | + continue | ||
136 | + image = frame | ||
137 | + image_list.append(image) | ||
138 | + # MTCNN 데이터 저장 | ||
139 | + face_list.append(face.numpy()) | ||
140 | + return face_list, image_list | ||
141 | + | ||
142 | + def mainthread(self): | ||
143 | + t = threading.currentThread() | ||
144 | + #asyncio.set_event_loop(self.event_loop) | ||
145 | + x1 = int(self.cam_width / 2 - self.detecting_square[0] / 2) | ||
146 | + x2 = int(self.cam_width / 2 + self.detecting_square[0] / 2) | ||
147 | + y1 = int(self.cam_height / 2 - self.detecting_square[1] / 2) | ||
148 | + y2 = int(self.cam_height / 2 + self.detecting_square[1] / 2) | ||
149 | + detected_time = None | ||
150 | + while getattr(t, "do_run", True): | ||
151 | + ret, frame = self.cap.read() | ||
152 | + | ||
153 | + # model에 이용하기 위해 convert | ||
154 | + converted = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | ||
155 | + | ||
156 | + # 사각형 영역만 검사 (속도 차이 큼) | ||
157 | + face_list, image_list = self.detect_face(converted[y1:y2, x1:x2]) | ||
158 | + | ||
159 | + # 얼굴이 인식된 경우 파란색 사각형을 띄움 | ||
160 | + if face_list: | ||
161 | + frame = cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 3) | ||
162 | + else: | ||
163 | + frame = cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 3) | ||
164 | + | ||
165 | + # show image | ||
166 | + converted = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | ||
167 | + image = Image.fromarray(converted) | ||
168 | + image = ImageTk.PhotoImage(image) | ||
169 | + self.label.configure(image=image) | ||
170 | + self.label.image = image # kind of double buffering | ||
171 | + | ||
172 | + # 얼굴이 인식되면 멤버함수에 넣음 | ||
173 | + if face_list: | ||
174 | + self.face_list = face_list | ||
175 | + self.image_list = image_list | ||
176 | + # 2초 후에 사진이 찍힘 | ||
177 | + if detected_time is None: | ||
178 | + detected_time = time.time() | ||
179 | + else: | ||
180 | + self.alert.config(text= "얼굴이 인식되었습니다. %f초 후 사진을 촬영합니다"%(2-(time.time()-detected_time)), fg="red") | ||
181 | + if time.time() - detected_time >= 2: | ||
182 | + self.thread.do_run = False | ||
183 | + self.detected = True | ||
184 | + self.alert.config(text= "얼굴을 등록해주세요. 올바르게 촬영되지 않았을 경우 다시촬영을 눌러주세요.", fg="blue") | ||
185 | + else: | ||
186 | + detected_time = None | ||
187 | + self.face_list = [] | ||
188 | + self.image_list = [] | ||
189 | + | ||
190 | + | ||
191 | + async def wait(self, n): | ||
192 | + await asyncio.sleep(n) | ||
193 | + | ||
194 | + async def send_face(self): | ||
195 | + try: | ||
196 | + async with websockets.connect(self.uri) as websocket: | ||
197 | + for face, image in zip(self.face_list, self.image_list): | ||
198 | + #type: np.float32 | ||
199 | + send = json.dumps({'action': 'register', 'student_id':self.studentID, 'student_name':self.studentName, 'MTCNN': face.tolist()}) | ||
200 | + await websocket.send(send) | ||
201 | + recv = await websocket.recv() | ||
202 | + data = json.loads(recv) | ||
203 | + if data['status'] == 'success': | ||
204 | + tk.messagebox.showinfo("등록완료", self.studentID.get() + ' ' + self.studentName.get()) | ||
205 | + except Exception as e: | ||
206 | + tk.messagebox.showinfo("등록실패", e) | ||
207 | + | ||
208 | + def stop(self): | ||
209 | + self.thread.do_run = False | ||
210 | + # self.thread.join() # there is a freeze problem | ||
211 | + # self.event_loop.close() | ||
212 | + self.cap.release() | ||
213 | + self.parent.destroy() | ||
214 | + | ||
215 | + | ||
216 | +if __name__ == '__main__': | ||
217 | + root = tk.Tk() | ||
218 | + Register(root) | ||
219 | + root.mainloop() | ||
220 | + | ... | ... |
-
Please register or login to post a comment