这次真的成为CV程序猿了😅(人脸识别登录)附代码

2,430 阅读6分钟

由于我本专业是光电信息科学与工程,最近课设要做一个人脸识别。对于我这种半途转前端的on9仔,当然是不会做啦。通过查找资料发现,可以利用opencv这个开源的计算机视觉和机器学习软件库来实现,我就想着上网CTRL+CV一段代码交差啦哈哈哈😅

灵光一闪

竟然可以做到人脸识别,那岂不是可以在我的项目的登录系统中加入人脸登录,那样面试的时候又有东西可吹了😁

废话不多,直接开干。

思路

  1. 前端注册时上传自己的大头照,后端通过OpenCV训练
  2. 登录时前端通过tracking.js识别出自己的人脸,再利用canvas转正base64的图片上传给后端,后端拿这张图片进行识别

技术栈

  • 前端:Vue2 + tracking.js
  • 后端:Python + OpenCV + Flask

不会Python的小伙伴不要担心,我也不会哈哈哈🤣🤣,只用调用opencv的库就ok了

人脸识别示范

我这里用的是这篇文章的代码用Python进行人脸识别[包括源代码],里面有源码和打开教程(本来打算直接就用来交差的)

可以点我这里下载源代码 人脸识别代码,或者文章后面有我封装好的代码

打开后,在终端运行以下命令,安装依赖

pip install numpy opencv-python

pip install dlib

pip install face_recognition

然后执行一下main.py文件,就可以看到运行的结果了,

res.png

总结一下

通过Python利用OpenCV先训练train文件夹下的图片,然后我们拿test文件夹下的图片用来测试,OpenCV会通过train下最像的图片,放回该图片的文件名(人名)

前端实践

注册

这一部分只要把用户名(最后识别出来的名字)和图片上传给后端

注意 照片一定要清晰的大头照,不然OpenCV训练不了会报错

login1.png

<form
      method="post"
      action="http://localhost:3000/register/"
      enctype="multipart/form-data"
      target="hideIframe1"
    >
      <h3>注册</h3>
      <input type="text" placeholder="账号" name="username" />
      <input type="file" name="photo" />
      <button style="margin-top: 10px" type="submit">注册账号</button>
</form>

tracking.js

tracking.js的安装网上说不建议用nmp,我们可以到官网上下载tracking.js然后把文件放在src/assets 目录下文件夹更名为 tracking 方便引入

track.png

之后我们只要在需要的页面上引用

import "@/assets/tracking/build/data/face-min.js";

人脸登录

这一步,就是调用摄像头,识别出人脸,把人脸图片上传到后端,后端会返回识别后的结果

注意 我这里只是本地调试,上线需要使用有https的域名 否则无法调用摄像头

    <div class="video-box">
      <video id="video" width="320" height="240" preload autoplay loop muted></video>
      <canvas id="canvas" width="320" height="240"></canvas>
    </div>
    <canvas id="screenshotCanvas" width="320" height="240"></canvas>

// 初始化设置
    async init() {
      this.video = document.getElementById("video");
      this.screenshotCanvas = document.getElementById("screenshotCanvas");

      let canvas = document.getElementById("canvas");
      let context = canvas.getContext("2d");

      // 固定写法
      let tracker = new window.tracking.ObjectTracker("face");
      tracker.setInitialScale(4);
      tracker.setStepSize(2);
      tracker.setEdgesDensity(0.1);
      window.tracking.track("#video", tracker, {
        camera: true,
      });
       // 我这里写了个sleep函数等两秒再执行,避免还没反应过来,摄像头一打开就识别上传了
      await this.sleep(2000);

      let _this = this;
      tracker.on("track", function (event) {
        // 检测出人脸 绘画人脸位置
        context.clearRect(0, 0, canvas.width, canvas.height);
        event.data.forEach(function (rect) {
          context.strokeStyle = "#0764B7";
          context.strokeRect(rect.x, rect.y, rect.width, rect.height);

          // 上传图片
          _this.uploadLock && _this.screenshotAndUpload();
        });
      });
    },
    // 上传图片
    async screenshotAndUpload() {
      // 上锁避免重复发送请求
      this.uploadLock = false;

      // 绘制当前帧图片转换为base64格式
      let canvas = this.screenshotCanvas;
      let video = this.video;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      console.log("camvas", canvas);
      // 将图片保存为png格式
      let base64Img = canvas.toDataURL("image/png");

      // 使用 base64Img 请求接口即可
      // console.log("base64Img:", base64Img);
      
      //我后端地址是localhost:3000,我这里用了webpack代理解决跨域
      axios.post("/api/check", {
          data: base64Img,
        }).then((res) => {
          。。。。。。
        });

      // 请求接口成功以后打开锁
      // await this.sleep(1500);
      // this.uploadLock = true;
    },

到这一步前端的工作就全部完成了

后端实践

Flask开启服务器

我真的不会Python😅😅,但是思路上后端要做的事情都一样,我先上网搜了下发现可以利用Flask开启一个本地的服务器

from flask import Flask, request
app = Flask(__name__)

@app.route("/test/")
def test():
    return "hello world"

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=3000)

运行起来,在浏览器访问localhost:3000/tset/,就可以看到hello word

这样我们只需要写两个接口注册和识别

注册

我们只需要把前端传来的图片存在本地的,并以username命名图片

