smartpi 1.1.5__py3-none-any.whl → 1.1.6__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.
Files changed (68) hide show
  1. smartpi/__init__.py +1 -1
  2. smartpi/onnx_text_workflow.pyc +0 -0
  3. smartpi/posenet_utils.pyc +0 -0
  4. smartpi/rknn_text_workflow.pyc +0 -0
  5. {smartpi-1.1.5.dist-info → smartpi-1.1.6.dist-info}/METADATA +2 -3
  6. {smartpi-1.1.5.dist-info → smartpi-1.1.6.dist-info}/RECORD +8 -68
  7. smartpi/__init__.pyc +0 -0
  8. smartpi/_gui.py +0 -66
  9. smartpi/ai_asr.py +0 -1037
  10. smartpi/ai_llm.py +0 -934
  11. smartpi/ai_tts.py +0 -938
  12. smartpi/ai_vad.py +0 -83
  13. smartpi/audio.py +0 -125
  14. smartpi/base_driver.py +0 -618
  15. smartpi/camera.py +0 -84
  16. smartpi/color_sensor.py +0 -18
  17. smartpi/cw2015.py +0 -179
  18. smartpi/flash.py +0 -130
  19. smartpi/humidity.py +0 -20
  20. smartpi/led.py +0 -19
  21. smartpi/light_sensor.py +0 -72
  22. smartpi/local_model.py +0 -432
  23. smartpi/mcp_client.py +0 -100
  24. smartpi/mcp_fastmcp.py +0 -322
  25. smartpi/mcp_intent_recognizer.py +0 -408
  26. smartpi/models/__init__.pyc +0 -0
  27. smartpi/models/snakers4_silero-vad/__init__.pyc +0 -0
  28. smartpi/models/snakers4_silero-vad/hubconf.pyc +0 -0
  29. smartpi/motor.py +0 -177
  30. smartpi/move.py +0 -218
  31. smartpi/onnx_hand_workflow.py +0 -201
  32. smartpi/onnx_image_workflow.py +0 -176
  33. smartpi/onnx_pose_workflow.py +0 -482
  34. smartpi/onnx_text_workflow.py +0 -173
  35. smartpi/onnx_voice_workflow.py +0 -437
  36. smartpi/posemodel/__init__.pyc +0 -0
  37. smartpi/posenet_utils.py +0 -222
  38. smartpi/rknn_hand_workflow.py +0 -245
  39. smartpi/rknn_image_workflow.py +0 -405
  40. smartpi/rknn_pose_workflow.py +0 -592
  41. smartpi/rknn_text_workflow.py +0 -240
  42. smartpi/rknn_voice_workflow.py +0 -394
  43. smartpi/servo.py +0 -178
  44. smartpi/temperature.py +0 -18
  45. smartpi/tencentcloud-speech-sdk-python/__init__.pyc +0 -0
  46. smartpi/tencentcloud-speech-sdk-python/asr/__init__.pyc +0 -0
  47. smartpi/tencentcloud-speech-sdk-python/asr/flash_recognizer.pyc +0 -0
  48. smartpi/tencentcloud-speech-sdk-python/asr/speech_recognizer.pyc +0 -0
  49. smartpi/tencentcloud-speech-sdk-python/common/__init__.pyc +0 -0
  50. smartpi/tencentcloud-speech-sdk-python/common/credential.pyc +0 -0
  51. smartpi/tencentcloud-speech-sdk-python/common/log.pyc +0 -0
  52. smartpi/tencentcloud-speech-sdk-python/common/utils.pyc +0 -0
  53. smartpi/tencentcloud-speech-sdk-python/soe/__init__.pyc +0 -0
  54. smartpi/tencentcloud-speech-sdk-python/soe/speaking_assessment.pyc +0 -0
  55. smartpi/tencentcloud-speech-sdk-python/tts/__init__.pyc +0 -0
  56. smartpi/tencentcloud-speech-sdk-python/tts/flowing_speech_synthesizer.pyc +0 -0
  57. smartpi/tencentcloud-speech-sdk-python/tts/speech_synthesizer.pyc +0 -0
  58. smartpi/tencentcloud-speech-sdk-python/tts/speech_synthesizer_ws.pyc +0 -0
  59. smartpi/tencentcloud-speech-sdk-python/vc/__init__.pyc +0 -0
  60. smartpi/tencentcloud-speech-sdk-python/vc/speech_convertor_ws.pyc +0 -0
  61. smartpi/text_gte_model/__init__.pyc +0 -0
  62. smartpi/text_gte_model/config/__init__.pyc +0 -0
  63. smartpi/text_gte_model/gte/__init__.pyc +0 -0
  64. smartpi/touch_sensor.py +0 -16
  65. smartpi/trace.py +0 -120
  66. smartpi/ultrasonic.py +0 -20
  67. {smartpi-1.1.5.dist-info → smartpi-1.1.6.dist-info}/WHEEL +0 -0
  68. {smartpi-1.1.5.dist-info → smartpi-1.1.6.dist-info}/top_level.txt +0 -0
