Graduate

Update roll book

...@@ -166,7 +166,7 @@ def verify(): ...@@ -166,7 +166,7 @@ def verify():
166 def tempAttendance(): 166 def tempAttendance():
167 attendance_db = pymysql.connect(read_default_file="./DB.cnf") 167 attendance_db = pymysql.connect(read_default_file="./DB.cnf")
168 cursor = attendance_db.cursor(pymysql.cursors.DictCursor) 168 cursor = attendance_db.cursor(pymysql.cursors.DictCursor)
169 - sql = "SELECT ls.student_id, s.student_name, sa.status FROM lecture_students AS ls LEFT JOIN student_attendance AS sa ON ls.student_id = sa.student_id LEFT JOIN student AS s ON ls.student_id = s.student_id;" 169 + sql = "SELECT ls.student_id as student_id, s.student_name as student_name, ifnull(sa.status, 'absent') as status FROM lecture_students AS ls LEFT JOIN student_attendance AS sa ON ls.student_id = sa.student_id LEFT JOIN student AS s ON ls.student_id = s.student_id;"
170 mycursor.execute(sql) 170 mycursor.execute(sql)
171 data = mycursor.fetchall() 171 data = mycursor.fetchall()
172 return render_template('attendance.html', output_data = data) 172 return render_template('attendance.html', output_data = data)
......
1 -<!doctype html> 1 +<!DOCTYPE html>
2 <html> 2 <html>
3 <head> 3 <head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1"> 5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 -<title>Web Attendance System Register</title> 6 +<title>Attendance</title>
7 -<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css"> 7 +<table>
8 -<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nanum+Gothic:400,700,800&amp;subset=korean"> 8 + <thead>
9 -<style>body,h1,h2,h3,h4,h5,p {font-family: "Nanum+Gothic", sans-serif}</style> 9 + <tr>
10 -<style> 10 + <th>학번</th>
11 -#container { 11 + <th>이름</th>
12 - margin: 15px auto; 12 + <th>출결</th>
13 -} 13 + </tr>
14 -#videoInput { 14 + </thead>
15 - background-color: #666; 15 + <tbody>
16 -} 16 + {% for row in output_data %}
17 -#canvasOutput { 17 + <tr>
18 - background-color: #666; 18 + <td>{{row['student_id']}}</td>
19 -} 19 + <td>{{row['student_name']}}</td>
20 -#student_id { 20 + <td>{{row['status']}}</td>
21 - margin-top: 10px; 21 + </tr>
22 - margin-bottom: 5px; 22 + {% endfor %}
23 -} 23 + </tbody>
24 -#student_name { 24 +</table>
25 - margin-bottom: 10px;
26 -}
27 -</style>
28 -<script type='text/javascript' src="{{url_for('static', filename='js/opencv.js')}}"></script>
29 -<script type='text/javascript' src="{{url_for('static', filename='js/utils.js')}}"></script>
30 -<script type='text/javascript' src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
31 -<script type='text/javascript'>
32 -var b64encoded = '';
33 -var streaming = false;
34 -function init()
35 -{
36 - let video = document.getElementById('videoInput');
37 - let container = document.getElementById('container');
38 - let canvasOutput = document.getElementById("canvasOutput");
39 - if (navigator.mediaDevices.getUserMedia){
40 - navigator.mediaDevices.getUserMedia({ video: true })
41 - .then(function (stream) {
42 - video.srcObject = stream;
43 - video.addEventListener('canplay', () => {
44 - var screenWidth = $(document).width();
45 - var screenHeight = $(document).height();
46 - var headerHeight = $('#header').height();
47 - var inputformHeight = $('#inputForm').height();
48 - var ratio = 1.0;
49 - video.width = video.videoWidth;
50 - video.height = video.videoHeight;
51 - if (video.width > screenWidth || headerHeight + video.height + inputformHeight > screenHeight){
52 - ratio = Math.min(screenWidth / (video.width * 1.0), screenHeight / ((headerHeight + video.height + inputformHeight) * 1.0));
53 - }
54 - container.style.width = Math.round(video.width * ratio) + 'px';
55 - container.style.height = Math.round(video.height * ratio) + 'px';
56 - canvasOutput.width = Math.round(video.width * ratio);
57 - canvasOutput.height = Math.round(video.height * ratio);
58 - load_cascade();
59 - });
60 - }).catch(function (err0r) {
61 - console.log("Something went wrong!");
62 - streaming = false;
63 - });
64 - }
65 -}
66 -
67 -function load_cascade()
68 -{
69 - let faceCascadeFile = 'haarcascade_frontalface_default.xml'
70 - let faceCascadeURL = 'static/js/haarcascade_frontalface_default.xml'
71 - let utils = new Utils('errorMessage');
72 - utils.createFileFromUrl(faceCascadeFile, faceCascadeURL, () => {
73 - main();
74 - });
75 -}
76 -
77 -function main()
78 -{
79 - let video = document.getElementById("videoInput");
80 - let canvasOutput = document.getElementById("canvasOutput");
81 - let canvasContext = canvasOutput.getContext('2d');
82 - let src = new cv.Mat(video.height, video.width, cv.CV_8UC4);
83 - let dst = new cv.Mat(video.height, video.width, cv.CV_8UC4);
84 - let msize = new cv.Size(video.width / 4, video.height / 4);
85 - let dsize = new cv.Size(canvasOutput.width, canvasOutput.height);
86 - let cap = new cv.VideoCapture(video);
87 - let faces = new cv.RectVector();
88 - let classifier = new cv.CascadeClassifier();
89 -
90 - class Tracker{
91 - constructor(){
92 - this.arr = new Array();
93 - }
94 - register = function(x, y, width, height) {
95 - var x_center = (x + width) / 2;
96 - var y_center = (y + height) / 2;
97 - var now = Date.now()
98 - this.arr = this.arr.filter(ent => now - ent.time < 300);
99 - for (const prop in this.arr){
100 - var prop_x_center = (this.arr[prop].x + this.arr[prop].width) / 2;
101 - var prop_y_center = (this.arr[prop].y + this.arr[prop].height) / 2;
102 - if (Math.abs(x_center - prop_x_center) < 10 && Math.abs(y_center - prop_y_center) < 10){
103 - this.arr[prop].x = x;
104 - this.arr[prop].y = y;
105 - this.arr[prop].width = width;
106 - this.arr[prop].height = height;
107 - this.arr[prop].time = now;
108 - return this.arr[prop].init_time;
109 - }
110 - }
111 - var ent = {x: x, y: y, width: width, height: height, time: now, init_time: now}
112 - this.arr.push(ent)
113 - return now;
114 - }
115 - };
116 -
117 - var tracker = new Tracker();
118 - classifier.load('haarcascade_frontalface_default.xml');
119 - const FPS = 30;
120 - function processVideo() {
121 - try {
122 - if (!streaming) {
123 - // clean and stop.
124 - src.delete();
125 - dst.delete();
126 - faces.delete();
127 - classifier.delete();
128 - return;
129 - }
130 - let begin = Date.now();
131 - // start processing.
132 - cap.read(src);
133 - cv.flip(src, src, 1);
134 - src.copyTo(dst);
135 - // detect faces.
136 - classifier.detectMultiScale(dst, faces, 1.1, 5, 0, msize);
137 - // draw faces.
138 - for (let i = 0; i < faces.size(); ++i) {
139 - let face = faces.get(i);
140 - let point1 = new cv.Point(face.x, face.y);
141 - let point2 = new cv.Point(face.x + face.width, face.y + face.height);
142 - cv.rectangle(dst, point1, point2, [255, 0, 0, 255], 8);
143 - let cropped = new cv.Mat();
144 - let margin_x = 0;
145 - let margin_y = 0;
146 - if (face.width > face.height)
147 - {
148 - margin_y = (face.width - face.height) / 2;
149 - }
150 - else
151 - {
152 - margin_x = (face.height - face.width) / 2;
153 - }
154 - Math.max(face.width, face.height)
155 - Math.min(face.width, face.height)
156 - let rect = new cv.Rect(Math.max(face.x-margin_x, 0), Math.max(face.y-margin_y, 0), Math.min(face.width+margin_x, src.cols), Math.min(face.height+margin_y, src.rows));
157 - cropped = src.roi(rect);
158 - let tempCanvas = document.createElement("canvas");
159 - cv.imshow(tempCanvas,cropped);
160 - if (Date.now() - tracker.register(face.x, face.y, face.width, face.height) > 1000){
161 - // 1초동안 인식되면 사진 촬영 종료하고 등록 버튼 활성화
162 - cv.rectangle(dst, point1, point2, [0, 255, 0, 255], 8);
163 - b64encoded = tempCanvas.toDataURL("image/jpeg", 0.6);
164 - toggle_streaming();
165 - change_notice("촬영 완료! 정보를 등록해주세요<br>제대로 촬영되지 않은 경우 버튼을 눌러 다시 촬영할 수 있습니다.");
166 - activate_sender();
167 - }
168 - }
169 - // to do resize preview
170 - cv.resize(dst, dst, dsize, 0, 0, cv.INTER_AREA);
171 - cv.imshow('canvasOutput', dst);
172 - // schedule the next one.
173 - let delay = 1000/FPS - (Date.now() - begin);
174 - setTimeout(processVideo, delay);
175 - } catch (err) {
176 - console.log(err);
177 - }
178 - }
179 - setTimeout(processVideo, 0);
180 -}
181 -
182 -function activate_sender()
183 -{
184 - let sender = document.getElementById("sender");
185 - sender.disabled = false;
186 -}
187 -
188 -function toggle_streaming()
189 -{
190 - let streamButton = document.getElementById("streamButton");
191 - streaming = !streaming;
192 - if (streaming){
193 - streamButton.value = "촬영중지";
194 - change_notice("얼굴이 인식되면 얼굴을 촬영합니다");
195 - }
196 - else{
197 - streamButton.value = "촬영시작";
198 - change_notice("촬영 시작 버튼을 누르면 얼굴을 촬영합니다");
199 - }
200 - main();
201 -}
202 -
203 -function change_notice(text)
204 -{
205 - let notice = document.getElementById("notice");
206 - notice.innerHTML = text;
207 -}
208 -
209 -function submit()
210 -{
211 - let student_id = document.getElementById('student_id').value;
212 - let student_name = document.getElementById('student_name').value;
213 - if (b64encoded === '')
214 - {
215 - alert("얼굴을 먼저 촬영해주세요");
216 - return;
217 - }
218 - if (!(student_id.length && student_name.length))
219 - {
220 - alert("학번과 이름을 입력해주세요");
221 - return;
222 - }
223 - b64encoded = b64encoded.replace('data:image/jpeg;base64,', '')
224 - $.ajax({
225 - type: "POST",
226 - url: "/register",
227 - dataType: "json",
228 - data: {'image':b64encoded, 'student_id':student_id, 'student_name':student_name},
229 - success: function(data){
230 - if (data.status == "success"){
231 - alert("등록 성공");
232 - }
233 - else if (data.status == "already"){
234 - alert("등록 실패: 이미 등록된 학번입니다. 학번을 변경해주세요.");
235 - }
236 - }
237 - })
238 -}
239 -
240 -</script>
241 -</head>
242 -<body onload="cv['onRuntimeInitialized']=()=>{init();};" class="w3-light-grey">
243 -<!-- w3-content defines a container for fixed size centered content,
244 -and is wrapped around the whole page content, except for the footer in this example -->
245 - <div class="w3-content">
246 - <!-- Header -->
247 - <header id="header" class="w3-container w3-center">
248 - <h1><b>얼굴 등록</b></h1>
249 - <p>Made by <span class="w3-tag">정해갑</span></p>
250 - </header>
251 -
252 - <div class="w3-row", style='text-align:center'>
253 - <h2 id="notice">촬영 시작 버튼을 누르면 얼굴을 촬영합니다</h2>
254 - <input id="streamButton" type="button" onclick="toggle_streaming()" value="활영시작">
255 - <div id="container">
256 - <video autoplay="true" id="videoInput" style="display: none; object-fit:cover;"></video>
257 - <canvas id="canvasOutput">/canvas>
258 - </div>
259 - <div id="inputForm">
260 - <strong>얼굴 이미지는 서버에 저장되지 않습니다</strong><br>
261 - <strong>(복원 불가능한 512차원 벡터로 변환됩니다)</strong><br>
262 - <strong>학번과 이름은 임의로 입력해주세요</strong><br>
263 - <strong>예)1234/홍길동 등</strong><br>
264 - 학번: <input type="text" id="student_id"><br>
265 - 이름: <input type="text" id="student_name"><br>
266 - <input id="sender" type="button" onclick="submit()" value="등록" disabled>
267 - </div>
268 - </div>
269 - </div>
270 -</body>
271 -</html>
272 -
...\ No newline at end of file ...\ No newline at end of file
......