一天到晚闲的没事干,用Raspberry Pi + OpenCV + Streamlit 做了一个人脸识别

介绍

如何创建面部识别应用程序的记录,具体来说,所做如下:

  • 尝试使用Raspberry Pi 4 + CameraModule3 进行拍摄
  • 尝试使用 OpenCV 对相机拍摄的图像进行面部识别处理
  • 尝试使用 Streamlit 将以上内容转换为 Web 应用程序

开发环境

  • Raspberry Pi 4
  • Bullseye 64bit
  • Python 3.9.2
  • CameraModule3

使用 Raspberry Pi 和相机拍摄

在这个步骤中遇到了很多麻烦,过几天再出篇文章,单独进行总结

使用 OpenCV 对拍摄图像进行人脸识别处理

安装包

安装 picamera2 和 OpenCV。

pip install picamera2
pip install opencv-python

下载训练好的模型

yunet_n_640_640.onnx
face_recognizer_fast.onnx

保存面部图像

从图像中检测人脸,将其剪切出来并保存为人脸图像。

源代码
import os
import argparse
import cv2

def main():

    parser = argparse.ArgumentParser("generate aligned face images from an image")
    parser.add_argument("image", help="input image file path (./image.jpg)")
    args = parser.parse_args()

    path = args.image
    directory = os.path.dirname(args.image)
    if not directory:
        directory = os.path.dirname(__file__)
        path = os.path.join(directory, args.image)

    image = cv2.imread(path)
    if image is None:
        exit()

    channels = 1 if len(image.shape) == 2 else image.shape[2]
    if channels == 1:
        image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    if channels == 4:
        image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)

    weights = "onnx/yunet_n_640_640.onnx"
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
    weights = "onnx/face_recognizer_fast.onnx"
    face_recognizer = cv2.FaceRecognizerSF_create(weights, "")

    height, width, _ = image.shape
    face_detector.setInputSize((width, height))

    _, faces = face_detector.detect(image)

    aligned_faces = []
    if faces is not None:
        for face in faces:
            aligned_face = face_recognizer.alignCrop(image, face)
            aligned_faces.append(aligned_face)

    for i, aligned_face in enumerate(aligned_faces):
        cv2.imshow("aligned_face {:03}".format(i + 1), aligned_face)
        cv2.imwrite(os.path.join(directory, "face{:03}.jpg".format(i + 1)), aligned_face)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()
执行命令
python generate_aligned_faces.py input.jpg
输入图像(input.jpg)

在这里插入图片描述

执行结果
face001.jpgface002.jpg
1234

提取特征并保存为字典

从面部图像中提取特征并将其保存为特征字典。

源代码
import os
import sys
import argparse
import numpy as np
import cv2

def main():

    parser = argparse.ArgumentParser("generate face feature dictionary from an face image")
    parser.add_argument("image", help="input face image file path (./<face名>.jpg)")
    args = parser.parse_args()
    print(args.image)

    path = args.image
    directory = os.path.dirname(args.image)
    if not directory:
        directory = os.path.dirname(__file__)
        path = os.path.join(directory, args.image)

    image = cv2.imread(path)
    if image is None:
        exit()

    channels = 1 if len(image.shape) == 2 else image.shape[2]
    if channels == 1:
        image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    if channels == 4:
        image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)

    weights = "onnx/face_recognizer_fast.onnx"
    face_recognizer = cv2.FaceRecognizerSF_create(weights, "")

    face_feature = face_recognizer.feature(image)
    print(face_feature)
    print(type(face_feature))

    basename = os.path.splitext(os.path.basename(args.image))[0]
    dictionary = os.path.join(directory, basename)
    np.save(dictionary, face_feature)

if __name__ == '__main__':
    main()
执行命令
python generate_feature_dictionary.py face001.jpg
输入图像(为每个图像执行的源代码)
face001.jpgface002.jpg
1234
执行结果

输出face001.npy和face002.npy。

人脸识别处理

从捕获的图像中检测人脸,提取特征,并将其与特征字典进行比较以进行面部识别。

源代码
import cv2
import os
import glob
import numpy as np
from picamera2 import Picamera2
from libcamera import controls

COSINE_THRESHOLD = 0.363
NORML2_THRESHOLD = 1.128

def match(recognizer, feature1, dictionary):
    for element in dictionary:
        user_id, feature2 = element
        score = recognizer.match(feature1, feature2, cv2.FaceRecognizerSF_FR_COSINE)
        if score > COSINE_THRESHOLD:
            return True, (user_id, score)
    return False, ("", 0.0)

