HTTP.UTF_8过时

今天发现项目里org.apache.http.protocol包下的HTTP.UTF_8过时了,而在HTTP里没找到UTF-8的替代品

结果从网络搜索发现可以用这个替代:StandardCharsets.UTF_8

这个类在java.nio.charset下

本人机器jdk版本1.8

#!/usr/bin/env python # -*- coding: utf-8 -*- "修改:将多个回调函数进行了融合,避免冲突的同时更加好打理: 新增最终播报模块" import rospy import actionlib import os import threading import time import numpy as np from cv_bridge import CvBridge, CvBridgeError from sensor_msgs.msg import Image from geometry_msgs.msg import PoseStamped, Twist from my_control.msg import simulation, simulation_result from std_msgs.msg import String, Bool from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal from actionlib_msgs.msg import GoalStatus from pyzbar import pyzbar import cv2 from playsound import playsound from darknet_ros_msgs.msg import BoundingBoxes, BoundingBox from xf_mic_asr_offline.srv import Get_Offline_Result_srv,Get_Offline_Result_srvRequest class AutomationNode: # 状态定义 - 添加新状态 STATE_IDLE = "IDLE" # 空闲状态 STATE_NAVIGATING = "NAVIGATING" # 导航状态 STATE_ARRIVED = "ARRIVED" # 抵达目标点 STATE_SCANNING = "SCANNING" # 搜索二维码 STATE_PLAYING = "PLAYING" # 播放音频 STATE_SEARCHING = "SEARCHING" # 搜索目标物品 STATE_FOUND_TARGET = "FOUND_TARGET" # 找到目标物品 STATE_WAITING_FOR_TASK3 = "WAITING_FOR_TASK3" # 等待任务3指令 STATE_TRAFFIC_LIGHT_DETECTION = "TRAFFIC_LIGHT_DETECTION" # 交通灯识别状态 STATE_TASK3_NAVIGATING = "TASK3_NAVIGATING" # 任务3导航状态 STATE_WAITING_FOR_SIMULATION_RESULT = "WAITING_FOR_SIMULATION_RESULT" # 新增状态:等待模拟结果 STATE_WAITING_FOR_LINE_RESULT = "WAITING_FOR_LINE_RESULT"#等待寻线任务 STATE_SIMULATION = "GO_TO_DO_SIMULATION" def __init__(self): rospy.init_node('automation_node', anonymous=True) # 状态变量 self.current_state = self.STATE_IDLE self.target_waypoint = None self.qr_detected = False self.waypoint_index = 0 self.task3_started = False # 任务3是否已启动 self.sound_state_1 = 1 self.image = None self.light = False # ROS通信设置 self.bridge = CvBridge() self.speech_sub = rospy.Subscriber('/mic/recognise_result', String, self.speech_callback) self.arrival_sub = rospy.Subscriber('/arrival_status', Bool, self.arrival_callback) self.status_pub = rospy.Publisher('/task_status', String, queue_size=10) self.qr_result_pub = rospy.Publisher('/qrcode_result', String, queue_size=10) self.cmd_vel_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10) self.yolo_sub = rospy.Subscriber('/darknet_ros/bounding_boxes', BoundingBoxes, self.yolo_callback) self.image_sub =rospy.Subscriber('/usb_cam/image_raw', Image, self.image_callback) self.simulation_start_pub = rospy.Publisher('/simulation_start', simulation, queue_size=10) self.simulation_result_sub = rospy.Subscriber('/simulation_result', simulation_result, self.simulation_result_callback) # 二维码参数配置 self.display = rospy.get_param('~display', False) self.min_size = rospy.get_param('~min_size', 100) self.process_counter = 0 self.process_every_n = 5 self.qr_simulation = None # MoveBase动作客户端 - 等待服务器启动 self.move_base_client = actionlib.SimpleActionClient('move_base', MoveBaseAction) rospy.loginfo("等待move_base服务器...") self.move_base_client.wait_for_server() # 无限期等待直到服务器可用 rospy.loginfo("move_base服务器已连接") rospy.loginfo("等待xf_asr_offline_node服务器...") rospy.wait_for_service('/xf_asr_offline_node/get_offline_recognise_result_srv') rospy.loginfo("xf_asr_offline_node服务器已连接") self.speech_recognition = rospy.ServiceProxy('/xf_asr_offline_node/get_offline_recognise_result_srv',Get_Offline_Result_srv) # 预定义航点(根据实际环境配置) self.waypoints = [ # 任务1相关航点 self.create_pose(1.84581,0.56147, 0.0, 0.0, 0.0, 0.999966, -0.00827442), # 新增过渡点0 self.create_pose(1.00982, 0.630106, 0.0, 0.0, 0.0, 0.999848, 0.017445), # 点1: 扫描点,识别二维码 self.create_pose(0.238816, 1.0809, 0.0, 0.0, 0.0, -0.00275711, 0.999996), # 新增过渡点1 self.create_pose(1.91287, 1.5377, 0.0, 0.0, 0.0, 0.999987,0.00516634), # 新增过渡点2 self.create_pose(0.239635, 2.22449, 0.0, 0.0, 0.0,0.625221, 0.780448), # 点2: 任务1出口 # 任务2搜索航点 self.create_pose(1.55117,2.70212, 0.0, 0.0, 0.0, -0.610833, 0.791759), # 点3: 搜索点1 self.create_pose(1.30051, 4.05638, 0.0, 0.0, 0.0, 0.216052, 0.976382), # 点4: 搜索点2 self.create_pose(0.550306, 3.7162, 0.0, 0.0, 0.0, 0.815828, 0.578295), # 点5: 搜索点3 # 任务2终点 self.create_pose(1.09864, 3.57304, 0.0, 0.0, 0.0, 0.21246, 0.97717), # 点6: 任务2的终点 # 任务3相关航点 self.create_pose(2.63748, 3.98935, 0.0, 0.0, 0.0, 0.707858, 0.706355), # 点7: 路灯1 self.create_pose(2.57195, 3.44993, 0.0, 0.0, 0.0, -0.60105, 0.799212), # 点8: 路口1 self.create_pose(4.46442, 4.08407, 0.0, 0.0, 0.0, 0.643035, 0.765837), # 点9: 路灯2) self.create_pose(4.53154, 3.52776, 0.0, 0.0, 0.0, -0.771258, 0.636523) # 点10: 路口2 ] # 航点索引常量(提高可读性) - 更新索引 self.WAYPOINT_TRANSITION0 = 0 # 新增的过渡点0 self.WAYPOINT_SCAN = 1 # 点1: 扫描点,识别二维码 self.WAYPOINT_TRANSITION1 = 2 # 新增过渡点1 self.WAYPOINT_TRANSITION2 = 3 # 新增过渡点2 self.WAYPOINT_TASK1_EXIT = 4 # 点2: 任务1出口 self.WAYPOINT_SEARCH1 = 5 # 点3: 搜索点1 self.WAYPOINT_SEARCH2 = 6 # 点4: 搜索点2 self.WAYPOINT_SEARCH3 = 7 # 点5: 搜索点3 self.WAYPOINT_TASK2_END = 8 # 点6: 任务2的终点 self.WAYPOINT_LIGHT1 = 9 # 点7: 路灯1) self.WAYPOINT_CROSS1 = 10 # 点8: 路口1) self.WAYPOINT_LIGHT2 = 11 # 点9: 路灯2) self.WAYPOINT_CROSS2 = 12 # 点10: 路口2) # 音频文件映射 - 添加新音频 self.audio_files = { "Fruit": "/home/ucar/ucar_ws/src/mp3/Fruit.mp3", "Vegetable": "/home/ucar/ucar_ws/src/mp3/Vegetable.mp3", "Dessert": "/home/ucar/ucar_ws/src/mp3/Dessert.mp3", "apple": "/home/ucar/ucar_ws/src/mp3/apple.mp3", "watermelon": "/home/ucar/ucar_ws/src/mp3/watermelon.mp3", "banana": "/home/ucar/ucar_ws/src/mp3/banana.mp3", "potato": "/home/ucar/ucar_ws/src/mp3/potato.mp3", "tomato": "/home/ucar/ucar_ws/src/mp3/tomato.mp3", "chili": "/home/ucar/ucar_ws/src/mp3/chili.mp3", "cake": "/home/ucar/ucar_ws/src/mp3/cake.mp3", "coco": "/home/ucar/ucar_ws/src/mp3/coco.mp3", "milk": "/home/ucar/ucar_ws/src/mp3/milk.mp3", "load_1": "/home/ucar/ucar_ws/src/mp3/load_1.mp3", "load_2": "/home/ucar/ucar_ws/src/mp3/load_2.mp3", "A": "/home/ucar/ucar_ws/src/mp3/simulation_A.mp3", "B": "/home/ucar/ucar_ws/src/mp3/simulation_B.mp3", "C": "/home/ucar/ucar_ws/src/mp3/simulation_C.mp3" } # 目标搜索相关变量 self.qr_result = 0 self.target_object = None self.search_timeout = 5.0 # 每个搜索点的超时时间(秒) self.search_start_time = None self.search_timer = None self.search_timer_state = True self.search_acount_state = True self.search_acount_current = 0 self.search_acount_target = 80 self.search_target = False # 目标类别映射 self.test1_categories = { 1: ['apple', 'watermelon', 'banana'], 2: ['potato', 'tomato', 'chili'], 3: ['cake', 'coco', 'milk'] } # 任务3相关变量 self.light_timout1 =True self.light_timout2 = True self.traffic_light_detected = False self.traffic_light_result = None # 'red' 或 'green' #仿真任务相关 self.simulation_result_received = False self.simulation_result1 = None self.simulation_result2 = None self.msg_send = True self.simulation_timeout_timer = None # 特殊任务超时定时器 #价格计算相关 self.goods_1 = None self.goods_2 = None #寻线相关任务 self.line_pub = rospy.Publisher('/line_start', String, queue_size=10) self.line_result_sub = rospy.Subscriber('/line_result', String, self.line_result_callback) self.line_sent = True rospy.loginfo("自动化节点已启动,等待语音指令...") rospy.on_shutdown(self.shutdown) def create_pose(self, x, y, z, qx, qy, qz, qw): """创建导航目标位姿""" pose = PoseStamped() pose.header.frame_id = "map" pose.header.stamp = rospy.Time.now() pose.pose.position.x = x pose.pose.position.y = y pose.pose.position.z = z pose.pose.orientation.x = qx pose.pose.orientation.y = qy pose.pose.orientation.z = qz pose.pose.orientation.w = qw return pose def navigate_to_waypoint(self, waypoint_index): """导航到指定航点""" if waypoint_index >= len(self.waypoints): rospy.logwarn("无效的航点索引: %d", waypoint_index) return self.waypoint_index = waypoint_index waypoint = self.waypoints[waypoint_index] rospy.loginfo("导航到航点 %d", waypoint_index + 1) # 根据任务设置状态 if self.task3_started: self.current_state = self.STATE_TASK3_NAVIGATING else: self.current_state = self.STATE_NAVIGATING self.status_pub.publish("NAVIGATING_TO_TARGET") # 使用move_base动作客户端发送目标 goal = MoveBaseGoal() goal.target_pose = waypoint self.move_base_client.send_goal(goal) # 设置定时器检查导航状态 rospy.Timer(rospy.Duration(0.3), self.check_navigation_status, oneshot=False) def check_navigation_status(self, event): """检查导航状态(使用move_base动作)""" state = self.move_base_client.get_state() if state == GoalStatus.SUCCEEDED: rospy.loginfo_once("成功到达航点 {}".format(self.waypoint_index + 1)) self.handle_arrival() # 停止定时器 event.new_message = None # 停止定时器 elif state in [GoalStatus.ABORTED, GoalStatus.PREEMPTED]: rospy.logwarn("导航失败,尝试重新规划...") self.navigate_to_waypoint(self.waypoint_index) # 停止当前定时器 event.new_message = None def handle_arrival(self): """处理到达航点后的逻辑""" self.current_state = self.STATE_ARRIVED self.status_pub.publish("ARRIVED_AT_TARGET") # 发布到达状态 arrival_pub = rospy.Publisher('/arrival_status', Bool, queue_size=10) arrival_pub.publish(True) def speech_callback(self, msg): """处理语音识别结果""" rospy.loginfo("收到语音指令: {}".format(msg.data)) # 处理初始指令 if "ok" in msg.data.lower() and self.current_state == self.STATE_IDLE: rospy.loginfo("收到语音指令,开始导航到航点1") self.navigate_to_waypoint(self.WAYPOINT_TRANSITION0) elif "fail" in msg.data.lower() and self.current_state == self.STATE_IDLE: # 创建服务请求 req = Get_Offline_Result_srvRequest() # 根据实际srv定义设置请求字段 req.offline_recognise_start = 1 #设置1为确认启用 req.confidence_threshold = 40 #置信度 req.time_per_order = 3 #启动持续时间 self.speech_recognition(req) def arrival_callback(self, msg): """处理到达状态""" if not msg.data or self.current_state != self.STATE_ARRIVED: return rospy.loginfo_once("处理航点 {} 的到达逻辑".format(self.waypoint_index+1)) # 新增过渡点0 - 直接导航到下一个点(二维码扫描点) if self.waypoint_index == self.WAYPOINT_TRANSITION0: rospy.loginfo("到达过渡点0, 继续导航到二维码扫描点") self.navigate_to_waypoint(self.WAYPOINT_SCAN) # 任务1: 二维码扫描点 elif self.waypoint_index == self.WAYPOINT_SCAN: rospy.loginfo_once("启动二维码识别...") self.current_state = self.STATE_SCANNING self.status_pub.publish("SCANNING") self.image_sub = rospy.Subscriber('/usb_cam/image_raw', Image, self.image_callback) # 过渡点1 elif self.waypoint_index == self.WAYPOINT_TRANSITION1: rospy.loginfo("继续导航到过渡点2") self.navigate_to_waypoint(self.WAYPOINT_TRANSITION2) # 过渡点2 elif self.waypoint_index == self.WAYPOINT_TRANSITION2: rospy.loginfo("继续导航到任务1出口") self.navigate_to_waypoint(self.WAYPOINT_TASK1_EXIT) # 任务1出口 elif self.waypoint_index == self.WAYPOINT_TASK1_EXIT: rospy.loginfo_once("到达任务1出口,开始任务2") #self.navigate_to_waypoint(self.WAYPOINT_SEARCH1) self.navigate_to_waypoint(self.WAYPOINT_SEARCH1) # 任务2搜索点 elif self.waypoint_index in [self.WAYPOINT_SEARCH1, self.WAYPOINT_SEARCH2, self.WAYPOINT_SEARCH3]: rospy.loginfo_once("在航点 {} 开始搜索目标...".format(self.waypoint_index+1)) self.start_searching() # 任务2终点 elif self.waypoint_index == self.WAYPOINT_TASK2_END: rospy.loginfo_once("到达任务2终点,执行特殊任务...") rospy.sleep(3) self.execute_special_task() # 任务3: 路灯1 elif self.waypoint_index == self.WAYPOINT_LIGHT1: rospy.loginfo("到达路灯1,开始识别交通灯...") self.current_state = self.STATE_TRAFFIC_LIGHT_DETECTION self.status_pub.publish("DETECTING_TRAFFIC_LIGHT") self.traffic_light_detected = False self.traffic_light_result = None # 设置超时定时器 if self.search_timer and self.light_timout1: self.light_timout1 = False self.search_timer.shutdown() self.search_timer = rospy.Timer(rospy.Duration(self.search_timeout), self.handle_traffic_light_timeout, oneshot=True) # 任务3: 路灯2 elif self.waypoint_index == self.WAYPOINT_LIGHT2: rospy.loginfo("到达路灯2,开始识别交通灯...") self.current_state = self.STATE_TRAFFIC_LIGHT_DETECTION self.status_pub.publish("DETECTING_TRAFFIC_LIGHT") self.traffic_light_detected = False self.traffic_light_result = None # 设置超时定时器 if self.search_timer and self.light_timout2: self.light_timout2 = False self.search_timer.shutdown() self.search_timer = rospy.Timer(rospy.Duration(self.search_timeout), self.handle_traffic_light_timeout, oneshot=True) # 任务3: 路口1或路口2 elif self.waypoint_index in [self.WAYPOINT_CROSS1, self.WAYPOINT_CROSS2]: rospy.loginfo("到达路口{},准备进行下一步任务...".format(self.waypoint_index-7)) self.status_pub.publish("TASK3_COMPLETED") self.current_state = self.STATE_IDLE self.task3_started = False # 根据到达的路口播放不同的音频 if self.waypoint_index == self.WAYPOINT_CROSS1: rospy.loginfo("在路口1播放load_1音频") audio_file = self.audio_files.get("load_1") else: rospy.loginfo("在路口2播放load_2音频") audio_file = self.audio_files.get("load_2") if self.sound_state_1 == 1: self.sound_state_1 = 0 if audio_file and os.path.exists(audio_file): threading.Thread(target=self.play_audio_thread, args=(audio_file,)).start() # 发送line话题 if self.line_sent: rospy.loginfo("发送line话题: start") self.line_pub.publish("start") self.line_sent = False self.current_state = self.STATE_WAITING_FOR_LINE_RESULT def start_searching(self): """启动目标搜索""" if self.current_state == self.STATE_FOUND_TARGET: return rospy.loginfo_once("在航点 {} 开始搜索".format(self.waypoint_index+1)) self.current_state = self.STATE_SEARCHING self.status_pub.publish("SEARCHING_TARGET") if self.search_acount_state == True: self.search_acount_current += 1 twist = Twist() twist.angular.z = 0.2 self.cmd_vel_pub.publish(twist) if self.search_acount_current % 10 == 0: rospy.loginfo('当前已搜索{}次, 目标搜索次数为{}'.format(self.search_acount_current,self.search_acount_target)) if self.search_acount_current == self.search_acount_target or self.search_acount_current > self.search_acount_target: self.handle_search_timeout() def handle_search_timeout(self): """处理搜索超时""" if self.current_state != self.STATE_SEARCHING: return # 检查是否已找到目标 if self.current_state == self.STATE_FOUND_TARGET: rospy.loginfo("搜索超时但已找到目标,忽略超时") return rospy.logwarn("航点 {} 搜索超时".format(self.waypoint_index+1)) self.search_acount_state = False self.search_acount_current = 0 if self.waypoint_index == self.WAYPOINT_SEARCH1: rospy.loginfo("前往搜索点2 ") self.search_acount_state = True self.navigate_to_waypoint(self.WAYPOINT_SEARCH2) elif self.waypoint_index == self.WAYPOINT_SEARCH2: rospy.loginfo("前往搜索点3") self.search_acount_state = True self.navigate_to_waypoint(self.WAYPOINT_SEARCH3) else: rospy.logwarn("所有搜索点均未找到目标,前往任务终点") self.search_acount_state = False self.search_acount_current = 0 self.navigate_to_waypoint(self.WAYPOINT_TASK2_END) ##if next_index <= max_search_index: # rospy.loginfo("前往下一个搜索点: {}".format(next_index+1)) # self.navigate_to_waypoint(next_index) #else: # rospy.logwarn("所有搜索点均未找到目标,前往任务终点") # self.navigate_to_waypoint(self.WAYPOINT_TASK2_END) def cancel_current_search(self): if self.search_timer is not None: try: self.search_acount_state = False rospy.loginfo_once("搜索定时器已取消") except Exception as e: rospy.logwarn("取消定时器时出错: {}".format(e)) finally: self.search_acount_current = 0 def handle_traffic_light_timeout(self, event): """处理交通灯识别超时""" if self.current_state == self.STATE_TRAFFIC_LIGHT_DETECTION: rospy.logwarn("交通灯识别超时,执行默认操作") # 如果在路灯1,默认前往路口1 if self.waypoint_index == self.WAYPOINT_LIGHT1: rospy.loginfo("默认前往路口1(点9)") self.navigate_to_waypoint(self.WAYPOINT_CROSS1) # 如果在路灯2,默认前往路口2 elif self.waypoint_index == self.WAYPOINT_LIGHT2: rospy.loginfo("默认前往路口2(点11)") self.navigate_to_waypoint(self.WAYPOINT_CROSS2) def image_callback(self, data): """统一的图像处理回调函数""" try: cv_image = self.bridge.imgmsg_to_cv2(data, "bgr8") # 统一翻转图像 cv_image = cv2.flip(cv_image, 1) self.image = cv_image except CvBridgeError as e: rospy.logerr("图像转换错误: {}".format(e)) return # 状态分发处理 if self.current_state == self.STATE_SCANNING: self._handle_qr_scanning(cv_image) self.process_counter +=1 elif self.current_state == self.STATE_FOUND_TARGET: self._handle_target_tracking(cv_image) elif self.current_state == self.STATE_TRAFFIC_LIGHT_DETECTION: # 交通灯检测仍通过YOLO回调处理,这里只需显示图像 if self.display: cv2.imshow("Traffic Light Detection", cv_image) cv2.waitKey(1) def _handle_qr_scanning(self, cv_image): """处理二维码扫描""" if self.process_counter % self.process_every_n != 0: return # 跳过处理 decoded_objects = pyzbar.decode(cv_image) results = [] for obj in decoded_objects: data = obj.data.decode('utf-8') results.append(data) if results: result_msg = ", ".join(results) rospy.loginfo("识别到二维码: {}".format(result_msg)) self.qr_result_pub.publish(result_msg) self.qr_detected = True self.current_state = self.STATE_PLAYING self.status_pub.publish("PLAYING_AUDIO") # 取消图像订阅(任务完成后) #if self.image_sub: # self.image_sub.unregister() # self.image_sub = None self.play_audio(result_msg) rospy.sleep(3) self.navigate_to_waypoint(self.WAYPOINT_TRANSITION1) # 可选:显示图像 if self.display: for obj in decoded_objects: points = obj.polygon if len(points) > 4: hull = cv2.convexHull(np.array([point for point in points], dtype=np.float32)) hull = list(map(tuple, np.squeeze(hull))) else: hull = points n = len(hull) for j in range(n): cv2.line(cv_image, hull[j], hull[(j+1) % n], (0, 255, 0), 3) cv2.imshow("QR Scanning", cv_image) cv2.waitKey(1) def _handle_target_tracking(self, cv_image): """处理目标跟踪""" if not self.target_object: return self.search_acount_state = False self.search_acount_current = 0 # 计算目标位置 target_center_x = (self.xmin + self.xmax) // 2 target_width = self.xmax - self.xmin frame_center_x = cv_image.shape[1] // 2 # 视觉控制逻辑 twist = Twist() # 横向控制(转向) lateral_error = target_center_x - frame_center_x twist.angular.z = 0.01 * lateral_error if abs(lateral_error) > 30 else 0.0 # 纵向控制(前进/停止) if target_width < 120: twist.linear.x = 0.25 else: # 到达目标面前 twist.linear.x = 0.0 twist.angular.z = 0.0 rospy.loginfo("已到达目标 {} 面前!".format(self.target_object)) self.status_pub.publish("ARRIVED_AT_TARGET") self.goods_1 = self.target_object rospy.loginfo_once("货物1为{}".format(self.goods_1)) # 播放目标音频 audio_file = self.audio_files.get(self.target_object) #self.play_audio(audio_file) if audio_file and os.path.exists(audio_file): threading.Thread(target=playsound, args=(audio_file,)).start() rospy.sleep(2) # 导航到任务2终点 rospy.loginfo("前往任务2终点...") self.navigate_to_waypoint(self.WAYPOINT_TASK2_END) # 重置目标状态 self.target_object = None self.current_state = self.STATE_WAITING_FOR_SIMULATION_RESULT # 发布控制命令 self.cmd_vel_pub.publish(twist) # 可选:显示跟踪框 if self.display: cv2.rectangle(cv_image, (self.xmin, self.ymin), (self.xmax, self.ymax), (0, 255, 0), 2) cv2.circle(cv_image, (target_center_x, (self.ymin + self.ymax)//2), 5, (0, 0, 255), -1) cv2.imshow("Target Tracking", cv_image) cv2.waitKey(1) def play_audio(self, qr_code): """播放音频文件""" rospy.loginfo("播放音频: {}".format(qr_code)) # 查找对应的音频文件 audio_file = None for key in self.audio_files: if key in qr_code: audio_file = self.audio_files[key] break if audio_file and os.path.exists(audio_file): # 播放音频(异步) threading.Thread(target=self.play_audio_thread, args=(audio_file,)).start() # 设置二维码结果 if "Fruit" in qr_code: self.qr_result = 1 self.qr_simulation = "fruit" elif "Vegetable" in qr_code: self.qr_result = 2 self.qr_simulation = "vegetable" elif "Dessert" in qr_code: self.qr_result = 3 self.qr_simulation = "dessert" else: rospy.logwarn("未找到匹配的音频文件: {}".format(qr_code) ) def play_audio_thread(self, audio_file): """线程中播放音频""" try: playsound(audio_file) rospy.loginfo("音频播放完成") except Exception as e: rospy.logerr("音频播放错误: {}".format(e)) def yolo_callback(self, msg): """处理YOLO检测结果""" # 目标搜索状态 if self.current_state == self.STATE_SEARCHING or self.current_state == self.STATE_FOUND_TARGET: test1_targets = self.test1_categories.get(self.qr_result, []) for bbox in msg.bounding_boxes: if bbox.Class in test1_targets: rospy.loginfo_once("发现目标: {}".format(bbox.Class)) self.target_object = bbox.Class self.xmin = bbox.xmin self.ymin = bbox.ymin self.xmax = bbox.xmax self.ymax = bbox.ymax # 取消搜索定时器 #self.cancel_current_search() self.search_acount_state = False self.search_acount_current = 0 # 更新状态 self.current_state = self.STATE_FOUND_TARGET self.status_pub.publish("TARGET_FOUND") # 订阅图像用于目标跟踪 #if not self.image_sub: # self.image_sub = rospy.Subscriber('/usb_cam/image_raw', Image, self.yolo_image_callback) break # 交通灯识别状态 elif self.current_state == self.STATE_TRAFFIC_LIGHT_DETECTION : # 检查交通灯类别 for bbox in msg.bounding_boxes: if bbox.Class in ['red', 'green']: rospy.loginfo("识别到交通灯: {}".format(bbox.Class) ) self.traffic_light_result = bbox.Class self.traffic_light_detected = True if bbox.Class == 'green': self.xmin = bbox.xmin self.ymin = bbox.ymin self.xmax = bbox.xmax self.ymax = bbox.ymax target_center_x = (self.xmin + self.xmax) // 2 target_width = self.xmax - self.xmin frame_center_x = self.image.shape[1] // 2 # 视觉控制逻辑 twist = Twist() # 横向控制(转向) lateral_error = target_center_x - frame_center_x twist.angular.z = 0.005 * lateral_error if abs(lateral_error) > 50 else 0.0 # 纵向控制(前进/停止) if target_width < 140: twist.linear.x = 0.5 else: # 到达目标面前 twist.linear.x = 0.0 twist.angular.z = 0.0 self.light = True # 取消超时定时器 if self.search_timer: self.search_timer.shutdown() self.search_timer = None # 根据识别结果决定下一步 if self.light == True: self.handle_traffic_light_result() elif bbox.Class == 'red' : self.handle_traffic_light_result() break def handle_traffic_light_result(self): """根据交通灯识别结果决定导航路径并播放音频""" if self.waypoint_index == self.WAYPOINT_LIGHT1: # 路灯1 if self.traffic_light_result == 'green': rospy.loginfo("路灯1为绿灯,播放load_1音频并前往路口1") # 播放音频 if self.sound_state_1 == 1: self.sound_state_1 = 0 audio_file = self.audio_files.get("load_1") if audio_file and os.path.exists(audio_file): threading.Thread(target=self.play_audio_thread, args=(audio_file,)).start() else: rospy.logwarn("load_1音频文件不存在!") # 导航到路口1 self.navigate_to_waypoint(self.WAYPOINT_CROSS1) else: # 红灯 rospy.loginfo("路灯1为红灯,前往路灯2") self.navigate_to_waypoint(self.WAYPOINT_LIGHT2) elif self.waypoint_index == self.WAYPOINT_LIGHT2: # 路灯2 if self.traffic_light_result == 'green': rospy.loginfo("路灯2为绿灯,播放load_2音频并前往路口2") # 播放音频 if self.sound_state_1 == 1: self.sound_state_1 = 0 audio_file = self.audio_files.get("load_1") if audio_file and os.path.exists(audio_file): threading.Thread(target=self.play_audio_thread, args=(audio_file,)).start() else: rospy.logwarn("load_1音频文件不存在!") # 导航到路口2 self.navigate_to_waypoint(self.WAYPOINT_CROSS2) else: # 红灯 rospy.loginfo("路灯2为红灯,默认前往路口2") self.navigate_to_waypoint(self.WAYPOINT_CROSS2) def execute_special_task(self): """执行任务二终点的特殊任务""" rospy.loginfo_once("执行特殊任务: 发布simulation_start话题") # 创建并发布simulation_start消息 if self.msg_send == True: self.msg_send = False sim_msg = simulation() sim_msg.start = "start" sim_msg.myclass_send = str(self.qr_simulation) # 二维码结果 self.simulation_start_pub.publish(sim_msg) # 进入等待结果状态 self.current_state = self.STATE_WAITING_FOR_SIMULATION_RESULT self.status_pub.publish("WAITING_FOR_SIMULATION_RESULT") # 重置结果接收标志 self.simulation_result_received = False # 设置超时定时器(2分50秒 = 170秒) rospy.loginfo_once("设置特殊任务超时定时器(170秒)") self.simulation_timeout_timer = rospy.Timer(rospy.Duration(170), self.handle_simulation_timeout, oneshot=True) rospy.loginfo_once("已发布simulation_start,等待simulation_result...") def simulation_result_callback(self, msg): """处理simulation_result消息""" if self.current_state != self.STATE_WAITING_FOR_SIMULATION_RESULT: return rospy.loginfo("收到simulation_result: stop={}, room={}, myclass={}".format(msg.stop,msg.room,msg.myclass)) # 检查消息格式 if msg.stop == 'stop': self.simulation_result1 = msg.room self.simulation_result2 = msg.myclass self.simulation_result_received = True self.goods_2 = self.simulation_result2 # 根据str2的内容播放相应的音频 self.handle_simulation_result() def handle_simulation_result(self): """处理模拟结果并导航到路灯1(点7)""" if not self.simulation_result_received or not self.simulation_result1: return # 取消超时定时器 if self.simulation_timeout_timer: self.simulation_timeout_timer.shutdown() self.simulation_timeout_timer = None rospy.loginfo("处理模拟结果: {}".format(self.simulation_result1)) # 检查结果是否有效(A, B, C) if self.simulation_result1 in ["A", "B", "C"]: # 播放相应的音频 audio_file = self.audio_files.get(self.simulation_result1) if audio_file and os.path.exists(audio_file): rospy.loginfo("播放结果音频: {}".format(self.simulation_result1) ) threading.Thread(target=self.play_audio_thread, args=(audio_file,)).start() else: rospy.logwarn("找不到结果音频文件:{}".format(self.simulation_result1)) rospy.sleep(2) # 导航到路灯1(点7)开始任务3 rospy.loginfo("特殊任务完成,导航到路灯1开始任务3") self.task3_started = True # 标记任务3已开始 self.navigate_to_waypoint(self.WAYPOINT_LIGHT1) # 直接导航到路灯1 def handle_simulation_timeout(self, event): """处理特殊任务超时""" if self.current_state == self.STATE_WAITING_FOR_SIMULATION_RESULT: rospy.logwarn("特殊任务超时(170秒),未收到simulation_result,将直接前往路灯1") # 取消超时定时器 if self.simulation_timeout_timer: self.simulation_timeout_timer.shutdown() self.simulation_timeout_timer = None # 导航到路灯1(点7) rospy.loginfo("导航到路灯1") self.task3_started = True # 标记任务3已开始 self.navigate_to_waypoint(self.WAYPOINT_LIGHT1) def line_result_callback(self, msg): """处理line_result消息""" if self.current_state != self.STATE_WAITING_FOR_LINE_RESULT: return if msg.data == 'over': rospy.loginfo("收到line_result: over, 执行Final_voice") self.Final_voice() rospy.loginfo("等待3秒后关闭节点...") rospy.sleep(3) rospy.signal_shutdown("任务完成,关闭节点") def Final_voice(self): if self.qr_result == 1: if self.goods_1 == 'apple': if self.goods_2 == 'apple': playsound("/home/ucar/ucar_ws/src/mp3/2apple.mp3") elif self.goods_2 == 'banana': playsound("/home/ucar/ucar_ws/src/mp3/apple_banana.mp3") elif self.goods_2 == 'watermelon': playsound("/home/ucar/ucar_ws/src/mp3/apple_watermelon.mp3") elif self.goods_1 == 'banana': if self.goods_2 == 'banana': playsound("/home/ucar/ucar_ws/src/mp3/2banana.mp3") elif self.goods_2 == 'apple': playsound("/home/ucar/ucar_ws/src/mp3/apple_banana.mp3") elif self.goods_2 == 'watermelon': playsound("/home/ucar/ucar_ws/src/mp3/banana_watermelon.mp3") elif self.goods_1 == 'watermelon': if self.goods_2 == 'watermelon': playsound("/home/ucar/ucar_ws/src/mp3/2watermelon.mp3") elif self.goods_2 == 'apple': playsound("/home/ucar/ucar_ws/src/mp3/apple_watermelon.mp3") elif self.goods_2 == 'banana': playsound("/home/ucar/ucar_ws/src/mp3/banana_watermelon.mp3") if self.qr_result == 2: if self.goods_1 == 'cake': if self.goods_2 == 'cake': playsound("/home/ucar/ucar_ws/src/mp3/2cake.mp3") elif self.goods_2 == 'coco': playsound("/home/ucar/ucar_ws/src/mp3/cake_coco.mp3") elif self.goods_2 == 'milk': playsound("/home/ucar/ucar_ws/src/mp3/milk_cake.mp3") elif self.goods_1 == 'coco': if self.goods_2 == 'coco': playsound("/home/ucar/ucar_ws/src/mp3/2coco.mp3") elif self.goods_2 == 'cake': playsound("/home/ucar/ucar_ws/src/mp3/cake_coco.mp3") elif self.goods_2 == 'milk': playsound("/home/ucar/ucar_ws/src/mp3/milk_coco.mp3") elif self.goods_1 == 'milk': if self.goods_2 == 'milk': playsound("/home/ucar/ucar_ws/src/mp3/2milk.mp3") elif self.goods_2 == 'coco': playsound("/home/ucar/ucar_ws/src/mp3/milk_coco.mp3") elif self.goods_2 == 'cake': playsound("/home/ucar/ucar_ws/src/mp3/milk_cake.mp3") if self.qr_result == 3: if self.goods_1 == 'potato': if self.goods_2 == 'potato': playsound("/home/ucar/ucar_ws/src/mp3/2potato.mp3") elif self.goods_2 == 'chili': playsound("/home/ucar/ucar_ws/src/mp3/chili_potato.mp3") elif self.goods_2 == 'tomato': playsound("/home/ucar/ucar_ws/src/mp3/tomato_potato.mp3") elif self.goods_1 == 'chili': if self.goods_2 == 'chili': playsound("/home/ucar/ucar_ws/src/mp3/2chili.mp3") elif self.goods_2 == 'potato': playsound("/home/ucar/ucar_ws/src/mp3/chili_potato.mp3") elif self.goods_2 == 'tomato': playsound("/home/ucar/ucar_ws/src/mp3/chili_tomato.mp3") elif self.goods_1 == 'tomato': if self.goods_2 == 'tomato': playsound("/home/ucar/ucar_ws/src/mp3/2tomato.mp3") elif self.goods_2 == 'chili': playsound("/home/ucar/ucar_ws/src/mp3/chili_tomato.mp3") elif self.goods_2 == 'potato': playsound("/home/ucar/ucar_ws/src/mp3/tomato_potato.mp3") def shutdown(self): """节点关闭时的清理工作""" rospy.loginfo("关闭节点...") if self.image_sub: self.image_sub.unregister() if self.search_timer: self.search_timer.shutdown() if self.simulation_timeout_timer: self.simulation_timeout_timer.shutdown() self.cmd_vel_pub.publish(Twist()) # 停止机器人运动 if __name__ == '__main__': try: node = AutomationNode() rospy.spin() except rospy.ROSInterruptException: pass 我想在识别交通灯的时候也实现目标跟随
08-07
# -*- coding: utf-8 -*- import threading import time import sys import inspect import ctypes import os import cv2 import numpy as np import logging from MvCameraControl_class import * from datetime import datetime from ctypes import * from enum import Enum from ctypes import byref, cast, POINTER, c_ubyte from MvCameraControl_class import MvCamera from CameraParams_header import MV_FRAME_OUT_INFO_EX, PixelType_Gvsp_BGR8_Packed, MV_OK # 配置日志系统 def setup_logging(log_level=logging.INFO): """配置全局日志系统""" logging.basicConfig( level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("camera_operation.log"), logging.StreamHandler() ] ) logging.info("日志系统初始化完成") # 强制关闭线程 def async_raise(tid, exctype): """安全终止线程""" tid = ctypes.c_long(tid) if not inspect.isclass(exctype): exctype = type(exctype) res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) if res == 0: raise ValueError("invalid thread id") elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) raise SystemError("PyThreadState_SetAsyncExc failed") def stop_thread(thread): """停止指定线程""" async_raise(thread.ident, SystemExit) # 触发模式常量定义(兼容不同SDK版本) try: from CameraParams_header import MV_TRIGGER_MODE_ON, MV_TRIGGER_MODE_OFF, MV_TRIGGER_SOURCE_SOFTWARE except ImportError: # 如果导入失败,使用默认值(兼容旧版本) logging.warning("CameraParams_header 导入触发模式常量失败,使用默认值") MV_TRIGGER_MODE_ON = 1 MV_TRIGGER_MODE_OFF = 0 MV_TRIGGER_SOURCE_SOFTWARE = 7 # 像素格式常量定义(兼容不同SDK版本) try: from CameraParams_header import * # 如果导入成功,使用SDK中的常量定义 PIXEL_FORMATS = { "MONO8": PixelType_Gvsp_Mono8, "MONO10": PixelType_Gvsp_Mono10, "MONO12": PixelType_Gvsp_Mono12, "BAYER_BG8": PixelType_Gvsp_BayerBG8, "BAYER_GB8": PixelType_Gvsp_BayerGB8, "BAYER_GR8": PixelType_Gvsp_BayerGR8, "BAYER_RG8": PixelType_Gvsp_BayerRG8, "RGB8": PixelType_Gvsp_RGB8_Packed, "YUV422": PixelType_Gvsp_YUV422_Packed, "YUV422_YUYV": PixelType_Gvsp_YUV422_YUYV_Packed } except ImportError: # 如果导入失败,使用默认值(兼容旧版本) logging.warning("CameraParams_header 导入失败,使用默认像素格式常量") PIXEL_FORMATS = { "MONO8": 0x01080001, "MONO10": 0x01100003, "MONO12": 0x01100005, "BAYER_BG8": 0x0108000B, "BAYER_GB8": 0x0108000A, "BAYER_GR8": 0x01080008, "BAYER_RG8": 0x01080009, "RGB8": 0x02180014, "YUV422": 0x02100032, "YUV422_YUYV": 0x0210001F } # 像素格式枚举 class PixelFormat(Enum): MONO8 = PIXEL_FORMATS["MONO8"] MONO10 = PIXEL_FORMATS["MONO10"] MONO12 = PIXEL_FORMATS["MONO12"] BAYER_BG8 = PIXEL_FORMATS["BAYER_BG8"] BAYER_GB8 = PIXEL_FORMATS["BAYER_GB8"] BAYER_GR8 = PIXEL_FORMATS["BAYER_GR8"] BAYER_RG8 = PIXEL_FORMATS["BAYER_RG8"] RGB8 = PIXEL_FORMATS["RGB8"] YUV422 = PIXEL_FORMATS["YUV422"] YUV422_YUYV = PIXEL_FORMATS["YUV422_YUYV"] def is_mono_data(enGvspPixelType): """判断是否为单色图像格式""" mono_formats = [ PIXEL_FORMATS["MONO8"], PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"], 0x010C0004, # Mono10 Packed 0x010C0006, # Mono12 Packed 0x01080002, # Mono8 Signed 0x0110000C # Mono16 ] return enGvspPixelType in mono_formats def is_color_data(enGvspPixelType): """判断是否为彩色图像格式""" color_formats = [ # Bayer格式 PIXEL_FORMATS["BAYER_BG8"], PIXEL_FORMATS["BAYER_GB8"], PIXEL_FORMATS["BAYER_GR8"], PIXEL_FORMATS["BAYER_RG8"], 0x01100011, # BayerBG10 0x01100010, # BayerGB10 0x0110000E, # BayerGR10 0x0110000F, # BayerRG10 0x01100017, # BayerBG12 0x01100016, # BayerGB12 0x01100014, # BayerGR12 0x01100015, # BayerRG12 # YUV格式 PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"], # RGB格式 PIXEL_FORMATS["RGB8"], 0x0220001E, # RGB10_Packed 0x02300020, # RGB12_Packed 0x02400021, # RGB16_Packed 0x02180032 # BGR8_Packed ] return enGvspPixelType in color_formats def get_pixel_format_name(pixel_value): """根据像素值获取可读的名称""" for name, value in PIXEL_FORMATS.items(): if value == pixel_value: return name return f"未知格式: 0x{pixel_value:08X}" # 相机操作类 class CameraOperation: # 状态码定义 MV_OK = 0 MV_E_CALLORDER = -2147483647 MV_E_PARAMETER = -2147483646 MV_E_NO_DATA = -2147483645 MV_E_SAVE_IMAGE = -2147483644 MV_E_STATE = -2147483643 # 在 CamOperation_class.py 中修改 CameraOperation 类 def __init__(self, obj_cam, st_device_list, n_connect_num=0): """ 初始化相机操作对象 :param obj_cam: 相机对象 :param st_device_list: 设备列表 :param n_connect_num: 连接编号 """ self.obj_cam = obj_cam self.st_device_list = st_device_list self.n_connect_num = n_connect_num # 状态标志 - 确保所有属性初始化 self.b_open_device = False self.b_start_grabbing = False self.b_thread_running = False self.b_exit = False # 帧接收状态 - 关键修复:显式初始化 self.b_frame_received = False # 线程相关 self.h_thread_handle = None # 图像数据 self.st_frame_info = None self.buf_save_image = None self.n_save_image_size = 0 self.current_frame = None # 参数 self.frame_rate = 0.0 self.exposure_time = 0.0 self.gain = 0.0 # 线程安全锁 self.buf_lock = threading.Lock() # 图像缓冲区锁 self.frame_lock = threading.Lock() # 当前帧锁 self.param_lock = threading.Lock() # 参数锁 self.frame_count = 0 # 帧计数 self.last_frame_time = None # 最后帧时间 self.is_streaming = False # 取流状态 logging.info("相机操作对象初始化完成") # 添加 is_frame_available 方法 def is_frame_available(self): """检查是否有可用帧图像""" try: # 检查SDK是否返回有效帧 if hasattr(self, 'cam') and self.cam: return self.cam.is_frame_available() return False except Exception as e: logging.error(f"检查帧可用性失败: {str(e)}") return False def capture_frame(self): """捕获当前帧""" try: return self.cam.get_current_frame() except Exception as e: logging.error(f"捕获帧失败: {str(e)}") return None # 在获取图像的方法中更新 last_frame def get_current_frame(self): """获取当前帧图像""" try: # 假设这是获取图像的实际实现 frame = self.cam.get_image() # 伪代码,实际调用SDK方法 self.last_frame = frame # 更新最后捕获的帧 return frame except Exception as e: logging.error(f"获取图像失败: {str(e)}") return None # 在开始取流的方法中设置标志 def start_grabbing(self, hwnd=None): """开始取流""" ret = self.cam.start_grabbing(hwnd) if ret == MV_OK: self.is_grabbing = True return ret # 在停止取流的方法中重置标志 def Stop_grabbing(self): """停止取流""" ret = self.cam.stop_grabbing() if ret == MV_OK: self.is_grabbing = False self.last_frame = None # 清除最后帧 self.image_buffer = [] # 清空缓冲区 return ret def is_ready(self): """检查设备是否就绪""" return self.b_open_device and not self.b_exit def open_device(self): """打开相机设备""" if self.b_open_device: logging.warning("设备已打开,无需重复操作") return self.MV_OK if self.n_connect_num < 0: logging.error("无效的连接编号") return self.MV_E_CALLORDER try: # 选择设备并创建句柄 nConnectionNum = int(self.n_connect_num) stDeviceList = cast(self.st_device_list.pDeviceInfo[int(nConnectionNum)], POINTER(MV_CC_DEVICE_INFO)).contents self.obj_cam = MvCamera() # 创建相机句柄 ret = self.obj_cam.MV_CC_CreateHandle(stDeviceList) if ret != self.MV_OK: self.obj_cam.MV_CC_DestroyHandle() logging.error(f"创建句柄失败: {hex(ret)}") return ret # 打开设备 ret = self.obj_cam.MV_CC_OpenDevice() if ret != self.MV_OK: logging.error(f"打开设备失败: {hex(ret)}") return ret # 设备已成功打开 self.b_open_device = True self.b_exit = False logging.info("设备打开成功") # 探测网络最佳包大小(仅对GigE相机有效) if stDeviceList.nTLayerType in [MV_GIGE_DEVICE, MV_GENTL_GIGE_DEVICE]: nPacketSize = self.obj_cam.MV_CC_GetOptimalPacketSize() if int(nPacketSize) > 0: ret = self.obj_cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize) if ret != self.MV_OK: logging.warning(f"设置包大小失败: {hex(ret)}") else: logging.warning(f"获取最佳包大小失败: {hex(nPacketSize)}") # 获取采集帧率使能状态 stBool = c_bool(False) ret = self.obj_cam.MV_CC_GetBoolValue("AcquisitionFrameRateEnable", stBool) if ret != self.MV_OK: logging.warning(f"获取帧率使能状态失败: {hex(ret)}") # 现在设备已打开,可以安全设置触发模式 ret = self.set_continue_mode() if ret != self.MV_OK: logging.warning(f"设置连续模式失败: {hex(ret)}") return self.MV_OK except Exception as e: logging.exception(f"打开设备异常: {str(e)}") # 尝试清理资源 try: if hasattr(self, 'obj_cam'): self.obj_cam.MV_CC_CloseDevice() self.obj_cam.MV_CC_DestroyHandle() except: pass self.b_open_device = False return self.MV_E_STATE def close_device(self): """关闭相机设备""" if not self.b_open_device: logging.warning("设备未打开,无需关闭") return self.MV_OK try: # 停止采集(如果正在采集) if self.b_start_grabbing: self.stop_grabbing() # 关闭设备 ret = self.obj_cam.MV_CC_CloseDevice() if ret != self.MV_OK: logging.error(f"关闭设备失败: {hex(ret)}") # 销毁句柄 ret = self.obj_cam.MV_CC_DestroyHandle() if ret != self.MV_OK: logging.error(f"销毁句柄失败: {hex(ret)}") self.b_open_device = False self.b_exit = True logging.info("设备关闭成功") return self.MV_OK except Exception as e: logging.exception(f"关闭设备异常: {str(e)}") return self.MV_E_STATE def start_grabbing(self, winHandle=None): """开始图像采集""" if not self.b_open_device: logging.error("设备未打开,无法开始采集") return self.MV_E_CALLORDER if self.b_start_grabbing: logging.warning("采集已在进行中") return self.MV_OK try: # 开始采集 ret = self.obj_cam.MV_CC_StartGrabbing() if ret != self.MV_OK: logging.error(f"开始采集失败: {hex(ret)}") return ret self.b_start_grabbing = True self.b_exit = False # 启动采集线程 self.h_thread_handle = threading.Thread( target=self.work_thread, args=(winHandle,), daemon=True ) self.h_thread_handle.start() self.b_thread_running = True logging.info("图像采集已开始") return self.MV_OK except Exception as e: logging.exception(f"开始采集异常: {str(e)}") return self.MV_E_STATE def stop_grabbing(self): """停止图像采集""" if not self.b_open_device: logging.error("设备未打开,无法停止采集") return self.MV_E_CALLORDER if not self.b_start_grabbing: logging.warning("采集未在进行中") return self.MV_OK try: # 设置退出标志 self.b_exit = True # 等待线程结束 if self.b_thread_running and self.h_thread_handle.is_alive(): self.h_thread_handle.join(timeout=2.0) if self.h_thread_handle.is_alive(): logging.warning("采集线程未正常退出,尝试强制终止") stop_thread(self.h_thread_handle) # 停止采集 ret = self.obj_cam.MV_CC_StopGrabbing() if ret != self.MV_OK: logging.error(f"停止采集失败: {hex(ret)}") return ret self.b_start_grabbing = False self.b_thread_running = False logging.info("图像采集已停止") return self.MV_OK except Exception as e: logging.exception(f"停止采集异常: {str(e)}") return self.MV_E_STATE def set_trigger_mode(self, enable=True, source=MV_TRIGGER_SOURCE_SOFTWARE): """ 设置触发模式 :param enable: 是否启用触发模式 :param source: 触发源 (默认软件触发) :return: 操作结果 """ if not self.b_open_device: logging.error("设备未打开,无法设置触发模式") return self.MV_E_CALLORDER try: mode = MV_TRIGGER_MODE_ON if enable else MV_TRIGGER_MODE_OFF ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", mode) if ret != self.MV_OK: logging.error(f"设置触发模式失败: {hex(ret)}") return ret if enable: ret = self.obj_cam.MV_CC_SetEnumValue("TriggerSource", source) if ret != self.MV_OK: logging.error(f"设置触发源失败: {hex(ret)}") return ret logging.info(f"触发模式设置成功: {'启用' if enable else '禁用'}") return self.MV_OK except Exception as e: logging.exception(f"设置触发模式异常: {str(e)}") return self.MV_E_STATE def set_continue_mode(self): """设置连续采集模式""" # 添加设备状态检查 if not self.b_open_device: logging.error("设备未打开,无法设置连续模式") return self.MV_E_CALLORDER logging.info("尝试设置连续采集模式") try: # 禁用触发模式 ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF) if ret != self.MV_OK: logging.error(f"禁用触发模式失败: {hex(ret)}") return ret # 设置采集模式为连续 ret = self.obj_cam.MV_CC_SetEnumValue("AcquisitionMode", 2) # 2表示连续采集 if ret != self.MV_OK: logging.error(f"设置连续采集模式失败: {hex(ret)}") return ret logging.info("连续采集模式设置成功") return self.MV_OK except Exception as e: logging.exception(f"设置连续模式异常: {str(e)}") return self.MV_E_STATE def trigger_once(self): """执行一次软触发""" if not self.b_open_device: logging.error("设备未打开,无法触发") return self.MV_E_CALLORDER try: ret = self.obj_cam.MV_CC_SetCommandValue("TriggerSoftware") if ret != self.MV_OK: logging.error(f"软触发失败: {hex(ret)}") return ret logging.info("软触发成功") return self.MV_OK except Exception as e: logging.exception(f"软触发异常: {str(e)}") return self.MV_E_STATE def get_parameters(self): """获取相机参数""" if not self.b_open_device: logging.error("设备未打开,无法获取参数") return self.MV_E_CALLORDER try: # 使用锁保护参数访问 with self.param_lock: # 初始化返回值为整数错误码 return_code = self.MV_OK # 帧率 stFrameRate = MVCC_FLOATVALUE() memset(byref(stFrameRate), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("AcquisitionFrameRate", stFrameRate) if ret == self.MV_OK: self.frame_rate = stFrameRate.fCurValue logging.debug(f"获取帧率成功: {self.frame_rate}") else: logging.warning(f"获取帧率失败: {hex(ret)}") if return_code == self.MV_OK: # 保留第一个错误码 return_code = ret # 曝光时间 stExposure = MVCC_FLOATVALUE() memset(byref(stExposure), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("ExposureTime", stExposure) if ret == self.MV_OK: self.exposure_time = stExposure.fCurValue logging.debug(f"获取曝光时间成功: {self.exposure_time}") else: logging.warning(f"获取曝光时间失败: {hex(ret)}") if return_code == self.MV_OK: return_code = ret # 增益 stGain = MVCC_FLOATVALUE() memset(byref(stGain), 0, sizeof(MVCC_FLOATVALUE)) ret = self.obj_cam.MV_CC_GetFloatValue("Gain", stGain) if ret == self.MV_OK: self.gain = stGain.fCurValue logging.debug(f"获取增益成功: {self.gain}") else: logging.warning(f"获取增益失败: {hex(ret)}") if return_code == self.MV_OK: return_code = ret # 返回整数错误码 return return_code except Exception as e: logging.exception(f"获取参数异常: {str(e)}") return self.MV_E_STATE # 添加兼容拼写错误的方法 def get_parame(self): """兼容拼写错误的方法名:get_parame""" logging.warning("调用get_parame方法 - 可能是拼写错误,建议使用get_parameters()") return self.get_parameters() # 添加动态属性处理 def __getattr__(self, name): # 处理保存方法 if name == "Save_Image": logging.warning("动态处理Save_Image调用 - 映射到save_image") return self.save_image if name == "Save_Bmp": logging.warning("动态处理Save_Bmp调用 - 映射到save_bmp") return self.save_bmp # 处理其他可能的拼写错误 method_map = { "get_parame": self.get_parame, "get_parm": self.get_parameters, "get_parmeter": self.get_parameters, "get_parma": self.get_parameters, "GetParameter": self.get_parameters, "GetParam": self.get_parameters } if name in method_map: logging.warning(f"动态处理{name}调用 - 可能是拼写错误") return method_map[name] # 关键修复:处理b_frame_received属性 if name == "b_frame_received": logging.warning("动态访问b_frame_received属性 - 提供默认值") return False raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") def set_param(self, frame_rate=None, exposure_time=None, gain=None): """ 设置相机参数 :param frame_rate: 帧率 (None表示不修改) :param exposure_time: 曝光时间 (None表示不修改) :param gain: 增益 (None表示不修改) :return: 操作结果 """ if not self.b_open_device: logging.error("设备未打开,无法设置参数") return self.MV_E_CALLORDER try: # 使用锁保护参数修改 with self.param_lock: # 禁用自动曝光 ret = self.obj_cam.MV_CC_SetEnumValue("ExposureAuto", 0) if ret != self.MV_OK: logging.warning(f"禁用自动曝光失败: {hex(ret)}") # 设置帧率 if frame_rate is not None: ret = self.obj_cam.MV_CC_SetFloatValue("AcquisitionFrameRate", float(frame_rate)) if ret != self.MV_OK: logging.error(f"设置帧率失败: {hex(ret)}") return ret self.frame_rate = float(frame_rate) # 设置曝光时间 if exposure_time is not None: ret = self.obj_cam.MV_CC_SetFloatValue("ExposureTime", float(exposure_time)) if ret != self.MV_OK: logging.error(f"设置曝光时间失败: {hex(ret)}") return ret self.exposure_time = float(exposure_time) # 设置增益 if gain is not None: ret = self.obj_cam.MV_CC_SetFloatValue("Gain", float(gain)) if ret != self.MV_OK: logging.error(f"设置增益失败: {hex(ret)}") return ret self.gain = float(gain) logging.info(f"参数设置成功: 帧率={self.frame_rate}, 曝光={self.exposure_time}, 增益={self.gain}") return self.MV_OK except Exception as e: logging.exception(f"设置参数异常: {str(e)}") return self.MV_E_STATE def work_thread(self, winHandle=None): """图像采集工作线程""" stOutFrame = MV_FRAME_OUT() memset(byref(stOutFrame), 0, sizeof(stOutFrame)) logging.info("采集线程启动") while not self.b_exit: try: # 获取图像缓冲区 ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000) if ret != self.MV_OK: if ret != self.MV_E_NO_DATA: # 忽略无数据错误 logging.warning(f"获取图像缓冲区失败: {hex(ret)}") time.sleep(0.01) continue # 更新帧信息 self.st_frame_info = stOutFrame.stFrameInfo # 关键修复:标记帧已接收 self.b_frame_received = True # 记录像素格式信息 pixel_format = get_pixel_format_name(self.st_frame_info.enPixelType) logging.debug(f"获取到图像: {self.st_frame_info.nWidth}x{self.st_frame_info.nHeight}, " f"格式: {pixel_format}, 大小: {self.st_frame_info.nFrameLen}字节") # 分配/更新图像缓冲区 frame_size = stOutFrame.stFrameInfo.nFrameLen with self.buf_lock: if self.buf_save_image is None or self.n_save_image_size < frame_size: self.buf_save_image = (c_ubyte * frame_size)() self.n_save_image_size = frame_size # 复制图像数据 cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, frame_size) # 更新当前帧 self.update_current_frame() # 显示图像(如果指定了窗口句柄) if winHandle is not None: stDisplayParam = MV_DISPLAY_FRAME_INFO() memset(byref(stDisplayParam), 0, sizeof(stDisplayParam)) stDisplayParam.hWnd = int(winHandle) stDisplayParam.nWidth = self.st_frame_info.nWidth stDisplayParam.nHeight = self.st_frame_info.nHeight stDisplayParam.enPixelType = self.st_frame_info.enPixelType stDisplayParam.pData = self.buf_save_image stDisplayParam.nDataLen = frame_size self.obj_cam.MV_CC_DisplayOneFrame(stDisplayParam) # 释放图像缓冲区 self.obj_cam.MV_CC_FreeImageBuffer(stOutFrame) except Exception as e: logging.exception(f"采集线程异常: {str(e)}") time.sleep(0.1) # 清理资源 with self.buf_lock: if self.buf_save_image is not None: del self.buf_save_image self.buf_save_image = None self.n_save_image_size = 0 logging.info("采集线程退出") def update_current_frame(self): """将原始图像数据转换为OpenCV格式并存储""" if not self.st_frame_info or not self.buf_save_image: logging.warning("更新当前帧时缺少帧信息或缓冲区") return try: # 获取图像信息 width = self.st_frame_info.nWidth height = self.st_frame_info.nHeight pixel_type = self.st_frame_info.enPixelType # 复制缓冲区数据 with self.buf_lock: buffer_copy = bytearray(self.buf_save_image) # 转换为numpy数组 np_buffer = np.frombuffer(buffer_copy, dtype=np.uint8) # 根据像素类型处理图像 frame = None if is_mono_data(pixel_type): # 单色图像 frame = np_buffer.reshape(height, width) elif pixel_type == PIXEL_FORMATS["BAYER_BG8"]: # Bayer BG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerBG2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_GB8"]: # Bayer GB格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGB2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_GR8"]: # Bayer GR格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGR2RGB) elif pixel_type == PIXEL_FORMATS["BAYER_RG8"]: # Bayer RG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerRG2RGB) elif pixel_type == PIXEL_FORMATS["RGB8"]: # RGB格式 frame = np_buffer.reshape(height, width, 3) elif pixel_type in [PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"]]: # YUV格式 frame = cv2.cvtColor(np_buffer.reshape(height, width, 2), cv2.COLOR_YUV2RGB_YUYV) else: # 尝试自动处理其他格式 if pixel_type in [PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"]]: # 10位或12位单色图像需要特殊处理 frame = self.process_high_bit_depth(np_buffer, width, height, pixel_type) else: logging.warning(f"不支持的像素格式: {get_pixel_format_name(pixel_type)}") return # 更新当前帧 - 使用线程安全锁 with self.frame_lock: self.current_frame = frame logging.debug(f"当前帧更新成功: {frame.shape if frame is not None else '无数据'}") except Exception as e: logging.exception(f"更新当前帧异常: {str(e)}") # 调试:保存原始数据用于分析 try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") debug_path = f"frame_debug_{timestamp}.bin" with open(debug_path, "wb") as f: f.write(buffer_copy) logging.info(f"已保存原始帧数据到: {debug_path}") except: logging.error("保存调试帧数据失败") def process_high_bit_depth(self, buffer, width, height, pixel_type): """处理高位深度图像格式""" try: # 10位或12位图像处理 if pixel_type == PIXEL_FORMATS["MONO10"]: # 将10位数据转换为16位 data_16bit = np.frombuffer(buffer, dtype=np.uint16) # 10位数据存储方式:每个像素占用2字节,但只有10位有效 data_16bit = (data_16bit >> 6) # 右移6位使10位数据对齐到低10位 frame = data_16bit.reshape(height, width).astype(np.uint16) elif pixel_type == PIXEL_FORMATS["MONO12"]: # 将12位数据转换为16位 data_16bit = np.frombuffer(buffer, dtype=np.uint16) # 12位数据存储方式:每个像素占用2字节,但只有12位有效 data_16bit = (data_16bit >> 4) # 右移4位使12位数据对齐到低12位 frame = data_16bit.reshape(height, width).astype(np.uint16) else: logging.warning(f"不支持的高位深度格式: {get_pixel_format_name(pixel_type)}") return None # 归一化到8位用于显示(如果需要) frame_8bit = cv2.convertScaleAbs(frame, alpha=(255.0/4095.0)) return frame_8bit except Exception as e: logging.exception(f"处理高位深度图像异常: {str(e)}") return None def get_current_frame(self): """获取当前帧的副本(线程安全)""" with self.frame_lock: if self.current_frame is None: return None # 返回副本以避免外部修改影响内部状态 return self.current_frame.copy() # 在 CameraOperation 类中添加/修改以下方法 def save_image(self, file_path, save_format="bmp", quality=95): """ 安全保存当前帧到文件 - 使用原始缓冲区数据 """ if not self.b_open_device or not self.b_start_grabbing: logging.error("设备未就绪,无法保存图像") return self.MV_E_CALLORDER # 使用缓冲区锁确保数据一致性 with self.buf_lock: if not self.buf_save_image or not self.st_frame_info: logging.error("无可用图像数据") return self.MV_E_NO_DATA # 获取图像信息 width = self.st_frame_info.nWidth height = self.st_frame_info.nHeight pixel_type = self.st_frame_info.enPixelType frame_size = self.st_frame_info.nFrameLen # 复制缓冲区数据 buffer_copy = bytearray(self.buf_save_image) try: # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) logging.info(f"创建目录: {directory}") # 根据像素类型处理图像 np_buffer = np.frombuffer(buffer_copy, dtype=np.uint8) # 根据像素格式转换图像 if is_mono_data(pixel_type): # 单色图像 frame = np_buffer.reshape(height, width) elif pixel_type == PIXEL_FORMATS["BAYER_BG8"]: # Bayer BG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerBG2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_GB8"]: # Bayer GB格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGB2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_GR8"]: # Bayer GR格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerGR2BGR) elif pixel_type == PIXEL_FORMATS["BAYER_RG8"]: # Bayer RG格式 frame = cv2.cvtColor(np_buffer.reshape(height, width), cv2.COLOR_BayerRG2BGR) elif pixel_type == PIXEL_FORMATS["RGB8"]: # RGB格式 frame = np_buffer.reshape(height, width, 3) frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) elif pixel_type in [PIXEL_FORMATS["YUV422"], PIXEL_FORMATS["YUV422_YUYV"]]: # YUV格式 frame = cv2.cvtColor(np_buffer.reshape(height, width, 2), cv2.COLOR_YUV2BGR_YUYV) else: # 尝试自动处理其他格式 if pixel_type in [PIXEL_FORMATS["MONO10"], PIXEL_FORMATS["MONO12"]]: # 10位或12位单色图像需要特殊处理 frame = self.process_high_bit_depth(np_buffer, width, height, pixel_type) else: logging.error(f"不支持的像素格式: {get_pixel_format_name(pixel_type)}") return self.MV_E_PARAMETER # 根据格式保存图像 save_format = save_format.lower() try: if save_format == "bmp": cv2.imwrite(file_path, frame) elif save_format in ["jpg", "jpeg"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) elif save_format == "png": cv2.imwrite(file_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 9]) elif save_format in ["tiff", "tif"]: cv2.imwrite(file_path, frame, [cv2.IMWRITE_TIFF_COMPRESSION, 1]) else: logging.error(f"不支持的图像格式: {save_format}") return self.MV_E_PARAMETER # 验证保存结果 if not os.path.exists(file_path): logging.error(f"文件保存失败: {file_path}") return self.MV_E_SAVE_IMAGE file_size = os.path.getsize(file_path) if file_size < 1024: # 小于1KB视为无效 logging.error(f"文件大小异常: {file_path} ({file_size} 字节)") os.remove(file_path) # 删除无效文件 return self.MV_E_SAVE_IMAGE logging.info(f"图像已保存: {file_path} ({file_size} 字节)") return self.MV_OK except Exception as e: logging.exception(f"保存图像异常: {str(e)}") return self.MV_E_SAVE_IMAGE except Exception as e: logging.exception(f"图像处理异常: {str(e)}") return self.MV_E_SAVE_IMAGE # 在 CameraOperation 类中添加/修改以下方法 # 兼容旧方法的保存接口 def save_jpg(self, file_path=None, quality=95): """保存为JPEG格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.jpg" return self.save_image(file_path, "jpg", quality) def save_bmp(self, file_path=None): """保存为BMP格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.bmp" return self.save_image(file_path, "bmp") def save_png(self, file_path=None): """保存为PNG格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.png" return self.save_image(file_path, "png") def save_tiff(self, file_path=None): """保存为TIFF格式""" if file_path is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = f"capture_{timestamp}.tiff" return self.save_image(file_path, "tiff") # 新增方法:检查帧是否可用 def is_frame_available(self): """检查是否有有效帧可用""" with self.frame_lock: return self.current_frame is not None and self.current_frame.size > 0 # 新增方法:获取帧状态详细信息 def get_frame_status(self): """获取当前帧状态详细信息""" # 安全访问b_frame_received属性 frame_received = getattr(self, 'b_frame_received', False) status = { "camera_open": self.b_open_device, "grabbing_started": self.b_start_grabbing, "thread_running": self.b_thread_running, "frame_received": frame_received, # 使用安全访问 "frame_size": self.n_save_image_size if self.buf_save_image else 0, "current_frame": self.current_frame is not None } if self.st_frame_info: status.update({ "width": self.st_frame_info.nWidth, "height": self.st_frame_info.nHeight, "pixel_format": get_pixel_format_name(self.st_frame_info.enPixelType), "frame_num": self.st_frame_info.nFrameNum }) return status 这是camoperatto_class.py目前的完整代码在不改变原本已有功能的前提下,把你刚刚提出的改进方案完整展示出来
07-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值