使用python实现刷脸登录

使用python实现刷脸登录

  • 前期准备
    • 环境配置
    • 硬件配置
  • 代码部分
    • 识别部分
  • 转换
  • 数据集
  • 模型的训练
  • 后期任务
  • 链接

这段时间学习了一下opencv和pytorch,然后准备做一个小的项目练一下手。主要实现的功能是刷脸登录系统。
源代码在GITHUB上,链接 github源代码连接

前期准备

环境配置

如果想要运行这份代码,需要先下载以下包:
pytorch、opencv、numpy、torchvision
有关pytorch的下载,你可以参考我的这篇文章使用anaconda安装pytorch

硬件配置

只需要电脑有摄像头,然后电脑能跑深度学习的代码(我提供的模型里面只包含了我自己的数据集训练的参数,所以如果要能识别你自己,你需要跑一遍深度学习的代码)

代码部分

这次编写代码我采用的是模块化的方式,把很多函数、不同功能都分散开封装成包,然后需要导入。下面就按运行的顺序开始堆上代码。
代码结构如图:
使用python实现刷脸登录_第1张图片

识别部分

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#coder:UstarZzz
#date:2019/7/19

import cv2
from func import train_model

class Recognizer():
    def __init__(self,cam_id,address):
        self.id = cam_id
        self.classfier = cv2.CascadeClassifier(address)
        self.color = (0,255,0)


    """
    to get capture object
    :parameter:none
    :return:cap
    if you want to use camera to capture pictures,you just need to use
    get_cap()
    but if you want to capture pictures in your specific video files,you should use
    get_cap(from_camera=False,path='your own path')
    """
    def get_cap(self,from_camera=True,path='video.mp4'):
        cv2.namedWindow('recognition area')
        if(from_camera == True):
            cap = cv2.VideoCapture(self.id)
        if(from_camera == False):
            cap = cv2.VideoCapture(path)
        return cap


    """
    to get state,frame
    :parameter:cap
    :returns:state,frame
    """
    def get_video(self,cap):
        state,frame = cap.read()
        return state,frame


    """
    to frame the portrait in the picture
    :parameter:frame
    :return:none
    """
    def rect_face(self,frame,num,save=False,path='E:/code/control_system/pic/zyx_test'):
        #change RGB to GREY
        grey = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
        facerects = self.classfier.detectMultiScale(grey,scaleFactor=1.2,minNeighbors=3,minSize=(32,32))
        if len(facerects)>0:
            for facerect in facerects:
                x,y,w,h = facerect
                if(save == True):
                    img_name = '%s/%d.jpg' % (path, num)
                    image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
                    cv2.imwrite(img_name, image)
                    cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), self.color, 2)
                    font = cv2.FONT_HERSHEY_SIMPLEX
                    cv2.putText(frame, 'num:%d' % (num), (x + 30, y + 30), font, 1, (255, 0, 255), 4)
                if(save == False):
                    image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
                    prediction,mark = train_model.test(all=False,from_camera=True,image=image)
                    font = cv2.FONT_HERSHEY_SIMPLEX
                    cv2.rectangle(frame,(x-10,y-10),(x+w+10,y+h+10),self.color,2)
                    if(mark > 600):
                        if(prediction == 0):
                            cv2.putText(frame, 'LiuDehua', (x + 30, y + 30), font, 1, (255, 0, 255), 4)
                        if(prediction == 1):
                            cv2.putText(frame, 'ZhengYuxing', (x + 30, y + 30), font, 1, (255, 0, 255), 4)
                    else:
                        pass


    """
    to show the picture
    :parameter:frame
    :return:keycode
    """
    def show_pic(self,frame):
        cv2.imshow('recognition area',frame)
        keycode = cv2.waitKey(10)
        return keycode


    """
    to release the window
    :parameter:cap
    :return:none
    """
    def release(self,cap):
        cap.release()
        cv2.destroyAllWindows()

