index.html 9.13 KB
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web Attendance System</title>
<style>
#container {
    margin: 15px auto;
    border: 10px #333 solid;
}
#videoInput {
    background-color: #666;
}
#canvasOutput {
    background-color: #666;
}
#messagebox {
    height: 300px;
    overflow-y: auto;
}
.w3-center {
text-align:center!important
}
.w3-container {
padding:0.01em 16px;
}
.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}
.message {
  padding: 20px;
  font-weight: bold;
  color: white;
}
.attend {
  background-color: #4CAF50;
}
.already {
  background-color: #2196F3;
}
.late {
  background-color: #ff9800;
}
.fail {
  background-color: #f44336;
}
</style>
<script type='text/javascript' src="{{url_for('static', filename='js/opencv.js')}}"></script>
<script type='text/javascript' src="{{url_for('static', filename='js/utils.js')}}"></script>
<script type='text/javascript' src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type='text/javascript'>

function init()
{
    let video = document.getElementById('videoInput');
    let container = document.getElementById('container');
    let canvasOutput = document.getElementById("canvasOutput");
    if (navigator.mediaDevices.getUserMedia){
        navigator.mediaDevices.getUserMedia({ video: true })
        .then(function (stream) {
                video.srcObject = stream;
            video.addEventListener('canplay', () => {
                var screenWidth = $(document).width();
                var screenHeight = $(document).height();
                var headerHeight = $('#header').height();
                var messageboxHeight = $('#messagebox').height();
                var ratio = 1.0;
                video.width = video.videoWidth;
                video.height = video.videoHeight;
                if (video.width > screenWidth || video.height + headerHeight + messageboxHeight > screenHeight){
                    ratio = Math.min(screenWidth / video.width * 1.0, screenHeight / ((video.height + headerHeight + messageboxHeight) * 1.0));
                }
                container.style.width = Math.round(video.width * ratio) + 'px';
                container.style.height = Math.round(video.height * ratio) + 'px';
                canvasOutput.width = Math.round(video.width * ratio);
                canvasOutput.height = Math.round(video.height * ratio);
                load_cascade();
            });
        }).catch(function (err0r) {
            console.log("Something went wrong!");
            streaming = false;
        });
    }
}

function load_cascade()
{
    let faceCascadeFile = 'haarcascade_frontalface_default.xml'
    let faceCascadeURL = 'static/js/haarcascade_frontalface_default.xml'
    let utils = new Utils('errorMessage');  
    utils.createFileFromUrl(faceCascadeFile, faceCascadeURL, () => {
        main()
    });
}


