client.py 6.16 KB
##################################################
#1. webcam에서 얼굴을 인식합니다.
#2. 얼굴일 영역이 15000 이상인 이미지를 서버에 전송
##################################################
import tkinter as tk
import tkinter.font
import tkinter.messagebox
import tkinter.scrolledtext
import threading
import numpy as np
import cv2
import asyncio
import websockets
import json
import base64
import configparser

from PIL import Image, ImageTk

config = configparser.ConfigParser()
config.read('./client.cnf')
uri = config['server']['uri']

class Client(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)

        # URI
        self.uri = uri

        # Cascade Model
        self.cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")

        # OpenCV
        self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        self.cam_width = 640
        self.cam_height = 480
        self.cap.set(3, self.cam_width)
        self.cap.set(4, self.cam_height)

        # Preprocessing
        self.margin = int(config['client']['margin'])
        image_size = int(config['client']['image_size'])
        self.image_size = (image_size, image_size)
        
        # cam에서 MTCNN 적용하는 영역
        self.detecting_square = (640, 480)

        # 영상 위에 사각형 색상 지정
        self.rectangle_color = (0, 0, 255)
        
        # tkinter GUI
        self.width = 740
        self.height = 700
        self.parent = parent
        self.parent.title("출석시스템")
        self.parent.geometry("%dx%d+100+100" % (self.width, self.height))
        self.pack()
        self.create_widgets()
        
        # Event loop and Thread
        self.event_loop = asyncio.new_event_loop()
        self.thread = threading.Thread(target=self.mainthread)
        self.thread.start()

    def create_widgets(self):
        image = np.zeros([self.cam_height, self.cam_width, 3], dtype=np.uint8)
        image = Image.fromarray(image)
        image = ImageTk.PhotoImage(image)

        font = tk.font.Font(family="맑은 고딕", size=15)
        
        self.alert = tk.Label(self, text="출석시스템", font=font)
        self.alert.grid(row=0, column=0, columnspan=20)
        self.label = tk.Label(self, image=image)
        self.label.grid(row=1, column=0, columnspan=20)

        self.log = tk.scrolledtext.ScrolledText(self, wrap = tk.WORD, state=tk.DISABLED, width = 96, height = 10)
        self.log.grid(row=2, column=0, columnspan=20)

        
        self.quit = tk.Button(self, text="나가기", fg="red", command=self.stop)
        self.quit.grid(row=3, column=10)

    def logging(self, text):
        self.log.config(state=tk.NORMAL)
        self.log.insert(tkinter.END, text)
        self.log.insert(tkinter.END, '\n')
        self.log.config(state=tk.DISABLED)
        
    
    def detect_face(self, frame):
        # detect face with cascade
        faces = self.cascade.detectMultiScale(
            frame,
            scaleFactor=1.2,
            minNeighbors=5,
            minSize=self.image_size
        )
        for (x, y, w, h) in faces:
            margin = int(self.margin / 2)
            image = frame[y-margin:y+h+margin, x-margin:x+w+margin]
            # BGR to RGB
            converted = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
            return converted, True
        return [], False

    def mainthread(self):
        t = threading.currentThread()
        asyncio.set_event_loop(self.event_loop)
        x1 = int(self.cam_width / 2 - self.detecting_square[0] / 2)
        x2 = int(self.cam_width / 2 + self.detecting_square[0] / 2)
        y1 = int(self.cam_height / 2 - self.detecting_square[1] / 2)
        y2 = int(self.cam_height / 2 + self.detecting_square[1] / 2)
        while getattr(t, "do_run", True):
            ret, frame = self.cap.read()
            # detect face
            face, detected = self.detect_face(frame[y1:y2, x1:x2])
            if detected:
                self.event_loop.run_until_complete(self.send_face(face))
            # 사각형 영역 표시
            frame = cv2.rectangle(frame, (x1, y1), (x2, y2), self.rectangle_color, 3)
            converted = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            # 거울상으로 보여준다
            converted = cv2.flip(converted,1)
            image = Image.fromarray(converted)
            image = ImageTk.PhotoImage(image)
            self.label.configure(image=image)
            self.label.image = image # kind of double buffering

    @asyncio.coroutine
    def set_rectangle_success(self):
        self.rectangle_color = (255, 0, 0)
        yield from asyncio.sleep(2)
        self.rectangle_color = (0, 0, 255)

    def set_rectangle_fail(self):
        self.rectangle_color = (0, 0, 255)
        
    async def send_face(self, image):
        try:
            async with websockets.connect(self.uri) as websocket:
                img = base64.b64encode(cv2.imencode('.jpg', image)[1]).decode()
                send = json.dumps({'action': 'verify', 'image': img})
                await websocket.send(send)
                recv = await asyncio.wait_for(websocket.recv(), timeout=1000)
                data = json.loads(recv)
                if data['status'] == 'attend':
                    student_id = data['student_id']
                    student_name = data['student_name']
                    self.logging(student_id + ' ' + student_name + ' is attend')
                    # change rectangle color
                    asyncio.ensure_future(self.set_rectangle_success())
                elif data['status'] == 'already':
                    # self.logging('already attend')
                    # change rectangle color
                    asyncio.ensure_future(self.set_rectangle_success())
                elif data['status'] == 'fail':
                    self.logging('failed')
                    self.set_rectangle_fail()
        except Exception as e:
            self.logging(e)

    def stop(self):
        self.thread.do_run = False
        #self.thread.join()
        #self.event_loop.close()
        self.cap.release()
        self.parent.destroy()

if __name__ == '__main__':
    root = tk.Tk()
    Client(root)
    root.mainloop()