这个代码是封装的一系列函数,主要是调用摄像头,然后一帧一帧的截图,然后进行保存操作或者识别操作。
先不管导入的训练模块,从头开始讲一下整个代码的运行流程。
首先定义一个类,初始化的时候需要给它你的camera的id,也就是摄像头编号,一般是0或者1.比如我的电脑自带的摄像头是1,我自己外接的一个摄像头是0。
之后定义一个检测器,用于人脸的检测。你可以先去https://opencv.org/releases/下载opencv,然后安装。之后在安装路径下的opencv/build/etc/haarcascades里面可以找到haarcascade_frontalface_alt2.xml,这个是用于人脸的检测。
之后定义后面框出你人脸的方框的颜色,是RGB格式,我选择一个绿色边框,所以我定义的颜色是(0,255,0)。
之后get_Cap(),get_video()都是很常规的opencv操作,我把它分开写了一下而已,get_video()返回的是state和frame,state表示你当前摄像头是否有捕捉到东西。frame就是你捕捉到的东西。
rect_face()进行的是框出你人脸的操作,并且根据save标志位的不同选择是否进行图片的保存(此时保存的所有图片大小并不是一样大,如果想reshape,后面会有相关代码)

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#coder:UstarZzz
#date:2019/7/22
"""
    to show the picture in the video and frame out the face
    if you want to save picture to train your model,you can use frame(path=your_path,save=True,number=1000)
"""

def frame(path='E:/code/control_system/pic/test_zyx',save=False,number=1000,from_camera=True,video_path="E:/code/control_system/video/video_train.mp4"):
    from utils import recognizer

    recognizer = recognizer.Recognizer(0, "F:/study/opencv/opencv/build/etc/haarcascades/haarcascade_frontalface_alt2.xml")
    cap = recognizer.get_cap(from_camera=from_camera,path=video_path)
    num = 0
    while cap.isOpened():
        state,frame = recognizer.get_video(cap)
        if not state:
            break
        recognizer.rect_face(frame,num,save=save,path=path)
        num = num + 1
        if(num>number):
            save = False
        keycode = recognizer.show_pic(frame)
        if keycode&0xff == ord('q'):
            break
    recognizer.release()

if __name__ == '__main__':
    #if you want to save picture to a specific place
    #please use frame(your_path,save=True,number=your_number)
    #if you want to save picture in the outside video,please use the following code:
    #frame(path='E:/code/control_system/pic/test_oth',save=True,number=1000,from_camera=False,video_path="")
    #or if you just want to see the outside video,please use:
    #frame(from_camera=False,video_path="")


    # frame(path='E:/code/control_system/pic/train_oth',save=True,number=2000,
    #       from_camera=False,video_path="E:/code/control_system/video/video_train.mp4")
    #frame(path='E:/code/control_system/pic/zyx_pic',save=True,number=2500)

    #if you just want to use camera to frame out yourself,please use frame()
    frame()

这一段代码是调用上面所写的代码,进行一整个识别操作,如果你想保存摄像头中的图片,你可以使用frame(path=‘E:/code/control_system/pic/zyx_pic’,save=True,number=2500),如果你想

保存你自己本地一段视频里面的图片,你可以使用
frame(path=‘E:/code/control_system/pic/train_oth’,save=True,number=2000,from_camera=False,video_path=“E:/code/control_system/video/video_train.mp4”)

其中,num代表的是你想要保存的图片数量。path是你要保存的路径。(小心:如果你从你本地复制path路径的话,复制出来是"E:\code\control_system\video\video_train.mp4",你必须保证path路径是"/“而不是”",因为""的话可能有转义,如你的路径是\b…,那么\b就会发生转义,就不是你的路径了,就会报错)

转换

在保存完图片之后,为了后面自己定义数据集,你需要先将图片名字命名为标签+id的格式,比如可以命名为"0 1.jpg"

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#coder:UstarZzz
#date:2019/7/25
"""
This code is to change the name of the picture in your folder to your specific name
"""

import os
class BatchRename():
  def __init__(self):
    self.path = 'E:/code/control_system/pic/test_zyx'
  def rename(self):
    filelist = os.listdir(self.path)
    total_num = len(filelist)
    i = 0
    for item in filelist:
      if item.endswith('.jpg'):
        src = os.path.join(os.path.abspath(self.path), item)
        dst = os.path.join(os.path.abspath(self.path), str(1) + ' ' + str(i) + '.jpg')
        os.rename(src, dst)
        print('converting %s to %s ...' % (src, dst))
        i = i + 1
    print('total %d to rename & converted %d jpgs' % (total_num, i))
if __name__ == '__main__':
  demo = BatchRename()
  demo.rename()

你可以更改self.path路径来指定保存的位置(本来开始是分开写,一个源地址一个目的地址的,但是后来在修改之前已经将文件传到github上了,懒得改了)
首先去你指定的路径下搜索图片数量,然后对每个图片进行命名操作。

数据集

