镝赋洧 发表于 2025-7-14 13:21:52

高通手机跑AI系列之——468个面部关键点提取

(原创作者@CSDN_伊利丹~怒风)
环境准备

手机
测试手机型号:Redmi K60 Pro
处理器:第二代骁龙8移动--8gen2
运行内存:8.0GB ,LPDDR5X-8400,67.0 GB/s
摄像头:前置16MP+后置50MP+8MP+2MP
AI算力:NPU 48Tops INT8 && GPU 1536ALU x 2 x 680MHz = 2.089 TFLOPS
提示:任意手机均可以,性能越好的手机速度越快
软件
APP:AidLux 2.0
系统环境:Ubuntu 20.04.3 LTS
提示:AidLux登录后代码运行更流畅,在代码运行时保持AidLux APP在前台运行,避免代码运行过程中被系统回收进程,另外屏幕保持常亮,一般息屏后一段时间,手机系统会进入休眠状态,如需长驻后台需要给APP权限。
算法Demo

Demo代码介绍

下面的代码Demo实现了一个基于深度学习的实时人脸检测与面部关键点识别,468个人脸部关键点精准定位并支持多个人同时检测,支持关键点3D坐标,主要功能包括:
1. 摄像头初始化:代码会自动检测可用的 USB 摄像头,如果没有找到,则使用默认的前置摄像头。
2. 双模型架构:

[*]使用 BlazeFace 模型进行人脸检测,该模型可以快速定位图像中的人脸位置
[*]使用面部关键点识别模型识别 468 个面部关键点,包括眼睛、眉毛、嘴巴等特征点
3. 图像预处理:

[*]将图像填充为正方形,确保在不改变宽高比的情况下调整大小
[*]转换颜色空间(BGR 到 RGB)
[*]归一化像素值到 [-1, 1] 范围
[*]调整图像大小以适应模型输入要求
4. 实时处理:

[*]系统采用了优化策略,只在未检测到人脸时执行人脸检测
[*]人脸检测成功后,仅对检测到的人脸区域执行关键点识别
[*]计算帧率以监控系统性能
5. 结果可视化:

[*]在图像上绘制面部关键点
[*]连接特定关键点形成面部特征线,如眼睛轮廓、眉毛、嘴巴等
[*]提供直观的视觉反馈
6. 硬件加速:

[*]人脸检测模型使用 CPU 加速
[*]面部关键点识别模型使用 GPU 加速,提高处理速度
7. 人脸跟踪:

[*]通过比较连续帧的人脸特征值来跟踪人脸
[*]当特征值变化过大时,重新执行人脸检测
这个应用程序在实时视频流中能够准确地检测人脸并识别面部关键点,可用于表情分析、人脸动画、增强现实等多种应用场景。
Demo中算法模型特点分析

这段代码使用了两个深度学习模型通过AidLite框架来实现人脸检测和面部关键点识别功能:
1. 人脸检测模型 (face_detection_front.tflite)

[*]模型路径: models/face_detection_front.tflite
[*]输入尺寸: 128x128
[*]硬件加速: CPU (配置为TYPE_CPU)
[*]作用:

[*]负责在视频帧中快速定位人脸位置
[*]输出人脸的边界框坐标(归一化值)
[*]使用BlazeFace 算法实现轻量级高效检测
[*]输出 896 个预测框(每个框包含边界坐标和置信度)

2. 面部关键点识别模型 (face_landmark.tflite)

[*]模型路径: models/face_landmark.tflite
[*]输入尺寸: 192x192
[*]硬件加速: GPU (配置为TYPE_GPU)
[*]作用:

[*]在检测到的人脸区域内精确定位 468 个面部关键点
[*]关键点覆盖眼睛、眉毛、鼻子、嘴巴、耳朵等部位
[*]输出包含 3D 坐标(x,y,z)的面部网格
[*]用于绘制详细的面部特征线和轮廓

模型协同工作流程

[*]人脸检测:

