Graduate

Update roll book

...@@ -162,6 +162,15 @@ def verify(): ...@@ -162,6 +162,15 @@ def verify():
162 attendance_db.close() 162 attendance_db.close()
163 return send 163 return send
164 164
165 +@app.route('/attendance.html')
166 +def tempAttendance():
167 + attendance_db = pymysql.connect(read_default_file="./DB.cnf")
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;"
170 + mycursor.execute(sql)
171 + data = mycursor.fetchall()
172 + return render_template('attendance.html', output_data = data)
173 +
165 @app.route('/robots.txt') 174 @app.route('/robots.txt')
166 def robots(): 175 def robots():
167 return send_from_directory(app.static_folder, request.path[1:]) 176 return send_from_directory(app.static_folder, request.path[1:])
......
1 +<!doctype html>
2 +<html>
3 +<head>
4 +<meta charset="utf-8">
5 +<meta name="viewport" content="width=device-width, initial-scale=1">
6 +<title>Web Attendance System Register</title>
7 +<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
8 +<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nanum+Gothic:400,700,800&amp;subset=korean">
9 +<style>body,h1,h2,h3,h4,h5,p {font-family: "Nanum+Gothic", sans-serif}</style>
10 +<style>
11 +#container {
12 + margin: 15px auto;
13 +}
14 +#videoInput {
15 + background-color: #666;
16 +}
17 +#canvasOutput {
18 + background-color: #666;
19 +}
20 +#student_id {
21 + margin-top: 10px;
22 + margin-bottom: 5px;
23 +}
24 +#student_name {
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 +