269 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			269 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import time
 | ||
| import threading
 | ||
| import re
 | ||
| import numpy as np
 | ||
| import sys
 | ||
| import os
 | ||
| import signal
 | ||
| from openai import OpenAI
 | ||
| from picamera2 import Picamera2
 | ||
| import io
 | ||
| from PIL import Image
 | ||
| import base64
 | ||
| 
 | ||
| # 配置项目路径(根据你的实际路径修改)
 | ||
| PROJECT_ROOT = "/home/duckpi/open_duck_mini_ws/OPEN_DUCK_MINI/Open_Duck_Mini_Runtime-2"
 | ||
| ONNX_MODEL_PATH = "/home/duckpi/open_duck_mini_ws/OPEN_DUCK_MINI/Open_Duck_Mini-2/BEST_WALK_ONNX_2.onnx"
 | ||
| sys.path.append(PROJECT_ROOT)
 | ||
| 
 | ||
| # 导入运动控制模块
 | ||
| from v2_rl_walk_mujoco import RLWalk
 | ||
| 
 | ||
| # API配置(替换为你的实际密钥)
 | ||
| ARK_API_KEY = "390d517c-129a-41c1-bf3d-458048007b69"
 | ||
| ARK_MODEL_ID = "doubao-seed-1-6-250615"
 | ||
| 
 | ||
| class SimpleTTS:
 | ||
|     """简化的TTS模块,仅用于测试反馈"""
 | ||
|     def speak(self, text):
 | ||
|         print(f"[语音反馈] {text}")
 | ||
| 
 | ||
| class MotionController:
 | ||
|     """运动控制封装"""
 | ||
|     def __init__(self):
 | ||
|         try:
 | ||
|             self.rl_walk = RLWalk(
 | ||
|                 onnx_model_path=ONNX_MODEL_PATH,
 | ||
|                 cutoff_frequency=40,
 | ||
|                 pid=[30, 0, 0]
 | ||
|             )
 | ||
|             self.walk_thread = threading.Thread(target=self.rl_walk.run, daemon=True)
 | ||
|             self.walk_thread.start()
 | ||
|             time.sleep(1)
 | ||
|             print("✅ 运动控制模块初始化成功")
 | ||
|         except Exception as e:
 | ||
|             print(f"❌ 运动控制初始化失败:{str(e)}")
 | ||
|             sys.exit(1)
 | ||
|     
 | ||
|     def execute_motion(self, action_name: str, seconds: float):
 | ||
|         """执行指定动作"""
 | ||
|         try:
 | ||
|             if action_name == "move_forward":
 | ||
|                 print(f"🚶 前进{seconds}秒...")
 | ||
|                 self.rl_walk.last_commands[0] = 0.17
 | ||
|             elif action_name == "move_backward":
 | ||
|                 print(f"🚶 后退{seconds}秒...")
 | ||
|                 self.rl_walk.last_commands[0] = -0.17
 | ||
|             elif action_name == "turn_left":
 | ||
|                 print(f"🔄 左转{seconds}秒...")
 | ||
|                 self.rl_walk.last_commands[2] = 1.1
 | ||
|             elif action_name == "turn_right":
 | ||
|                 print(f"🔄 右转{seconds}秒...")
 | ||
|                 self.rl_walk.last_commands[2] = -1.1
 | ||
|             
 | ||
|             time.sleep(seconds)
 | ||
|             self.rl_walk.last_commands = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
 | ||
|             print("✅ 动作完成")
 | ||
|         except Exception as e:
 | ||
|             print(f"❌ 动作执行失败:{str(e)}")
 | ||
|             self.rl_walk.last_commands = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
 | ||
| 
 | ||
| class CameraModule:
 | ||
|     """摄像头模块封装"""
 | ||
|     def __init__(self):
 | ||
|         try:
 | ||
|             self.camera = Picamera2()
 | ||
|             cam_config = self.camera.create_still_configuration(main={"size": (320, 240)})
 | ||
|             self.camera.configure(cam_config)
 | ||
|             self.camera.start()
 | ||
|             print("✅ 摄像头模块初始化成功")
 | ||
|         except Exception as e:
 | ||
|             print(f"❌ 摄像头初始化失败:{str(e)}")
 | ||
|             sys.exit(1)
 | ||
|     
 | ||
|     def capture_base64(self):
 | ||
|         """拍摄并返回base64编码图像"""
 | ||
|         try:
 | ||
|             img_array = self.camera.capture_array()
 | ||
