smartpi 0.1.34__py3-none-any.whl → 0.1.36__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
smartpi/__init__.py CHANGED
@@ -4,5 +4,5 @@ from .base_driver import P1, P2, P3, P4, P5, P6, M1, M2, M3, M4, M5, M6
4
4
  __all__ = ["base_driver","gui","ultrasonic","touch_sensor","temperature","humidity","light_sensor","color_sensor","motor","servo","led","flash",
5
5
  "P1", "P2", "P3", "P4", "P5", "P6", "M1", "M2", "M3", "M4", "M5", "M6"]
6
6
 
7
- __version__ = "0.1.34"
7
+ __version__ = "0.1.36"
8
8
 
smartpi/base_driver.py CHANGED
@@ -415,6 +415,7 @@ def single_operate_sensor(op_struct: bytes, block_time: float) -> Optional[bytes
415
415
  def P_port_init(port:bytes) -> Optional[bytes]:
416
416
  servo_str=[0xA0, 0x0F, 0x00, 0xBE]
417
417
  servo_str[0]=0XA0+port
418
+ time.sleep(0.005)
418
419
  response = single_operate_sensor(servo_str,0)
419
420
  if response:
420
421
  return 0
smartpi/camera.py ADDED
@@ -0,0 +1,84 @@
1
+ # coding: utf-8
2
+ import cv2
3
+ import os
4
+ import time
5
+ import platform
6
+
7
+ class Camera:
8
+ def __init__(self, indexes=[0, 1, 2, 3], target_width=640, target_height=480):
9
+ self.cap = None
10
+ self.indexes = indexes
11
+ self.target_width = target_width
12
+ self.target_height = target_height
13
+ self.open_camera()
14
+
15
+ def open_camera(self):
16
+ """打开摄像头(硬件加速+参数优化)"""
17
+ for idx in self.indexes:
18
+ try:
19
+ # 适配linux/Android的V4L2硬件加速(RK芯片优先)
20
+ if platform.system() == "Linux":
21
+ cap = cv2.VideoCapture(idx, cv2.CAP_V4L2)
22
+ # 尝试启用硬件加速(兼容不同OpenCV版本)
23
+ try:
24
+ # 对于较新版本的OpenCV
25
+ if hasattr(cv2, 'CAP_PROP_HW_ACCELERATION') and hasattr(cv2, 'VIDEO_ACCELERATION_ANY'):
26
+ cap.set(cv2.CAP_PROP_HW_ACCELERATION, cv2.VIDEO_ACCELERATION_ANY)
27
+ except AttributeError as ae:
28
+ print(f"硬件加速设置不支持,使用默认配置: {ae}")
29
+ else:
30
+ cap = cv2.VideoCapture(idx)
31
+
32
+ if cap.isOpened():
33
+ # 尝试设置分辨率
34
+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.target_width)
35
+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.target_height)
36
+
37
+ # 获取实际设置的分辨率
38
+ actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
39
+ actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
40
+
41
+ print(f"摄像头 {idx} 已打开, 分辨率: {actual_width}x{actual_height}")
42
+ self.cap = cap
43
+ return True
44
+ except Exception as e:
45
+ print(f"尝试打开摄像头 {idx} 失败: {e}")
46
+ continue
47
+
48
+ print("无法打开任何摄像头")
49
+ return False
50
+
51
+ def read_frame(self):
52
+ """读取一帧并自动处理错误"""
53
+ if not self.cap or not self.cap.isOpened():
54
+ return False, None
55
+
56
+ ret, frame = self.cap.read()
57
+ if not ret:
58
+ print("读取帧失败,尝试重新打开摄像头...")
59
+ self.release()
60
+ time.sleep(1)
61
+ if self.open_camera():
62
+ return self.read_frame()
63
+ return False, None
64
+
65
+ # 调整到目标分辨率
66
+ if frame.shape[1] != self.target_width or frame.shape[0] != self.target_height:
67
+ frame = cv2.resize(frame, (self.target_width, self.target_height))
68
+
69
+ return True, frame
70
+
71
+ def get_resolution(self):
72
+ """获取当前分辨率"""
73
+ if self.cap and self.cap.isOpened():
74
+ return (
75
+ int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
76
+ int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
77
+ )
78
+ return self.target_width, self.target_height
79
+
80
+ def release(self):
81
+ """释放摄像头资源"""
82
+ if self.cap and self.cap.isOpened():
83
+ self.cap.release()
84
+ self.cap = None
smartpi/color_sensor.py CHANGED
@@ -8,6 +8,7 @@ def get_value(port:bytes) -> Optional[bytes]:
8
8
  color_str=[0xA0, 0x04, 0x00, 0xBE]
