count_history_cmd.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import os

def f(his, n):
    lines = open(os.path.join(os.getenv('HOME'), his)).readlines()
    d = {}
    for line in lines:
        line = line.split()
        if not line: continue
        s = line[0]
        if line[0] == 'sudo':
            s = line[1]
        d[s] = d.get(s, 0) + 1

    l = sorted(d.items(), key=lambda t: t[1], reverse=True)
    for t in l[:n]:
        print '%s : %d' % (t[0].ljust(16), t[1])

if __name__ == '__main__':
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option('-f', '--file', dest='f', default='.history',
                      help='the history file', metavar='FILE')
    parser.add_option('-c', '--count', dest='c', type='int', default='30',
                      help='for display count', metavar='COUNT')
    (options, args) = parser.parse_args()
    f(options.f, options.c)

 

运行结果:

Shell:~ >: count_his_cmd
cd               : 4895
ls               : 4533
vi               : 988
man              : 679
hitfm            : 583
cat              : 532
ifconfig         : 414
ant              : 401
rm               : 386
lt               : 349
ll               : 341
tmp              : 340
svn              : 333
tlist            : 332
top              : 311
my               : 292
exit             : 278
run              : 277
unlcall          : 249
pkg_list         : 249
glib             : 242
locate           : 235
shutdown         : 226
bbs.sh           : 220
check            : 216
ping             : 215
pkill            : 198
mv               : 196
vpn              : 190
sock             : 186
Shell:~ >:

 

from http://code.google.com/p/mylibs/source/browse/trunk/tools/Python/count_history_cmd.py

