查看: 19|回复: 0

[经验] 基于米尔瑞芯微RK3576核心板/开发板的人脸疲劳检测应用方案

[复制链接]
  • TA的每日心情

    2019-10-15 10:48
  • 签到天数: 7 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 8 小时前 | 显示全部楼层 |阅读模式
    分享到:
    本帖最后由 swiftman 于 2024-12-19 11:14 编辑

    本篇源自:优秀创作者 lulugl

    本文将介绍基于米尔电子MYD-LR3576开发板(米尔基于瑞芯微 RK3576开发板)的人脸疲劳检测方案测试。

    米尔基于RK3576核心板/开发板

    【前言】
    人脸疲劳检测:一种通过分析人脸特征来判断一个人是否处于疲劳状态的技术。其原理主要基于计算机视觉和机器学习方法。当人疲劳时,面部会出现一些特征变化,如眼睛闭合程度增加、眨眼频率变慢、打哈欠、头部姿态改变等。
    例如,通过检测眼睛的状态来判断疲劳程度是一个关键部分。正常情况下,人的眨眼频率相对稳定,而当疲劳时,眨眼频率会降低,并且每次眨眼时眼睛闭合的时间可能会延长。同时,头部可能会不自觉地下垂或者摇晃,这些特征都可以作为疲劳检测的依据。米尔MYC-LR3576采用8核CPU+搭载6 TOPS的NPU加速器,3D GPU,能够非常轻松的实现这个功能,下面就如何实现这一功能分享如下:
    【硬件】
    1、米尔MYC-LR3576开发板
    2、USB摄像头
    【软件】
    1、v4l2
    2、openCV
    3、dlib库:dlib 是一个现代化的 C++ 工具包,它包含了许多用于机器学习、图像处理、数值计算等多种任务的算法和工具。它的设计目标是提供高性能、易于使用的库,并且在开源社区中被广泛应用。
    【实现步骤】
    1、安装python-opencv
    2、安装dlib库
    3、安装v4l2库
    代码实现】
    1、引入cv2、dlib以及线程等:
    1. <font color="#000000">import cv2
    2. import dlib
    3. import numpy as np
    4. import time
    5. from concurrent.futures import ThreadPoolExecutor
    6. import threading</font>
    复制代码

    2、初始化dlib的面部检测器和特征点预测器
    1. <font color="#000000">detector = dlib.get_frontal_face_detector()
    2. predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')</font>
    复制代码

    3、定义计算眼睛纵横比的函数
    1. <font color="#000000">def eye_aspect_ratio(eye):
    2.     A = np.linalg.norm(np.array(eye[1]) - np.array(eye[5]))
    3.     B = np.linalg.norm(np.array(eye[2]) - np.array(eye[4]))
    4.     C = np.linalg.norm(np.array(eye[0]) - np.array(eye[3]))
    5.     ear = (A + B) / (2.0 * C)                                                                                
    6.     return ear</font>
    复制代码

    4、定义计算头部姿势的函数
    1. <font color="#000000">def get_head_pose(shape):
    2.     # 定义面部特征点的三维坐标
    3.     object_points = np.array([
    4.         (0.0, 0.0, 0.0),             # 鼻尖
    5.         (0.0, -330.0, -65.0),        # 下巴
    6.         (-225.0, 170.0, -135.0),     # 左眼左眼角
    7.         (225.0, 170.0, -135.0),      # 右眼右眼角
    8.         (-150.0, -150.0, -125.0),    # 左嘴角
    9.         (150.0, -150.0, -125.0)      # 右嘴角
    10.     ], dtype=np.float32)

    11.     image_pts = np.float32([shape[i] for i in [30, 8, 36, 45, 48, 54]])
    12.     size = frame.shape
    13.     focal_length = size[1]
    14.     center = (size[1] // 2, size[0] // 2)
    15.     camera_matrix = np.array(
    16.         [[focal_length, 0, center[0]],
    17.          [0, focal_length, center[1]],
    18.          [0, 0, 1]], dtype="double"
    19.     )

    20.     dist_coeffs = np.zeros((4, 1))
    21.     (success, rotation_vector, translation_vector) = cv2.solvePnP(
    22.         object_points, image_pts, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE
    23.     )

    24.     rmat, _ = cv2.Rodrigues(rotation_vector)
    25.     angles, _, _, _, _, _ = cv2.RQDecomp3x3(rmat)
    26.     return angles</font>
    复制代码

    5、定义眼睛纵横比阈值和连续帧数阈值
    1. <font color="#000000">EYE_AR_THRESH = 0.3
    2. EYE_AR_CONSEC_FRAMES = 48</font>
    复制代码

    6、打开摄像头
    我们先使用v4l2-ctl --list-devices来例出接在开发板上的列表信息:
    1. <font color="#000000">USB Camera: USB Camera (usb-xhci-hcd.0.auto-1.2):
    2.         /dev/video60
    3.         /dev/video61
    4.         /dev/media7</font>
    复制代码

    在代码中填入60为摄像头的编号:
    1. <font color="#000000">cap = cv2.VideoCapture(60)
    2. cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)  # 降低分辨率
    3. cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 320)</font>
    复制代码

    7、创建多线程处理函数,实现采集与分析分离:
    1. <font color="#000000"># 多线程处理函数
    2. def process_frame(frame):
    3.     global COUNTER, TOTAL
    4.     gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    5.     faces = detector(gray, 0)  # 第二个参数为0,表示不使用upsampling

    6.     for face in faces:
    7.         landmarks = predictor(gray, face)
    8.         shape = [(landmarks.part(i).x, landmarks.part(i).y) for i in range(68)]
    9.         
    10.         left_eye = shape[36:42]
    11.         right_eye = shape[42:48]

    12.         left_ear = eye_aspect_ratio(left_eye)
    13.         right_ear = eye_aspect_ratio(right_eye)
    14.         ear = (left_ear + right_ear) / 2.0

    15.         if ear < EYE_AR_THRESH:
    16.             with lock:
    17.                 COUNTER += 1
    18.         else:
    19.             with lock:
    20.                 if COUNTER >= EYE_AR_CONSEC_FRAMES:
    21.                     TOTAL += 1
    22.                 COUNTER = 0

    23.         # 绘制68个特征点
    24.         for n in range(0, 68):
    25.             x, y = shape[n]
    26.             cv2.circle(frame, (x, y), 2, (0, 255, 0), -1)

    27.         cv2.putText(frame, f"Eye AR: {ear:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)
    28.         cv2.putText(frame, f"Blink Count: {TOTAL}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)

    29.         # 计算头部姿势
    30.         angles = get_head_pose(shape)
    31.         pitch, yaw, roll = angles
    32.         cv2.putText(frame, f"Pitch: {pitch:.2f}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)
    33.         cv2.putText(frame, f"Yaw: {yaw:.2f}", (10, 150), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)
    34.         cv2.putText(frame, f"Roll: {roll:.2f}", (10, 180), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)

    35.         # 判断疲劳状态
    36.         if COUNTER >= EYE_AR_CONSEC_FRAMES or abs(pitch) > 30 or abs(yaw) > 30 or abs(roll) > 30:
    37.             cv2.putText(frame, "Fatigue Detected!", (10, 210), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)

    38.     return frame</font>
    复制代码
    8、创建图像显示线程:
    1. <font color="#000000">with ThreadPoolExecutor(max_workers=2) as executor:
    2.     future_to_frame = {}
    3.     while True:
    4.         ret, frame = cap.read()
    5.         if not ret:
    6.             break

    7.         # 提交当前帧到线程池
    8.         future = executor.submit(process_frame, frame.copy())
    9.         future_to_frame[future] = frame

    10.         # 获取已完成的任务结果
    11.         for future in list(future_to_frame.keys()):
    12.             if future.done():
    13.                 processed_frame = future.result()
    14.                 cv2.imshow("Frame", processed_frame)
    15.                 del future_to_frame[future]
    16.                 break

    17.         # 计算帧数
    18.         fps_counter += 1
    19.         elapsed_time = time.time() - start_time
    20.         if elapsed_time > 1.0:
    21.             fps = fps_counter / elapsed_time
    22.             fps_counter = 0
    23.             start_time = time.time()
    24.             cv2.putText(processed_frame, f"FPS: {fps:.2f}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

    25.         if cv2.waitKey(1) & 0xFF == ord('q'):</font>
    复制代码

    实现效果:

    根据检测的结果,我们就可以来实现疲劳提醒等等的功能。
    整体代码如下:
    1. <font color="#000000">import cv2
    2. import dlib
    3. import numpy as np
    4. import time
    5. from concurrent.futures import ThreadPoolExecutor
    6. import threading

    7. # 初始化dlib的面部检测器和特征点预测器
    8. detector = dlib.get_frontal_face_detector()
    9. predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

    10. # 修改字体大小
    11. font_scale = 0.5  # 原来的字体大小是0.7,现在改为0.5

    12. # 定义计算眼睛纵横比的函数
    13. def eye_aspect_ratio(eye):
    14.     A = np.linalg.norm(np.array(eye[1]) - np.array(eye[5]))
    15.     B = np.linalg.norm(np.array(eye[2]) - np.array(eye[4]))
    16.     C = np.linalg.norm(np.array(eye[0]) - np.array(eye[3]))
    17.     ear = (A + B) / (2.0 * C)                                                                                
    18.     return ear

    19. # 定义计算头部姿势的函数
    20. def get_head_pose(shape):
    21.     # 定义面部特征点的三维坐标
    22.     object_points = np.array([
    23.         (0.0, 0.0, 0.0),             # 鼻尖
    24.         (0.0, -330.0, -65.0),        # 下巴
    25.         (-225.0, 170.0, -135.0),     # 左眼左眼角
    26.         (225.0, 170.0, -135.0),      # 右眼右眼角
    27.         (-150.0, -150.0, -125.0),    # 左嘴角
    28.         (150.0, -150.0, -125.0)      # 右嘴角
    29.     ], dtype=np.float32)

    30.     image_pts = np.float32([shape[i] for i in [30, 8, 36, 45, 48, 54]])
    31.     size = frame.shape
    32.     focal_length = size[1]
    33.     center = (size[1] // 2, size[0] // 2)
    34.     camera_matrix = np.array(
    35.         [[focal_length, 0, center[0]],
    36.          [0, focal_length, center[1]],
    37.          [0, 0, 1]], dtype="double"
    38.     )

    39.     dist_coeffs = np.zeros((4, 1))
    40.     (success, rotation_vector, translation_vector) = cv2.solvePnP(
    41.         object_points, image_pts, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE
    42.     )

    43.     rmat, _ = cv2.Rodrigues(rotation_vector)
    44.     angles, _, _, _, _, _ = cv2.RQDecomp3x3(rmat)
    45.     return angles

    46. # 定义眼睛纵横比阈值和连续帧数阈值
    47. EYE_AR_THRESH = 0.3
    48. EYE_AR_CONSEC_FRAMES = 48

    49. # 初始化计数器
    50. COUNTER = 0
    51. TOTAL = 0

    52. # 创建锁对象
    53. lock = threading.Lock()

    54. # 打开摄像头
    55. cap = cv2.VideoCapture(60)
    56. cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)  # 降低分辨率
    57. cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 320)

    58. # 初始化帧计数器和时间戳
    59. fps_counter = 0
    60. start_time = time.time()

    61. # 多线程处理函数
    62. def process_frame(frame):
    63.     global COUNTER, TOTAL
    64.     gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    65.     faces = detector(gray, 0)  # 第二个参数为0,表示不使用upsampling

    66.     for face in faces:
    67.         landmarks = predictor(gray, face)
    68.         shape = [(landmarks.part(i).x, landmarks.part(i).y) for i in range(68)]
    69.         
    70.         left_eye = shape[36:42]
    71.         right_eye = shape[42:48]

    72.         left_ear = eye_aspect_ratio(left_eye)
    73.         right_ear = eye_aspect_ratio(right_eye)
    74.         ear = (left_ear + right_ear) / 2.0

    75.         if ear < EYE_AR_THRESH:
    76.             with lock:
    77.                 COUNTER += 1
    78.         else:
    79.             with lock:
    80.                 if COUNTER >= EYE_AR_CONSEC_FRAMES:
    81.                     TOTAL += 1
    82.                 COUNTER = 0

    83.         # 绘制68个特征点
    84.         for n in range(0, 68):
    85.             x, y = shape[n]
    86.             cv2.circle(frame, (x, y), 2, (0, 255, 0), -1)

    87.         cv2.putText(frame, f"Eye AR: {ear:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)
    88.         cv2.putText(frame, f"Blink Count: {TOTAL}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)

    89.         # 计算头部姿势
    90.         angles = get_head_pose(shape)
    91.         pitch, yaw, roll = angles
    92.         cv2.putText(frame, f"Pitch: {pitch:.2f}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)
    93.         cv2.putText(frame, f"Yaw: {yaw:.2f}", (10, 150), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)
    94.         cv2.putText(frame, f"Roll: {roll:.2f}", (10, 180), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)

    95.         # 判断疲劳状态
    96.         if COUNTER >= EYE_AR_CONSEC_FRAMES or abs(pitch) > 30 or abs(yaw) > 30 or abs(roll) > 30:
    97.             cv2.putText(frame, "Fatigue Detected!", (10, 210), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 255), 2)

    98.     return frame

    99. with ThreadPoolExecutor(max_workers=2) as executor:
    100.     future_to_frame = {}
    101.     while True:
    102.         ret, frame = cap.read()
    103.         if not ret:
    104.             break

    105.         # 提交当前帧到线程池
    106.         future = executor.submit(process_frame, frame.copy())
    107.         future_to_frame[future] = frame

    108.         # 获取已完成的任务结果
    109.         for future in list(future_to_frame.keys()):
    110.             if future.done():
    111.                 processed_frame = future.result()
    112.                 cv2.imshow("Frame", processed_frame)
    113.                 del future_to_frame[future]
    114.                 break

    115.         # 计算帧数
    116.         fps_counter += 1
    117.         elapsed_time = time.time() - start_time
    118.         if elapsed_time > 1.0:
    119.             fps = fps_counter / elapsed_time
    120.             fps_counter = 0
    121.             start_time = time.time()
    122.             cv2.putText(processed_frame, f"FPS: {fps:.2f}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

    123.         if cv2.waitKey(1) & 0xFF == ord('q'):
    124.             break

    125. # 释放摄像头并关闭所有窗口
    126. cap.release()
    127. cv2.destroyAllWindows()</font>
    复制代码

    【总结】
    【米尔MYC-LR3576核心板及开发板】
    这块开发板性能强大,能轻松实现对人脸的疲劳检测,通过计算结果后进入非常多的工业、人工智能等等的实用功能。


    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /5 下一条



    手机版|小黑屋|与非网

    GMT+8, 2024-12-19 20:11 , Processed in 0.126437 second(s), 18 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.