[*]使用preprocess_img_pad将输入图像填充为正方形并缩放到 128x128
[*]通过blazeface算法处理检测结果,获取人脸边界框
[*]只有在未检测到人脸时才执行此步骤,提高效率

[*]关键点识别:

[*]从检测到的人脸区域中裁剪出 ROI(感兴趣区域)
[*]将 ROI 调整为 192x192 输入到关键点模型
[*]输出 468 个关键点的 3D 坐标
[*]通过draw_landmarks函数在原图上绘制关键点和连线

技术特点

[*]双阶段处理:先检测人脸再识别关键点,减少计算量
[*]硬件优化:CPU 用于轻量级人脸检测,GPU 用于复杂的关键点识别
[*]实时性:通过 Aidlux 平台的加速配置实现手机端上的实时视频处理
[*]鲁棒性:

[*]使用stride8输出值监控人脸稳定性
[*]当人脸特征变化过大时(阈值 0.5)重新执行人脸检测
[*]前置摄像头自动翻转图像以适应镜像场景

这两个模型组合使用,可以在移动设备上实现高效的实时人脸分析,广泛应用于表情识别、AR 滤镜、人机交互等场景。
import time
from time import sleep
import math
import sys
import numpy as np
from blazeface import *
import aidlite
import os
import subprocess
import aidcv as cv2

# 摄像头设备目录
root_dir = "/sys/class/video4linux/"    继续展开代码# 图像预处理函数,用于TFLite模型输入
def preprocess_image_for_tflite32(image, model_image_size=192):
   """
   对图像进行预处理,使其适合TFLite模型输入
   1. 将BGR格式转换为RGB格式
   2. 调整图像大小为模型输入尺寸
   3. 添加批次维度
   4. 归一化像素值到[-1, 1]范围
   5. 转换为float32类型
   """
   image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
   image = cv2.resize(image, (model_image_size, model_image_size))
   image = np.expand_dims(image, axis=0)
   image = (2.0 / 255.0) * image - 1.0
   image = image.astype('float32')

   return image

# 图像填充和预处理函数
def preprocess_img_pad(img, image_size=128):
   """
   对图像进行填充和预处理
   1. 将图像填充为正方形
   2. 保存原始填充图像用于后续显示
   3. 调整填充后图像大小为模型输入尺寸
   4. 归一化并添加批次维度
   """
   # fit the image into a 128x128 square
   shape = np.r_
   pad_all = (shape.max() - shape[:2]).astype('uint32')
   pad = pad_all // 2
   img_pad_ori = np.pad(
       img,
       ((pad, pad_all - pad), (pad, pad_all - pad), (0, 0)),
       mode='constant')
   img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
   img_pad = np.pad(
       img,
       ((pad, pad_all - pad), (pad, pad_all - pad), (0, 0)),
       mode='constant')
   img_small = cv2.resize(img_pad, (image_size, image_size))
   img_small = np.expand_dims(img_small, axis=0)
   img_small = (2.0 / 255.0) * img_small - 1.0
   img_small = img_small.astype('float32')

   return img_pad_ori, img_small, pad

# 在图像上绘制检测框
def plot_detections(img, detections, with_keypoints=True):
   """
   在图像上绘制人脸检测框
   1. 根据检测结果计算人脸区域
   2. 调整区域大小,确保包含完整人脸
   3. 在图像上绘制矩形框
   """
   output_img = img
   print(img.shape)
   x_min = 0
   x_max = 0
   y_min = 0
   y_max = 0
   print("Found %d faces" % len(detections))
   for i in range(len(detections)):
       ymin = detections * img.shape
       xmin = detections * img.shape
       ymax = detections * img.shape
       xmax = detections * img.shape
       w = int(xmax - xmin)
       h = int(ymax - ymin)
       h = max(w, h)
       h = h * 1.5

       x = (xmin + xmax) / 2.
       y = (ymin + ymax) / 2.

       xmin = x - h / 2.
       xmax = x + h / 2.
       ymin = y - h / 2. - 0.08 * h
       ymax = y + h / 2. - 0.08 * h

       x_min = int(xmin)
       y_min = int(ymin)
       x_max = int(xmax)
       y_max = int(ymax)
       p1 = (int(xmin), int(ymin))
       p2 = (int(xmax), int(ymax))
       cv2.rectangle(output_img, p1, p2, (0, 255, 255), 2, 1)

   return x_min, y_min, x_max, y_max