function main()
{
    let video = document.getElementById("videoInput");
    let canvasOutput = document.getElementById("canvasOutput");
    let canvasContext = canvasOutput.getContext('2d');
    let src = new cv.Mat(video.height, video.width, cv.CV_8UC4);
    let dst = new cv.Mat(video.height, video.width, cv.CV_8UC4);
    let dsize = new cv.Size(canvasOutput.width, canvasOutput.height);
    let cap = new cv.VideoCapture(video);
    let faces = new cv.RectVector();
    let classifier = new cv.CascadeClassifier();
    class Tracker{
        constructor(){
            this.arr = new Array();
        }
        register = function(x, y, width, height) {
        var x_center = (x + width) / 2;
        var y_center = (y + height) / 2;
        var now = Date.now()
        this.arr = this.arr.filter(ent => now - ent.time < 300);
            for (const prop in this.arr){
            var prop_x_center = (this.arr[prop].x + this.arr[prop].width) / 2;
            var prop_y_center = (this.arr[prop].y + this.arr[prop].height) / 2;
                if (Math.abs(x_center - prop_x_center) < 10 && Math.abs(y_center - prop_y_center) < 10){
                this.arr[prop].x = x;
                this.arr[prop].y = y;
                this.arr[prop].width = width;
                this.arr[prop].height = height;
                this.arr[prop].time = now;
                return false;
            }
        }
        var ent = {x: x, y: y, width: width, height: height, time: now}
        this.arr.push(ent)
        return true;
        }
    };
    var tracker = new Tracker();
    var streaming = true;

    classifier.load('haarcascade_frontalface_default.xml');

    const FPS = 30;
    function processVideo() {
        try {
            if (!streaming) {
                // clean and stop.
                src.delete();
                dst.delete();
                faces.delete();
                classifier.delete();
                return;
            }
            let begin = Date.now();
            // start processing.
            cap.read(src);
            cv.flip(src, src, 1);
            src.copyTo(dst);
            // detect faces.
            let msize = new cv.Size(video.width / 4, video.height / 4);
            classifier.detectMultiScale(dst, faces, 1.1, 3, 0, msize);
            // draw faces.
            for (let i = 0; i < faces.size(); ++i) {
                let face = faces.get(i);
                let point1 = new cv.Point(face.x, face.y);
                let point2 = new cv.Point(face.x + face.width, face.y + face.height);
                cv.rectangle(dst, point1, point2, [255, 0, 0, 255], 8);
                let cropped = new cv.Mat();
                let margin_x = 0;
                let margin_y = 0;
                if (face.width > face.height)
                {
                    margin_y = (face.width - face.height) / 2;
                }
                else
                {
                    margin_x = (face.height - face.width) / 2;
                }
                Math.max(face.width, face.height)
                Math.min(face.width, face.height)
                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));
                cropped = src.roi(rect);
                let tempCanvas = document.createElement("canvas");
                cv.imshow(tempCanvas,cropped);
                if (tracker.register(face.x, face.y, face.width, face.height)){
                    let b64encoded = tempCanvas.toDataURL("image/jpeg", 1.0);
                    b64encoded = b64encoded.replace('data:image/jpeg;base64,', '');
                    $.ajax({
                        type: "POST",
                        url: "/verify",
                        dataType: "json",
                        data: {'image':b64encoded},
                        success: function(data){
                           if (data.status == "attend"){
                               var newHead = "<div class='message attend'>";
                               var newTail = "</div>";
                               var newContent = '[' + data.student_id + '/' + data.student_name + ']' + "출석되었습니다.";
                               $('#messagebox').prepend(newHead + newContent + newTail).stop().animate({ scrollTop: 0 }, 1000);
                           }
                           else if (data.status == "already"){
                               var newHead = "<div class='message already'>";
                               var newTail = "</div>";
                               var newContent = '[' + data.student_id + '/' + data.student_name + ']' + "이미 출석되었습니다.";
                               $('#messagebox').prepend(newHead + newContent + newTail).stop().animate({ scrollTop: 0 }, 1000);
                           }
                           else if (data.status == "fail"){
                               var newHead = "<div class='message fail'>";
                               var newTail = "</div>";
                               var newContent = "인식 실패";
                               $('#messagebox').prepend(newHead + newContent + newTail).stop().animate({ scrollTop: 0 }, 1000);
                           }
                        }
                    });
                }
            }
            // to do resize preview
            cv.resize(dst, dst, dsize, 0, 0, cv.INTER_AREA);
            cv.imshow('canvasOutput', dst);
            // schedule the next one.
            let delay = 1000/FPS - (Date.now() - begin);
            setTimeout(processVideo, delay);
        } catch (err) {
            console.log(err);
        }
    }
    setTimeout(processVideo, 0);
}
</script>
</head>
<body onload="cv['onRuntimeInitialized']=()=>{ init(); };">
    <div id="header">
    <!-- Header -->
        <header class="w3-container w3-center w3-padding-32"> 
            <h1><b>얼굴 인식 출석 시스템</b></h1>
            <p>Made by <span class="w3-tag">정해갑</span></p>
        </header>
    </div>
    <div id="container">
        <video autoplay="true" id="videoInput" style="display: none; object-fit: cover;"></video>
        <canvas id="canvasOutput"></canvas>
    </div>
    <div id="messagebox">

    </div>
</body>
</html>