|             img_byte = io.BytesIO()
 | ||
|             Image.fromarray(img_array).save(img_byte, format="JPEG", quality=80)
 | ||
|             return base64.b64encode(img_byte.getvalue()).decode("utf-8")
 | ||
|         except Exception as e:
 | ||
|             print(f"❌ 拍摄失败:{str(e)}")
 | ||
|             return None
 | ||
| 
 | ||
| class ObjectTracker:
 | ||
|     """物品追踪核心类"""
 | ||
|     def __init__(self, target_name="万用表", tts=None, motion=None, camera=None):
 | ||
|         self.target_name = target_name
 | ||
|         self.tracking_active = False
 | ||
|         self.target_lost_count = 0
 | ||
|         self.max_lost_count = 5  # 连续丢失次数阈值
 | ||
|         self.tts = tts or SimpleTTS()
 | ||
|         self.motion = motion or MotionController()
 | ||
|         self.camera = camera or CameraModule()
 | ||
|         self.client = OpenAI(
 | ||
|             base_url="https://ark.cn-beijing.volces.com/api/v3",
 | ||
|             api_key=ARK_API_KEY
 | ||
|         )
 | ||
|         print(f"✅ 追踪器初始化完成,目标:{self.target_name}")
 | ||
| 
 | ||
|     def get_object_position(self, image_base64):
 | ||
|         """获取目标在图像中的位置信息"""
 | ||
|         try:
 | ||
|             response = self.client.chat.completions.create(
 | ||
|                 model=ARK_MODEL_ID,
 | ||
|                 messages=[{
 | ||
|                     "role": "user",
 | ||
|                     "content": [
 | ||
|                         {"type": "image_url", "image_url": f"data:image/jpeg;base64,{image_base64}"},
 | ||
|                         {"type": "text", "text": (f"请识别图像中的'{self.target_name}',返回其位置信息。"
 | ||
|                                                "格式要求:中心X坐标(0-100),中心Y坐标(0-100),宽度占比(0-100),高度占比(0-100)。"
 | ||
|                                                "如果未找到,返回'未找到'")}
 | ||
|                     ]
 | ||
|                 }]
 | ||
|             )
 | ||
|             
 | ||
|             content = response.choices[0].message.content
 | ||
|             if "未找到" in content:
 | ||
|                 return None
 | ||
|                 
 | ||
|             # 提取数字信息
 | ||
|             nums = re.findall(r"\d+\.?\d*", content)
 | ||
|             if len(nums) >= 4:
 | ||
|                 return {
 | ||
|                     "center_x": float(nums[0]),
 | ||
|                     "center_y": float(nums[1]),
 | ||
|                     "width": float(nums[2]),
 | ||
|                     "height": float(nums[3])
 | ||
|                 }
 | ||
|             return None
 | ||
|             
 | ||
|         except Exception as e:
 | ||
|             print(f"❌ 目标识别出错: {str(e)}")
 | ||
|             return None
 | ||
|     
 | ||
|     def track_object(self):
 | ||
|         """追踪主循环"""
 | ||
|         self.tts.speak(f"开始追踪{self.target_name}")
 | ||
|         self.tracking_active = True
 | ||
|         self.target_lost_count = 0
 | ||
|         
 | ||
|         try:
 | ||
|             while self.tracking_active:
 | ||
|                 # 1. 采集图像
 | ||
|                 image_base64 = self.camera.capture_base64()
 | ||
|                 if not image_base64:
 | ||
|                     time.sleep(1)
 | ||
|                     continue
 | ||
|                 
 | ||
|                 # 2. 识别目标位置
 | ||
|                 pos = self.get_object_position(image_base64)
 | ||
|                 if not pos:
 | ||
|                     self.target_lost_count += 1
 | ||
|                     print(f"⚠️ 未找到{self.target_name},已连续丢失{self.target_lost_count}次")
 | ||
|                     
 | ||
|                     if self.target_lost_count >= self.max_lost_count:
 | ||
|                         self.tts.speak(f"已丢失{self.target_name},停止追踪")
 | ||
|                         self.stop_tracking()
 | ||
|                     time.sleep(1)
 | ||
|                     continue
 | ||
|                 
 | ||
|                 # 重置丢失计数
 | ||
|                 self.target_lost_count = 0
 | ||
|                 print(f"🎯 发现{self.target_name} - 中心X: {pos['center_x']}, 宽度占比: {pos['width']}")
 | ||
|                 
 | ||
|                 # 3. 根据位置控制移动
 | ||
