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 +1 -1
- smartpi/base_driver.py +1 -0
- smartpi/camera.py +84 -0
- smartpi/color_sensor.py +1 -0
- smartpi/humidity.py +1 -0
- smartpi/led.py +1 -0
- smartpi/light_sensor.py +11 -6
- smartpi/motor.py +11 -2
- smartpi/move.py +63 -46
- smartpi/onnx_hand_workflow.py +201 -0
- smartpi/onnx_image_workflow.py +176 -0
- smartpi/onnx_pose_workflow.py +482 -0
- smartpi/onnx_text_workflow.py +173 -0
- smartpi/onnx_voice_workflow.py +437 -0
- smartpi/posenet_utils.py +222 -0
- smartpi/rknn_hand_workflow.py +245 -0
- smartpi/rknn_image_workflow.py +405 -0
- smartpi/rknn_pose_workflow.py +592 -0
- smartpi/rknn_text_workflow.py +240 -0
- smartpi/rknn_voice_workflow.py +394 -0
- smartpi/servo.py +10 -0
- smartpi/temperature.py +1 -0
- smartpi/touch_sensor.py +1 -0
- smartpi/trace.py +18 -11
- smartpi/ultrasonic.py +1 -0
- {smartpi-0.1.34.dist-info → smartpi-0.1.36.dist-info}/METADATA +1 -1
- smartpi-0.1.36.dist-info/RECORD +32 -0
- smartpi-0.1.34.dist-info/RECORD +0 -20
- {smartpi-0.1.34.dist-info → smartpi-0.1.36.dist-info}/WHEEL +0 -0
- {smartpi-0.1.34.dist-info → smartpi-0.1.36.dist-info}/top_level.txt +0 -0
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.
|
|
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
smartpi/humidity.py
CHANGED
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return
|
|
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)
|