# 在图像上绘制面部网格
def draw_mesh(image, mesh, mark_size=2, line_width=1):
   """
   在图像上绘制面部网格
   1. 将归一化的关键点坐标转换为图像坐标
   2. 在每个关键点位置绘制圆点
   3. 连接关键点形成面部轮廓(眼睛等)
   """
   # The mesh are normalized which means we need to convert it back to fit
   # the image size.
   image_size = image.shape
   mesh = mesh * image_size
   for point in mesh:
       cv2.circle(image, (point, point),
                  mark_size, (0, 255, 128), -1)

   # Draw the contours.
   # Eyes
   left_eye_contour = np.array(,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh,
                              mesh, ]).astype(np.int32)
   right_eye_contour = np.array(,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh,
                                 mesh]).astype(np.int32)
   # Lips
   cv2.polylines(image, , False,
               (255, 255, 255), line_width, cv2.LINE_AA)

# 在图像上绘制面部关键点
def draw_landmarks(image, mesh):
   """
   在图像上绘制面部关键点和连接线
   1. 将归一化的关键点坐标转换为图像坐标
   2. 在每个关键点位置绘制圆点
   3. 连接特定关键点形成面部特征线(眉毛、眼睛、嘴巴等)
   """
   image_size = image.shape
   mesh = mesh * image_size
   landmark_point = []
   for point in mesh:
       landmark_point.append((int(point), int(point)))
       cv2.circle(image, (int(point), int(point)), 2, (255, 255, 0), -1)

   if len(landmark_point) > 0:


       # 左眉毛(55:内側、46:外側)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2, -3)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2, -3)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2, -3)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2, -3)

       # 右眉毛(285:内側、276:外側)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)

       # 左目 (133:目頭、246:目尻)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)

       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)

       # 右目 (362:目頭、466:目尻)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)

       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)

       # 口 (308:右端、78:左端)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)

       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255), 2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)
       cv2.line(image, landmark_point, landmark_point, (0, 0, 255),
                2)

   return image

# 获取摄像头ID
def get_cap_id():
   """
   获取可用的USB摄像头ID
   1. 通过系统命令查询视频设备
   2. 筛选出USB摄像头
   3. 返回最小的可用摄像头ID
   """
   try:
       # 构造命令,使用awk处理输出
       cmd = "ls -l /sys/class/video4linux | awk -F ' -> ' '/usb/{sub(/.*video/, \"\", $2); print $2}'"
       result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
       output = result.stdout.strip().split()

       # 转换所有捕获的编号为整数,找出最小值
       video_numbers = list(map(int, output))
       if video_numbers:
         return min(video_numbers)
       else:
         return None
   except Exception as e:
       print(f"An error occurred: {e}")
       return None

# 初始化人脸检测模型参数
inShape =[]# 输入张量形状
outShape= [,]# 输出张量形状
model_path="models/face_detection_front.tflite"# 人脸检测模型路径
model_path2="models/face_landmark.tflite"# 面部关键点识别模型路径
inShape2 =[]# 第二个模型的输入张量形状
outShape2= [,]# 第二个模型的输出张量形状

# 初始化人脸检测模型
# 创建Model实例对象,并设置模型相关参数
model = aidlite.Model.create_instance(model_path)
if model is None:
   print("Create face_detection_front model failed !")