9
9
  color_str[0]=0XA0+port
10
10
  color_str[2]=1
11
+ time.sleep(0.005)
11
12
  response = base_driver.single_operate_sensor(color_str,0)
12
13
  if response == None:
13
14
  return None
smartpi/humidity.py CHANGED
@@ -9,6 +9,7 @@ def get_value(port:bytes) -> Optional[bytes]:
9
9
  humi_str=[0XA0, 0X0C, 0X01, 0X71, 0X00, 0XBE]
10
10
  humi_str[0]=0XA0+port
11
11
  humi_str[4]=0X01
12
+ time.sleep(0.005)
12
13
  response = base_driver.single_operate_sensor(humi_str,0)
13
14
  if response == None:
14
15
  return None
smartpi/led.py CHANGED
@@ -10,6 +10,7 @@ def set_color(port:bytes,command:bytes) -> Optional[bytes]:
10
10
  color_lamp_str[0]=0XA0+port
11
11
  color_lamp_str[2]=command
12
12
  # response = base_driver.single_operate_sensor(color_lamp_str,0)
13
+ time.sleep(0.005)
13
14
  base_driver.write_data(0X01, 0X02, color_lamp_str)
14
15
  # if response == None:
15
16
  # return None
smartpi/light_sensor.py CHANGED
@@ -9,7 +9,7 @@ def turn_off(port:bytes) -> Optional[bytes]:
9
9
  light_str[0]=0XA0+port
10
10
  light_str[2]=0x03
11
11
  # response = base_driver.single_operate_sensor(light_str,0)
12
- # base_driver.write_data(0X01, 0X02, light_str)
12
+ base_driver.write_data(0X01, 0X02, light_str)
13
13
  # if response == None:
14
14
  # return None
15
15
  # else:
@@ -20,6 +20,7 @@ def get_value(port:bytes) -> Optional[bytes]:
20
20
  light_str=[0xA0, 0x02, 0x00, 0xBE]
21
21
  light_str[0]=0XA0+port
22
22
  light_str[2]=0x01
23
+ time.sleep(0.005)
23
24
  response = base_driver.single_operate_sensor(light_str,0)
24
25
  if response == None:
25
26
  return None
@@ -35,17 +36,20 @@ def set_threshold(port:bytes,threshold:int) -> Optional[bytes]:
35
36
  light_str[2]=0x04
36
37
  light_str[4]=threshold//256
37
38
  light_str[5]=threshold%256
38
- response = base_driver.single_operate_sensor(light_str,0)
39
- if response == None:
40
- return None
41
- else:
42
- return 0
39
+ time.sleep(0.005)
40
+ base_driver.write_data(0X01, 0X02, light_str)
41
+ # response = base_driver.single_operate_sensor(light_str,0)
42
+ # if response == None:
43
+ # return None
44
+ # else:
45
+ return 0
43
46
 
44
47
  #�����ֵ��ȡ port:����P�˿ڣ�
45
48
  def get_threshold(port:bytes) -> Optional[bytes]:
46
49
  light_str=[0xA0, 0x02, 0x00, 0xBE]
47
50
  light_str[0]=0XA0+port
48
51
  light_str[2]=0x05
52
+ time.sleep(0.005)
49
53
  response = base_driver.single_operate_sensor(light_str,0)
50
54
  if response == None:
51
55
  return None
@@ -59,6 +63,7 @@ def get_bool_data(port:bytes) -> Optional[bytes]:
59
63
  light_str=[0xA0, 0x02, 0x00, 0xBE]
60
64
  light_str[0]=0XA0+port