@@ -1,245 +0,0 @@
1
- import cv2
2
- import numpy as np
3
- import mediapipe as mp
4
- import json
5
- from PIL import Image
6
- import os
7
- from rknnlite.api import RKNNLite # 导入RKNNLite
8
- import time # 用于时间测量
9
-
10
- class GestureWorkflow:
11
- def __init__(self, model_path):
12
- # 确保model_path是绝对路径
13
- self.model_path = os.path.abspath(model_path)
14
-
15
- # 初始化MediaPipe Hands
16
- self.mp_hands = mp.solutions.hands
17
- self.hands = self.mp_hands.Hands(
18
- static_image_mode=False, # 视频流模式 如果只是获取照片的手势关键点 请设置为True
19
- max_num_hands=1,#如果想要检测双手,请设置成2
20
- min_detection_confidence=0.5,#手势关键点的阈值
21
- model_complexity=0#使用最简单的模型 如果效果不准确 可以考虑设置比较复制的模型 1
22
- )
23
-
24
- # 初始化元数据
25
- self.min_vals = None
26
- self.max_vals = None
27
- self.class_labels = None
28
-
29
- # 加载模型和元数据
30
- self.load_model(self.model_path)
31
-
32
- def load_model(self, model_path):
33
- """加载RKNN模型并解析元数据"""
34
- # 创建RKNNLite实例
35
- self.rknn_lite = RKNNLite()
36
-
37
- # 加载RKNN模型
38
- ret = self.rknn_lite.load_rknn(model_path)
39
- if ret != 0:
40
- raise RuntimeError(f'加载RKNN模型失败, 错误码: {ret}')
41
-
42
- # 初始化运行时环境 强制使用npu core_mask=RKNNLite.NPU_CORE_0
43
- ret = self.rknn_lite.init_runtime()
44
- if ret != 0:
45
- raise RuntimeError(f'初始化NPU运行时失败, 错误码: {ret}')
46
-
47
- # 从同目录的JSON文件加载元数据
48
- metadata_path = self._get_metadata_path(model_path)
49
- self._load_metadata(metadata_path)
50
-
51
- def _get_metadata_path(self, model_path):
52
- """获取元数据文件的绝对路径"""
53
- # 尝试与模型同目录的JSON文件
54
- base_dir = os.path.dirname(model_path)
55
- base_name = os.path.basename(model_path)
56
- metadata_name = os.path.splitext(base_name)[0] + 'rknn_metadata.json'
57
- metadata_path = os.path.join(base_dir, metadata_name)
58
-
59
- # 如果文件不存在,尝试默认名称
60
- if not os.path.exists(metadata_path):
61
- metadata_path = os.path.join(base_dir, 'rknn_metadata.json')
62
-
63
- return metadata_path
64
-
65
- def _load_metadata(self, metadata_path):
66
- """从JSON文件加载元数据"""
67
- try:
68
- with open(metadata_path, 'r', encoding='utf-8') as f:
69
- metadata = json.load(f)
70
-
71
- self.class_labels = metadata.get('classes', ["点赞", "点踩", "胜利", "拳头", "我爱你", "手掌"])
72
- min_max = metadata.get('minMax', {})
73
- self.min_vals = min_max.get('min', [])
74
- self.max_vals = min_max.get('max', [])
75
-
76
- print(f"从 {metadata_path} 加载元数据成功")
77
- print(f"类别标签: {self.class_labels}")
78
-
79
- except Exception as e:
80
- print(f"加载元数据失败: {e}")
81
- # 设置默认值
82
- self.class_labels = ["点赞", "点踩", "胜利", "拳头", "我爱你", "手掌"]
83
- self.min_vals = []
84
- self.max_vals = []
85
-
86
- def preprocess_image(self, image, target_width=224, target_height=224):
87
- """
88
- 预处理图像:保持比例缩放并居中放置在目标尺寸的画布上
89
- 返回处理后的OpenCV图像 (BGR格式)
90
- """
91
- # 将OpenCV图像转换为PIL格式
92
- image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
93
- pil_image = Image.fromarray(image_rgb)
94
-
95
- # 计算缩放比例
96
- width, height = pil_image.size
97
- scale = min(target_width / width, target_height / height)
98
-
99
- # 计算新尺寸和位置
100
- new_width = int(width * scale)
101
- new_height = int(height * scale)
102
- x = (target_width - new_width) // 2
103
- y = (target_height - new_height) // 2
104
-
105
- # 创建白色背景画布并粘贴缩放后的图像
106
- canvas = Image.new('RGB', (target_width, target_height), (255, 255, 255))
107
- resized_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
108
- canvas.paste(resized_image, (x, y))
109
-
110
- # 转换回OpenCV格式
111
- processed_image = np.array(canvas)
112
- return cv2.cvtColor(processed_image, cv2.COLOR_RGB2BGR)
113
-
114
- def extract_hand_keypoints(self, image):
115
- """从图像中提取手部关键点"""
116
- # 转换图像为RGB格式并处理
117
- image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
118
- results = self.hands.process(image_rgb)
119
-
120
- if results.multi_hand_landmarks:
121
- # 只使用检测到的第一只手
122
- landmarks = results.multi_hand_world_landmarks[0]
123
-
124
- # 提取关键点坐标
125
- keypoints = []
126
- for landmark in landmarks.landmark:
127
- keypoints.extend([landmark.x, landmark.y, landmark.z])
128
-
129
- return np.array(keypoints, dtype=np.float32)
130
- return None
131
-
132
- def normalize_keypoints(self, keypoints):
133
- """归一化关键点数据"""
134
- if not self.min_vals or not self.max_vals or len(self.min_vals) != len(keypoints):
135
- # 如果没有归一化参数或长度不匹配,返回原始数据
136
- return keypoints
137
-
138
- normalized = []
139
- for i, value in enumerate(keypoints):
140
- min_val = self.min_vals[i]
141
- max_val = self.max_vals[i]
142
- if max_val - min_val > 1e-6: # 避免除以零
143
- normalized.append((value - min_val) / (max_val - min_val))
144
- else:
145
- normalized.append(0.0)
146
-
147
- return np.array(normalized, dtype=np.float32)
148
-
149
- def predict_frame(self, frame):
150
- """执行手势分类预测(直接处理图像帧)"""
151
- # 记录开始时间
152
- start_time = time.time()
153
-
154
- # 预处理图像
155
- processed_image = self.preprocess_image(frame, 224, 224)
156
-
157
- # 提取关键点
158
- keypoints = self.extract_hand_keypoints(processed_image)
159
- min_time = time.time()
160
- hand_time = min_time - start_time
161
- #print(f"关键点识别耗时: {hand_time:.4f}秒")
162
- if keypoints is None:
163
- # 记录结束时间并计算耗时
164
- end_time = time.time()
165
- #print(f"识别耗时: {end_time - start_time:.4f}秒 (未检测到手部)")
166
- return None, {"error": "未检测到手部", "processing_time": end_time - start_time}
167
-
168
- # 归一化关键点
169
- normalized_kps = self.normalize_keypoints(keypoints)
170
-
171
- # 准备输入数据 (1, 63) 形状
172
- input_data = normalized_kps.reshape(1, -1).astype(np.float32)
173
-
174
- # 使用RKNN Lite进行推理
175
- try:
176
- outputs = self.rknn_lite.inference(inputs=[input_data])
177
- predictions = outputs[0][0]
178
-
179
- # 获取预测结果
180
- class_id = np.argmax(predictions)
181
- confidence = float(predictions[class_id])
182
-
183
- # 获取类别标签
184
- label = self.class_labels[class_id] if class_id < len(self.class_labels) else f"未知类别 {class_id}"
185
-
186
- # 返回原始结果和格式化结果
187
- raw_result = predictions.tolist()
188
- formatted_result = {
189
- 'class': label,
190
- 'confidence': confidence,
191
- 'class_id': class_id,
192
- 'probabilities': raw_result
193
- }
194
-
195
- # 记录结束时间并计算耗时
196
- end_time = time.time()
197
- rknn_time= end_time - min_time
198
- processing_time = end_time - start_time
199
- print(f"rknn识别耗时: {rknn_time:.4f}秒")
200
- print(f"总共识别耗时: {processing_time:.4f}秒 - 识别结果: {label} (置信度: {confidence:.2f})")
201
-
202
- return raw_result, formatted_result
203
-
204
- except Exception as e:
205
- # 记录结束时间并计算耗时
206
- end_time = time.time()
207
- print(f"推理失败: {e}, 耗时: {end_time - start_time:.4f}秒")
208
- return None, {"error": f"推理失败: {str(e)}", "processing_time": end_time - start_time}
209
-
210
- def release(self):
211
- """释放资源"""
212
- if hasattr(self, 'rknn_lite'):
213
- self.rknn_lite.release()
214
- print("NPU资源已释放")
215
-
216
- def __del__(self):
217
- """析构函数自动释放资源"""
218
- self.release()
219
-
220
- # 保留原始方法以兼容旧代码
221
- def predict(self, image_path):
222
- """执行手势分类预测(从文件路径)"""
223
- # 确保图像路径是绝对路径
224
- absolute_image_path = os.path.abspath(image_path)
225
-
226
- try:
227
- # 使用PIL库读取图像,避免libpng版本问题
228
- pil_image = Image.open(absolute_image_path)
229
- # 转换为RGB格式
230
- rgb_image = pil_image.convert('RGB')
231
- # 转换为numpy数组
232
- image_array = np.array(rgb_image)
233
- # 转换为BGR格式(OpenCV使用的格式)
234
- image = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
235
-
236
- if image is None:
237
- raise ValueError(f"无法读取图像: {absolute_image_path}")
238
-
239
- return self.predict_frame(image)
240
- except Exception as e:
241
- # 如果PIL失败,尝试使用cv2作为备选
242
- image = cv2.imread(absolute_image_path)
243
- if image is None:
244
- raise ValueError(f"无法读取图像: {absolute_image_path}")
245
- return self.predict_frame(image)
@@ -1,405 +0,0 @@
1
- import numpy as np # 用于数值计算和数组操作
2
- from PIL import Image # 用于图像文件处理
3
- import cv2 # 用于视频帧处理(OpenCV)
4
- import os # 用于文件路径操作
5
- import json # 用于解析元数据JSON文件
6
- from rknnlite.api import RKNNLite # 用于瑞芯微NPU的RKNN模型推理
7
- import time # 用于计时,评估推理性能
8
-
9
- class ImageWorkflow:
10
- """
11
- 图像分类工作流类(适配RKNN Lite):封装了RKNN Lite模型加载、元数据解析、图像/视频帧预处理、
12
- NPU推理执行和结果格式化的完整流程,专为瑞芯微NPU硬件加速设计,采用全局归一化(与ONNX分类保持一致)
13
- 支持从图像文件或视频帧(numpy数组)进行推理
14
- """
15
- def __init__(self, model_path=None):
16
- """
17
- 初始化RKNN图像推理工作流实例
18
-
19
- 参数:
20
- model_path: RKNN模型文件路径(.rknn格式),可选,可后续通过load_model加载
21
-
22
- 属性说明:
23
- self.rknn_lite: RKNN Lite推理实例,用于与NPU交互执行模型推理
24
- self.classes: 类别标签列表,如["猫", "狗", "鸟"],从元数据文件加载
25
- self.metadata: 模型元数据字典,存储模型描述等信息
26
- self.input_shape: 模型输入形状,固定为[1, 224, 3, 224]([batch, height, channels, width])
27
- """
28
- self.rknn_lite = None # RKNN Lite推理核心实例
29
- self.classes = [] # 类别标签列表
30
- self.metadata = {} # 模型元数据(如作者、版本等)
31
- # 固定输入形状:[批量大小, 图像高度, 通道数, 图像宽度],适配多数RKNN图像分类模型
32
- self.input_shape = [1, 224, 3, 224]
33
-
34
- # 若初始化时提供模型路径,则自动加载模型
35
- if model_path:
36
- self.load_model(model_path)
37
-
38
- def load_model(self, model_path):
39
- """
40
- 加载RKNN模型并初始化NPU运行时,同时解析配套的元数据(主要是类别标签)
41
-
42
- 参数:
43
- model_path: RKNN模型文件路径(.rknn格式)
44
-
45
- 执行流程:
46
- 1. 初始化RKNN Lite实例
47
- 2. 加载.rknn模型文件,校验模型完整性
48
- 3. 初始化NPU硬件运行时环境
49
- 4. 自动获取并加载配套的元数据文件(仅读取类别标签)
50
- 5. 强制设置输入形状以确保兼容性
51
- """
52
- try:
53
- # 1. 初始化RKNN Lite核心实例
54
- self.rknn_lite = RKNNLite()
55
-
56
- # 2. 加载RKNN模型文件,返回错误码需校验
57
- ret = self.rknn_lite.load_rknn(model_path)
58
- if ret != 0:
59
- raise RuntimeError(f'加载RKNN模型失败,错误码: {ret}(请检查模型路径和文件完整性)')
60
-
61
- # 3. 初始化NPU运行时环境(需硬件支持 如:灵芯派)
62
- ret = self.rknn_lite.init_runtime()
63
- if ret != 0:
64
- raise RuntimeError(f'初始化NPU运行时失败,错误码: {ret}(请检查硬件驱动和权限)')
65
-
66
- # 4. 自动获取元数据文件路径并加载(仅关注类别标签)
67
- metadata_path = self._get_metadata_path(model_path)
68
- self._load_metadata(metadata_path)
69
-
70
- # 5. 解析并固定输入形状(确保模型输入格式统一)
71
- self._parse_input_shape()
72
-
73
- print(f"模型加载成功: {model_path}")
74
- print(f"输入形状: {self.input_shape}")
75
- print(f"类别数量: {len(self.classes)}")
76
-
77
- except Exception as e:
78
- print(f"模型加载失败: {e}")
79
-
80
- def _get_metadata_path(self, model_path):
81
- """
82
- (私有方法,仅内部调用)获取模型配套的元数据文件路径
83
-
84
- 路径逻辑:
85
- 1. 优先查找与模型同名的元数据文件(如model.rknn → model_rknn_metadata.json)
86
- 2. 若未找到,查找模型目录下的通用元数据文件(rknn_metadata.json)
87
-
88
- 参数:
89
- model_path: RKNN模型文件路径
90
-
91
- 返回:
92
- 元数据文件的完整路径(即使文件不存在,也返回推导路径)
93
- """
94
- # 提取模型所在目录和文件名(不含后缀)
95
- base_dir = os.path.dirname(model_path)
96
- base_name = os.path.basename(model_path)
97
- # 构建与模型同名的元数据文件名(如"model.rknn" → "model_rknn_metadata.json")
98
- metadata_name = os.path.splitext(base_name)[0] + '_rknn_metadata.json'
99
- metadata_path = os.path.join(base_dir, metadata_name)
100
-
101
- # 若同名元数据文件不存在,使用通用文件名
102
- if not os.path.exists(metadata_path):
103
- metadata_path = os.path.join(base_dir, 'rknn_metadata.json')
104
- print(f"未找到同名元数据文件,尝试通用路径: {metadata_path}")
105
-
106
- return metadata_path
107
-
108
- def _load_metadata(self, metadata_path):
109
- """
110
- (私有方法,仅内部调用)从JSON格式的元数据文件加载类别标签
111
-
112
- 加载内容:
113
- - classes: 类别标签列表(如["cat", "dog"])
114
-
115
- 参数:
116
- metadata_path: 元数据JSON文件路径
117
-
118
- 说明:
119
- 若元数据文件不存在或解析失败,会默认将类别列表设为空,不影响模型基础推理
120
- """
121
- try:
122
- # 读取JSON文件并解析
123
- with open(metadata_path, 'r', encoding='utf-8') as f:
124
- metadata = json.load(f)
125
-
126
- # 仅提取类别标签(无则为空列表)
127
- self.classes = metadata.get('classes', [])
128
-
129
- print(f"从 {metadata_path} 加载元数据成功")
130
- print(f"类别标签: {self.classes}")
131
-
132
- except FileNotFoundError:
133
- print(f"警告: 元数据文件 {metadata_path} 不存在,将使用默认类别索引")
134
- self.classes = []
135
- except json.JSONDecodeError:
136
- print(f"警告: {metadata_path} 不是合法的JSON文件,将使用默认类别索引")
137
- self.classes = []
138
- except Exception as e:
139
- print(f"加载元数据失败: {e},将使用默认类别索引")
140
- self.classes = []
141
-
142
- def _parse_input_shape(self):
143
- """
144
- (私有方法,仅内部调用)强制设置模型输入形状,确保兼容性
145
-
146
- 说明:
147
- 固定输入形状为[1, 224, 3, 224]([batch, height, channels, width]),
148
- 避免因模型导出时的动态维度(如-1)导致NPU推理异常,适配多数图像分类场景
149
- """
150
- self.input_shape = [1, 224, 3, 224]
151
- print(f"强制使用输入形状: {self.input_shape}(确保NPU推理兼容性)")
152
-
153
- def _preprocess(self, image_path):
154
- """
155
- (私有方法,仅内部调用)图像文件预处理:将图像转为RKNN模型要求的输入格式
156
-
157
- 预处理流程(与ONNX版本保持一致的全局归一化):
158
- 1. 读取图像文件并转为RGB格式(去除Alpha通道,避免干扰)
159
- 2. 调整图像尺寸至224×224(匹配输入形状的height和width)
160
- 3. 像素值归一化(0-255 → 0-1)并转为float32类型(全局归一化)
161
- 4. 调整维度顺序(HWC → HCW),匹配输入形状的[height, channels, width]
162
- 5. 添加batch维度,最终形状为[1, 224, 3, 224]
163
-
164
- 参数:
165
- image_path: 图像文件路径(如"test.jpg"、"sample.png")
166
-
167
- 返回:
168
- 预处理后的numpy数组(形状为[self.input_shape]),失败则返回None
169
- """
170
- try:
171
- # 1. 读取图像并转为RGB(PIL默认读取为RGB,部分格式需显式转换)
172
- img = Image.open(image_path).convert("RGB")
173
-
174
- # 2. 从输入形状提取目标尺寸:height=224,width=224
175
- target_h, target_w = self.input_shape[1], self.input_shape[3]
176
- # 双线性插值调整尺寸(平衡速度和画质)
177
- img = img.resize((target_w, target_h), Image.BILINEAR)
178
-
179
- # 3. 转为numpy数组并全局归一化(像素值从0-255映射到0-1)
180
- img_array = np.array(img).astype(np.float32) / 255.0 # 形状: [224, 224, 3](HWC)
181
-
182
- # 4. 调整维度顺序:HWC(高度、宽度、通道)→ HCW(高度、通道、宽度)
183
- # 匹配RKNN输入形状的[height, channels, width]要求
184
- img_array = img_array.transpose(0, 2, 1) # 维度索引0=H,1=W,2=C → 转置后0=H,1=C,2=W
185
-
186
- # 5. 添加batch维度(模型要求输入为[batch, H, C, W],批量大小=1)
187
- return np.expand_dims(img_array, axis=0) # 最终形状: [1, 224, 3, 224]
188
-
189
- except FileNotFoundError:
190
- print(f"图像预处理失败: 图像文件 {image_path} 不存在")
191
- return None
192
- except Exception as e:
193
- print(f"图像预处理失败: {e}")
194
- return None
195
-
196
- def inference(self, data, model_path=None):
197
- """
198
- 从图像文件执行RKNN模型推理(适用于单张图像分类)
199
-
200
- 参数:
201
- data: 图像文件路径(如"test.jpg")
202
- model_path: 可选,若未加载模型则从该路径加载
203
-
204
- 返回:
205
- raw: 原始推理结果(numpy数组,形状为[n_classes],存储每个类别的概率)
206
- formatted: 格式化结果(字典),包含以下键:
207
- - 'class': 预测类别名称(或索引)
208
- - 'confidence': 预测置信度(百分比,0-100)
209
- - 'probabilities': 所有类别的概率列表
210
-
211
- 说明:
212
- 推理过程包含计时功能,可用于评估单张图像的处理速度
213
- """
214
- start_time = time.time() # 记录推理开始时间,用于计算耗时
215
-
216
- # 若未加载模型且提供了路径,则先加载模型
217
- if model_path and not self.rknn_lite:
218
- self.load_model(model_path)
219
-
220
- # 预处理图像(转为模型可接受的格式)
221
- input_data = self._preprocess(data)
222
- if input_data is None:
223
- print("推理终止:图像预处理失败")
224
- return None, None # 预处理失败则返回空值
225
-
226
- try:
227
- # 执行RKNN推理:inputs参数为列表(支持多输入模型,此处单输入)
228
- outputs = self.rknn_lite.inference(inputs=[input_data])
229
- # 假设模型输出形状为[1, n_classes],取第一个样本的结果(批量大小=1)
230
- raw = outputs[0][0]
231
-
232
- # 格式化推理结果(转为人类可读格式)
233
- formatted = self._format_result(raw)
234
-
235
- # 计算并打印推理总耗时(预处理+推理)
236
- end_time = time.time()
237
- print(f"推理耗时: {end_time - start_time:.4f}秒 - 识别结果: {formatted['class']} ({formatted['confidence']}%)")
238
-
239
- return raw, formatted
240
-
241
- except Exception as e:
242
- print(f"推理失败: {e}")
243
- return None, None
244
-
245
- def inference_frame(self, frame_data, model_path=None):
246
- """
247
- 从视频帧数据执行RKNN模型推理(适用于实时视频流处理)
248
-
249
- 参数:
250
- frame_data: 视频帧numpy数组(如OpenCV读取的帧,格式为BGR)
251
- model_path: 可选,若未加载模型则从该路径加载
252
-
253
- 返回:
254
- raw: 原始推理结果(numpy数组,形状为[n_classes])
255
- formatted: 格式化结果(字典),包含以下键:
256
- - 'class': 预测类别名称(或索引)
257
- - 'confidence': 预测置信度(百分比)
258
- - 'probabilities': 所有类别的概率列表
259
- - 'preprocess_time': 预处理耗时(秒)
260
- - 'inference_time': 模型推理耗时(秒)
261
-
262
- 说明:
263
- 1. 专门适配OpenCV读取的视频帧(默认BGR格式),需转为RGB
264
- 2. 分别测量预处理和推理时间,便于性能分析
265
- """
266
- total_start_time = time.time() # 记录总时间开始
267
-
268
- # 若未加载模型且提供了路径,则先加载模型
269
- if model_path and not self.rknn_lite:
270
- self.load_model(model_path)
271
-
272
- # 测量预处理时间
273
- preprocess_start = time.time()
274
- # 预处理视频帧(转为模型可接受的格式)
275
- input_data = self._preprocess_frame(frame_data)
276
- preprocess_time = time.time() - preprocess_start
277
-
278
- if input_data is None:
279
- print("帧推理终止:帧数据预处理失败")
280
- return None, None
281
-
282
- try:
283
- # 测量推理时间
284
- inference_start = time.time()
285
- # 执行RKNN推理(NPU硬件加速)
286
- outputs = self.rknn_lite.inference(inputs=[input_data])
287
- inference_time = time.time() - inference_start
288
-
289
- # 取第一个样本的推理结果(批量大小=1)
290
- raw = outputs[0][0]
291
-
292
- # 格式化推理结果
293
- formatted = self._format_result(raw)
294
-
295
- # 添加时间信息到返回结果
296
- formatted['preprocess_time'] = preprocess_time
297
- formatted['inference_time'] = inference_time
298
-
299
- # 计算总耗时
300
- total_time = time.time() - total_start_time
301
- print(f"帧推理耗时: {total_time:.4f}秒 - 识别结果: {formatted['class']} ({formatted['confidence']}%)")
302
-
303
- return raw, formatted
304
-
305
- except Exception as e:
306
- print(f"帧数据推理失败: {e}")
307
- return None, None
308
-
309
- def _preprocess_frame(self, frame_data):
310
- """
311
- (私有方法,仅内部调用)视频帧数据预处理:适配RKNN模型输入格式
312
-
313
- 预处理流程(针对OpenCV帧,与ONNX版本保持一致的全局归一化):
314
- 1. 校验输入是否为numpy数组(OpenCV帧默认格式)
315
- 2. BGR转RGB(OpenCV默认读取为BGR,模型要求RGB)
316
- 3. 调整尺寸至224×224(线性插值,平衡速度)
317
- 4. 像素值归一化(0-255 → 0-1)并转为float32(全局归一化)
318
- 5. 维度顺序调整(HWC → HCW)
319
- 6. 添加batch维度
320
-
321
- 参数:
322
- frame_data: 视频帧numpy数组(形状通常为[H, W, 3],BGR格式)
323
-
324
- 返回:
325
- 预处理后的numpy数组(形状为[self.input_shape]),失败则返回None
326
- """
327
- try:
328
- # 1. 校验帧数据类型(必须是numpy数组)
329
- if not isinstance(frame_data, np.ndarray):
330
- print("错误: 帧数据必须是numpy数组(OpenCV读取的帧格式)")
331
- return None
332
-
333
- # 2. BGR格式转为RGB格式(模型要求RGB输入)
334
- img = cv2.cvtColor(frame_data, cv2.COLOR_BGR2RGB)
335
-
336
- # 3. 调整尺寸至目标大小(224×224)
337
- target_h, target_w = self.input_shape[1], self.input_shape[3]
338
- # 线性插值(cv2.INTER_LINEAR):比双线性更快,适合实时场景
339
- img = cv2.resize(img, (target_w, target_h), interpolation=cv2.INTER_LINEAR)
340
-
341
- # 4. 全局归一化并转为float32类型(0-255 → 0-1)
342
- img_array = img.astype(np.float32) / 255.0 # 形状: [224, 224, 3](HWC)
343
-
344
- # 5. 调整维度顺序:HWC → HCW(匹配模型输入要求)
345
- img_array = img_array.transpose(0, 2, 1) # 0=H,1=W,2=C → 0=H,1=C,2=W
346
-
347
- # 6. 添加batch维度,最终形状: [1, 224, 3, 224]
348
- return np.expand_dims(img_array, axis=0)
349
-
350
- except Exception as e:
351
- print(f"帧数据预处理失败: {e}")
352
- return None
353
-
354
- def _format_result(self, predictions):
355
- """
356
- (私有方法,仅内部调用)将原始推理结果格式化为人类可读的字典
357
-
358
- 参数:
359
- predictions: 原始推理结果(numpy数组,形状为[n_classes],存储每个类别的概率)
360
-
361
- 返回:
362
- 格式化结果字典,包含以下键:
363
- - 'class': 预测类别(若有类别标签则显示名称,否则显示索引)
364
- - 'confidence': 预测置信度(百分比,四舍五入为整数)
365
- - 'probabilities': 所有类别的概率列表(保留原始精度)
366
- """
367
- # 找到概率最大的类别索引(即预测类别)
368
- class_idx = np.argmax(predictions)
369
- # 计算置信度(转为百分比并取整)
370
- confidence = int(round(predictions[class_idx] * 100))
371
-
372
- # 确定类别名称(若标签列表存在且索引合法,显示名称,否则显示索引)
373
- if self.classes and 0 <= class_idx < len(self.classes):
374
- class_name = self.classes[class_idx]
375
- else:
376
- class_name = str(class_idx)
377
-
378
- return {
379
- 'class': class_name,
380
- 'confidence': confidence,
381
- 'probabilities': predictions.tolist() # 转为列表便于后续处理(如JSON序列化)
382
- }
383
-
384
- def release(self):
385
- """
386
- 释放RKNN Lite资源和NPU运行时环境
387
-
388
- 说明:
389
- 主动调用此方法可避免内存泄漏,尤其在多次创建ImageWorkflow实例时
390
- 析构函数会自动调用此方法,但建议在不需要推理时主动释放
391
- """
392
- if hasattr(self, 'rknn_lite') and self.rknn_lite:
393
- self.rknn_lite.release()
394
- print("RKNN Lite资源(含NPU运行时)已释放")
395
-
396
- def __del__(self):
397
- """
398
- 析构函数:对象被销毁时自动释放RKNN资源
399
-
400
- 说明:
401
- 确保程序退出或对象回收时,NPU资源被正确释放,避免占用硬件资源
402
- """
403
- self.release()
404
-
405
-