@app.route('/register/', methods=['POST'])
def register():
    img = request.files.get('photo')  # 从post请求中获取图片数据
    username = request.form.get('username', '')
    suffix = '.' + img.filename.split('.')[-1]  # 获取文件后缀名
    basedir = os.path.abspath(os.path.dirname(__file__))  # 获取当前文件路径
    photo = '/static/uploads/' + username + suffix  # 拼接相对路径,并把username作为图片名字
    img_path = basedir + photo  # 拼接图片完整保存路径,
    img.save(img_path)  # 保存图片
    return {'msg': 'ok'}

识别

这里我们拿到的是前端传来base64格式的图片,我们要把它转成图片并保存在本地(temp.png)

@app.route('/check', methods=['POST'])
def check():
    src = request.data
    print(src)
    data = str(src).split(',')[1][:-3]
    img_data = base64.b64decode(data)
    with open("./test/temp.png", 'wb') as f:
        f.write(img_data)
    这里利用OpenCV识别这张图片,然后把结果返回
    return {'msg': 'ok'}

重点来了

由于我真的一点也不会Python,我的想法很简单,打算把main.py里的代码包装成一个函数最后把识别出来的数组return,我在/check接口中接收。

我咨询了一下我同学一些语法问题,他是视觉识别这一块的大佬(10多个省奖),他听了后觉得代码写的非常的不优雅,于是乎他把所有main.py的代码封装成一个类,并把全部代码写入一个文件中,代码如下

完整后端代码

  • face_distances这个值越小说明精度越高,大于这个值就返回["no match"]
  • 识别完放回一个的数组msg:['Chris]
import face_recognition as fr
import cv2
import numpy as np
import os, base64
from flask import Flask, request, Response, render_template
app=Flask(__name__)

class face:

    def __init__(self, train_path):

        self.known_names = []
        self.known_name_encodings = []
        self.train_path = train_path

    def init(self):
        images = os.listdir(self.train_path)
        for _ in images:
            image = fr.load_image_file(self.train_path + _)
            image_path = self.train_path + _
            encoding = fr.face_encodings(image)[0]

            self.known_name_encodings.append(encoding)
            self.known_names.append(os.path.splitext(os.path.basename(image_path))[0].capitalize())

    def add(self, img_path):
        image = fr.load_image_file(img_path)
        encoding = fr.face_encodings(image)[0]
        self.known_name_encodings.append(encoding)
        self.known_names.append(os.path.splitext(os.path.basename(img_path))[0].capitalize())
        # print(self.known_names)

    def compare(self, img_path):
        image = img_path
        image = cv2.imread(image)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        face_locations = fr.face_locations(image)
        face_encodings = fr.face_encodings(image, face_locations)
        names = []

        for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
            matches = fr.compare_faces(self.known_name_encodings, face_encoding)
            name = ""

            face_distances = fr.face_distance(self.known_name_encodings, face_encoding)
            print(face_distances)
            if min(face_distances) > 0.45:
            // face_distances这个值越小说明精度越高,大于这个值就返回["no match"]
                return ["no match"]
            best_match = np.argmin(face_distances)

            if matches[best_match]:
                name = self.known_names[best_match]
                names.append(name)

            cv2.rectangle(image, (left, top), (right, bottom), (0, 0, 255), 2)
            cv2.rectangle(image, (left, bottom - 15), (right, bottom), (0, 0, 255), cv2.FILLED)
            font = cv2.FONT_HERSHEY_DUPLEX
            cv2.putText(image, name, (left + 6, bottom - 6), font, 1.0, (255, 255, 255), 1)

        print(names)
        return names
        # cv2.imshow("Result", image)
        # cv2.imwrite("./output.jpg", image)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()

@app.route('/register/', methods=['POST'])
def register():
    global recognition
    img = request.files.get('photo')  # 从post请求中获取图片数据
    username = request.form.get('username', '')
    suffix = '.' + img.filename.split('.')[-1]  # 获取文件后缀名
    basedir = os.path.abspath(os.path.dirname(__file__))  # 获取当前文件路径
    photo = '/train/' + username + suffix  # 拼接相对路径
    img_path = basedir + photo  # 拼接图片完整保存路径,时间戳命名文件防止重复
    img.save(img_path)  # 保存图片
    recognition.add(img_path)
    return {'msg': 'ok'}

@app.route('/check', methods=['POST'])
def check():
    global recognition
    src = request.data
    data = str(src).split(',')[1][:-3]
    img_data = base64.b64decode(data)
    with open("./test/temp.png", 'wb') as f:
        f.write(img_data)
    names = recognition.compare("./test/temp.png")
    // 放回一个识别的数组
    return {'msg': names}


if __name__ == "__main__":
    recognition = face("./train/")
    recognition.init()
    app.run(host='127.0.0.1', port=3000)

到这一步后端所有的工作也做完啦

演示

登录

我英文名叫Chris,照片就放简历的大头照哈哈哈

login3.png

识别

face1.png

成功后提示,接下来操作... (自由发挥)

suss.png

整个人脸登录已完成,恭喜自己成为CV程序猿哈哈哈

END

  • 感想

可能很多小伙伴会说,为什么不自己写一个人脸识别的算法呀,你都根本不懂Python,这只是调库。我想说对于我们这种小白,我们可以站在巨人的肩膀上,利用市面上各种开源的库,来结合自己的需求,完成公司的项目。我们先锻炼自己的编码思维,再对自己感兴趣的领域进行专研。✊✊

  • 谢谢大家的支持,希望这篇文章可以帮助到有需要的小伙伴,有各种问题可以评论区留言或者私信我🤞🤞