61
65
  light_str[2]=0x06
66
+ time.sleep(0.005)
62
67
  response = base_driver.single_operate_sensor(light_str,0)
63
68
  if response == None:
64
69
  return None
smartpi/motor.py CHANGED
@@ -8,7 +8,8 @@ from smartpi import base_driver
8
8
  #��������ȡ port:����M�˿ڣ�
9
9
  def get_motor_encoder(port:bytes) -> Optional[bytes]:
10
10
  motor_str=[0xA0, 0x01, 0x01, 0xBE]
11
- motor_str[0]=0XA0+port
11
+ motor_str[0]=0XA0+port
12
+ time.sleep(0.005)
12
13
  response = base_driver.single_operate_sensor(motor_str,0)
13
14
  if response == None:
14
15
  return None
@@ -22,6 +23,7 @@ def reset_motor_encoder(port:bytes) -> Optional[bytes]:
22
23
  motor_str=[0xA0, 0x01, 0x03, 0xBE]
23
24
  motor_str[0]=0XA0+port
24
25
  # response = base_driver.single_operate_sensor(motor_str,0)
26
+ time.sleep(0.005)
25
27
  base_driver.write_data(0X01, 0X02, motor_str)
26
28
  # if response == None:
27
29
  # return None
@@ -34,6 +36,7 @@ def set_motor_direction(port:bytes,direc:bytes) -> Optional[bytes]:
34
36
  motor_str[0]=0XA0+port
35
37
  motor_str[4]=direc
36
38
  # response = base_driver.single_operate_sensor(motor_str,0)
39
+ time.sleep(0.005)
37
40
  base_driver.write_data(0X01, 0X02, motor_str)
38
41
  # if response == None:
39
42
  # return None
@@ -56,6 +59,7 @@ def set_motor(port:bytes,speed:int) -> Optional[bytes]:
56
59
  motor_str[4]=m_par
57
60
 
58
61
  # response = base_driver.single_operate_sensor(motor_str,0)
62
+ time.sleep(0.005)
59
63
  base_driver.write_data(0X01, 0X02, motor_str)
60
64
  # if response == None:
61
65
  # return None
@@ -67,6 +71,7 @@ def set_motor_stop(port:bytes) -> Optional[bytes]:
67
71
  motor_str=[0xA0, 0x01, 0x0B, 0xBE]
68
72
  motor_str[0]=0XA0+port
69
73
  # response = base_driver.single_operate_sensor(motor_str,0)
74
+ time.sleep(0.005)
70
75
  base_driver.write_data(0X01, 0X02, motor_str)
71
76
  # if response == None:
72
77
  # return None
@@ -91,6 +96,7 @@ def set_motor_angle(port:bytes,speed:int,degree:int) -> Optional[bytes]:
91
96
  motor_str[6]=degree//256
92
97
  motor_str[7]=degree%256
93
98
  # response = base_driver.single_operate_sensor(motor_str,0)
99
+ time.sleep(0.005)
94
100
  base_driver.write_data(0X01, 0X02, motor_str)
95
101
  # if response == None:
96
102
  # return None
@@ -122,6 +128,7 @@ def set_motor_second(port:bytes,speed:int,second:float) -> Optional[bytes]:
122
128
  motor_str[9]=byte_array[3]
123
129
 
124
130
  # response = base_driver.single_operate_sensor(motor_str,0)
131
+ time.sleep(0.005)
125
132
  base_driver.write_data(0X01, 0X02, motor_str)
126
133
  # if response == None:
127
134
  # return None
@@ -145,6 +152,7 @@ def set_motor_constspeed(port:bytes,speed:int) -> Optional[bytes]:
145
152
  motor_str[4]=m_par
146
153
 
147
154
  # response = base_driver.single_operate_sensor(motor_str,0)
155
+ time.sleep(0.005)
148
156
  base_driver.write_data(0X01, 0X02, motor_str)
149
157
  # if response == None:
150
158
  # return None
@@ -154,7 +162,8 @@ def set_motor_constspeed(port:bytes,speed:int) -> Optional[bytes]:
154
162
  #�����ٶȶ�ȡ port:����M�˿ڣ�
