Showing
6 changed files
with
64 additions
and
43 deletions
1 | ################################################## | 1 | ################################################## |
2 | #1. webcam에서 얼굴을 인식합니다. | 2 | #1. webcam에서 얼굴을 인식합니다. |
3 | -#2. 얼굴일 확률이 97% 이상이고 영역이 15000 이상인 이미지를 서버에 전송 | 3 | +#2. 얼굴일 영역이 15000 이상인 이미지를 서버에 전송 |
4 | ################################################## | 4 | ################################################## |
5 | import tkinter as tk | 5 | import tkinter as tk |
6 | import tkinter.font | 6 | import tkinter.font |
... | @@ -39,7 +39,9 @@ class Client(tk.Frame): | ... | @@ -39,7 +39,9 @@ class Client(tk.Frame): |
39 | self.cap.set(4, self.cam_height) | 39 | self.cap.set(4, self.cam_height) |
40 | 40 | ||
41 | # Preprocessing | 41 | # Preprocessing |
42 | - self.image_size = (160, 160) | 42 | + self.margin = int(config['client']['margin']) |
43 | + image_size = int(config['client']['image_size']) | ||
44 | + self.image_size = (image_size, image_size) | ||
43 | 45 | ||
44 | # cam에서 MTCNN 적용하는 영역 | 46 | # cam에서 MTCNN 적용하는 영역 |
45 | self.detecting_square = (500, 300) | 47 | self.detecting_square = (500, 300) |
... | @@ -96,7 +98,8 @@ class Client(tk.Frame): | ... | @@ -96,7 +98,8 @@ class Client(tk.Frame): |
96 | minSize=self.image_size | 98 | minSize=self.image_size |
97 | ) | 99 | ) |
98 | for (x, y, w, h) in faces: | 100 | for (x, y, w, h) in faces: |
99 | - image = frame[y:y+h, x:x+w] | 101 | + margin = int(self.margin / 2) |
102 | + image = frame[y-margin:y+h+margin, x-margin:x+w+margin] | ||
100 | return image, True | 103 | return image, True |
101 | return [], False | 104 | return [], False |
102 | 105 | ||
... | @@ -126,36 +129,45 @@ class Client(tk.Frame): | ... | @@ -126,36 +129,45 @@ class Client(tk.Frame): |
126 | self.label.image = image # kind of double buffering | 129 | self.label.image = image # kind of double buffering |
127 | 130 | ||
128 | @asyncio.coroutine | 131 | @asyncio.coroutine |
129 | - def set_rectangle(self): | 132 | + def set_rectangle_success(self): |
130 | self.rectangle_color = (255, 0, 0) | 133 | self.rectangle_color = (255, 0, 0) |
131 | yield from asyncio.sleep(2) | 134 | yield from asyncio.sleep(2) |
132 | self.rectangle_color = (0, 0, 255) | 135 | self.rectangle_color = (0, 0, 255) |
133 | 136 | ||
137 | + def set_rectangle_fail(self): | ||
138 | + self.rectangle_color = (0, 0, 255) | ||
139 | + | ||
134 | async def send_face(self, image): | 140 | async def send_face(self, image): |
135 | try: | 141 | try: |
136 | async with websockets.connect(self.uri) as websocket: | 142 | async with websockets.connect(self.uri) as websocket: |
137 | img = base64.b64encode(cv2.imencode('.jpg', image)[1]).decode() | 143 | img = base64.b64encode(cv2.imencode('.jpg', image)[1]).decode() |
138 | send = json.dumps({'action': 'verify', 'image': img}) | 144 | send = json.dumps({'action': 'verify', 'image': img}) |
139 | await websocket.send(send) | 145 | await websocket.send(send) |
140 | - recv = await websocket.recv() | 146 | + recv = await asyncio.wait_for(websocket.recv(), timeout=1000) |
141 | data = json.loads(recv) | 147 | data = json.loads(recv) |
142 | - if data['action'] == 'success': | 148 | + if data['status'] == 'attend': |
143 | - self.logging('succeed') | 149 | + student_id = data['student_id'] |
150 | + student_name = data['student_name'] | ||
151 | + self.logging(student_id + ' ' + student_name + ' is attend') | ||
144 | # change rectangle color | 152 | # change rectangle color |
145 | - asyncio.ensure_future(self.set_rectangle()) | 153 | + asyncio.ensure_future(self.set_rectangle_success()) |
146 | - elif data['action'] == 'fail': | 154 | + elif data['status'] == 'already': |
155 | + # self.logging('already attend') | ||
156 | + # change rectangle color | ||
157 | + asyncio.ensure_future(self.set_rectangle_success()) | ||
158 | + elif data['status'] == 'fail': | ||
147 | self.logging('failed') | 159 | self.logging('failed') |
160 | + self.set_rectangle_fail() | ||
148 | except Exception as e: | 161 | except Exception as e: |
149 | self.logging(e) | 162 | self.logging(e) |
150 | 163 | ||
151 | def stop(self): | 164 | def stop(self): |
152 | self.thread.do_run = False | 165 | self.thread.do_run = False |
153 | - self.thread.join() | 166 | + #self.thread.join() |
154 | - self.event_loop.close() | 167 | + #self.event_loop.close() |
155 | self.cap.release() | 168 | self.cap.release() |
156 | self.parent.destroy() | 169 | self.parent.destroy() |
157 | 170 | ||
158 | - | ||
159 | if __name__ == '__main__': | 171 | if __name__ == '__main__': |
160 | root = tk.Tk() | 172 | root = tk.Tk() |
161 | Client(root) | 173 | Client(root) | ... | ... |
... | @@ -26,7 +26,6 @@ class Register(tk.Frame): | ... | @@ -26,7 +26,6 @@ class Register(tk.Frame): |
26 | 26 | ||
27 | # URI | 27 | # URI |
28 | self.uri = config['server']['uri'] | 28 | self.uri = config['server']['uri'] |
29 | - | ||
30 | # Cascade Model | 29 | # Cascade Model |
31 | self.cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml") | 30 | self.cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml") |
32 | 31 | ||
... | @@ -36,9 +35,12 @@ class Register(tk.Frame): | ... | @@ -36,9 +35,12 @@ class Register(tk.Frame): |
36 | self.cam_height = 480 | 35 | self.cam_height = 480 |
37 | self.cap.set(3, self.cam_width) | 36 | self.cap.set(3, self.cam_width) |
38 | self.cap.set(4, self.cam_height) | 37 | self.cap.set(4, self.cam_height) |
39 | - self.image_size = (160, 160) | 38 | + image_size = int(config['register']['image_size']) |
39 | + self.image_size = (image_size, image_size) | ||
40 | + self.margin = int(config['register']['margin']) | ||
40 | 41 | ||
41 | # Application Function | 42 | # Application Function |
43 | + self.taking_time = float(config['register']['taking_time']) | ||
42 | self.detecting_square = (400, 400) | 44 | self.detecting_square = (400, 400) |
43 | self.detected = False | 45 | self.detected = False |
44 | self.face = [] | 46 | self.face = [] |
... | @@ -120,7 +122,8 @@ class Register(tk.Frame): | ... | @@ -120,7 +122,8 @@ class Register(tk.Frame): |
120 | minSize=self.image_size | 122 | minSize=self.image_size |
121 | ) | 123 | ) |
122 | for (x, y, w, h) in faces: | 124 | for (x, y, w, h) in faces: |
123 | - image = frame[y:y+h, x:x+w] | 125 | + margin = int(self.margin / 2) |
126 | + image = frame[y-margin:y+h+margin, x-margin:x+w+margin] | ||
124 | return image, True | 127 | return image, True |
125 | return [], False | 128 | return [], False |
126 | 129 | ||
... | @@ -157,12 +160,12 @@ class Register(tk.Frame): | ... | @@ -157,12 +160,12 @@ class Register(tk.Frame): |
157 | # 얼굴이 인식되면 멤버함수에 넣음 | 160 | # 얼굴이 인식되면 멤버함수에 넣음 |
158 | if detected: | 161 | if detected: |
159 | self.face = face | 162 | self.face = face |
160 | - # 2초 후에 사진이 찍힘 | 163 | + # 0.5초 후에 사진이 찍힘 |
161 | if detected_time is None: | 164 | if detected_time is None: |
162 | detected_time = time.time() | 165 | detected_time = time.time() |
163 | else: | 166 | else: |
164 | - self.alert.config(text= "얼굴이 인식되었습니다. %f초 후 사진을 촬영합니다"%(2-(time.time()-detected_time)), fg="red") | 167 | + self.alert.config(text= "얼굴이 인식되었습니다. %f초 후 사진을 촬영합니다"%(self.taking_time-(time.time()-detected_time)), fg="red") |
165 | - if time.time() - detected_time >= 2: | 168 | + if time.time() - detected_time >= self.taking_time: |
166 | self.thread.do_run = False | 169 | self.thread.do_run = False |
167 | self.detected = True | 170 | self.detected = True |
168 | self.alert.config(text= "얼굴을 등록해주세요. 올바르게 촬영되지 않았을 경우 다시촬영을 눌러주세요.", fg="blue") | 171 | self.alert.config(text= "얼굴을 등록해주세요. 올바르게 촬영되지 않았을 경우 다시촬영을 눌러주세요.", fg="blue") |
... | @@ -180,9 +183,9 @@ class Register(tk.Frame): | ... | @@ -180,9 +183,9 @@ class Register(tk.Frame): |
180 | async with websockets.connect(self.uri) as websocket: | 183 | async with websockets.connect(self.uri) as websocket: |
181 | img = base64.b64encode(cv2.imencode('.jpg', self.face)[1]).decode() | 184 | img = base64.b64encode(cv2.imencode('.jpg', self.face)[1]).decode() |
182 | send = json.dumps({'action': 'register', | 185 | send = json.dumps({'action': 'register', |
183 | - 'student_id':self.studentID.get(), | 186 | + 'student_id': self.studentID.get(), |
184 | - 'student_name':self.studentName.get(), | 187 | + 'student_name': self.studentName.get(), |
185 | - 'tensor': img}) | 188 | + 'image': img}) |
186 | await websocket.send(send) | 189 | await websocket.send(send) |
187 | recv = await websocket.recv() | 190 | recv = await websocket.recv() |
188 | data = json.loads(recv) | 191 | data = json.loads(recv) | ... | ... |
1 | [verification_server] | 1 | [verification_server] |
2 | host=localhost | 2 | host=localhost |
3 | port=3000 | 3 | port=3000 |
4 | -model=model/20200816-080621 | 4 | +model=models/20200816-080621 |
5 | threshold=0.75 | 5 | threshold=0.75 |
6 | image_size=160 | 6 | image_size=160 |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -11,7 +11,6 @@ import configparser | ... | @@ -11,7 +11,6 @@ import configparser |
11 | import logging | 11 | import logging |
12 | from datetime import datetime | 12 | from datetime import datetime |
13 | 13 | ||
14 | -lock = asyncio.Lock() | ||
15 | clients = set() | 14 | clients = set() |
16 | 15 | ||
17 | config = configparser.ConfigParser() | 16 | config = configparser.ConfigParser() |
... | @@ -27,23 +26,21 @@ image_size = int(config['verification_server']['image_size']) | ... | @@ -27,23 +26,21 @@ image_size = int(config['verification_server']['image_size']) |
27 | threshold = float(config['verification_server']['threshold']) | 26 | threshold = float(config['verification_server']['threshold']) |
28 | 27 | ||
29 | print('connect to DB') | 28 | print('connect to DB') |
30 | -db = pymysql.connect( | 29 | +attendance_db = pymysql.connect( |
31 | read_default_file="./DB.cnf" | 30 | read_default_file="./DB.cnf" |
32 | ) | 31 | ) |
33 | 32 | ||
34 | async def register(websocket): | 33 | async def register(websocket): |
35 | - async with lock: | ||
36 | clients.add(websocket) | 34 | clients.add(websocket) |
37 | - #remote_ip = websocket.remote_address[0] | 35 | + remote_ip = websocket.remote_address[0] |
38 | - #msg='[{ip}] connected'.format(ip=remote_ip) | 36 | + msg='[{ip}] connected'.format(ip=remote_ip) |
39 | - #print(msg) | 37 | + print(msg) |
40 | 38 | ||
41 | async def unregister(websocket): | 39 | async def unregister(websocket): |
42 | - async with lock: | ||
43 | clients.remove(websocket) | 40 | clients.remove(websocket) |
44 | - #remote_ip = websocket.remote_address[0] | 41 | + remote_ip = websocket.remote_address[0] |
45 | - #msg='[{ip}] disconnected'.format(ip=remote_ip) | 42 | + msg='[{ip}] disconnected'.format(ip=remote_ip) |
46 | - #print(msg) | 43 | + print(msg) |
47 | 44 | ||
48 | def resize(image): | 45 | def resize(image): |
49 | resized = cv2.resize(image, (image_size, image_size), interpolation=cv2.INTER_CUBIC) | 46 | resized = cv2.resize(image, (image_size, image_size), interpolation=cv2.INTER_CUBIC) |
... | @@ -63,22 +60,20 @@ def get_distance(arr1, arr2): | ... | @@ -63,22 +60,20 @@ def get_distance(arr1, arr2): |
63 | 60 | ||
64 | async def thread(websocket, path): | 61 | async def thread(websocket, path): |
65 | await register(websocket) | 62 | await register(websocket) |
66 | - cursor = db.cursor(pymysql.cursors.DictCursor) | 63 | + cursor = attendance_db.cursor(pymysql.cursors.DictCursor) |
64 | + remote_ip = websocket.remote_address[0] | ||
67 | try: | 65 | try: |
68 | async for message in websocket: | 66 | async for message in websocket: |
69 | data = json.loads(message) | 67 | data = json.loads(message) |
70 | - remote_ip = websocket.remote_address[0] | ||
71 | - msg='[{ip}] connected'.format(ip=remote_ip) | ||
72 | - print(msg) | ||
73 | if data['action'] == 'register': | 68 | if data['action'] == 'register': |
74 | # log | 69 | # log |
75 | msg='[{ip}] register face'.format(ip=remote_ip) | 70 | msg='[{ip}] register face'.format(ip=remote_ip) |
76 | print(msg) | 71 | print(msg) |
77 | - | 72 | + student_id = data['student_id'] |
73 | + student_name = data['student_name'] | ||
78 | # 학생을 찾음 | 74 | # 학생을 찾음 |
79 | sql = "SELECT student_id FROM student WHERE student_id = %s;" | 75 | sql = "SELECT student_id FROM student WHERE student_id = %s;" |
80 | rows_count = cursor.execute(sql, (student_id)) | 76 | rows_count = cursor.execute(sql, (student_id)) |
81 | - | ||
82 | # DB에 학생이 없으면 등록 | 77 | # DB에 학생이 없으면 등록 |
83 | if rows_count == 0: | 78 | if rows_count == 0: |
84 | sql = "INSERT INTO student(student_id, student_name) VALUES (%s, %s)" | 79 | sql = "INSERT INTO student(student_id, student_name) VALUES (%s, %s)" |
... | @@ -132,7 +127,8 @@ async def thread(websocket, path): | ... | @@ -132,7 +127,8 @@ async def thread(websocket, path): |
132 | for row_data in result: | 127 | for row_data in result: |
133 | db_embedding = np.frombuffer(row_data['embedding'], dtype=np.float32) | 128 | db_embedding = np.frombuffer(row_data['embedding'], dtype=np.float32) |
134 | db_embedding = db_embedding.reshape((1,512)) | 129 | db_embedding = db_embedding.reshape((1,512)) |
135 | - distance = await get_distance(embedding, db_embedding) | 130 | + distance = get_distance(embedding, db_embedding) |
131 | + print(distance) | ||
136 | if (distance < threshold): | 132 | if (distance < threshold): |
137 | verified_id = row_data['student_id'] | 133 | verified_id = row_data['student_id'] |
138 | break | 134 | break |
... | @@ -143,19 +139,22 @@ async def thread(websocket, path): | ... | @@ -143,19 +139,22 @@ async def thread(websocket, path): |
143 | # 인증 성공 | 139 | # 인증 성공 |
144 | # 오늘 이미 출석 됐는지 확인 | 140 | # 오늘 이미 출석 됐는지 확인 |
145 | sql = "SELECT DATE(timestamp) FROM student_attendance WHERE (lecture_id=%s) AND (student_id=%s) AND (DATE(timestamp) = CURDATE());" | 141 | sql = "SELECT DATE(timestamp) FROM student_attendance WHERE (lecture_id=%s) AND (student_id=%s) AND (DATE(timestamp) = CURDATE());" |
146 | - rows_count = cursor.execute(sql, ('0', verified_id)) | 142 | + cursor.execute(sql, ('0', verified_id)) |
147 | - | ||
148 | # 출석 기록이 없는 경우에만 | 143 | # 출석 기록이 없는 경우에만 |
149 | - if rows_count == 0: | 144 | + if cursor.rowcount == 0: |
150 | # 테이블 맨 뒤에 datetime attribute가 있음. 서버 시간 가져오게 default로 설정해둠. | 145 | # 테이블 맨 뒤에 datetime attribute가 있음. 서버 시간 가져오게 default로 설정해둠. |
151 | sql = "INSERT INTO student_attendance(lecture_id, student_id, status) VALUES (%s, %s, %s)" | 146 | sql = "INSERT INTO student_attendance(lecture_id, student_id, status) VALUES (%s, %s, %s)" |
152 | # TODO: attend / late 처리 | 147 | # TODO: attend / late 처리 |
153 | cursor.execute(sql, ('0', verified_id, 'attend')) | 148 | cursor.execute(sql, ('0', verified_id, 'attend')) |
154 | attendance_db.commit() | 149 | attendance_db.commit() |
150 | + sql = "SELECT student_name FROM student WHERE student_id = %s" | ||
151 | + cursor.execute(sql, (verified_id)) | ||
152 | + row_data = cursor.fetchone() | ||
153 | + verified_name = row_data['student_name'] | ||
155 | # log 작성 | 154 | # log 작성 |
156 | msg='[{ip}] verification success {id}'.format(ip=remote_ip, id=verified_id) | 155 | msg='[{ip}] verification success {id}'.format(ip=remote_ip, id=verified_id) |
157 | print(msg) | 156 | print(msg) |
158 | - send = json.dumps({'status': 'success', 'student_id': verified_id}) | 157 | + send = json.dumps({'status': 'attend', 'student_id': verified_id, 'student_name': verified_name}) |
159 | else: | 158 | else: |
160 | msg='[{ip}] verification failed: {id} is already verified'.format(ip=remote_ip, id=verified_id) | 159 | msg='[{ip}] verification failed: {id} is already verified'.format(ip=remote_ip, id=verified_id) |
161 | print(msg) | 160 | print(msg) | ... | ... |
-
Please register or login to post a comment