def main():
    camera = Picamera2()
    camera.configure(camera.create_preview_configuration(main={"format": 'XRGB8888', "size": (640, 480)}))
    camera.start()
    camera.set_controls({'AfMode': controls.AfModeEnum.Continuous})

    dictionary = []
    files = glob.glob(os.path.join("sample_data", "*.npy"))
    for file in files:
        feature = np.load(file)
        user_id = os.path.splitext(os.path.basename(file))[0]
        dictionary.append((user_id, feature))
    
    weights = "onnx/yunet_n_640_640.onnx"
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
    weights = "onnx/face_recognizer_fast.onnx"
    face_recognizer = cv2.FaceRecognizerSF_create(weights, "")

    while True:
        image = camera.capture_array()

        channels = 1 if len(image.shape) == 2 else image.shape[2]
        if channels == 1:
            image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
        if channels == 4:
            image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)

        height, width, _ = image.shape
        face_detector.setInputSize((width, height))

        result, faces = face_detector.detect(image)
        faces = faces if faces is not None else []

        for face in faces:

            aligned_face = face_recognizer.alignCrop(image, face)
            feature = face_recognizer.feature(aligned_face)

            result, user = match(face_recognizer, feature, dictionary)

            box = list(map(int, face[:4]))
            color = (0, 255, 0) if result else (0, 0, 255)
            thickness = 2
            cv2.rectangle(image, box, color, thickness, cv2.LINE_AA)

            id, score = user if result else ("unknown", 0.0)
            text = "{0} ({1:.2f})".format(id, score)
            position = (box[0], box[1] - 10)
            font = cv2.FONT_HERSHEY_SIMPLEX
            scale = 0.6
            cv2.putText(image, text, position, font, scale, color, thickness, cv2.LINE_AA)

        cv2.imshow("face recognition", image)
        key = cv2.waitKey(1)
        if key == ord('q'):
            break

    camera.close()
    cv2.destroyAllWindows()
    
if __name__ == '__main__':
    main()
执行命令
python camface_recognizer.py
执行结果

在这里插入图片描述

使用 Streamlit 的 Web 应用程序

关于 Streamlit

Streamlit 是一个开源框架,可使用 Python 创建 Web 应用程序。它拥有丰富的UI组件,可以用简短的代码直观地编写,可以轻松地使用WebUI创建应用程序。适合小规模开发,例如演示应用程序。

安装包

安装与 Streamlit 相关的软件包。

pip install streamlit
pip install streamlit-webrtc
pip install streamlit_server_state

在 Web 应用程序屏幕上显示捕获的视频

import cv2
import os
import glob
import numpy as np
from picamera2 import Picamera2
from libcamera import controls
import streamlit as st
from streamlit_server_state import server_state, server_state_lock

def get_camera() -> Picamera2:
    with server_state_lock["camera"]:
        if "camera" not in server_state:
            camera = Picamera2()
            camera.configure(camera.create_preview_configuration(main={
                "format": 'XRGB8888',
                "size": (640, 480)
            }))
            camera.start()
            camera.set_controls({'AfMode': controls.AfModeEnum.Continuous})
            server_state.camera = camera
    return server_state.camera

def main():

    st.markdown("# Face Recognition Application")
    
    camera = get_camera()

    try:
        image_loc = st.empty()
        while True:
            image = camera.capture_array()

            channels = 1 if len(image.shape) == 2 else image.shape[2]
            if channels == 1:
                image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
            if channels == 4:
                image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
            
            image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
            image_loc.image(image)
    except KeyboardInterrupt:
        print('app stop!!')
    except Exception as e:
        st.markdown("### 错误。")
        st.markdown(f'{e}')

    camera.close()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()
执行命令
python run_streamlit.py

添加人脸识别处理

import cv2
import os
import glob
import numpy as np
from picamera2 import Picamera2
from libcamera import controls
import streamlit as st
from streamlit_server_state import server_state, server_state_lock

COSINE_THRESHOLD = 0.363
NORML2_THRESHOLD = 1.128

GLOB_PATH = '*********/sample_data/*.npy'
FACE_DETECTOR_WEIGHTS = '*********/onnx/yunet_n_640_640.onnx'
FACE_RECOGNIZER_WEIGHTS = '*********/onnx/face_recognizer_fast.onnx'