155
163
  def get_motor_speed(port:bytes) -> Optional[bytes]:
156
164
  motor_str=[0xA0, 0x01, 0x10, 0xBE]
157
- motor_str[0]=0XA0+port
165
+ motor_str[0]=0XA0+port
166
+ time.sleep(0.005)
158
167
  response = base_driver.single_operate_sensor(motor_str,0)
159
168
  if response == None:
160
169
  return None
smartpi/move.py CHANGED
@@ -19,12 +19,13 @@ def run_second(dir:bytes,speed:bytes,second:bytes) -> Optional[bytes]:
19
19
 
20
20
  move_str[6]=speed
21
21
  move_str[8]=second
22
-
23
- response = base_driver.single_operate_sensor(move_str,0)
24
- if response == None:
25
- return None
26
- else:
27
- return 0
22
+ time.sleep(0.005)
23
+ base_driver.write_data(0X01, 0X02, move_str)
24
+ # response = base_driver.single_operate_sensor(move_str,0)
25
+ # if response == None:
26
+ # return None
27
+ # else:
28
+ return 0
28
29
 
29
30
  #以速度移动x度:dir:方向forward、backward、turnright、turnleft:speed:0~100:angle:65535
30
31
  def run_angle(dir:bytes,speed:bytes,angle:int) -> Optional[bytes]:
@@ -43,11 +44,13 @@ def run_angle(dir:bytes,speed:bytes,angle:int) -> Optional[bytes]:
43
44
  move_str[8]=angle//256
44
45
  move_str[9]=angle%256
45
46
 
46
- response = base_driver.single_operate_sensor(move_str,0)
47
- if response == None:
48
- return None
49
- else:
50
- return 0
47
+ time.sleep(0.005)
48
+ base_driver.write_data(0X01, 0X02, move_str)
49
+ # response = base_driver.single_operate_sensor(move_str,0)
50
+ # if response == None:
51
+ # return None
52
+ # else:
53
+ return 0
51
54
 
52
55
  #以速度移动:dir:方向forward、backward、turnright、turnleft;speed:0~100;
53
56
  def run(dir:bytes,speed:bytes) -> Optional[bytes]:
@@ -64,11 +67,13 @@ def run(dir:bytes,speed:bytes) -> Optional[bytes]:
64
67
 
65
68
  move_str[6]=speed
66
69
 
67
- response = base_driver.single_operate_sensor(move_str,0)
68
- if response == None:
69
- return None
70
- else:
71
- return 0
70
+ time.sleep(0.005)
71
+ base_driver.write_data(0X01, 0X02, move_str)
72
+ # response = base_driver.single_operate_sensor(move_str,0)
73
+ # if response == None:
74
+ # return None
75
+ # else:
76
+ return 0
72
77
 
73
78
  #设置左右轮速度移动x秒:Lspeed:-100~100;Rspeed:-100~100;second:1~255
74
79
  def run_speed_second(Lspeed:int,Rspeed:int,second:bytes) -> Optional[bytes]:
@@ -98,11 +103,13 @@ def run_speed_second(Lspeed:int,Rspeed:int,second:bytes) -> Optional[bytes]:
98
103
 
99
104
  move_str[8]=second
100
105
 
101
- response = base_driver.single_operate_sensor(move_str,0)
102
- if response == None:
103
- return None
104
- else:
105
- return 0
106
+ time.sleep(0.005)
107
+ base_driver.write_data(0X01, 0X02, move_str)
108
+ # response = base_driver.single_operate_sensor(move_str,0)
109
+ # if response == None:
110
+ # return None
111
+ # else:
112
+ return 0
106
113
 
107
114
  #设置左右轮速度移动:Lspeed:-100~100;Rspeed:-100~100;
108
115
  def run_speed(Lspeed:int,Rspeed:int) -> Optional[bytes]:
@@ -130,11 +137,13 @@ def run_speed(Lspeed:int,Rspeed:int) -> Optional[bytes]:
130
137
 
131
138
  move_str[4]=m_par
132
139
 
