找回密码
 立即注册
首页 业界区 安全 高通手机跑AI系列之——姿态识别

高通手机跑AI系列之——姿态识别

呼延冰枫 2025-9-26 10:56:49
(原创作者@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代码介绍

这段代码是一个姿态检测模型的实时姿态估计应用,它使用了两个模型级联工作:一个用于检测人体,另一个用于在检测到的人体上识别关键点。下面是添加了详细中文注释的代码:
代码功能特点介绍

  • 双模型级联处理:使用两个模型协同工作,第一个模型负责检测人体,第二个模型负责在检测到的人体上识别详细的关键点。
  • 自适应摄像头选择:代码会自动检测并优先使用 USB 摄像头,如果没有 USB 摄像头,则会尝试使用设备内置摄像头。
  • 图像处理优化

    • 图像预处理包括调整大小、填充和归一化
    • 保持原始图像的宽高比,避免变形
    • 支持图像水平翻转,使显示更符合用户习惯

  • 高性能推理

    • 使用 aidlite 框架进行模型推理
    • 姿态检测模型使用 CPU 加速
    • 关键点识别模型使用 GPU 加速
    • 多线程支持,提高处理效率

  • 精确的姿态关键点识别

    • 检测人体 22 个关键点(上半身模型)
    • 支持关键点连接,形成完整的姿态骨架
    • 提供置信度阈值过滤,确保检测准确性

  • 灵活的 ROI 提取

    • 基于检测结果动态提取感兴趣区域
    • 支持旋转不变性,即使人体倾斜也能准确提取
    • 自动调整 ROI 大小,适应不同距离的人体

  • 直观的可视化

    • 清晰显示检测到的人体边界框
    • 绘制关键点和连接线,形成直观的姿态骨架
    • 支持自定义颜色和大小,便于区分不同姿态

  • 鲁棒的错误处理

    • 摄像头打开失败自动重试
    • 模型加载和推理错误检测
    • 异常情况优雅处理,确保程序稳定运行

这个应用可以用于多种场景,如健身指导、动作分析、人机交互等,通过识别和跟踪人体关键点,可以实时分析人体姿态并提供反馈。
Demo中的算法模型分析

这段代码使用了两个模型用AidLite 框架进行人体姿态检测和关键点识别,它们分别是:
1. 姿态检测模型 (pose_detection.tflite)

  • 作用:从输入图像中检测人体的大致位置和姿态。
  • 输入:128×128 像素的 RGB 图像。
  • 输出:包含边界框和关键点的预测结果(896 个候选框,每个框有 12 个坐标值)。
  • 特点:

    • 轻量级设计,适合实时处理。
    • 使用锚框机制提高检测精度。
    • 输出人体的粗略位置和关键点(如眼睛、耳朵、肩膀等)。
    • 采用 CPU 加速,平衡性能与精度。

2. 上半身姿态关键点模型(pose_landmark_upper_body.tflite)

  • 作用:在检测到的人体区域内,精确识别上半身的 22 个关键点。
  • 输入:256×256 像素的 RGB 图像(ROI 区域)。
  • 输出:31 个关键点的坐标(每个点包含 x、y、z 坐标和可见性)。
  • 特点:

    • 高精度识别肩部、肘部、手腕等关节位置。
    • 使用 GPU 加速,提升复杂模型的推理速度。
    • 支持多角度和遮挡场景下的姿态估计。
    • 输出每个关键点的置信度,用于过滤不可靠的检测结果。

模型协同工作流程

  • 姿态检测:先使用第一个模型快速定位人体位置。
  • ROI 提取:基于检测结果裁剪并旋转感兴趣区域(ROI)。
  • 关键点识别:将 ROI 输入第二个模型,获取精细的上半身关键点。
  • 坐标映射:将归一化的关键点坐标映射回原始图像空间。
这种级联模型的设计兼顾了效率和精度,适合实时视频流处理。
Demo代码
  1. import math
  2. import numpy as np
  3. from scipy.special import expit
  4. import time
  5. from time import sleep
  6. import aidlite
  7. import os
  8. import subprocess
  9. import aidcv as cv2
复制代码
继续展开代码
  1. # 摄像头设备路径
  2. root_dir = "/sys/class/video4linux/"
  3. <p>def resize_pad(img):<br>
  4. """<br>
  5. 调整图像大小并填充,使其适合检测器输入</p>
  6. [code]人脸和手掌检测器网络分别需要256x256和128x128的输入图像。
  7. 此函数会保持原始图像的宽高比进行缩放,并在需要时添加填充。
  8. 返回值:
  9.     img1: 256x256大小的图像
  10.     img2: 128x128大小的图像
  11.     scale: 原始图像与256x256图像之间的缩放因子
  12.     pad: 原始图像中添加的填充像素
  13. """
  14. size0 = img.shape
  15. if size0[0]>=size0[1]:
  16.     h1 = 256
  17.     w1 = 256 * size0[1] // size0[0]
  18.     padh = 0
  19.     padw = 256 - w1
  20.     scale = size0[1] / w1
  21. else:
  22.     h1 = 256 * size0[0] // size0[1]
  23.     w1 = 256
  24.     padh = 256 - h1
  25.     padw = 0
  26.     scale = size0[0] / h1
  27. padh1 = padh//2
  28. padh2 = padh//2 + padh%2
  29. padw1 = padw//2
  30. padw2 = padw//2 + padw%2
  31. img1 = cv2.resize(img, (w1,h1))
  32. img1 = np.pad(img1, ((padh1, padh2), (padw1, padw2), (0,0)), 'constant', constant_values=(0,0))
  33. pad = (int(padh1 * scale), int(padw1 * scale))
  34. img2 = cv2.resize(img1, (128,128))
  35. return img1, img2, scale, pad
复制代码
def denormalize_detections(detections, scale, pad):
"""
将归一化的检测坐标映射回原始图像坐标
  1. 人脸和手掌检测器网络需要256x256和128x128的输入图像,
  2. 因此输入图像会被填充和缩放。此函数将归一化坐标映射回原始图像坐标。
  3. 输入:
  4.     detections: nxm张量。n是检测到的对象数量。
  5.         m是4+2*k,其中前4个值是边界框坐标,k是检测器输出的额外关键点数量。
  6.     scale: 用于调整图像大小的缩放因子
  7.     pad: x和y维度上的填充量
  8. """
  9. detections[:, 0] = detections[:, 0] * scale * 256 - pad[0]
  10. detections[:, 1] = detections[:, 1] * scale * 256 - pad[1]
  11. detections[:, 2] = detections[:, 2] * scale * 256 - pad[0]
  12. detections[:, 3] = detections[:, 3] * scale * 256 - pad[1]
  13. detections[:, 4::2] = detections[:, 4::2] * scale * 256 - pad[1]
  14. detections[:, 5::2] = detections[:, 5::2] * scale * 256 - pad[0]
  15. return detections
复制代码
def _decode_boxes(raw_boxes, anchors):
"""
将预测结果转换为实际坐标
  1. 使用锚框将模型预测转换为实际边界框坐标,一次性处理整个批次。
  2. """
  3. boxes = np.zeros_like(raw_boxes)
  4. x_center = raw_boxes[..., 0] / 128.0 * anchors[:, 2] + anchors[:, 0]
  5. y_center = raw_boxes[..., 1] / 128.0 * anchors[:, 3] + anchors[:, 1]
  6. w = raw_boxes[..., 2] / 128.0 * anchors[:, 2]
  7. h = raw_boxes[..., 3] / 128.0 * anchors[:, 3]
  8. boxes[..., 0] = y_center - h / 2.  # ymin
  9. boxes[..., 1] = x_center - w / 2.  # xmin
  10. boxes[..., 2] = y_center + h / 2.  # ymax
  11. boxes[..., 3] = x_center + w / 2.  # xmax
  12. for k in range(4):
  13.     offset = 4 + k*2
  14.     keypoint_x = raw_boxes[..., offset    ] / 128.0 * anchors[:, 2] + anchors[:, 0]
  15.     keypoint_y = raw_boxes[..., offset + 1] / 128.0 * anchors[:, 3] + anchors[:, 1]
  16.     boxes[..., offset    ] = keypoint_x
  17.     boxes[..., offset + 1] = keypoint_y
  18. return boxes
复制代码
def _tensors_to_detections(raw_box_tensor, raw_score_tensor, anchors):
"""
将神经网络输出转换为检测结果
  1. 神经网络输出是一个形状为(b, 896, 16)的张量,包含边界框回归预测,
  2. 以及一个形状为(b, 896, 1)的张量,包含分类置信度。
  3. 此函数将这两个"原始"张量转换为适当的检测结果。
  4. 返回一个(num_detections, 17)的张量列表,每个张量对应批次中的一张图像。
  5. """
  6. detection_boxes = _decode_boxes(raw_box_tensor, anchors)
  7. thresh = 100.0
  8. raw_score_tensor = np.clip(raw_score_tensor, -thresh, thresh)
  9. detection_scores = expit(raw_score_tensor)
  10. # 注意:我们从分数张量中去掉了最后一个维度,因为只有一个类别。
  11. # 现在我们可以简单地使用掩码来过滤掉置信度太低的框。
  12. mask = detection_scores >= 0.75
  13. # 由于批次中的每张图像可能有不同数量的检测结果,
  14. # 因此使用循环一次处理一个图像。
  15. boxes = detection_boxes[mask]
  16. scores = detection_scores[mask]
  17. scores = scores[..., np.newaxis]
  18. return np.hstack((boxes, scores))
复制代码
def py_cpu_nms(dets, thresh):
"""
纯Python实现的非极大值抑制算法
  1. 用于过滤重叠的检测框,保留置信度最高的框。
  2. """  
  3. x1 = dets[:, 0]  
  4. y1 = dets[:, 1]  
  5. x2 = dets[:, 2]  
  6. y2 = dets[:, 3]  
  7. scores = dets[:, 12]  
  8. areas = (x2 - x1 + 1) * (y2 - y1 + 1)  
  9. # 按置信度从大到小排序,获取索引  
  10. order = scores.argsort()[::-1]  
  11. # keep列表存储最终保留的边框  
  12. keep = []  
  13. while order.size > 0:  
  14.     # order[0]是当前分数最大的窗口,肯定要保留  
  15.     i = order[0]  
  16.     keep.append(dets[i])  
  17.     # 计算窗口i与其他所有窗口的交叠部分的面积,矩阵计算
  18.     xx1 = np.maximum(x1[i], x1[order[1:]])  
  19.     yy1 = np.maximum(y1[i], y1[order[1:]])  
  20.     xx2 = np.minimum(x2[i], x2[order[1:]])  
  21.     yy2 = np.minimum(y2[i], y2[order[1:]])  
  22.     w = np.maximum(0.0, xx2 - xx1 + 1)  
  23.     h = np.maximum(0.0, yy2 - yy1 + 1)  
  24.     inter = w * h  
  25.     # 计算IoU(交并比)  
  26.     ovr = inter / (areas[i] + areas[order[1:]] - inter)  
  27.     # ind为所有与窗口i的IoU值小于阈值的窗口的索引  
  28.     inds = np.where(ovr <= thresh)[0]  
  29.     # 下一次计算前要把窗口i去除,所以索引加1  
  30.     order = order[inds + 1]  
  31. return keep
复制代码
[/code]
模型位置
  1. 人脸和手掌检测器网络需要256x256和128x128的输入图像,
  2. 因此输入图像会被填充和缩放。此函数将归一化坐标映射回原始图像坐标。
  3. 输入:
  4.     detections: nxm张量。n是检测到的对象数量。
  5.         m是4+2*k,其中前4个值是边界框坐标,k是检测器输出的额外关键点数量。
  6.     scale: 用于调整图像大小的缩放因子
  7.     pad: x和y维度上的填充量
  8. """
  9. detections[:, 0] = detections[:, 0] * scale * 256 - pad[0]
  10. detections[:, 1] = detections[:, 1] * scale * 256 - pad[1]
  11. detections[:, 2] = detections[:, 2] * scale * 256 - pad[0]
  12. detections[:, 3] = detections[:, 3] * scale * 256 - pad[1]
  13. detections[:, 4::2] = detections[:, 4::2] * scale * 256 - pad[1]
  14. detections[:, 5::2] = detections[:, 5::2] * scale * 256 - pad[0]
  15. return detections
复制代码
模型效果

1.gif


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册