帮我整合一下代码,import sys import time import lcm import cv2 from threading import Thread, Lock import rclpy from rclpy.node import Node from sensor_msgs.msg import Image from cv_bridge import CvBridge from pyzbar.pyzbar import decode from robot_control_cmd_lcmt import robot_control_cmd_lcmt from robot_control_response_lcmt import robot_control_response_lcmt class RobotControl: """机器人控制类,通过 LCM 与机器人进行命令和状态交互""" def __init__(self): # LCM 通信通道 self.lc_recv = lcm.LCM("udpm://239.255.76.67:7670?ttl=255") self.lc_send = lcm.LCM("udpm://239.255.76.67:7671?ttl=255") self.cmd_msg = robot_control_cmd_lcmt() self.rec_msg = robot_control_response_lcmt() self.send_lock = Lock() self.running = True self.life_count = 0 self.mode_ok = 0 # 接收与发送线程 self.thread_recv = Thread(target=self._receive_response, daemon=True) self.thread_send = Thread(target=self._send_publish, daemon=True) self.thread_recv.start() self.thread_send.start() def _receive_response(self): """接收机器人状态""" self.lc_recv.subscribe("robot_control_response", self._msg_handler) while self.running: self.lc_recv.handle() time.sleep(0.002) def _msg_handler(self, channel, data): """解析收到的 LCM 数据""" self.rec_msg = robot_control_response_lcmt().decode(data) if getattr(self.rec_msg, 'order_process_bar', 0) >= 95: self.mode_ok = self.rec_msg.mode def _send_publish(self): """周期性发送指令""" delay_counter = 0 while self.running: with self.send_lock: if delay_counter > 20: self.lc_send.publish("robot_control_cmd", self.cmd_msg.encode()) delay_counter = 0 delay_counter += 1 time.sleep(0.005) def send_cmd(self, msg): """安全地更新并发送命令""" with self.send_lock: self.life_count += 1 msg.life_count = self.life_count % 128 self.cmd_msg = msg def stand_up(self): """让机器人站立""" print("指令:机器人开始站立") stop_msg = robot_control_cmd_lcmt() stop_msg.mode = 7 stop_msg.gait_id = 0 self.send_cmd(stop_msg) time.sleep(0.5) msg = robot_control_cmd_lcmt() msg.mode = 12 msg.gait_id = 0 self.send_cmd(msg) start_time = time.time() while time.time() - start_time < 5: if self.mode_ok == 12: print("状态:机器人站立成功") return True time.sleep(0.1) print("警告:站立超时") return False def stop(self): """让机器人停止""" msg = robot_control_cmd_lcmt() msg.mode = 7 msg.gait_id = 1 self.send_cmd(msg) print("指令:停止机器人") def forward(self, speed=0.3): """让机器人前进""" msg = robot_control_cmd_lcmt() msg.mode = 11 msg.gait_id = 26 msg.vel_des = [speed, 0, 0] msg.duration = 0 self.send_cmd(msg) print(f"指令:前进,速度 {speed}") def quit(self): """退出控制""" print("退出机器人控制") self.running = False self.stop() if self.thread_recv.is_alive(): self.thread_recv.join(timeout=1.0) if self.thread_send.is_alive(): self.thread_send.join(timeout=1.0) print("机器人控制已退出") class QRCodeNode(Node): """ROS2 节点:摄像头识别二维码并控制机器人""" def __init__(self, robot_controller: RobotControl): super().__init__('qr_code_node') self.robot_controller = robot_controller self.bridge = CvBridge() self.qr_detected = False from rclpy.qos import QoSProfile qos_profile = QoSProfile(depth=10) self.create_subscription(Image, '/image', self.image_callback, qos_profile) cv2.namedWindow("Camera Feed", cv2.WINDOW_NORMAL) cv2.resizeWindow("Camera Feed", 640, 480) self.get_logger().info("二维码检测节点已启动") def image_callback(self, msg): """接收图像并进行二维码识别""" try: cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8") except Exception as e: self.get_logger().error(f"图像转换失败: {e}") return # 图像增强(提高对比度) enhanced = cv2.convertScaleAbs(cv_image, alpha=2.0, beta=50) gray = cv2.cvtColor(enhanced, cv2.COLOR_BGR2GRAY) decoded_objs = decode(gray) if decoded_objs and not self.qr_detected: qr_data = decoded_objs[0].data.decode('utf-8') rect = decoded_objs[0].rect self.get_logger().info(f"检测到二维码: {qr_data}") self.qr_detected = True cv2.rectangle(cv_image, (rect.left, rect.top), (rect.left + rect.width, rect.top + rect.height), (0, 255, 0), 2) cv2.putText(cv_image, qr_data, (rect.left, rect.top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) self.handle_qr_command(qr_data) cv2.imshow("Camera Feed", cv_image) cv2.waitKey(1) def handle_qr_command(self, content): """根据二维码内容执行命令""" self.robot_controller.forward(speed=0.3) self.get_logger().info("执行二维码指令:前进") def main(): rclpy.init() robot_ctrl = RobotControl() if not robot_ctrl.stand_up(): print("机器人站立失败,退出程序") robot_ctrl.quit() sys.exit(1) qr_node = QRCodeNode(robot_ctrl) try: rclpy.spin(qr_node) except KeyboardInterrupt: print("程序被用户终止") finally: qr_node.destroy_node() robot_ctrl.quit() cv2.destroyAllWindows() rclpy.shutdown() if __name__ == "__main__": main() 这个是识别二维码的程序,''' This demo show the communication interface of MR813 motion control board based on Lcm. Dependency: - robot_control_cmd_lcmt.py - robot_control_response_lcmt.py ''' import lcm import sys import os import time import threading import numpy as np from threading import Thread, Lock from robot_control_cmd_lcmt import robot_control_cmd_lcmt from robot_control_response_lcmt import robot_control_response_lcmt import rclpy from rclpy.node import Node from sensor_msgs.msg import Image, CameraInfo import cv2 from cv_bridge import CvBridge from std_msgs.msg import Bool def show_image_node(args=None): rclpy.init(args=args) image_display_node = ImageDisplayNode() rclpy.spin(image_display_node) image_display_node.destroy_node() rclpy.shutdown() cv2.destroyAllWindows() def main(args=None): Ctrl = Robot_Ctrl() Ctrl.run() msg = robot_control_cmd_lcmt() try: global image_display_node image_display_node = None def start_image_node(): global image_display_node rclpy.init(args=args) image_display_node = ImageDisplayNode() rclpy.spin(image_display_node) image_display_node.destroy_node() rclpy.shutdown() cv2.destroyAllWindows() img_thread = threading.Thread(target=start_image_node) img_thread.daemon = True img_thread.start() while image_display_node is None: time.sleep(0.1) msg.mode = 12 # Recovery stand msg.gait_id = 0 msg.life_count += 1 Ctrl.Send_cmd(msg) Ctrl.Wait_finish(12, 0) msg.mode = 11 msg.gait_id = 27 msg.rpy_des[1] = 0.4 msg.pos_des[2] = 0.2 msg.vel_des = [0.3,0,0] msg.duration = 0 msg.step_height = [0.01,0.01] msg.life_count += 1 Ctrl.Send_cmd(msg) time.sleep(6) # 等待二维码或箭头识别结果 qr_result = None arrow_result = None for _ in range(100): # 最多等待10秒 qr_result = image_display_node.qr_result arrow_result = image_display_node.arrow_result if qr_result is not None or arrow_result is not None: if qr_result is not None: print(f"终端输出:识别到二维码内容为 {qr_result}") if arrow_result is not None: print(f"终端输出:识别到绿色箭头方向为 {arrow_result}") break time.sleep(0.1) # 根据二维码内容或箭头方向执行左转或右转 if qr_result == "A-1" or arrow_result == "right": print("执行右转动作") msg.mode = 11 msg.gait_id = 27 msg.rpy_des[1] = 0.4 msg.pos_des[2] = 0.2 msg.vel_des = [0,-0.2,0] # 右转 msg.duration = 0 msg.step_height = [0.01,0.01] msg.life_count += 1 Ctrl.Send_cmd(msg) time.sleep(6) elif qr_result == "A-2" or arrow_result == "left": print("执行左转动作") msg.mode = 11 msg.gait_id = 27 msg.rpy_des[1] = 0.4 msg.pos_des[2] = 0.2 msg.vel_des = [0,0.2,0] # 左转 msg.duration = 0 msg.step_height = [0.01,0.01] msg.life_count += 1 Ctrl.Send_cmd(msg) time.sleep(6) else: print("未识别到有效二维码或箭头,保持原地") msg.mode = 7 # PureDamper msg.gait_id = 0 msg.life_count += 1 Ctrl.Send_cmd(msg) Ctrl.Wait_finish(7, 0) except KeyboardInterrupt: pass Ctrl.quit() sys.exit() class Robot_Ctrl(object): def __init__(self): self.rec_thread = Thread(target=self.rec_responce) self.send_thread = Thread(target=self.send_publish) self.lc_r = lcm.LCM("udpm://239.255.76.67:7670?ttl=255") self.lc_s = lcm.LCM("udpm://239.255.76.67:7671?ttl=255") self.cmd_msg = robot_control_cmd_lcmt() self.rec_msg = robot_control_response_lcmt() self.send_lock = Lock() self.delay_cnt = 0 self.mode_ok = 0 self.gait_ok = 0 self.runing = 1 def run(self): self.lc_r.subscribe("robot_control_response", self.msg_handler) self.send_thread.start() self.rec_thread.start() def msg_handler(self, channel, data): self.rec_msg = robot_control_response_lcmt().decode(data) if(self.rec_msg.order_process_bar >= 95): self.mode_ok = self.rec_msg.mode else: self.mode_ok = 0 def rec_responce(self): while self.runing: self.lc_r.handle() time.sleep( 0.002 ) def Wait_finish(self, mode, gait_id): count = 0 while self.runing and count < 2000: #10s if self.mode_ok == mode and self.gait_ok == gait_id: return True else: time.sleep(0.005) count += 1 def send_publish(self): while self.runing: self.send_lock.acquire() if self.delay_cnt > 20: # Heartbeat signal 10HZ, It is used to maintain the heartbeat when life count is not updated self.lc_s.publish("robot_control_cmd",self.cmd_msg.encode()) self.delay_cnt = 0 self.delay_cnt += 1 self.send_lock.release() time.sleep( 0.005 ) def Send_cmd(self, msg): self.send_lock.acquire() self.delay_cnt = 50 self.cmd_msg = msg self.send_lock.release() def quit(self): self.runing = 0 self.rec_thread.join() self.send_thread.join() class ImageDisplayNode(Node): def __init__(self): super().__init__('image_display_node') self.bridge = CvBridge() self.line_centers = [] self.line_centers_lock = Lock() self.qr_result = None self.arrow_result = None self.camera_info = None qos_profile = rclpy.qos.QoSProfile( reliability=rclpy.qos.QoSReliabilityPolicy.BEST_EFFORT, history=rclpy.qos.QoSHistoryPolicy.KEEP_LAST, depth=10 ) # 原有订阅 self.subscription = self.create_subscription( Image, '/rgb_camera/image_raw', self.listener_callback, qos_profile ) self.camera_info_sub = self.create_subscription( CameraInfo, '/rgb_camera/camera_info', self.camera_info_callback, qos_profile ) # 新增订阅 /image self.image_sub = self.create_subscription( Image, '/image', self.image_callback, qos_profile ) # 新增订阅 /img_trans_signal_in self.signal_in_sub = self.create_subscription( Bool, '/img_trans_signal_in', self.signal_in_callback, qos_profile ) # 新增发布 /img_trans_signal_out self.signal_out_pub = self.create_publisher( Bool, '/img_trans_signal_out', qos_profile ) def camera_info_callback(self, msg): self.camera_info = msg self.get_logger().info("收到相机参数信息") def image_callback(self, msg): # 处理 /image topic 的图像消息 try: cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8") self.get_logger().info("收到 /image topic 图像") # 可以在此处添加处理逻辑 except Exception as e: self.get_logger().error(f"/image topic 图像转换失败: {e}") def signal_in_callback(self, msg): # 处理 /img_trans_signal_in 信号 self.get_logger().info(f"收到 /img_trans_signal_in 信号: {msg.data}") # 示例:收到信号后发布一个响应信号 out_msg = Bool() out_msg.data = True self.signal_out_pub.publish(out_msg) self.get_logger().info("已发布 /img_trans_signal_out 信号") def listener_callback(self, msg): try: cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8") except Exception as e: self.get_logger().error(f"Error converting image: {e}") return # ----------- 二维码检测 ----------- qr_detector = cv2.QRCodeDetector() data, bbox, _ = qr_detector.detectAndDecode(cv_image) if bbox is not None and data and bbox.size > 0 and len(data) > 2: bbox = bbox.astype(int) qr_area = cv2.contourArea(bbox) if qr_area > 200: cv2.polylines(cv_image, [bbox], True, (0,255,0), 2) x, y = bbox[0][0] cv2.putText(cv_image, f"QR: {data}", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2) self.get_logger().info(f"检测到二维码内容: {data}") if self.qr_result is None and (data == "A-1" or data == "A-2"): self.qr_result = data # ----------- 只识别上半区绿色箭头 ----------- h_img, w_img = cv_image.shape[:2] upper_half = cv_image[0:int(h_img*0.5), :] # 只取上半区 hsv = cv2.cvtColor(upper_half, cv2.COLOR_BGR2HSV) lower_green = np.array([40, 60, 60]) upper_green = np.array([85, 255, 255]) mask_green = cv2.inRange(hsv, lower_green, upper_green) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) mask_green = cv2.morphologyEx(mask_green, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours(mask_green, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) arrow_direction = None for cnt in contours: area = cv2.contourArea(cnt) if area < 1500: continue approx = cv2.approxPolyDP(cnt, 0.03*cv2.arcLength(cnt, True), True) if 6 <= len(approx) <= 8: x, y, w, h = cv2.boundingRect(approx) aspect_ratio = w / h if h > 0 else 0 if 0.5 < aspect_ratio < 2.5: approx_full = approx + [0, 0] cv2.drawContours(cv_image, [approx_full], 0, (0,255,0), 3) pts = approx.reshape(-1, 2) max_dist = 0 tip_idx = 0 for i in range(len(pts)): for j in range(i+1, len(pts)): dist = np.linalg.norm(pts[i] - pts[j]) if dist > max_dist: max_dist = dist tip_idx = i tip = pts[tip_idx] M = cv2.moments(cnt) if M["m00"] != 0: cx = int(M["m10"]/M["m00"]) cy = int(M["m01"]/M["m00"]) vec = tip - np.array([cx, cy]) angle = np.arctan2(vec[1], vec[0]) * 180 / np.pi if -45 <= angle <= 45: arrow_direction = "right" elif 45 < angle <= 135: arrow_direction = "down" elif angle > 135 or angle < -135: arrow_direction = "left" else: arrow_direction = "up" tip_full = (tip[0], tip[1]) cv2.putText(cv_image, f"Arrow: {arrow_direction}", tip_full, cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2) # 只保存一次箭头方向 if self.arrow_result is None and arrow_direction in ["left", "right"]: self.arrow_result = arrow_direction break # 只处理一个箭头 timestamp = int(time.time() * 1000) cv2.imwrite(f"/tmp/ros_detect_result_{timestamp}.jpg", cv_image) cv2.namedWindow("Image window", cv2.WINDOW_NORMAL) cv2.resizeWindow("Image window", 900, 900) cv2.imshow("Image window", cv_image) cv2.waitKey(1) if __name__ == '__main__': main() 这个是在检测完二维码以后控制机器狗的运动的程序
08-17
# client_monitor.py import socket import threading import subprocess import time import json import psutil import logging from datetime import datetime, timedelta import os class LinuxSystemMonitor: def __init__(self, host='0.0.0.0', port=8888): self.host = host self.port = port self.is_running = True self.realtime_monitoring = False self.log_dir = "/var/log/system_monitor" self.setup_logging() def setup_logging(self): """设置日志系统""" if not os.path.exists(self.log_dir): os.makedirs(self.log_dir) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(f"{self.log_dir}/monitor.log"), logging.StreamHandler() ] ) self.logger = logging.getLogger(__name__) def get_system_performance(self): """获取系统性能数据""" try: # CPU使用率 cpu_percent = psutil.cpu_percent(interval=1) cpu_count = psutil.cpu_count() # 内存使用情况 memory = psutil.virtual_memory() memory_info = { 'total': memory.total, 'available': memory.available, 'percent': memory.percent, 'used': memory.used } # 磁盘IO disk_io = psutil.disk_io_counters() disk_info = { 'read_bytes': disk_io.read_bytes if disk_io else 0, 'write_bytes': disk_io.write_bytes if disk_io else 0, 'read_count': disk_io.read_count if disk_io else 0, 'write_count': disk_io.write_count if disk_io else 0 } # 网络使用情况 net_io = psutil.net_io_counters() network_info = { 'bytes_sent': net_io.bytes_sent, 'bytes_recv': net_io.bytes_recv, 'packets_sent': net_io.packets_sent, 'packets_recv': net_io.packets_recv } # 磁盘使用率 disk_usage = {} for partition in psutil.disk_partitions(): try: usage = psutil.disk_usage(partition.mountpoint) disk_usage[partition.mountpoint] = { 'total': usage.total, 'used': usage.used, 'free': usage.free, 'percent': usage.percent } except PermissionError: continue # 系统负载 load_avg = os.getloadavg() if hasattr(os, 'getloadavg') else (0, 0, 0) return { 'timestamp': datetime.now().isoformat(), 'cpu': { 'percent': cpu_percent, 'count': cpu_count }, 'memory': memory_info, 'disk_io': disk_info, 'network': network_info, 'disk_usage': disk_usage, 'load_avg': load_avg, 'boot_time': psutil.boot_time() } except Exception as e: self.logger.error(f"获取系统性能数据失败: {e}") return {} def execute_system_command(self, command): """执行系统命令""" try: result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=30 ) return { 'returncode': result.returncode, 'stdout': result.stdout, 'stderr': result.stderr } except subprocess.TimeoutExpired: return {'error': '命令执行超时'} except Exception as e: return {'error': str(e)} def save_performance_log(self, data): """保存性能日志到文件""" try: date_str = datetime.now().strftime('%Y-%m-%d') log_file = f"{self.log_dir}/performance_{date_str}.log" with open(log_file, 'a') as f: f.write(json.dumps(data) + '\n') except Exception as e: self.logger.error(f"保存日志失败: {e}") def handle_client_connection(self, client_socket, address): """处理客户端连接""" self.logger.info(f"新的连接来自: {address}") try: while True: # 接收数据 data = client_socket.recv(1024).decode('utf-8') if not data: break try: command = json.loads(data) self.logger.info(f"接收到指令: {command}") response = self.process_command(command) client_socket.send(json.dumps(response).encode('utf-8')) except json.JSONDecodeError: error_response = { 'status': 'ERROR', 'message': '无效的JSON格式' } client_socket.send(json.dumps(error_response).encode('utf-8')) except Exception as e: self.logger.error(f"处理连接时出错: {e}") finally: client_socket.close() self.logger.info(f"连接关闭: {address}") def process_command(self, command): """处理指令""" cmd_type = command.get('type', '') if cmd_type == 'GET_HISTORY': return self.get_history_logs(command.get('data', {})) elif cmd_type == 'REALTIME_START': self.realtime_monitoring = True return {'status': 'SUCCESS', 'message': '实时监控已启动'} elif cmd_type == 'REALTIME_STOP': self.realtime_monitoring = False return {'status': 'SUCCESS', 'message': '实时监控已停止'} elif cmd_type == 'SYSTEM_INFO': return { 'status': 'SUCCESS', 'data': self.get_system_performance() } else: return {'status': 'ERROR', 'message': '未知指令'} def get_history_logs(self, params): """获取历史日志""" try: days = params.get('days', 1) log_files = [] for i in range(days): date_str = (datetime.now() - timedelta(days=i)).strftime('%Y-%m-%d') log_file = f"{self.log_dir}/performance_{date_str}.log" if os.path.exists(log_file): log_files.append(log_file) return {'status': 'SUCCESS', 'log_files': log_files} except Exception as e: return {'status': 'ERROR', 'message': str(e)} def start_realtime_monitoring(self, client_socket): """启动实时监控""" while self.realtime_monitoring: try: system_info = self.get_system_performance() if system_info: data = { 'type': 'REALTIME_DATA', 'data': system_info, 'timestamp': time.time() } client_socket.send(json.dumps(data).encode('utf-8')) time.sleep(5) # 5秒间隔 except Exception as e: self.logger.error(f"实时监控错误: {e}") break def start_server(self): """启动服务器""" server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: server_socket.bind((self.host, self.port)) server_socket.listen(5) self.logger.info(f"监控客户端启动在 {self.host}:{self.port}") # 启动性能监控线程 monitor_thread = threading.Thread(target=self.performance_monitor_worker) monitor_thread.daemon = True monitor_thread.start() while self.is_running: try: client_socket, address = server_socket.accept() client_thread = threading.Thread( target=self.handle_client_connection, args=(client_socket, address) ) client_thread.daemon = True client_thread.start() except Exception as e: self.logger.error(f"接受连接时出错: {e}") except Exception as e: self.logger.error(f"启动服务器失败: {e}") finally: server_socket.close() def performance_monitor_worker(self): """性能监控工作线程""" while self.is_running: try: system_info = self.get_system_performance() if system_info: self.save_performance_log(system_info) time.sleep(300) # 5分钟记录一次 except Exception as e: self.logger.error(f"性能监控错误: {e}") time.sleep(60) def main(): monitor = LinuxSystemMonitor() try: monitor.start_server() except KeyboardInterrupt: monitor.is_running = False print("监控客户端已停止") if __name__ == "__main__": main()以上为client_monitor.py的具体代码,该如何修正使得能获取系统性能,生成完整的代码
11-14
import serial import socket import struct import time import traceback import numpy as np import threading from threading import Thread from queue import Queue from servo_utils import init_system # 从独立舵机模块导入 class AcousticProcessor: def __init__(self, k210_port="/dev/ttyACM0", tcp_port=123, servo_port="/dev/ttyACM1"): # -------------------------- 基础配置 -------------------------- self.k210_port = k210_port self.tcp_port = tcp_port self.servo_port = servo_port # K210串口初始化 self.k210_ser = serial.Serial( port=k210_port, baudrate=460800, timeout=0.1, bytesize=8, parity='N', stopbits=1 ) # -------------------------- 线程与状态管理 -------------------------- self.serial_lock = threading.Lock() self.running = False self.paused = False self.buffer = b"" # 串口接收缓冲区 self.angle_queue = Queue(maxsize=10) # 声源角度队列 # -------------------------- 舵机相关配置 -------------------------- self.servo_running = False self.servo_thread = None self.uservo = None # 舵机管理器实例 self.servo_uart = None # 舵机串口实例 self.SERVO_ID = 0 # 与扫描到的舵机ID一致(需根据实际调整) # 舵机稳动优化参数 self.ANGLE_DEAD_ZONE = 2.0 # 角度死区(±2度内不动作) self.smooth_alpha = 0.7 # 强化角度平滑系数 self.smoothed_angle = None # 平滑后的角度 # 修复:初始化舵机转动记录(避免AttributeError) self.servo_turn_time = 0.0 # 最后一次舵机转动时间戳 self.last_executed_angle = None # 上次实际转动的角度 # -------------------------- 噪声滤波参数 -------------------------- self.SERVO_QUIET_DELAY = 1.5 # 舵机转动后1.5秒内视为噪声期 self.LOW_FREQ_THRESHOLD = 0.3 # 低频噪声判断阈值(能量集中) self.BASE_STRENGTH_THRESHOLD = 0.6 # 基础声源强度阈值 # -------------------------- TCP服务器配置 -------------------------- self.tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.tcp_server.bind(("0.0.0.0", tcp_port)) self.tcp_server.listen(1) self.tcp_server.settimeout(1) # 优化:设置accept超时,避免退出阻塞 self.client_conn = None self.client_addr = None self.client_connected = False self.tcp_thread = None self.data_thread = None print("声学成像系统初始化完成") def smooth_angle(self, new_angle): """强化角度平滑,减少抖动""" if self.smoothed_angle is None: self.smoothed_angle = new_angle else: self.smoothed_angle = self.smooth_alpha * new_angle + (1 - self.smooth_alpha) * self.smoothed_angle return self.smoothed_angle def send_k210_command(self, cmd): """发送指令到K210,延长超时至2秒确保响应""" with self.serial_lock: if not self.k210_ser or not self.k210_ser.is_open: try: self.k210_ser.open() except Exception as e: print(f"K210串口打开失败: {e}") return False try: cmd_msg = f"{cmd}\r\n".encode("utf-8") self.k210_ser.write(cmd_msg) time.sleep(0.5) # 等待K210准备响应 start_time = time.time() resp = b"" # 2秒超时等待响应 while time.time() - start_time < 2.0: if self.k210_ser.in_waiting > 0: resp += self.k210_ser.read(self.k210_ser.in_waiting) if b"STARTED" in resp and cmd == "START": return True elif b"STOPPED" in resp and cmd == "STOP": return True time.sleep(0.01) print(f"指令[{cmd}]超时无响应(已等待2秒)") return False except Exception as e: print(f"发送[{cmd}]指令失败: {e}") return False def tcp_serve(self): """TCP服务器逻辑(优化:超时退出,避免阻塞)""" print("等待PC端连接...") while self.running: try: # 超时后抛出socket.timeout,进入循环检查running状态 self.client_conn, self.client_addr = self.tcp_server.accept() print(f"PC端已连接:{self.client_addr}") self.client_connected = True self.client_conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 保持心跳连接 while self.running and self.client_connected: try: self.client_conn.sendall(b"\x00") # 心跳包 time.sleep(5) except: self.client_connected = False print("PC端连接断开") break except socket.timeout: continue # 超时后继续循环,检查是否需要退出 except Exception as e: if self.running: print(f"TCP异常:{e}") self.client_connected = False time.sleep(1) def send_to_client(self, data): """发送声源数据到PC端""" if not self.client_connected or not self.client_conn: return False try: # 打包格式:包头 + 12路方向+1个角度+1个强度(共14个float) + 包尾 data_pack = self.HEADER + struct.pack("<14f", *data['directions'], data['angle'], data['strength']) + self.TAIL self.client_conn.sendall(data_pack) return True except Exception as e: print(f"发送数据到PC失败: {e}") self.client_connected = False return False def process_k210_data(self): """处理K210数据(优化:串口指数退避重连+3层噪声滤波)""" reconnect_count = 0 # 重连次数计数 max_reconnect_delay = 10 # 最大重连间隔(秒) # 声学数据包格式定义 self.HEADER = b"\xAA\x55" self.TAIL = b"\x55\xAA" self.DATA_LEN = 56 # 14个float × 4字节 self.PACKET_LEN = len(self.HEADER) + self.DATA_LEN + len(self.TAIL) while self.running: if self.paused: time.sleep(0.1) continue with self.serial_lock: # 优化:串口断开后指数退避重连 if not self.k210_ser or not self.k210_ser.is_open: reconnect_delay = min(reconnect_count * 2, max_reconnect_delay) print(f"K210串口断开,{reconnect_delay}秒后重试(第{reconnect_count+1}次)...") time.sleep(reconnect_delay) try: self.k210_ser.open() print("K210串口重连成功!") reconnect_count = 0 # 重置重连计数 except Exception as e: print(f"K210串口重连失败: {e}") reconnect_count += 1 continue try: # 读取串口数据到缓冲区 if self.k210_ser.in_waiting > 0: new_data = self.k210_ser.read(self.k210_ser.in_waiting) self.buffer += new_data # 查找数据包头部 header_idx = self.buffer.find(self.HEADER) if header_idx == -1: # 缓冲区过大时清空,避免内存占用 if len(self.buffer) > 100: self.buffer = b"" time.sleep(0.01) continue # 移除包头前的无效数据 if header_idx > 0: self.buffer = self.buffer[header_idx:] # 检查数据包长度是否足够 if len(self.buffer) < self.PACKET_LEN: time.sleep(0.01) continue # 验证包尾,过滤无效包 packet_candidate = self.buffer[:self.PACKET_LEN] if packet_candidate[-len(self.TAIL):] != self.TAIL: self.buffer = self.buffer[len(self.HEADER):] # 跳过无效包头 continue # 解包有效数据 data_start = len(self.HEADER) data_end = -len(self.TAIL) data_part = packet_candidate[data_start:data_end] try: unpacked_data = struct.unpack("<14f", data_part) directions = unpacked_data[:12] # 12路麦克风方向能量 angle = unpacked_data[12] # 声源角度 strength = unpacked_data[13] # 声源强度 # -------------------------- 3层噪声滤波 -------------------------- current_time = time.time() # 1. 动态强度阈值:舵机转动后阈值翻倍 if current_time - self.servo_turn_time < self.SERVO_QUIET_DELAY: strength_threshold = self.BASE_STRENGTH_THRESHOLD * 2.0 else: strength_threshold = self.BASE_STRENGTH_THRESHOLD # 2. 频率滤波:通过能量分布判断低频噪声(舵机噪声) directions_std = np.std(directions) # 能量分布标准差(小→集中→低频) is_low_freq_noise = directions_std < self.LOW_FREQ_THRESHOLD # 3. 过滤无效数据(强度不足或低频噪声) if strength < strength_threshold or is_low_freq_noise: print(f"过滤无效声源 - 强度:{strength:.2f}(阈值:{strength_threshold:.2f}),低频噪声:{is_low_freq_noise}") self.buffer = self.buffer[self.PACKET_LEN:] continue # 有效数据:放入角度队列+发送到PC print(f"接收有效数据 - 角度: {angle:.1f}°, 强度: {strength:.2f}") if self.angle_queue.full(): self.angle_queue.get() # 队列满时移除 oldest self.angle_queue.put(angle) if self.client_connected: self.send_to_client({ "directions": directions, "angle": angle, "strength": strength }) except struct.error as e: print(f"数据解包错误: {e}") finally: # 移除此数据包,处理后续数据 self.buffer = self.buffer[self.PACKET_LEN:] except Exception as e: print(f"数据处理错误:{e}") time.sleep(0.1) def track_sound_source(self): """舵机追踪逻辑(修复:angle_diff未定义的NameError + 稳动优化)""" while self.servo_running: if not self.angle_queue.empty(): raw_angle = self.angle_queue.get() target_angle = self.smooth_angle(raw_angle) # 角度范围转换(-135° ~ 135°) if target_angle > 180: target_angle = target_angle - 360 target_angle = max(-135, min(135, target_angle)) # 修复:提前定义angle_diff(兼容首次运行,避免NameError) angle_diff = float('inf') # 首次运行设为无穷大(不触发死区) if self.last_executed_angle is not None: angle_diff = abs(target_angle - self.last_executed_angle) # 死区判断:角度变化小于阈值则不动作 if angle_diff < self.ANGLE_DEAD_ZONE: time.sleep(0.1) continue # 驱动舵机转动(平缓参数) try: self.uservo.set_servo_angle( servo_id=self.SERVO_ID, angle=target_angle, is_mturn=False, velocity=40, # 降低转速(原60dps),减少惯性 t_acc=300, # 延长加速时间(原100ms) t_dec=300 # 延长减速时间(原100ms) ) # 更新舵机状态记录 self.servo_turn_time = time.time() self.last_executed_angle = target_angle # 统一打印逻辑(兼容首次运行) if self.last_executed_angle is None: print(f"舵机初始转向:{target_angle:.1f}°") else: print(f"舵机已转向:{target_angle:.1f}°(变化:{angle_diff:.1f}°)") except Exception as e: print(f"舵机控制失败:{e}") time.sleep(0.1) def start(self): """启动系统:初始化舵机+启动线程+交互指令""" self.running = True self.paused = False # 初始化舵机(从servo_utils导入,接收3个返回值) try: self.uservo, self.servo_uart, _ = init_system(self.servo_port) except TypeError: self.uservo, self.servo_uart, _ = init_system() # 启动舵机追踪线程(若舵机初始化成功) if not self.uservo: print("❌ 舵机初始化失败,仅启动声源定位功能") else: self.servo_running = True self.servo_thread = Thread(target=self.track_sound_source, daemon=True) self.servo_thread.start() print("✅ 舵机追踪线程已启动") # 启动TCP服务器线程和K210数据处理线程 self.tcp_thread = Thread(target=self.tcp_serve, daemon=True) self.tcp_thread.start() self.data_thread = Thread(target=self.process_k210_data, daemon=True) self.data_thread.start() # 显示控制指令 time.sleep(1) print("\n===== 控制指令 =====") print("start - 启动K210数据采集") print("stop - 停止K210数据采集") print("pause - 暂停处理") print("resume- 恢复处理") print("exit - 退出程序") print("====================") # 交互指令循环 try: while self.running: cmd = input("> ").strip().lower() if cmd == "start": if self.paused: print("请先输入resume恢复程序") else: if self.send_k210_command("START"): print("K210已启动,接收数据中...") else: print("启动失败,建议检查K210接线/程序") elif cmd == "stop": if self.send_k210_command("STOP"): print("K210已停止") else: print("停止失败") elif cmd == "pause": self.paused = True print("程序已暂停") elif cmd == "resume": self.paused = False print("程序已恢复") elif cmd == "exit": self.stop() break else: print("未知指令,请输入start/stop/pause/resume/exit") except KeyboardInterrupt: print("\n程序被用户中断") self.stop() except Exception as e: print(f"主循环异常: {e}") self.stop() def stop(self): """停止系统:释放资源+终止线程""" print("\n正在停止程序...") self.running = False self.servo_running = False # 停止K210数据采集 try: self.send_k210_command("STOP") except: pass # 等待线程终止(超时2秒) threads_to_join = [] if self.servo_thread and self.servo_thread.is_alive(): threads_to_join.append(self.servo_thread) if self.tcp_thread and self.tcp_thread.is_alive(): threads_to_join.append(self.tcp_thread) if self.data_thread and self.data_thread.is_alive(): threads_to_join.append(self.data_thread) for thread in threads_to_join: thread.join(timeout=2.0) # 关闭网络连接 if self.client_conn: try: self.client_conn.close() except: pass try: self.tcp_server.close() except: pass # 关闭串口 if self.k210_ser and self.k210_ser.is_open: self.k210_ser.close() if self.servo_uart and self.servo_uart.is_open: self.servo_uart.close() print("程序已完全停止") if __name__ == "__main__": # 直接运行时使用默认端口 processor = AcousticProcessor( k210_port="/dev/ttyACM0", servo_port="/dev/ttyACM1" ) processor.start() # fin_servoACM1.py(独立舵机控制入口,无循环依赖) import time from servo_utils import init_system, safe_query_status, safe_set_damping # 从独立模块导入 # 指令解析函数(原逻辑保持不变) def parse_single_cmd(parts, online_ids, mode): if mode == "angle" and len(parts) != 4: return None, None, None, "❌ 单舵机单圈指令:angle ID 角度 转速(例:angle 0 90 60)" if mode == "mturn" and len(parts) != 4: return None, None, None, "❌ 单舵机多圈指令:mturn ID 角度 转速(例:mturn 1 720 60)" if mode == "damping" and len(parts) != 3: return None, None, None, "❌ 单舵机阻尼指令:damping ID 功率(例:damping 0 500)" try: servo_id = int(parts[1]) if servo_id not in online_ids: return None, None, None, f"❌ ID={servo_id}未在线,在线ID:{online_ids}" if mode in ["angle", "mturn"]: target = float(parts[2]) velocity = float(parts[3]) return servo_id, target, velocity, None if mode == "damping": power = int(parts[2]) return servo_id, power, None, None except ValueError: return None, None, None, "❌ 输入必须为数字!" def parse_sync_cmd(parts, online_ids): if len(parts) != 5 or parts[1] != "angle": return None, None, "❌ 同步指令格式:sync angle 角度1 角度2 转速(例:sync angle 90 -90 60)" try: angle0 = float(parts[2]) angle1 = float(parts[3]) velocity = float(parts[4]) return [angle0, angle1], velocity, None except ValueError: return None, None, "❌ 输入必须为数字!" # 交互式控制逻辑(原逻辑保持不变) def control_interactive(uservo, online_ids): print("\n===== 双舵机控制终端 =====") print("📌 单舵机单独控制:") print(" angle ID 角度 转速 → 单圈(例:angle 0 90 60)") print(" mturn ID 角度 转速 → 多圈(例:mturn 1 720 60)") print(" damping ID 功率 → 阻尼(例:damping 0 500)") print(" status ID → 查状态(例:status 1)") print("📌 双舵机同步控制:") print(" sync angle 角度1 角度2 转速 → 同步单圈(例:sync angle 90 -90 60)") print("📌 辅助指令:list / exit") print("======================================") status_info = { 0: "执行指令中", 1: "指令错误", 2: "堵转错误", 3: "电压过高", 4: "电压过低", 5: "电流错误", 6: "功率错误", 7: "温度错误" } while True: cmd = input("\n请输入指令 > ").strip().lower() if cmd == "exit": break elif cmd == "list": print(f"✅ 在线舵机ID:{online_ids}") elif cmd.startswith("status"): parts = cmd.split() if len(parts) != 2: print("❌ 格式:status ID(例:status 0)") continue try: servo_id = int(parts[1]) info = safe_query_status(uservo, servo_id) if info: print(f"📊 ID={servo_id} 状态:角度{info['angle']:.1f}° | 电压{info['voltage']:.2f}V | 温度{info['temp_c']:.1f}℃") else: print(f"❌ ID={servo_id} 状态查询失败") except Exception as e: print(f"❌ 错误:{e}") # 其他指令(angle/mturn/damping/sync)逻辑保持不变,省略重复代码... # 主程序入口(保持不变) if __name__ == "__main__": print("===== 双舵机控制程序启动 =====") uservo, uart, online_ids = init_system() if uservo and uart and online_ids: try: control_interactive(uservo, online_ids) finally: uart.close() print("\n程序已退出,串口已关闭") # RK3568_f.py - 声源定位与舵机追踪主程序(基于公共模块) from acoustic_common import AcousticProcessor # 导入公共处理类 if __name__ == "__main__": # 配置硬件端口(根据实际接线修改) K210_PORT = "/dev/ttyACM0" # K210麦克风阵列连接的串口 TCP_PORT = 123 # 与PC通信的TCP端口 SERVO_PORT = "/dev/ttyACM1" # 舵机连接的串口 # 初始化系统并启动 processor = AcousticProcessor( k210_port=K210_PORT, tcp_port=TCP_PORT, servo_port=SERVO_PORT ) processor.start()
11-08
# -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np import time from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QGridLayout, QSpinBox ) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\BasicDemo") import ctypes from ctypes import cast, POINTER from datetime import datetime import logging import socket import serial import skimage import platform from CameraConstants import * import threading import time import sys class ManagedThread(threading.Thread): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._stop_event = threading.Event() # 设置为非守护线程 self.daemon = False def stop(self): """安全停止线程""" self._stop_event.set() def should_stop(self): """检查是否应该停止""" return self._stop_event.is_set() def worker(): """线程工作函数""" try: while not threading.current_thread().should_stop(): # 模拟工作 time.sleep(1) # 安全输出 sys.stdout.write("Working...\n") except Exception as e: # 避免在关闭时使用stderr pass def main(): # 创建并启动线程 threads = [] for _ in range(3): t = ManagedThread(target=worker) t.start() threads.append(t) try: # 主程序逻辑 time.sleep(5) finally: # 安全停止所有线程 for t in threads: t.stop() for t in threads: t.join(timeout=2.0) # 设置超时避免无限等待 # 确保所有输出完成 sys.stdout.flush() sys.stderr.flush() # 在导入部分添加 from CameraParams_header import ( MV_GIGE_DEVICE, MV_USB_DEVICE, MV_GENTL_CAMERALINK_DEVICE, MV_GENTL_CXP_DEVICE, MV_GENTL_XOF_DEVICE ) # ===== 路径修复 ===== def fix_sdk_path(): """修复海康SDK的加载路径""" if getattr(sys, 'frozen', False): # 打包模式 base_path = sys._MEIPASS mvimport_path = os.path.join(base_path, "MvImport") if mvimport_path not in sys.path: sys.path.insert(0, mvimport_path) if sys.platform == 'win32': os.environ['PATH'] = base_path + os.pathsep + os.environ['PATH'] try: ctypes.WinDLL(os.path.join(base_path, "MvCamCtrldll.dll")) except OSError as e: print(f"核心DLL加载失败: {e}") sys.exit(1) else: # 开发模式 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # 立即执行路径修复 fix_sdk_path() # ===== 正确导入SDK模块 ===== try: from MvImport.MvCameraControl_class import MvCamera print("成功导入MvCamera类") from CameraParams_header import * from MvErrorDefine_const import * except ImportError as e: print(f"SDK导入失败: {e}") sys.exit(1) # 配置日志系统 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动") # 全局变量 current_sample_path = "" detection_history = [] isGrabbing = False isOpen = False obj_cam_operation = None frame_monitor_thread = None sensor_monitor_thread = None sensor_controller = None MV_OK = 0 MV_E_CALLORDER = -2147483647 # ==================== 传感器通讯模块 ==================== class SensorController: def __init__(self): self.connected = False self.running = False self.connection = None def connect(self, config): try: if config['type'] == 'serial': self.connection = serial.Serial( port=config['port'], baudrate=config['baudrate'], timeout=config.get('timeout', 1.0) ) else: self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connection.connect((config['ip'], config['port'])) self.connection.settimeout(config.get('timeout', 1.0)) self.connected = True self.running = True logging.info(f"传感器连接成功: {config}") return True except Exception as e: logging.error(f"传感器连接失败: {str(e)}") return False def disconnect(self): if self.connection: try: self.connection.close() except: pass self.connection = None self.connected = False self.running = False logging.info("传感器已断开") def read_data(self): if not self.connected: return None return { 'tension': np.random.uniform(10.0, 20.0), 'speed': np.random.uniform(1.0, 5.0), 'temperature': np.random.uniform(20.0, 30.0), 'humidity': np.random.uniform(40.0, 60.0) } def wait_for_material(self, delay_seconds=0): if not self.connected: logging.warning("未连接传感器,跳过等待") return False logging.info(f"等待布料到达,延迟 {delay_seconds} 秒") start_time = time.time() while time.time() - start_time < delay_seconds: QThread.msleep(100) if not self.running: return False logging.info("布料已到位,准备拍摄") return True class SensorMonitorThread(QThread): data_updated = pyqtSignal(dict) def __init__(self, sensor_controller): super().__init__() self.sensor_controller = sensor_controller self.running = True def run(self): while self.running: if self.sensor_controller and self.sensor_controller.connected: try: data = self.sensor_controller.read_data() if data: self.data_updated.emit(data) except Exception as e: logging.error(f"传感器数据读取错误: {str(e)}") QThread.msleep(500) def stop(self): self.running = False self.wait(2000) def wait_for_material(self, delay_seconds): return self.sensor_controller.wait_for_material(delay_seconds) # ==================== 相机帧监控线程 ==================== class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) # 用于发送状态消息的信号 def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True self.frame_count = 0 self.last_time = time.time() def run(self): """监控相机帧状态的主循环""" while self.running: try: if self.cam_operation and self.cam_operation.is_grabbing: # 获取帧统计信息 frame_info = self.get_frame_info() if frame_info: fps = frame_info.get('fps', 0) dropped = frame_info.get('dropped', 0) status = f"FPS: {fps:.1f} | 丢帧: {dropped}" self.frame_status.emit(status) else: self.frame_status.emit("取流中...") else: self.frame_status.emit("相机未取流") except Exception as e: self.frame_status.emit(f"监控错误: {str(e)}") # 每500ms检查一次 QThread.msleep(500) def stop(self): """停止监控线程""" self.running = False self.wait(1000) # 等待线程结束 def calculate_fps(self): """计算当前帧率""" current_time = time.time() elapsed = current_time - self.last_time if elapsed > 0: fps = self.frame_count / elapsed self.frame_count = 0 self.last_time = current_time return fps return 0 def get_frame_info(self): """获取帧信息""" try: # 更新帧计数 self.frame_count += 1 # 返回帧信息 return { 'fps': self.calculate_fps(), 'dropped': 0 # 实际应用中需要从相机获取真实丢帧数 } except Exception as e: logging.error(f"获取帧信息失败: {str(e)}") return None # ==================== 优化后的检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05, sensor_data=None): if sensor_data: speed_factor = min(1.0 + sensor_data['speed'] * 0.1, 1.5) env_factor = 1.0 + abs(sensor_data['temperature'] - 25) * 0.01 + abs(sensor_data['humidity'] - 50) * 0.005 adjusted_threshold = threshold * speed_factor * env_factor logging.info(f"根据传感器数据调整阈值: 原始={threshold:.4f}, 调整后={adjusted_threshold:.4f}") else: adjusted_threshold = threshold try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) try: orb = cv2.ORB_create(nfeatures=200) keypoints1, descriptors1 = orb.detectAndCompute(sample_image, None) keypoints2, descriptors2 = orb.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning("无法提取特征描述符,跳过配准") aligned_sample = sample_image else: bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) matches = sorted(matches, key=lambda x: x.distance) if len(matches) > 10: src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info("图像配准成功,使用配准后样本") else: aligned_sample = sample_image logging.warning("无法计算单应性矩阵,使用原始样本") else: aligned_sample = sample_image logging.warning("特征点匹配不足,跳过图像配准") except Exception as e: logging.error(f"图像配准失败: {str(e)}") aligned_sample = sample_image try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None try: from skimage.metrics import structural_similarity as compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True, data_range=255 ) except ImportError: from skimage.measure import compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) except Exception as e: logging.error(f"SSIM计算失败: {str(e)}") abs_diff = cv2.absdiff(aligned_sample, test_image_gray) ssim_diff = abs_diff.astype(np.float32) / 255.0 ssim_score = 1.0 - np.mean(ssim_diff) ssim_diff = (1 - ssim_diff) * 255 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) _, thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels is_qualified = diff_ratio <= adjusted_threshold marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: y, x = prop.centroid cv2.putText(marked_image, f"Defect", (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) return is_qualified, diff_ratio, marked_image # ==================== 传感器控制的质量检测流程 ==================== def sensor_controlled_check(): global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller logging.info("质量检测启动") sensor_data = None if sensor_controller and sensor_controller.connected: sensor_data = sensor_controller.read_data() if not sensor_data: QMessageBox.warning(mainWindow, "传感器警告", "无法读取传感器数据,将使用默认参数", QMessageBox.Ok) else: logging.info("未连接传感器,使用默认参数检测") check_print_with_sensor(sensor_data) def check_print_with_sensor(sensor_data=None): global isGrabbing, obj_cam_operation, current_sample_path, detection_history logging.info("检测印花质量按钮按下") if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return if not obj_cam_operation: QMessageBox.warning(mainWindow, "错误", "相机未正确初始化!", QMessageBox.Ok) return if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: test_image = obj_cam_operation.get_current_frame() progress.setValue(30) if test_image is None: QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return diff_threshold = mainWindow.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(50) is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, test_image, threshold=diff_threshold, sensor_data=sensor_data ) progress.setValue(70) if is_qualified is None: QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n差异占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'sensor_data': sensor_data if sensor_data else {} } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) finally: progress.close() def update_diff_display(diff_ratio, is_qualified): mainWindow.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") if is_qualified: mainWindow.lblDiffStatus.setText("状态: 合格") mainWindow.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.lblDiffStatus.setText("状态: 不合格") mainWindow.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") def update_diff_threshold(value): mainWindow.lblDiffValue.setText(f"{value}%") def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有可用帧 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() format_mapping = {".bmp": "bmp", ".png": "png", ".jpg": "jpg", ".jpeg": "jpg"} save_format = format_mapping.get(file_extension) if not save_format: QMessageBox.warning(mainWindow, "错误", "不支持的文件格式!", QMessageBox.Ok) return directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return try: ret = obj_cam_operation.save_image(file_path, save_format) if ret != MV_OK: strError = f"保存样本图像失败: {hex(ret)}" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) except Exception as e: QMessageBox.critical(mainWindow, "异常错误", f"保存图像时发生错误: {str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: img_data = np.fromfile(current_sample_path, dtype=np.uint8) sample_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if sample_img is None: raise Exception("无法加载图像") cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def update_sample_display(): global current_sample_path if current_sample_path: mainWindow.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") mainWindow.lblSamplePath.setToolTip(current_sample_path) mainWindow.bnPreviewSample.setEnabled(True) else: mainWindow.lblSamplePath.setText("当前样本: 未设置样本") mainWindow.bnPreviewSample.setEnabled(False) def update_history_display(): global detection_history mainWindow.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" mainWindow.cbHistory.addItem(f"[极客{timestamp}] {status} - 差异: {ratio}") def TxtWrapBy(start_str, end, all): start = all.find(start_str) if start >= 0: start += len(start_str) end = all.find(end, start) if end >= 0: return all[start:end].strip() def ToHexStr(num): if not isinstance(num, int): try: num = int(num) except: return f"<非整数:{type(num)}>" chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'} hexStr = "" if num < 0: num = num + 2 ** 32 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return "0x" + hexStr def decoding_char(c_ubyte_value): c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p) try: decode_str = c_char_p_value.value.decode('gbk') except UnicodeDecodeError: decode_str = str(c_char_p_value.value) return decode_str def enum_devices(): global deviceList, obj_cam_operation # 使用正确的常量方式 n_layer_type = ( MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE ) # 正确创建设备列表 deviceList = MV_CC_DEVICE_INFO_LIST() # 调用枚举函数 ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != 0: strError = "Enum devices fail! ret = :" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, "Info", "Find no device", QMessageBox.Ok) return ret print(f"Find {deviceList.nDeviceNum} devices!") # 处理设备信息 devList = [] for i in range(0, deviceList.nDeviceNum): # 获取设备信息指针 device_info = deviceList.pDeviceInfo[i] # 转换为正确的结构体类型 mvcc_dev_info = cast(device_info, POINTER(MV_CC_DEVICE_INFO)).contents # 根据设备类型提取信息 - 关键修复:使用正确的结构体名称 if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE: # 处理GigE设备信息 - 使用正确的stGigEInfo try: # 尝试访问新版SDK的结构体 st_gige_info = mvcc_dev_info.SpecialInfo.stGigEInfo except AttributeError: try: # 尝试访问旧版SDK的结构体 st_gige_info = mvcc_dev_info.SpecialInfo.stGEInfo except AttributeError: # 如果两种结构体都不存在,使用默认值 devList.append(f"[{i}]GigE: Unknown Device") continue user_defined_name = decoding_char(st_gige_info.chUserDefinedName) model_name = decoding_char(st_gige_info.chModelName) nip1 = ((st_gige_info.nCurrentIp & 0xff000000) >> 24) nip2 = ((st_gige_info.nCurrentIp & 0x00ff0000) >> 16) nip3 = ((st_gige_info.nCurrentIp & 0x0000ff00) >> 8) nip4 = (st_gige_info.nCurrentIp & 0x000000ff) devList.append(f"[{i}]GigE: {user_defined_name} {model_name}({nip1}.{nip2}.{nip3}.{nip4})") elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: # 处理USB设备信息 try: st_usb_info = mvcc_dev_info.SpecialInfo.stUsb3VInfo except AttributeError: try: st_usb_info = mvcc_dev_info.SpecialInfo.stUsbInfo except AttributeError: devList.append(f"[{i}]USB: Unknown Device") continue user_defined_name = decoding_char(st_usb_info.chUserDefinedName) model_name = decoding_char(st_usb_info.chModelName) strSerialNumber = "" for per in st_usb_info.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) devList.append(f"[{i}]USB: {user_defined_name} {model_name}({strSerialNumber})") else: # 处理其他类型设备 devList.append(f"[{i}]Unknown Device Type: {mvcc_dev_info.nTLayerType}") # 更新UI mainWindow.ComboDevices.clear() mainWindow.ComboDevices.addItems(devList) mainWindow.ComboDevices.setCurrentIndex(0) # 更新状态栏 mainWindow.statusBar().showMessage(f"找到 {deviceList.nDeviceNum} 个设备", 3000) return MV_OK # ===== 关键改进:相机操作函数 ===== def open_device(): global deviceList, nSelCamIndex, obj_cam_operation, isOpen, frame_monitor_thread, mainWindow if isOpen: QMessageBox.warning(mainWindow, "Error", '相机已打开!', QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = mainWindow.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, "Error", '请选择相机!', QMessageBox.Ok) return MV_E_CALLORDER # 创建相机控制对象 cam = MvCamera() # 初始化相机操作对象 - 确保传入有效的相机对象 obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = "打开设备失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 创建并启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(mainWindow.statusBar().showMessage) frame_monitor_thread.start() def start_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def stop_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return # 关键改进:添加连接状态检查 if not hasattr(obj_cam_operation, 'connected') or not obj_cam_operation.connected: QMessageBox.warning(mainWindow, "Error", "相机未连接", QMessageBox.Ok) return ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = "停止取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() def close_device(): global isOpen, isGrabbing, obj_cam_operation, frame_monitor_thread if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) if isOpen and obj_cam_operation: # 关键改进:确保相机对象存在 if hasattr(obj_cam_operation, 'cam') and obj_cam_operation.cam: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() def set_continue_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = "设置连续模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(True) mainWindow.radioTriggerMode.setChecked(False) mainWindow.bnSoftwareTrigger.setEnabled(False) def set_software_trigger_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = "设置触发模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(False) mainWindow.radioTriggerMode.setChecked(True) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing) def trigger_once(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.trigger_once() if ret != 0: strError = "软触发失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 尝试捕获当前帧 frame = obj_cam_operation.capture_frame() if frame is None: QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 确保图像有效 if frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0: QMessageBox.warning(mainWindow, "无效图像", "捕获的图像无效,请检查相机设置!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return # 确保文件扩展名正确 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() # 创建目录(如果不存在) directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return # 保存图像 try: # 使用OpenCV保存图像 if not cv2.imwrite(file_path, frame): raise Exception("OpenCV保存失败") # 更新状态 current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) # 显示成功消息 QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) # 可选:自动预览样本 preview_sample() except Exception as e: logging.error(f"保存图像失败: {str(e)}") QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 直接使用OpenCV加载图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: raise Exception("无法加载图像") # 显示图像 cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def start_grabbing(): global obj_cam_operation, isGrabbing ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def is_float(str): try: float(str) return True except ValueError: return False def get_param(): try: ret = obj_cam_operation.get_parameters() if ret != MV_OK: strError = "获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: mainWindow.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time)) mainWindow.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain)) mainWindow.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate)) except Exception as e: error_msg = f"获取参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def set_param(): frame_rate = mainWindow.edtFrameRate.text() exposure = mainWindow.edtExposureTime.text() gain = mainWindow.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = "设置参数失败: 参数必须是有效的浮点数" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) return MV_E_PARAMETER try: ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = "设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def enable_controls(): global isGrabbing, isOpen mainWindow.groupGrab.setEnabled(isOpen) mainWindow.paramgroup.setEnabled(isOpen) mainWindow.bnOpen.setEnabled(not isOpen) mainWindow.bnClose.setEnabled(isOpen) mainWindow.bnStart.setEnabled(isOpen and (not isGrabbing)) mainWindow.bnStop.setEnabled(isOpen and isGrabbing) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing and mainWindow.radioTriggerMode.isChecked()) mainWindow.bnSaveImage.setEnabled(isOpen and isGrabbing) mainWindow.bnCheckPrint.setEnabled(isOpen and isGrabbing) mainWindow.bnSaveSample.setEnabled(isOpen and isGrabbing) mainWindow.bnPreviewSample.setEnabled(bool(current_sample_path)) def update_sensor_display(data): if not data: return text = (f"张力: {data['tension']:.2f}N | " f"速度: {data['speed']:.2f}m/s | " f"温度: {data['temperature']:.1f}°C | " f"湿度: {data['humidity']:.1f}%") mainWindow.lblSensorData.setText(text) def connect_sensor(): global sensor_monitor_thread, sensor_controller sensor_type = mainWindow.cbSensorType.currentText() if sensor_controller is None: sensor_controller = SensorController() if sensor_type == "串口": config = { 'type': 'serial', 'port': mainWindow.cbComPort.currentText(), 'baudrate': int(mainWindow.cbBaudrate.currentText()), 'timeout': 1.0 } else: config = { 'type': 'ethernet', 'ip': mainWindow.edtIP.text(), 'port': int(mainWindow.edtPort.text()), 'timeout': 1.0 } if sensor_controller.connect(config): mainWindow.bnConnectSensor.setEnabled(False) mainWindow.bnDisconnectSensor.setEnabled(True) sensor_monitor_thread = SensorMonitorThread(sensor_controller) sensor_monitor_thread.data_updated.connect(update_sensor_display) sensor_monitor_thread.start() def disconnect_sensor(): global sensor_monitor_thread if sensor_controller: sensor_controller.disconnect() mainWindow.bnConnectSensor.setEnabled(True) mainWindow.bnDisconnectSensor.setEnabled(False) if sensor_monitor_thread and sensor_monitor_thread.isRunning(): sensor_monitor_thread.stop() sensor_monitor_thread.wait(2000) sensor_monitor_thread = None mainWindow.lblSensorData.setText("传感器数据: 未连接") def update_sensor_ui(index): mainWindow.serialGroup.setVisible(index == 0) mainWindow.ethernetGroup.setVisible(index == 1) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("布料印花检测系统") self.resize(1200, 800) central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 设备枚举区域 device_layout = QHBoxLayout() self.ComboDevices = QComboBox() self.bnEnum = QPushButton("枚举设备") self.bnOpen = QPushButton("打开设备") self.bnClose = QPushButton("关闭设备") device_layout.addWidget(self.ComboDevices) device_layout.addWidget(self.bnEnum) device_layout.addWidget(self.bnOpen) device_layout.addWidget(self.bnClose) main_layout.addLayout(device_layout) # 取流控制组 self.groupGrab = QGroupBox("取流控制") grab_layout = QHBoxLayout(self.groupGrab) self.bnStart = QPushButton("开始取流") self.bnStop = QPushButton("停止取流") self.radioContinueMode = QRadioButton("连续模式") self.radioTriggerMode = QRadioButton("触发模式") self.bnSoftwareTrigger = QPushButton("软触发") grab_layout.addWidget(self.bnStart) grab_layout.addWidget(self.bnStop) grab_layout.addWidget(self.radioContinueMode) grab_layout.addWidget(self.radioTriggerMode) grab_layout.addWidget(self.bnSoftwareTrigger) main_layout.addWidget(self.groupGrab) # 参数设置组 self.paramgroup = QGroupBox("相机参数") param_layout = QGridLayout(self.paramgroup) self.edtExposureTime = QLineEdit() self.edtGain = QLineEdit() self.edtFrameRate = QLineEdit() self.bnGetParam = QPushButton("获取参数") self.bnSetParam = QPushButton("设置参数") self.bnSaveImage = QPushButton("保存图像") param_layout.addWidget(QLabel("曝光时间:"), 0, 0) param_layout.addWidget(self.edtExposureTime, 0, 1) param_layout.addWidget(self.bnGetParam, 0, 2) param_layout.addWidget(QLabel("增益:"), 1, 0) param_layout.addWidget(self.edtGain, 1, 1) param_layout.addWidget(self.bnSetParam, 1, 2) param_layout.addWidget(QLabel("帧率:"), 2, 0) param_layout.addWidget(self.edtFrameRate, 2, 1) param_layout.addWidget(self.bnSaveImage, 2, 2) main_layout.addWidget(self.paramgroup) # 图像显示区域 self.widgetDisplay = QLabel() self.widgetDisplay.setMinimumSize(640, 480) self.widgetDisplay.setStyleSheet("background-color: black;") self.widgetDisplay.setAlignment(Qt.AlignCenter) self.widgetDisplay.setText("相机预览区域") main_layout.addWidget(self.widgetDisplay, 1) # 状态栏 #self.statusBar = QStatusBar() #self.setStatusBar(self.statusBar) # 创建自定义UI组件 self.setup_custom_ui() def setup_custom_ui(self): # 工具栏 toolbar = self.addToolBar("检测工具") self.bnCheckPrint = QPushButton("检测印花质量") self.bnSaveSample = QPushButton("保存标准样本") self.bnPreviewSample = QPushButton("预览样本") self.cbHistory = QComboBox() self.cbHistory.setMinimumWidth(300) toolbar.addWidget(self.bnCheckPrint) toolbar.addWidget(self.bnSaveSample) toolbar.addWidget(self.bnPreviewSample) toolbar.addWidget(QLabel("历史记录:")) toolbar.addWidget(self.cbHistory) # 状态栏样本路径 self.lblSamplePath = QLabel("当前样本: 未设置样本") self.statusBar().addPermanentWidget(self.lblSamplePath) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 差异度调整组 diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) self.lblDiffThreshold = QLabel("差异度阈值 (0-100%):") self.sliderDiffThreshold = QSlider(Qt.Horizontal) self.sliderDiffThreshold.setRange(0, 100) self.sliderDiffThreshold.setValue(5) self.lblDiffValue = QLabel("5%") self.lblCurrentDiff = QLabel("当前差异度: -") self.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") self.lblDiffStatus = QLabel("状态: 未检测") self.lblDiffStatus.setStyleSheet("font-size: 12px;") diff_layout.addWidget(self.lblDiffThreshold) diff_layout.addWidget(self.sliderDiffThreshold) diff_layout.addWidget(self.lblDiffValue) diff_layout.addWidget(self.lblCurrentDiff) diff_layout.addWidget(self.lblDiffStatus) right_layout.addWidget(diff_group) # 传感器控制面板 sensor_panel = QGroupBox("传感器控制") sensor_layout = QVBoxLayout(sensor_panel) sensor_type_layout = QHBoxLayout() self.lblSensorType = QLabel("传感器类型:") self.cbSensorType = QComboBox() self.cbSensorType.addItems(["串口", "以太网"]) sensor_type_layout.addWidget(self.lblSensorType) sensor_type_layout.addWidget(self.cbSensorType) sensor_layout.addLayout(sensor_type_layout) # 串口参数 self.serialGroup = QGroupBox("串口参数") serial_layout = QVBoxLayout(self.serialGroup) self.lblComPort = QLabel("端口:") self.cbComPort = QComboBox() if platform.system() == 'Windows': ports = [f"COM{i}" for i in range(1, 21)] else: ports = [f"/dev/ttyS{i}" for i in range(0, 4)] + [f"/dev/ttyUSB{i}" for i in range(0, 4)] self.cbComPort.addItems(ports) self.lblBaudrate = QLabel("波特率:") self.cbBaudrate = QComboBox() self.cbBaudrate.addItems(["96000", "19200", "38400", "57600", "115200"]) self.cbBaudrate.setCurrentText("115200") serial_layout.addWidget(self.lblComPort) serial_layout.addWidget(self.cbComPort) serial_layout.addWidget(self.lblBaudrate) serial_layout.addWidget(self.cbBaudrate) sensor_layout.addWidget(self.serialGroup) # 以太网参数 self.ethernetGroup = QGroupBox("以太网参数") ethernet_layout = QVBoxLayout(self.ethernetGroup) self.lblIP = QLabel("IP地址:") self.edtIP = QLineEdit("192.168.1.100") self.lblPort = QLabel("端口:") self.edtPort = QLineEdit("502") ethernet_layout.addWidget(self.lblIP) ethernet_layout.addWidget(self.edtIP) ethernet_layout.addWidget(self.lblPort) ethernet_layout.addWidget(self.edtPort) sensor_layout.addWidget(self.ethernetGroup) # 连接按钮 self.bnConnectSensor = QPushButton("连接传感器") self.bnDisconnectSensor = QPushButton("断开传感器") self.bnDisconnectSensor.setEnabled(False) sensor_layout.addWidget(self.bnConnectSensor) sensor_layout.addWidget(self.bnDisconnectSensor) # 延迟设置 delay_layout = QHBoxLayout() self.lblDelay = QLabel("触发延迟(秒):") self.spinDelay = QSpinBox() self.spinDelay.setRange(0, 60) self.spinDelay.setValue(0) self.spinDelay.setToolTip("传感器检测到布料后延迟拍摄的时间") delay_layout.addWidget(self.lblDelay) delay_layout.addWidget(self.spinDelay) sensor_layout.addLayout(delay_layout) # 传感器数据 self.lblSensorData = QLabel("传感器数据: 未连接") self.lblSensorData.setStyleSheet("font-size: 10pt;") sensor_layout.addWidget(self.lblSensorData) right_layout.addWidget(sensor_panel) right_layout.addStretch(1) # 停靠窗口 dock = QDockWidget("检测控制面板", self) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.addDockWidget(Qt.RightDockWidgetArea, dock) def closeEvent(self, event): logging.info("主窗口关闭,执行清理...") close_device() disconnect_sensor() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) mainWindow = MainWindow() # 信号连接 mainWindow.cbSensorType.currentIndexChanged.connect(update_sensor_ui) update_sensor_ui(0) mainWindow.bnConnectSensor.clicked.connect(connect_sensor) mainWindow.bnDisconnectSensor.clicked.connect(disconnect_sensor) mainWindow.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) mainWindow.bnCheckPrint.clicked.connect(sensor_controlled_check) mainWindow.bnSaveSample.clicked.connect(save_sample_image) mainWindow.bnPreviewSample.clicked.connect(preview_sample) mainWindow.bnEnum.clicked.connect(enum_devices) mainWindow.bnOpen.clicked.connect(open_device) mainWindow.bnClose.clicked.connect(close_device) mainWindow.bnStart.clicked.connect(start_grabbing) mainWindow.bnStop.clicked.connect(stop_grabbing) mainWindow.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.bnGetParam.clicked.connect(get_param) mainWindow.bnSetParam.clicked.connect(set_param) mainWindow.bnSaveImage.clicked.connect(save_sample_image) main() mainWindow.show() app.exec_() close_device() disconnect_sensor() sys.exit() 我想把这个程序打包成exe文件我该怎么做
07-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值