133
- response = base_driver.single_operate_sensor(move_str,0)
134
- if response == None:
135
- return None
136
- else:
137
- return 0
140
+ time.sleep(0.005)
141
+ base_driver.write_data(0X01, 0X02, move_str)
142
+ # response = base_driver.single_operate_sensor(move_str,0)
143
+ # if response == None:
144
+ # return None
145
+ # else:
146
+ return 0
138
147
 
139
148
  #设置左右轮功率移动:Lpower:0~100;Rpower:0~100;
140
149
  def run_power(Lpower:bytes,Rpower:bytes) -> Optional[bytes]:
@@ -143,11 +152,13 @@ def run_power(Lpower:bytes,Rpower:bytes) -> Optional[bytes]:
143
152
  move_str[4]=Rpower
144
153
  move_str[6]=Lpower
145
154
 
146
- response = base_driver.single_operate_sensor(move_str,0)
147
- if response == None:
148
- return None
149
- else:
150
- return 0
155
+ time.sleep(0.005)
156
+ base_driver.write_data(0X01, 0X02, move_str)
157
+ # response = base_driver.single_operate_sensor(move_str,0)
158
+ # if response == None:
159
+ # return None
160
+ # else:
161
+ return 0
151
162
 
152
163
  #设置最大功率:M1:0~100;M2:0~100;M3:0~100;M4:0~100;M5:0~100;M6:0~100;
153
164
  def set_maxpower(M1:bytes,M2:bytes,M3:bytes,M4:bytes,M5:bytes,M6:bytes) -> Optional[bytes]:
@@ -160,21 +171,25 @@ def set_maxpower(M1:bytes,M2:bytes,M3:bytes,M4:bytes,M5:bytes,M6:bytes) -> Optio
160
171
  move_str[12]=M5
161
172
  move_str[14]=M6
162
173
 
163
- response = base_driver.single_operate_sensor(move_str,0)
164
- if response == None:
165
- return None
166
- else:
167
- return 0
174
+ time.sleep(0.005)
175
+ base_driver.write_data(0X01, 0X02, move_str)
176
+ # response = base_driver.single_operate_sensor(move_str,0)
177
+ # if response == None:
178
+ # return None
179
+ # else:
180
+ return 0
168
181
 
169
182
  #马达停止
170
183
  def stop() -> Optional[bytes]:
171
184
  move_str=[0xA0, 0x01, 0x0A, 0xBE]
172
185
 
173
- response = base_driver.single_operate_sensor(move_str,0)
174
- if response == None:
175
- return None
176
- else:
177
- return 0
186
+ time.sleep(0.005)
187
+ base_driver.write_data(0X01, 0X02, move_str)
188
+ # response = base_driver.single_operate_sensor(move_str,0)
189
+ # if response == None:
190
+ # return None
191
+ # else:
192
+ return 0
178
193
 
179
194
  #设置左右轮方向:Lmotor:1~6;Rmotor:1~6;state: no_reversal、all_reversal、left_reversal、right_reversal
180
195
  def set_move_init(Lmotor:bytes,Rmotor:bytes,state:bytes) -> Optional[bytes]:
@@ -192,10 +207,12 @@ def set_move_init(Lmotor:bytes,Rmotor:bytes,state:bytes) -> Optional[bytes]:
192
207
  move_str[6]=Rmotor
193
208
  move_str[8]=Lmotor
194
209
 
195
- response = base_driver.single_operate_sensor(move_str,0)
196
- if response == None:
197
- return None
198
- else:
199
- return 0
210
+ time.sleep(0.005)
211
+ base_driver.write_data(0X01, 0X02, move_str)
212
+ # response = base_driver.single_operate_sensor(move_str,0)
213
+ # if response == None:
214
+ # return None
215
+ # else:
216
+ return 0
200
217
 
201
218
 