def match(recognizer, feature1, dictionary) -> tuple[bool,set[str,float]]:
    for element in dictionary:
        user_id, feature2 = element
        score = recognizer.match(feature1, feature2, cv2.FaceRecognizerSF_FR_COSINE)
        if score > COSINE_THRESHOLD:
            return True, (user_id, score)
    return False, ("", 0.0)

def get_camera() -> Picamera2:
    with server_state_lock["camera"]:
        if "camera" not in server_state:
            camera = Picamera2()
            camera.configure(camera.create_preview_configuration(main={
                "format": 'XRGB8888',
                "size": (640, 480)
            }))
            camera.start()
            camera.set_controls({'AfMode': controls.AfModeEnum.Continuous})
            server_state.camera = camera
    return server_state.camera

def recognize_face(face_recognizer, dictionary, image, face) -> np.ndarray:

    aligned_face = face_recognizer.alignCrop(image, face)
    feature = face_recognizer.feature(aligned_face)

    result, user = match(face_recognizer, feature, dictionary)

    box = list(map(int, face[:4]))
    color = (0, 255, 0) if result else (0, 0, 255)
    thickness = 2
    cv2.rectangle(image, box, color, thickness, cv2.LINE_AA)

    id, score = user if result else ("unknown", 0.0)
    text = "{0} ({1:.2f})".format(id, score)
    position = (box[0], box[1] - 10)
    font = cv2.FONT_HERSHEY_SIMPLEX
    scale = 0.6
    cv2.putText(image, text, position, font, scale, color, thickness, cv2.LINE_AA)

    return image

def main():

    st.markdown("# Face Recognition Application")

    dictionary = []
    files = glob.glob(GLOB_PATH)
    for file in files:
        feature = np.load(file)
        user_id = os.path.splitext(os.path.basename(file))[0]
        dictionary.append((user_id, feature))

    face_detector = cv2.FaceDetectorYN_create(FACE_DETECTOR_WEIGHTS, "", (0, 0))
    face_recognizer = cv2.FaceRecognizerSF_create(FACE_RECOGNIZER_WEIGHTS, "")
    
    camera = get_camera()

    try:
        image_loc = st.empty()
        while True:
            image = camera.capture_array()

            channels = 1 if len(image.shape) == 2 else image.shape[2]
            if channels == 1:
                image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
            if channels == 4:
                image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
            
            height, width, _ = image.shape
            face_detector.setInputSize((width, height))

            result, faces = face_detector.detect(image)
            faces = faces if faces is not None else []

            for face in faces:
                image = recognize_face(face_recognizer, dictionary,image, face)
            image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
            image_loc.image(image)
    except KeyboardInterrupt:
        print('app stop!!')
    except Exception as e:
        st.markdown("### 错误")
        st.markdown(f'{e}')

    camera.close()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()
执行命令
python run_streamlit.py
执行结果

在这里插入图片描述

我在 Streamlit 中偶然发现的事情

Streamlit 旨在每次用户访问应用程序时从头开始运行脚本。因此,如果在一个用户查看相机图像时另一个用户访问该应用程序,则相机的初始设置过程将重新执行,服务器将停止。我能够通过使用streamlit_server_state库在服务器内共享相机来解决这个问题,而不管会话如何。

与 AWS Rekognition 的比较视频

Amazon Rekognition 流视频事件处理来自新的或现有 Kinesis Video Streams 的视频。仅当你发送开始视频分析的通知时,Rekognition 才会开始处理你的 Kinesis Video 流,并且每个事件最多可以分析 120 秒的视频。只需为 Amazon Rekognition 处理的视频量付费。注意:Amazon Kinesis Video Streams 服务单独计费。

  • Amazon Rekognition Image 没那么贵,但是Amazon Rekognition Video 比我想象的要贵。
  • 如果使用Amazon Kinesis Video Streams流式传输视频,将需要支付额外的服务费。
  • 视频中的人脸搜索:0.15USD / 1m

树莓派

  • 如果你使用 OpenCV 等,它基本上是免费的。
  • Raspberry Pi 等硬件成本是在安装时产生的。
  • 由于它取决于硬件,因此可能会产生故障和运营成本等额外成本。
  • 但是,Amazon Rekognition Video 还需要外部摄像头,因此需要考虑如何使用硬件。
    存在一些因素,例如如何创建模型以及你想要多少精度,但是当仅考虑价格时,使用 Raspberry Pi 进行操作似乎要便宜得多。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端仙人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值