# 设置模型属性
model.set_model_properties(inShape, aidlite.DataType.TYPE_FLOAT32, outShape,aidlite.DataType.TYPE_FLOAT32)
# 创建Config实例对象,并设置配置信息
config = aidlite.Config.create_instance()
config.implement_type = aidlite.ImplementType.TYPE_FAST
config.framework_type = aidlite.FrameworkType.TYPE_TFLITE
config.accelerate_type = aidlite.AccelerateType.TYPE_CPU# 使用CPU加速
config.number_of_threads = 4# 使用4个线程

# 创建推理解释器对象
fast_interpreter = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model, config)
if fast_interpreter is None:
   print("face_detection_front model build_interpretper_from_model_and_config failed !")
# 完成解释器初始化
result = fast_interpreter.init()
if result != 0:
   print("face_detection_front model interpreter init failed !")
# 加载模型
result = fast_interpreter.load_model()
if result != 0:
   print("face_detection_front model interpreter load face_detection_front model failed !")
print("face_detection_front model model load success!")

# 初始化面部关键点识别模型
# 创建Model实例对象,并设置模型相关参数
model2 = aidlite.Model.create_instance(model_path2)
if model2 is None:
   print("Create face_landmark model failed !")
# 设置模型参数
model2.set_model_properties(inShape2, aidlite.DataType.TYPE_FLOAT32, outShape2,aidlite.DataType.TYPE_FLOAT32)
# 创建Config实例对象,并设置配置信息
config2 = aidlite.Config.create_instance()
config2.implement_type = aidlite.ImplementType.TYPE_FAST
config2.framework_type = aidlite.FrameworkType.TYPE_TFLITE
config2.accelerate_type = aidlite.AccelerateType.TYPE_GPU# 使用GPU加速
config2.number_of_threads = 4# 使用4个线程

# 创建推理解释器对象
fast_interpreter2 = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model2, config2)
if fast_interpreter2 is None:
   print("face_landmark model build_interpretper_from_model_and_config failed !")
# 完成解释器初始化
result = fast_interpreter2.init()
if result != 0:
   print("face_landmark model interpreter init failed !")
# 加载模型
result = fast_interpreter2.load_model()
if result != 0:
   print("face_landmark model interpreter load model failed !")
print("face_landmark model load success!")

# 加载人脸检测模型的锚点数据
anchors = np.load('models/anchors.npy').astype(np.float32)
aidlux_type="root"# Aidlux平台类型
# 0-后置,1-前置
camId = 1# 默认使用前置摄像头
opened = False

# 打开摄像头
while not opened:
   if aidlux_type == "basic":
       cap=cv2.VideoCapture(camId, device='mipi')
   else:
       capId = get_cap_id()
       print("usb camera id: ", capId)
       if capId is None:
          print ("no found usb camera")
          # 默认用1-前置摄像头打开相机,若打开失败,请尝试修改为0-后置
          cap=cv2.VideoCapture(1, device='mipi')
       else:
         camId = capId
         cap = cv2.VideoCapture(camId)
         cap.set(6, cv2.VideoWriter.fourcc('M','J','P','G'))# 设置视频编码格式
   if cap.isOpened():
       opened = True
   else:
       print("open camera failed")
       cap.release()
       time.sleep(0.5)

# 初始化人脸检测标志和位置变量
bFace = False
x_min, y_min, x_max, y_max = (0, 0, 0, 0)
fface = 0.0