@@ -0,0 +1,201 @@
1
+ import cv2
2
+ import numpy as np
3
+ import onnxruntime as ort
4
+ import mediapipe as mp
5
+ import json
6
+ from PIL import Image
7
+ import time # 用于时间测量
8
+
9
+ class GestureWorkflow:
10
+ def __init__(self, model_path):
11
+ # 初始化MediaPipe Hands
12
+ self.mp_hands = mp.solutions.hands
13
+ self.hands = self.mp_hands.Hands(
14
+ static_image_mode=False, # 视频流模式 如果只是获取照片的手势关键点 请设置为True
15
+ max_num_hands=1,#如果想要检测双手,请设置成2
16
+ min_detection_confidence=0.5,#手势关键点的阈值
17
+ model_complexity=0#使用最简单的模型 如果效果不准确 可以考虑设置比较复制的模型 1
18
+ )
19
+
20
+ # 初始化元数据
21
+ self.min_vals = None
22
+ self.max_vals = None
23
+ self.class_labels = None
24
+
25
+ # 加载模型和元数据
26
+ self.load_model(model_path)
27
+
28
+ def load_model(self, model_path):
29
+ """加载模型并解析元数据"""
30
+ # 初始化ONNX Runtime会话
31
+ self.session = ort.InferenceSession(model_path)
32
+
33
+ # 加载元数据
34
+ self._load_metadata()
35
+
36
+ def _load_metadata(self):
37
+ """从ONNX模型元数据中加载归一化参数和类别标签"""
38
+ model_meta = self.session.get_modelmeta()
39
+
40
+ # 检查custom_metadata_map是否存在
41
+ if hasattr(model_meta, 'custom_metadata_map'):
42
+ metadata = model_meta.custom_metadata_map
43
+ if 'minMaxValues' in metadata:
44
+ min_max_data = json.loads(metadata['minMaxValues'])
45
+ self.min_vals = min_max_data.get('min')
46
+ self.max_vals = min_max_data.get('max')
47
+
48
+ if 'classes' in metadata:
49
+ class_labels = json.loads(metadata['classes'])
50
+ self.class_labels = list(class_labels.values()) if isinstance(class_labels, dict) else class_labels
51
+ else:
52
+ # 对于旧版本的ONNX Runtime,使用metadata_props
53
+ for prop in model_meta.metadata_props:
54
+ if prop.key == 'minMaxValues':
55
+ min_max_data = json.loads(prop.value)
56
+ self.min_vals = min_max_data.get('min')
57
+ self.max_vals = min_max_data.get('max')
58
+ elif prop.key == 'classes':
59
+ class_labels = json.loads(prop.value)
60
+ self.class_labels = list(class_labels.values()) if isinstance(class_labels, dict) else class_labels
61
+
62
+ # 设置默认值
63
+ if self.class_labels is None:
64
+ self.class_labels = ["点赞", "点踩", "胜利", "拳头", "我爱你", "手掌"]
65
+
66
+ def preprocess_image(self, image, target_width=224, target_height=224):
67
+ """
68
+ 预处理图像:保持比例缩放并居中放置在目标尺寸的画布上
69
+ 返回处理后的OpenCV图像 (BGR格式)
70
+ """
71
+ # 将OpenCV图像转换为PIL格式
72
+ image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
73
+ pil_image = Image.fromarray(image_rgb)
74
+
75
+ # 计算缩放比例
76
+ width, height = pil_image.size
77
+ scale = min(target_width / width, target_height / height)
78
+
79
+ # 计算新尺寸和位置
80
+ new_width = int(width * scale)
81
+ new_height = int(height * scale)
82
+ x = (target_width - new_width) // 2
83
+ y = (target_height - new_height) // 2
84
+
85
+ # 创建白色背景画布并粘贴缩放后的图像
86
+ canvas = Image.new('RGB', (target_width, target_height), (255, 255, 255))
87
+ resized_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
88
+ canvas.paste(resized_image, (x, y))
89
+
90
+ # 转换回OpenCV格式
91
+ processed_image = np.array(canvas)
92
+ return cv2.cvtColor(processed_image, cv2.COLOR_RGB2BGR)
93
+
94
+ def extract_hand_keypoints(self, image):
95
+ """从图像中提取手部关键点"""
96
+ # 转换图像为RGB格式并处理
97
+ image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
98
+ results = self.hands.process(image_rgb)
99
+
100
+ if results.multi_hand_landmarks:
101
+ # 只使用检测到的第一只手
102
+ landmarks = results.multi_hand_world_landmarks[0]
103
+
104
+ # 提取关键点坐标
105
+ keypoints = []
106
+ for landmark in landmarks.landmark:
107
+ keypoints.extend([landmark.x, landmark.y, landmark.z])
108
+
109
+ return np.array(keypoints, dtype=np.float32)
110
+ return None
111
+
112
+ def normalize_keypoints(self, keypoints):
113
+ """归一化关键点数据"""
114
+ if self.min_vals is None or self.max_vals is None:
115
+ return keypoints # 如果没有归一化参数,返回原始数据
116
+
117
+ normalized = []
118
+ for i, value in enumerate(keypoints):
119
+ if i < len(self.min_vals) and i < len(self.max_vals):
120
+ min_val = self.min_vals[i]
121
+ max_val = self.max_vals[i]
122
+ if max_val - min_val > 0:
123
+ normalized.append((value - min_val) / (max_val - min_val))
124
+ else:
125
+ normalized.append(0)
126
+ else:
127
+ normalized.append(value)
128
+
129
+ return np.array(normalized, dtype=np.float32)
130
+
131
+ def predict_frame(self, frame):
132
+ """执行手势分类预测(直接处理图像帧)"""
133
+ # 记录开始时间
134
+ start_time = time.time()
135
+ # 预处理图像
136
+ processed_image = self.preprocess_image(frame, 224, 224)
137
+
138
+ # 提取关键点
139
+ keypoints = self.extract_hand_keypoints(processed_image)
140
+ min_time = time.time()
141
+ hand_time = min_time - start_time
142
+ #print(f"关键点识别耗时: {hand_time:.4f}秒")
143
+ if keypoints is None:
144
+ return None, {"error": "未检测到手部"}
145
+
146
+ # 归一化关键点
147
+ normalized_kps = self.normalize_keypoints(keypoints)
148
+
149
+ # 准备ONNX输入
150
+ input_data = normalized_kps.reshape(1, -1).astype(np.float32)
151
+
152
+ # 运行推理
153
+ input_name = self.session.get_inputs()[0].name
154
+ outputs = self.session.run(None, {input_name: input_data})
155
+ predictions = outputs[0][0]
156
+
157
+ # 获取预测结果
158
+ class_id = np.argmax(predictions)
159
+ confidence = float(predictions[class_id])
160
+
161
+ # 获取类别标签
162
+ label = self.class_labels[class_id] if class_id < len(self.class_labels) else f"未知类别 {class_id}"
163
+ end_time = time.time()
164
+ all_time = end_time - start_time
165
+ onnx_time = end_time - min_time
166
+ print(f"onnx耗时: {onnx_time:.4f}秒")
167
+ print(f"总耗时: {all_time:.4f}秒")
168
+ # 返回原始结果和格式化结果
169
+ raw_result = predictions.tolist()
170
+ formatted_result = {
171
+ 'class': label,
172
+ 'confidence': confidence,
173
+ 'class_id': class_id,
174
+ 'probabilities': raw_result
175
+ }
176
+
177
+ return raw_result, formatted_result
178
+
179
+ # 保留原始方法以兼容旧代码
180
+ def predict(self, image_path):
181
+ """执行手势分类预测(从文件路径)"""
182
+ try:
183
+ # 使用PIL库读取图像,避免libpng版本问题
184
+ pil_image = Image.open(image_path)
185
+ # 转换为RGB格式
186
+ rgb_image = pil_image.convert('RGB')
187
+ # 转换为numpy数组
188
+ image_array = np.array(rgb_image)
189
+ # 转换为BGR格式(OpenCV使用的格式)
190
+ image = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
191
+
192
+ if image is None:
193
+ raise ValueError(f"无法读取图像: {image_path}")
194
+
195
+ return self.predict_frame(image)
196
+ except Exception as e:
197
+ # 如果PIL失败,尝试使用cv2作为备选
198
+ image = cv2.imread(image_path)
199
+ if image is None:
200
+ raise ValueError(f"无法读取图像: {image_path}")
201
+ return self.predict_frame(image)