这里要制作自己的数据集了,我使用的是pytorch(tensorflow和keras版本的下午再写),需要将图片读取成torch.tensor类型,然后保存在numpy.array里面,之后将numpy.array转换成torch.tensor格式,然后送入到pytoch里面的封装的一个数据集的函数里面。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#coder:UstarZzz
#date:2019/7/22

"""
what this code do is to reshape the picture to (64,64,3) so as to satisfy the input,and it returns
ndarray(num,64,64,3)
"""

import cv2
import os
import numpy as np
from torchvision import transforms
from torch.utils import data
import torch
from random import choice
"""
This class is to change the shape of picture to (64,64,3),and it returns the number of images
"""
class Dataloader():
    def __init__(self,path="E:/code/control_system/pic/train"):
        self.path = path


    """
    to calculate the size which need to be added to the picture
    :parameter:h,w
    :return:(top,bottom,left,right)
    """
    def calc_size(self,h,w):
        top,bottom,left,right = (0,0,0,0)
        l = max(h,w)
        if h < l:
            dh = l - h
            top = dh // 2
            bottom = dh-top
        if w < l:
            dw = l - w
            left = dw // 2
            right = dw - left
        return top,bottom,left,right


    """
    to reshape the picture
    :parameter:image
    :return:reshaped picture(64,64)
    """
    def reshape(self,image):
        h, w, _ = image.shape
        top,bottom,left,right = self.calc_size(h,w)

        #to add balck block to the picture
        #cv2.BORDER_CONSTANT means the color of the block,it depends on the value
        constant = cv2.copyMakeBorder(image,top,bottom,left,right,cv2.BORDER_CONSTANT,value=[0,0,0])
        #reshape the picture to (64,64)
        return cv2.resize(constant,(64,64))


    """
    to read the picture in the given path
    :parameter:none
    :returns:images,labels
    """
    def load_pic(self):
        images = []
        for dir_item in os.listdir(self.path):
            abspath = os.path.abspath(os.path.join(self.path,dir_item))
            if dir_item.endswith('.jpg'):
                image = cv2.imread(abspath)
                image = self.reshape(image)
                if(int(dir_item.split(' ')[0])==1):
                    images.append((image,1))
                if(int(dir_item.split(' ')[0])==0):
                    images.append((image,0))
        return images


    """
    to load dataset
    :parameter:none
    :return:images(num,3,64,64),number
    """
    def load_dataset(self):
        images = self.load_pic()
        number = len(images)
        return images,number


"""
This class is to make your own dataset
train==True:you are loading train_data
train==False:you are loading test_data
number:the number of the picture in the test_dataset
"""

class Data_Creater(torch.utils.data.Dataset):
    def __init__(self,path="E:/code/control_system/pic/train",train=True,number=100):
        super(Data_Creater, self).__init__()
        self.dataloader =Dataloader(path)
        self.pic,self.number = self.dataloader.load_dataset()
        if(train==True):
            self.images = self.pic
        if(train==False):
            self.images = []
            for i in range(number):
                self.images.append(choice(self.pic))
        self.transforms = transforms.Compose([
                    transforms.ToTensor()
                ])
    def __getitem__(self, index):
        img, label = self.images[index]
        if self.transforms:
            data = self.transforms(img)
        else:
            t_img = np.asarray(img)
            data = torch.from_numpy(t_img)
        return data,label
    def __len__(self):
        return len(self.images)

if __name__ == '__main__':
    train_datasets = Data_Creater(path="E:/code/control_system/pic/train")
    print(len(train_datasets))

这里面我还进行了尺寸的操作,将大小不规则的图片尺寸变成64*64,从而进行模型的训练。如果你从本地读图片的话,读出来是(64,64,3),你需要进行通道的重新排序。

模型的训练

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#coder:UstarZzz
#date:2019/7/22
"""
to train a model that can recognize
"""
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.utils.data as Data
from utils import dataloader
import cv2
import numpy as np



class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(                      #input_size(3,64,64)
                in_channels=3,
                out_channels=32,
                kernel_size=5,
                stride=1,
                padding=2
            ),
            nn.Dropout(0.5),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)     #output_size(32,32,32)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(                      #input_size(32,32,32)
                in_channels=32,
                out_channels=64,
                kernel_size=5,
                stride=1,
                padding=2
            ),
            nn.Dropout(0.5),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)  # output_size(64,16,16)
        )
        self.out = nn.Linear(16*16*64,2)

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        """
        """
        x = x.view(x.size(0),-1)
        output =self.out(x)
        return output