# 主循环:实时处理视频流
while True:
   ret, frame=cap.read()
   if not ret:
       continue
   if frame is None:
       continue
   if camId == 1:# 如果是前置摄像头,水平翻转图像以获得镜像效果
       frame = cv2.flip(frame, 1)

   start_time = time.time()# 记录开始时间用于计算帧率

   # 预处理图像,添加填充
   img_pad, img, pad = preprocess_img_pad(frame, 128)

   # 如果没有检测到人脸,执行人脸检测
   if bFace == False:
       # 设置输入数据
       result = fast_interpreter.set_input_tensor(0, img.data)
       if result != 0:
         print("face_detection_front model interpreter set_input_tensor() failed")
       # 执行推理
       result = fast_interpreter.invoke()
       if result != 0:
         print("face_detection_front model interpreter invoke() failed")
       # 获取输出数据
       raw_boxes = fast_interpreter.get_output_tensor(0)
       if raw_boxes is None:
         print("sample : face_detection_front model interpreter->get_output_tensor(0) failed !")

       classificators = fast_interpreter.get_output_tensor(1)
       if classificators is None:
         print("sample : face_detection_front model interpreter->get_output_tensor(1) failed !")

       # 使用blazeface算法处理检测结果
       detections = blazeface(raw_boxes, classificators, anchors)

       if len(detections) > 0:
         bFace = True# 检测到人脸,设置标志为True

   # 如果已检测到人脸,执行面部关键点识别
   if bFace:
       for i in range(len(detections)):
         # 计算人脸区域
         ymin = detections * img_pad.shape
         xmin = detections * img_pad.shape
         ymax = detections * img_pad.shape
         xmax = detections * img_pad.shape
         w = int(xmax - xmin)
         h = int(ymax - ymin)
         h = max(w, h)
         h = h * 1.5# 扩大人脸区域

         x = (xmin + xmax) / 2.
         y = (ymin + ymax) / 2.

         # 调整人脸区域位置和大小
         xmin = x - h / 2.
         xmax = x + h / 2.
         ymin = y - h / 2.
         ymin = y - h / 2. - 0.08 * h
         ymax = y + h / 2. - 0.08 * h
         x_min = int(xmin)
         y_min = int(ymin)
         x_max = int(xmax)
         y_max = int(ymax)

         # 确保区域在图像范围内
         x_min = max(0, x_min)
         y_min = max(0, y_min)
         x_max = min(img_pad.shape, x_max)
         y_max = min(img_pad.shape, y_max)
         
         # 提取人脸区域
         roi_ori = img_pad
         # 预处理人脸区域用于关键点识别
         roi = preprocess_image_for_tflite32(roi_ori, 192)

         # 设置面部关键点识别模型的输入
         result = fast_interpreter2.set_input_tensor(0, roi.data)
         if result != 0:
               print("face_landmark model interpreter set_input_tensor() failed")

         # 执行面部关键点识别
         result = fast_interpreter2.invoke()
         if result != 0:
               print("face_landmark model interpreter set_input_tensor() failed")

         # 获取识别结果
         mesh = fast_interpreter2.get_output_tensor(0)
         if mesh is None:
               print("sample : face_landmark model interpreter->get_output_tensor(0) failed !")

         stride8 = fast_interpreter2.get_output_tensor(1)
         if stride8 is None:
               print("sample : face_landmark model interpreter->get_output_tensor(1) failed !")
         print(f"stride8.shape: {stride8.shape}")
         ffacetmp = stride8
         print('fface:', abs(fface - ffacetmp))
         
         # 人脸跟踪稳定性检测
         if abs(fface - ffacetmp) > 0.5:
               bFace = False# 如果变化过大,重置人脸检测标志
         fface = ffacetmp

         # 处理面部关键点数据
         mesh = mesh.reshape(468, 3) / 192
         # 在人脸区域上绘制关键点
         draw_landmarks(roi_ori, mesh)

         # 调整图像大小以适应显示
         shape = frame.shape
         x, y = img_pad.shape / 2, img_pad.shape / 2

         frame = img_pad / 2):int(y + shape / 2), int(x - shape / 2):int(x + shape / 2)]

   # 显示处理后的图像
   cv2.imshow("", frame)
   
   # 按ESC键退出
   if cv2.waitKey(1) == 27:
       break

# 释放资源
cap.release()
cv2.destroyAllWindows()模型位置

cd /opt/aidlux/app/aid-examples/face_detDemo效果



来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除
页: [1]
查看完整版本: 高通手机跑AI系列之——468个面部关键点提取