|                 self.control_movement(pos)
 | ||
|                 time.sleep(1.5)  # 控制追踪频率
 | ||
|                 
 | ||
|         except Exception as e:
 | ||
|             print(f"❌ 追踪过程出错: {str(e)}")
 | ||
|             self.stop_tracking()
 | ||
|     
 | ||
|     def control_movement(self, pos):
 | ||
|         """根据目标位置控制移动"""
 | ||
|         # 横向位置控制 (左右转向)
 | ||
|         if pos["center_x"] < 35:  # 目标偏左
 | ||
|             self.tts.speak("目标在左边,向左转")
 | ||
|             self.motion.execute_motion("turn_left", 0.8)
 | ||
|         elif pos["center_x"] > 65:  # 目标偏右
 | ||
|             self.tts.speak("目标在右边,向右转")
 | ||
|             self.motion.execute_motion("turn_right", 0.8)
 | ||
|         
 | ||
|         # 距离控制 (前进后退)
 | ||
|         if pos["width"] < 20:  # 目标过小,距离过远
 | ||
|             self.tts.speak("距离目标较远,前进")
 | ||
|             self.motion.execute_motion("move_forward", 1.5)
 | ||
|         elif pos["width"] > 40:  # 目标过大,距离过近
 | ||
|             self.tts.speak("距离目标过近,后退")
 | ||
|             self.motion.execute_motion("move_backward", 1)
 | ||
|         else:
 | ||
|             self.tts.speak("已对准目标,保持位置")
 | ||
|     
 | ||
|     def start_tracking(self):
 | ||
|         """启动追踪线程"""
 | ||
|         if not self.tracking_active:
 | ||
|             threading.Thread(target=self.track_object, daemon=True).start()
 | ||
|     
 | ||
|     def stop_tracking(self):
 | ||
|         """停止追踪"""
 | ||
|         self.tracking_active = False
 | ||
|         print(f"🛑 停止追踪{self.target_name}")
 | ||
| 
 | ||
| 
 | ||
| def main():
 | ||
|     # 初始化组件
 | ||
|     tts = SimpleTTS()
 | ||
|     motion = MotionController()
 | ||
|     camera = CameraModule()
 | ||
|     tracker = ObjectTracker(
 | ||
|         target_name="万用表",  # 可修改为其他目标,如"水杯"、"书本"
 | ||
|         tts=tts,
 | ||
|         motion=motion,
 | ||
|         camera=camera
 | ||
|     )
 | ||
|     
 | ||
|     # 信号处理(优雅退出)
 | ||
|     def handle_interrupt(signum, frame):
 | ||
|         print("\n🛑 收到退出信号,正在停止...")
 | ||
|         tracker.stop_tracking()
 | ||
|         motion.rl_walk.last_commands = [0.0, 0.0, 0.0]  # 停止运动
 | ||
|         camera.camera.stop()  # 关闭摄像头
 | ||
|         print("✅ 所有资源已释放,程序退出")
 | ||
|         sys.exit(0)
 | ||
|     
 | ||
|     signal.signal(signal.SIGINT, handle_interrupt)
 | ||
|     
 | ||
|     # 交互提示
 | ||
|     print("\n===== 物品追踪测试程序 =====")
 | ||
|     print("操作说明:")
 | ||
|     print("  s - 开始追踪目标")
 | ||
|     print("  t - 停止追踪")
 | ||
|     print("  q - 退出程序")
 | ||
|     print("===========================")
 | ||
|     
 | ||
|     # 主交互循环
 | ||
|     while True:
 | ||
|         cmd = input("请输入指令: ").strip().lower()
 | ||
|         if cmd == 's':
 | ||
|             if not tracker.tracking_active:
 | ||
|                 print("▶️ 开始追踪...")
 | ||
|                 tracker.start_tracking()
 | ||
|             else:
 | ||
|                 print("⚠️ 已经在追踪中")
 | ||
|         elif cmd == 't':
 | ||
|             if tracker.tracking_active:
 | ||
|                 tracker.stop_tracking()
 | ||
|                 print("⏹️ 已停止追踪")
 | ||
|             else:
 | ||
|                 print("⚠️ 没有正在进行的追踪")
 | ||
|         elif cmd == 'q':
 | ||
|             handle_interrupt(None, None)
 | ||
|         else:
 | ||
|             print("❓ 未知指令,请输入 s/t/q")
 | ||
| 
 | ||
| 
 | ||
| if __name__ == "__main__":
 | ||
|     
 | ||
|     main() |