高通手機跑AI系列之——手部姿勢跟蹤
(原創作者@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:AidLux2.0
系統環境:Ubuntu 20.04.3 LTS
提示:AidLux登錄后代碼運行更流暢,在代碼運行時保持AidLux APP在前臺運行,避免代碼運行過程中被系統回收進程,另外屏幕保持常亮,一般息屏后一段時間,手機系統會進入休眠狀態,如需長駐后臺需要給APP權限。
算法Demo
代碼功能分析
這段代碼是一個基于 AI 的手部檢測與識別程序,它通過攝像頭實時捕捉畫面,檢測畫面中的手部,并識別出手部的關鍵點位置,最終在畫面上繪制出手部輪廓和關鍵點。
代碼應用場景分析
這段代碼實現了實時手部檢測和關鍵點識別功能,可應用于多種場景:
-
手勢識別與交互系統
- 智能家居控制:通過特定手勢控制燈光、電器等設備
- 虛擬現實 / 增強現實 (VR/AR):手部動作作為交互輸入,增強沉浸感
- 游戲控制:替代傳統控制器,實現更自然的游戲操作
-
人機協作與機器人控制
- 工業機器人引導:通過手勢指揮機器人執行任務
- 遠程操作:操作人員通過手勢控制遠程設備
-
醫療與康復領域
- 手部運動康復訓練:監測患者手部動作,評估康復進度
- 手術輔助:醫生通過手勢操作醫療設備或查看影像資料
-
教育與演示
- 互動教學:教師通過手勢控制教學內容展示
- 演示系統:演講者通過手勢控制幻燈片或其他演示內容
-
安防監控
- 異常行為檢測:分析人員手部動作識別潛在威脅行為
- 身份驗證:結合手部特征進行身份識別
AidLite 推理引擎功能
AidLite 推理引擎是專為邊緣設備優化的輕量級 AI 推理引擎,具有以下核心功能:
-
多框架支持
- 支持 TensorFlow Lite、ONNX 等多種模型格式
- 代碼中使用了 TensorFlow Lite 格式的模型 (.tflite)
-
硬件加速
- 支持 GPU、CPU 、NPU等多種加速方式
- 手掌檢測模型使用 GPU 加速,提高實時性能
- 關鍵點識別模型使用 CPU 加速,保證精度
-
輕量化設計
- 專為資源受限的邊緣設備優化
- 低內存占用,高效的模型推理能力
-
易用的 API 接口
- 提供模型加載、輸入設置、推理執行、輸出獲取等完整流程的 API
- 代碼中通過aidlite.Model、aidlite.Config和aidlite.InterpreterBuilder等類實現模型管理和推理
OpenCV (代碼中的 CV) 功能
OpenCV 是計算機視覺領域的經典庫,在這段代碼中主要用于以下功能:
-
圖像采集與處理
- 通過cv2.VideoCapture獲取攝像頭實時視頻流
- 圖像預處理:顏色空間轉換 (cv2.cvtColor)、縮放 (cv2.resize) 等
-
圖像顯示與可視化
- 使用cv2.imshow顯示處理后的圖像
- 繪制檢測框 (cv2.rectangle)、關鍵點 (cv2.circle) 和連接線 (cv2.line)
-
輔助計算功能
- 計算手掌重心:cv2.moments計算圖像矩
- 邊界框計算:cv2.boundingRect計算包圍關鍵點的最小矩形
-
圖像操作
- 圖像翻轉:使用cv2.flip處理前置攝像頭的鏡像問題
- 區域提取:從原始圖像中提取手部區域進行單獨處理
OpenCV 提供的這些功能為 AI 模型的輸入準備和輸出結果可視化提供了基礎支持,使整個系統能夠實現從圖像采集到結果展示的完整流程。
AI 模型作用介紹
代碼中使用了兩個 AI 模型協同工作:
-
手掌檢測模型 (palm_detection.tflite)
- 這是一個輕量級的目標檢測模型,專門用于檢測圖像中的手掌。
- 模型輸入:128×128 像素的 RGB 圖像
- 模型輸出:包含手掌位置和邊界框信息
- 作用:快速定位圖像中的手掌,為后續的關鍵點識別提供感興趣區域 (ROI)
- 加速方式:使用 GPU 加速,提高檢測速度
-
手部關鍵點檢測模型 (hand_landmark.tflite)
- 該模型對手部區域進行更精細的分析,識別 21 個關鍵點
- 模型輸入:224×224 像素的 RGB 圖像(通常是手掌檢測模型輸出的 ROI)
- 模型輸出:21 個三維關鍵點坐標,表示手部的詳細姿態
- 作用:精確識別手指關節、指尖等部位的位置
- 加速方式:使用 CPU 加速,保證識別精度
這兩個模型結合使用,實現了從圖像中檢測手掌位置,到精確識別手部 21 個關鍵點的完整流程,能夠實時跟蹤手部動作和姿態。
DEMO代碼
import cv2
import time
from time import sleep
import subprocess
import math
import sys
import numpy as np
from blazeface import * # 導入BlazeFace人臉/手部檢測模型相關函數
import aidlite # AidLux平臺的AI推理框架
import os
# 獲取攝像頭設備ID,優先選擇USB攝像頭
def get_cap_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
# 圖像預處理函數,將圖像轉換為適合TFLite模型輸入的格式
def preprocess_image_for_tflite32(image, model_image_size=300):
try:
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 轉換顏色空間從BGR到RGB
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 # 歸一化處理,將像素值縮放到[-1,1]范圍
image = image.astype('float32') # 轉換數據類型為float32
except cv2.error as e:
print(str(e))
return image
# 在圖像上繪制手部檢測框
def plot_detections(img, detections, with_keypoints=True):
output_img = img
print(img.shape)
x_min=[0,0] # 存儲兩只手的最小x坐標
x_max=[0,0] # 存儲兩只手的最大x坐標
y_min=[0,0] # 存儲兩只手的最小y坐標
y_max=[0,0] # 存儲兩只手的最大y坐標
hand_nums=len(detections) # 檢測到的手的數量
print("Found %d hands" % hand_nums)
if hand_nums >2:
hand_nums=2 # 最多處理兩只手
for i in range(hand_nums):
ymin = detections[i][ 0] * img.shape[0] # 計算邊界框的y最小值
xmin = detections[i][ 1] * img.shape[1] # 計算邊界框的x最小值
ymax = detections[i][ 2] * img.shape[0] # 計算邊界框的y最大值
xmax = detections[i][ 3] * img.shape[1] # 計算邊界框的x最大值
w=int(xmax-xmin) # 計算邊界框寬度
h=int(ymax-ymin) # 計算邊界框高度
h=max(h,w) # 取寬高的最大值
h=h*224./128. # 調整高度尺寸
x=(xmin+xmax)/2. # 計算中心點x坐標
y=(ymin+ymax)/2. # 計算中心點y坐標
# 調整邊界框大小和位置
xmin=x-h/2.
xmax=x+h/2.
ymin=y-h/2.-0.18*h
ymax=y+h/2.-0.18*h
# 存儲邊界框坐標
x_min[i]=int(xmin)
y_min[i]=int(ymin)
x_max[i]=int(xmax)
y_max[i]=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=4, line_width=1):
"""Draw the mesh on an image"""
# The mesh are normalized which means we need to convert it back to fit
# the image size.
image_size = image.shape[0]
mesh = mesh * image_size
for point in mesh:
cv2.circle(image, (point[0], point[1]),
mark_size, (255, 0, 0), 4)
# 計算手掌的重心
def calc_palm_moment(image, landmarks):
image_width, image_height = image.shape[1], image.shape[0]
palm_array = np.empty((0, 2), int)
# 收集手掌區域的關鍵點
for index, landmark in enumerate(landmarks):
if math.isnan(landmark[0]):
landmark[0] = 0
if math.isnan(landmark[1]):
landmark[1] = 0
landmark_x = min(int(landmark[0] * image_width), image_width - 1)
landmark_y = min(int(landmark[1] * image_height), image_height - 1)
landmark_point = [np.array((landmark_x, landmark_y))]
if index == 0: # 手腕1
palm_array = np.append(palm_array, landmark_point, axis=0)
if index == 1: # 手腕2
palm_array = np.append(palm_array, landmark_point, axis=0)
if index == 5: # 食指:根部
palm_array = np.append(palm_array, landmark_point, axis=0)
if index == 9: # 中指:根部
palm_array = np.append(palm_array, landmark_point, axis=0)
if index == 13: # 無名指:根部
palm_array = np.append(palm_array, landmark_point, axis=0)
if index == 17: # 小指:根部
palm_array = np.append(palm_array, landmark_point, axis=0)
# 計算重心
M = cv2.moments(palm_array)
cx, cy = 0, 0
if M['m00'] != 0:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
return cx, cy
# 計算包圍手部關鍵點的矩形框
def calc_bounding_rect(image, landmarks):
image_width, image_height = image.shape[1], image.shape[0]
landmark_array = np.empty((0, 2), int)
# 收集所有關鍵點坐標
for _, landmark in enumerate(landmarks):
landmark_x = min(int(landmark[0] * image_width), image_width - 1)
landmark_y = min(int(landmark[0] * image_height), image_height - 1)
landmark_point = [np.array((landmark_x, landmark_y))]
landmark_array = np.append(landmark_array, landmark_point, axis=0)
# 計算包圍矩形
x, y, w, h = cv2.boundingRect(landmark_array)
return [x, y, x + w, y + h]
# 在圖像上繪制包圍矩形
def draw_bounding_rect(use_brect, image, brect):
if use_brect:
# 外接矩形
cv2.rectangle(image, (brect[0], brect[1]), (brect[2], brect[3]),
(0, 255, 0), 2)
return image
# 在圖像上繪制手部關鍵點和連接線
def draw_landmarks(image, cx, cy, landmarks):
image_width, image_height = image.shape[1], image.shape[0]
landmark_point = []
# 繪制關鍵點
for index, landmark in enumerate(landmarks):
# if landmark.visibility < 0 or landmark.presence < 0:
# continue
landmark_x = min(int(landmark[0] * image_width), image_width - 1)
landmark_y = min(int(landmark[1] * image_height), image_height - 1)
landmark_point.append((landmark_x, landmark_y))
# 根據關鍵點類型繪制不同大小和顏色的點
if index == 0: # 手腕1
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 1: # 手腕2
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 2: # 拇指:根部
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 3: # 拇指:第1關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 4: # 拇指:指尖
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
cv2.circle(image, (landmark_x, landmark_y), 12, (0, 255, 0), 2)
if index == 5: # 食指:根部
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 6: # 食指:第2關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 7: # 食指:第1關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 8: # 食指:指尖
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
cv2.circle(image, (landmark_x, landmark_y), 12, (0, 255, 0), 2)
if index == 9: # 中指:根部
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 10: # 中指:第2關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 11: # 中指:第1關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 12: # 中指:指尖
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
cv2.circle(image, (landmark_x, landmark_y), 12, (0, 255, 0), 2)
if index == 13: # 無名指:根部
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 14: # 無名指:第2關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 15: # 無名指:第1關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 16: # 無名指:指尖
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
cv2.circle(image, (landmark_x, landmark_y), 12, (0, 255, 0), 2)
if index == 17: # 小指:根部
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 18: # 小指:第2關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 19: # 小指:第1關節
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
if index == 20: # 小指:指尖
cv2.circle(image, (landmark_x, landmark_y), 5, (0, 255, 0), 2)
cv2.circle(image, (landmark_x, landmark_y), 12, (0, 255, 0), 2)
# 繪制連接線
if len(landmark_point) > 0:
# 拇指
cv2.line(image, landmark_point[2], landmark_point[3], (0, 255, 0), 2)
cv2.line(image, landmark_point[3], landmark_point[4], (0, 255, 0), 2)
# 食指
cv2.line(image, landmark_point[5], landmark_point[6], (0, 255, 0), 2)
cv2.line(image, landmark_point[6], landmark_point[7], (0, 255, 0), 2)
cv2.line(image, landmark_point[7], landmark_point[8], (0, 255, 0), 2)
# 中指
cv2.line(image, landmark_point[9], landmark_point[10], (0, 255, 0), 2)
cv2.line(image, landmark_point[10], landmark_point[11], (0, 255, 0), 2)
cv2.line(image, landmark_point[11], landmark_point[12], (0, 255, 0), 2)
# 無名指
cv2.line(image, landmark_point[13], landmark_point[14], (0, 255, 0), 2)
cv2.line(image, landmark_point[14], landmark_point[15], (0, 255, 0), 2)
cv2.line(image, landmark_point[15], landmark_point[16], (0, 255, 0), 2)
# 小指
cv2.line(image, landmark_point[17], landmark_point[18], (0, 255, 0), 2)
cv2.line(image, landmark_point[18], landmark_point[19], (0, 255, 0), 2)
cv2.line(image, landmark_point[19], landmark_point[20], (0, 255, 0), 2)
# 手掌
cv2.line(image, landmark_point[0], landmark_point[1], (0, 255, 0), 2)
cv2.line(image, landmark_point[1], landmark_point[2], (0, 255, 0), 2)
cv2.line(image, landmark_point[2], landmark_point[5], (0, 255, 0), 2)
cv2.line(image, landmark_point[5], landmark_point[9], (0, 255, 0), 2)
cv2.line(image, landmark_point[9], landmark_point[13], (0, 255, 0), 2)
cv2.line(image, landmark_point[13], landmark_point[17], (0, 255, 0), 2)
cv2.line(image, landmark_point[17], landmark_point[0], (0, 255, 0), 2)
# 繪制重心點
if len(landmark_point) > 0:
cv2.circle(image, (cx, cy), 12, (0, 255, 0), 2)
return image
# 初始化手掌檢測模型
inShape =[[1 , 128 ,128 ,3]] # 模型輸入形狀
outShape= [[1 , 896,18],[1,896,1]] # 模型輸出形狀
model_path="models/palm_detection.tflite" # 手掌檢測模型路徑
# 創建Model實例對象,并設置模型相關參數
model = aidlite.Model.create_instance(model_path)
if model is None:
print("Create palm_detection 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 # TensorFlow Lite框架類型
config.accelerate_type = aidlite.AccelerateType.TYPE_GPU # GPU加速
config.number_of_threads = 4 # 線程數
# 創建推理解釋器對象
fast_interpreter = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model, config)
if fast_interpreter is None:
print("palm_detection model build_interpretper_from_model_and_config failed !")
# 完成解釋器初始化
result = fast_interpreter.init()
if result != 0:
print("palm_detection model interpreter init failed !")
# 加載模型
result = fast_interpreter.load_model()
if result != 0:
print("palm_detection model interpreter load model failed !")
print("palm_detection model load success!")
# 初始化手部關鍵點檢測模型
model_path1="models/hand_landmark.tflite" # 手部關鍵點檢測模型路徑
inShape1 =[[1 , 224 , 224 ,3]] # 模型輸入形狀
outShape1= [[1 , 63],[1],[1]] # 模型輸出形狀
# 創建Model實例對象,并設置模型相關參數
model1 = aidlite.Model.create_instance(model_path1)
if model1 is None:
print("Create hand_landmark model failed !")
# 設置模型屬性
model1.set_model_properties(inShape1, aidlite.DataType.TYPE_FLOAT32, outShape1,
aidlite.DataType.TYPE_FLOAT32)
# 創建Config實例對象,并設置配置信息
config1 = aidlite.Config.create_instance()
config1.implement_type = aidlite.ImplementType.TYPE_FAST # 快速推理實現類型
config1.framework_type = aidlite.FrameworkType.TYPE_TFLITE # TensorFlow Lite框架類型
config1.accelerate_type = aidlite.AccelerateType.TYPE_CPU # CPU加速
config.number_of_threads = 4 # 線程數
# 創建推理解釋器對象
fast_interpreter1 = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model1, config1)
if fast_interpreter1 is None:
print("hand_landmark model build_interpretper_from_model_and_config failed !")
# 完成解釋器初始化
result = fast_interpreter1.init()
if result != 0:
print("hand_landmark model interpreter init failed !")
# 加載模型
result = fast_interpreter1.load_model()
if result != 0:
print("hand_landmark model interpreter load model failed !")
print("hand_landmark model load success!")
# 加載錨點數據,用于模型推理
anchors = np.load('models/anchors.npy').astype(np.float32)
# 設置Aidlux平臺類型和攝像頭ID
aidlux_type="basic"
# 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)
# 手檢測標志和坐標初始化
bHand=False
x_min=[0,0]
x_max=[0,0]
y_min=[0,0]
y_max=[0,0]
fface=0.0
use_brect=True
# 主循環:持續捕獲視頻幀并進行手部檢測和關鍵點識別
while True:
ret, frame=cap.read() # 讀取一幀視頻
if not ret:
continue
if frame is None:
continue
# 如果使用前置攝像頭,水平翻轉圖像以獲得自然的鏡像效果
if camId==1:
frame=cv2.flip(frame,1)
# 圖像預處理,為手掌檢測模型準備輸入
img = preprocess_image_for_tflite32(frame,128)
# 手部檢測和關鍵點識別流程
if bHand==False:
# 設置輸入數據
result = fast_interpreter.set_input_tensor(0, img.data)
if result != 0:
print("palm_detection model interpreter set_input_tensor() failed")
# 執行手掌檢測模型推理
result = fast_interpreter.invoke()
if result != 0:
print("palm_detection model interpreter invoke() failed")
# 獲取輸出數據
raw_boxes = fast_interpreter.get_output_tensor(0)
if raw_boxes is None:
print("sample : palm_detection model interpreter->get_output_tensor(0) failed !")
classificators = fast_interpreter.get_output_tensor(1)
if classificators is None:
print("sample : palm_detection model interpreter->get_output_tensor(1) failed !")
# 解析檢測結果
detections = blazeface(raw_boxes, classificators, anchors)
# 在圖像上繪制檢測框并獲取邊界框坐標
x_min,y_min,x_max,y_max=plot_detections(frame, detections[0])
# 如果檢測到至少一只手,則設置標志為True,準備進行關鍵點識別
if len(detections[0])>0 :
bHand=True
# 如果已檢測到手部,進行關鍵點識別
if bHand:
hand_nums=len(detections[0])
if hand_nums>2:
hand_nums=2
# 對每只檢測到的手進行關鍵點識別
for i in range(hand_nums):
print(x_min,y_min,x_max,y_max)
# 確保邊界框坐標在有效范圍內
xmin=max(0,x_min[i])
ymin=max(0,y_min[i])
xmax=min(frame.shape[1],x_max[i])
ymax=min(frame.shape[0],y_max[i])
# 提取手部區域
roi_ori=frame[ymin:ymax, xmin:xmax]
# 預處理手部區域圖像,為關鍵點檢測模型準備輸入
roi =preprocess_image_for_tflite32(roi_ori,224)
# 設置輸入數據
result = fast_interpreter1.set_input_tensor(0, roi.data)
if result != 0:
print("hand_landmark model interpreter set_input_tensor() failed")
# 執行手部關鍵點檢測模型推理
result = fast_interpreter1.invoke()
if result != 0:
print("hand_landmark model interpreter invoke() failed")
# 獲取輸出數據
mesh = fast_interpreter1.get_output_tensor(0)
if mesh is None:
print("sample : hand_landmark model interpreter->get_output_tensor(0) failed !")
# 重置手檢測標志,準備下一幀的檢測
bHand=False
# 處理關鍵點數據并在圖像上繪制
mesh = mesh.reshape(21, 3)/224
cx, cy = calc_palm_moment(roi_ori, mesh)
draw_landmarks(roi_ori,cx,cy,mesh)
frame[ymin:ymax, xmin:xmax]=roi_ori
# 顯示處理后的圖像
cv2.imshow("", frame)
模型位置
/opt/aidlux/app/aid-examples//hand_track
模型效果


浙公網安備 33010602011771號