def train():
    # Hyper Parameters
    EPOCH = 10
    BATCH_SIZE = 50
    LR = 0.0001

    # load train_dataset
    train_datasets = dataloader.Data_Creater(path="E:/code/control_system/pic/train")
    trainloader = Data.DataLoader(dataset=train_datasets, batch_size=BATCH_SIZE, shuffle=True)

    # load test_dataset
    test_datasets = dataloader.Data_Creater(path="E:/code/control_system/pic/test",train=False,number=100)
    testloader = Data.DataLoader(dataset=test_datasets, batch_size=BATCH_SIZE, shuffle=True)

    cnn =CNN()
    optimizer =torch.optim.Adam(cnn.parameters(),lr=LR)
    loss_func =nn.CrossEntropyLoss()

    for epoch in range(EPOCH):
        for step,(x,y) in enumerate(trainloader):
            x = x.type(torch.FloatTensor)
            y = y.type(torch.LongTensor)
            b_x = Variable(x)
            b_y = Variable(y)
            output =cnn(b_x)
            loss = loss_func(output,b_y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            if step%50 == 0:
                for step, (x, y) in enumerate(testloader):
                    x = x.type(torch.FloatTensor)
                    y = y.type(torch.LongTensor)
                    test_x = Variable(x)
                    test_output = cnn(test_x)
                    test_y = y.data.numpy()
                    pred_y =torch.max(test_output,dim=1)[1].data.numpy().squeeze()
                    accuracy =sum(pred_y==test_y)/test_y.size
                    print('Epoch:',epoch,'|train loss:%.4f'%loss.item(),'|test accuracy:',accuracy)
    torch.save(cnn.state_dict(),'E:/code/control_system/model/cnn.pkl')

def test(all=True,path="E:/code/control_system/pic/test/0 5.jpg",from_camera=True,image=None):
    cnn = CNN()
    cnn.load_state_dict(torch.load('E:/code/control_system/model/cnn.pkl'))
    # Hyper Parameters
    BATCH_SIZE = 50

    if(all==True):
        # load test_dataset
        test_datasets = dataloader.Data_Creater(path="E:/code/control_system/pic/test",train=False,number=1991)
        testloader = Data.DataLoader(dataset=test_datasets, batch_size=BATCH_SIZE, shuffle=True)

        for step, (x, y) in enumerate(testloader):
            x = x.type(torch.FloatTensor)
            y = y.type(torch.LongTensor)
            test_x = Variable(x)
            test_output = cnn(test_x)
            test_y = y.data.numpy()
            pred_y = torch.max(test_output, dim=1)[1].data.numpy().squeeze()
            accuracy = sum(pred_y == test_y) / test_y.size
            print('test accuracy:', accuracy)
    if(all==False):
        if(from_camera == False):
            image = cv2.imread(path)
        if(from_camera == True):
            image =image
        loader = dataloader.Dataloader()
        reshaped_image = loader.reshape(image)
        x = torch.from_numpy(reshaped_image)
        x = x.transpose(0,1)
        x = x.transpose(0,2)
        x = x.numpy()
        data = np.empty((1,3,64,64))
        data[0] = x
        data = torch.from_numpy(data)
        x = data.type(torch.FloatTensor)
        test_x = Variable(x)
        test_output = cnn(test_x)
        mark = int(torch.max(test_output,dim=1)[0].data.numpy().squeeze())
        pred_y = torch.max(test_output, dim=1)[1].data.numpy().squeeze()
        return pred_y,mark


if __name__ == '__main__':
    #train()
    #test()
    for i in range(10):
        prediction,mark = test(all=False, path="E:/code/control_system/pic/test/0 5.jpg", from_camera=False)
        if(mark > 600):
            if(prediction == 0):
                print("LiuDehua")
            if(prediction == 1):
                print("ZhengYuxing")
        else:
            print("Can not recognize")

我使用的是CNN模型,由于任务量比较小,所以选择10个EPOCH就够。实际上跑了几个EPOCH就已经收敛了。

后期任务

后期我准备加入一个姿态估计的操作,能够根据我的姿态来进行对我电脑的控制。

链接

github链接:GITHUB源代码下载与调试讲解
csdn下载链接:CSDN下载源代码

你可能感兴趣的:(深度学习,pytorch,python,刷脸登录,深度学习)