diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 6c0b863..288b36b 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/Fedding.py b/Fedding.py index 2f8f138..562d6f9 100644 --- a/Fedding.py +++ b/Fedding.py @@ -1,31 +1,38 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# Fedding.py (修正版 - 统一模型管理) import socket import binascii import time import threading import struct +import cv2 +import os +from pathlib import Path from pymodbus.client import ModbusTcpClient from pymodbus.exceptions import ModbusException +# 添加视觉模块路径 +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), 'src', 'vision')) +# 导入视觉处理模块 +from src.vision.anger_caculate import predict_obb_best_angle +from src.vision.resize_tuili_image_main import classify_image_weighted, load_global_rois, crop_and_resize, YOLO class FeedingControlSystem: def __init__(self, relay_host='192.168.0.18', relay_port=50000): - # 网络继电器配置 (作为Modbus TCP转RS485网关) + # 网络继电器配置 self.relay_host = relay_host self.relay_port = relay_port - - # 创建网络继电器的Modbus TCP客户端 self.relay_modbus_client = ModbusTcpClient(relay_host, port=relay_port) - # 定义继电器DO端口映射 - self.DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动门 - self.DOOR_LOWER_1 = 'door_lower_1' # DO1 - 出砼门控制1 - self.DOOR_LOWER_2 = 'door_lower_2' # DO2 - 出砼门控制2 + # 继电器映射 + self.DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动 + self.DOOR_LOWER_1 = 'door_lower_1' # DO1 - 上料斗出砼门 + self.DOOR_LOWER_2 = 'door_lower_2' # DO2 - 下料斗出砼门 self.BREAK_ARCH_UPPER = 'break_arch_upper' # DO3 - 上料斗破拱 self.BREAK_ARCH_LOWER = 'break_arch_lower' # DO4 - 下料斗破拱 - # 继电器控制命令 (原始Socket方式) + # 继电器命令(原始Socket)mudbus TCP模式 self.relay_commands = { self.DOOR_UPPER: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'}, self.DOOR_LOWER_1: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'}, @@ -46,47 +53,178 @@ class FeedingControlSystem: self.BREAK_ARCH_LOWER: 4 } - # 变频器配置 (通过网络继电器转发) + # 变频器配置(Modbus RTU 协议) self.inverter_config = { - 'slave_id': 1, # 变频器从机地址 - 'frequency_register': 0x01, # 频率设置寄存器 - 'start_register': 0x00, # 启动命令寄存器 - 'stop_register': 0x01 # 停止命令寄存器 + 'slave_id': 1, + 'frequency_register': 0x01, # 寄存器地址:0x01(对应2001H) + 'start_register': 0x00, # 启动命令:0x0001(正转运行) + 'stop_register': 0x01 # 停止命令:0x0000(停机) } - # 变送器配置 (通过网络继电器转发) + # 变送器配置(Modbus RTU) self.transmitter_config = { - 1: { # 上料斗变送器 - 'slave_id': 1, # 从机地址 - 'weight_register': 0x00, # 重量寄存器地址 - 'register_count': 2 # 读取寄存器数量 + 1: { # 上料斗 + 'slave_id': 1, + 'weight_register': 0x00, + 'register_count': 2 }, - 2: { # 下料斗变送器 - 'slave_id': 2, # 从机地址 - 'weight_register': 0x00, # 重量寄存器地址 - 'register_count': 2 # 读取寄存器数量 + 2: { # 下料斗 + 'slave_id': 2, + 'weight_register': 0x00, + 'register_count': 2 } } # 系统状态 self._running = False self._monitor_thread = None - self.min_required_weight = 50 # 最小需要重量(kg) + self._visual_control_thread = None + self._alignment_check_thread = None - # 状态标志 - self.upper_door_position = 'default' # default, to_mixer, returning - self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:第三阶段 + # 下料控制相关 + self.min_required_weight = 500 # 模具车最小需要重量(kg) + self.upper_door_position = 'default' # default(在搅拌楼下接料), over_lower(在下料斗上方), returning(返回中) + self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:等待上料, 4:等待模具车对齐 + self.upper_feeding_count = 0 # 上料斗已下料次数 (0, 1, 2) self.last_upper_weight = 0 self.last_lower_weight = 0 self.last_weight_time = time.time() + self.target_vehicle_weight = 5000 # 目标模具车重量(kg) + self.upper_buffer_weight = 500 # 上料斗缓冲重量(kg),每次下料多下这么多 + self.single_batch_weight = 2500 # 单次下料重量(kg) - # 添加权重读取错误计数 + # 错误计数 self.upper_weight_error_count = 0 self.lower_weight_error_count = 0 - self.max_error_count = 3 # 最大连续错误次数 + self.max_error_count = 3 + + # 下料阶段频率(Hz) + self.frequencies = [30.0, 40.0, 50.0] + + # 视觉系统接口 + self.overflow_detected = False # 堆料检测 + self.door_opening_large = False # 夹角 + self.vehicle_aligned = False # 模具车是否对齐 + + # 视觉控制参数 + self.angle_threshold = 60.0 # 角度阈值,超过此值认为开口过大 + self.target_angle = 20.0 # 目标角度 + self.min_angle = 10.0 # 最小角度 + self.max_angle = 80.0 # 最大角度 + self.angle_tolerance = 5.0 # 角度容差 + self.visual_control_enabled = True # 视觉控制使能 + self.last_angle = None # 上次检测角度 + self.visual_check_interval = 1.0 # 视觉检查间隔(秒) + self.alignment_check_interval = 0.5 # 对齐检查间隔(秒) + + # 模型路径配置 + self.angle_model_path = "src/vision/angle.pt" + self.overflow_model_path = "src/vision/overflow.pt" + self.alignment_model_path = "src/vision/alig.pt" # 模具车对齐检测模型 + self.roi_file_path = "src/vision/roi_coordinates/1_rois.txt" + + # 模型实例 + self.angle_model = None # 夹角检测模型实例 + self.overflow_model = None # 堆料检测模型实例 + self.alignment_model = None # 对齐检测模型实例 + + # 摄像头相关配置 + self.camera = None + self.camera_type = "ip" + self.camera_ip = "192.168.1.51" + self.camera_port = 554 + self.camera_username = "admin" + self.camera_password = "XJ123456" + self.camera_channel = 1 + self.current_image_path = "current_frame.jpg" + + def set_camera_config(self, camera_type="ip", ip=None, port=None, username=None, password=None, channel=1): + """ + 设置摄像头配置 + :param camera_type: 摄像头类型 "usb" 或 "ip" + :param ip: 网络摄像头IP地址 + :param port: 网络摄像头端口 + :param username: 网络摄像头用户名 + :param password: 网络摄像头密码 + :param channel: 摄像头通道号 + """ + self.camera_type = camera_type + if ip: + self.camera_ip = ip + if port: + self.camera_port = port + if username: + self.camera_username = username + if password: + self.camera_password = password + self.camera_channel = channel + + def set_angle_parameters(self, target_angle=20.0, min_angle=10.0, max_angle=80.0, threshold=60.0): + """ + 设置角度控制参数 + """ + self.target_angle = target_angle + self.min_angle = min_angle + self.max_angle = max_angle + self.angle_threshold = threshold + + def set_feeding_parameters(self, target_vehicle_weight=5000, upper_buffer_weight=500, single_batch_weight=2500): + """ + 设置下料参数 + :param target_vehicle_weight: 目标模具车重量(kg) + :param upper_buffer_weight: 上料斗缓冲重量(kg) + :param single_batch_weight: 单次下料重量(kg) + """ + self.target_vehicle_weight = target_vehicle_weight + self.upper_buffer_weight = upper_buffer_weight + self.single_batch_weight = single_batch_weight + + def load_all_models(self): + """ + 加载所有视觉检测模型 + """ + success = True + + # 加载夹角检测模型 + try: + if not os.path.exists(self.angle_model_path): + print(f"夹角检测模型不存在: {self.angle_model_path}") + success = False + else: + # 注意:angle.pt模型通过predict_obb_best_angle函数使用,不需要预加载 + print(f"夹角检测模型路径: {self.angle_model_path}") + except Exception as e: + print(f"检查夹角检测模型失败: {e}") + success = False + + # 加载堆料检测模型 + try: + if not os.path.exists(self.overflow_model_path): + print(f"堆料检测模型不存在: {self.overflow_model_path}") + success = False + else: + self.overflow_model = YOLO(self.overflow_model_path) + print(f"成功加载堆料检测模型: {self.overflow_model_path}") + except Exception as e: + print(f"加载堆料检测模型失败: {e}") + success = False + + # 加载对齐检测模型 + try: + if not os.path.exists(self.alignment_model_path): + print(f"对齐检测模型不存在: {self.alignment_model_path}") + success = False + else: + self.alignment_model = YOLO(self.alignment_model_path) + print(f"成功加载对齐检测模型: {self.alignment_model_path}") + except Exception as e: + print(f"加载对齐检测模型失败: {e}") + success = False + + return success def send_relay_command(self, command_hex): - """发送命令到网络继电器 (原始Socket方式)""" + """发送原始Socket命令""" try: byte_data = binascii.unhexlify(command_hex) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: @@ -107,39 +245,35 @@ class FeedingControlSystem: if response and len(response) >= 10: status_byte = response[9] status_bin = f"{status_byte:08b}"[::-1] - for key, bit_index in self.device_bit_map.items(): status_dict[key] = status_bin[bit_index] == '1' else: - print("读取继电器状态失败或响应无效") + print("读取继电器状态失败") return status_dict def control_relay(self, device, action): - """控制继电器开关""" + """控制继电器""" if device in self.relay_commands and action in self.relay_commands[device]: print(f"控制继电器 {device} {action}") self.send_relay_command(self.relay_commands[device][action]) time.sleep(0.1) else: - print(f"无效的继电器设备或动作: {device}, {action}") + print(f"无效设备或动作: {device}, {action}") def read_transmitter_data_via_relay(self, transmitter_id): - """通过网络继电器读取变送器数据 (Modbus TCP转RS485)""" + """读取变送器数据(Modbus TCP 转 RS485)""" try: - # 获取变送器配置 if transmitter_id not in self.transmitter_config: - print(f"无效的变送器ID: {transmitter_id}") + print(f"无效变送器ID: {transmitter_id}") return None config = self.transmitter_config[transmitter_id] - # 连接网络继电器的Modbus服务 if not self.relay_modbus_client.connect(): - print("无法连接到网络继电器Modbus服务") + print("无法连接网络继电器Modbus服务") return None - # 通过网络继电器读取变送器数据 result = self.relay_modbus_client.read_holding_registers( address=config['weight_register'], count=config['register_count'], @@ -147,57 +281,48 @@ class FeedingControlSystem: ) if isinstance(result, Exception): - print(f"读取变送器 {transmitter_id} 数据失败: {result}") + print(f"读取变送器 {transmitter_id} 失败: {result}") return None - # 解析重量数据 (假设是32位浮点数,由两个16位寄存器组成) if config['register_count'] == 2: - # 组合两个寄存器为32位浮点数 (大端序) + # 解析为 32 位整数(大端序) weight_bytes = struct.pack('>HH', result.registers[0], result.registers[1]) - weight = struct.unpack('>f', weight_bytes)[0] + weight = struct.unpack('>I', weight_bytes)[0] / 100.0 # 假设单位是 kg,精度两位 elif config['register_count'] == 1: - # 单个寄存器直接作为整数 weight = float(result.registers[0]) else: print(f"不支持的寄存器数量: {config['register_count']}") return None print(f"变送器 {transmitter_id} 读取重量: {weight}kg") - - # 重置错误计数 - if transmitter_id == 1: - self.upper_weight_error_count = 0 - else: - self.lower_weight_error_count = 0 - return weight except ModbusException as e: - print(f"变送器 {transmitter_id} Modbus通信错误: {e}") + print(f"Modbus通信错误: {e}") return None except Exception as e: - print(f"变送器 {transmitter_id} 数据解析错误: {e}") + print(f"数据解析错误: {e}") return None finally: self.relay_modbus_client.close() def set_inverter_frequency_via_relay(self, frequency): - """通过网络继电器设置变频器频率""" + """设置变频器频率""" try: - # 连接网络继电器的Modbus服务 if not self.relay_modbus_client.connect(): - print("无法连接到网络继电器Modbus服务") + print("无法连接网络继电器Modbus服务") return False - # 写入频率值 (假设频率值需要转换为Hz*10) + value = int(frequency * 100) + result = self.relay_modbus_client.write_register( self.inverter_config['frequency_register'], - int(frequency * 10), + value, slave=self.inverter_config['slave_id'] ) if isinstance(result, Exception): - print(f"设置变频器频率失败: {result}") + print(f"设置频率失败: {result}") return False print(f"设置变频器频率为 {frequency}Hz") @@ -209,10 +334,10 @@ class FeedingControlSystem: self.relay_modbus_client.close() def control_inverter_via_relay(self, action): - """通过网络继电器控制变频器启停""" + """控制变频器启停""" try: if not self.relay_modbus_client.connect(): - print("无法连接到网络继电器Modbus服务") + print("无法连接网络继电器Modbus服务") return False if action == 'start': @@ -225,16 +350,16 @@ class FeedingControlSystem: elif action == 'stop': result = self.relay_modbus_client.write_register( self.inverter_config['stop_register'], - 1, + 0, slave=self.inverter_config['slave_id'] ) print("停止变频器") else: - print(f"无效的变频器操作: {action}") + print(f"无效操作: {action}") return False if isinstance(result, Exception): - print(f"控制变频器失败: {result}") + print(f"控制失败: {result}") return False return True @@ -244,239 +369,523 @@ class FeedingControlSystem: finally: self.relay_modbus_client.close() - def read_transmitter_data(self, transmitter_id): - """读取变送器数据 (按需读取,仅在下料时)""" - return self.read_transmitter_data_via_relay(transmitter_id) - - def set_inverter_frequency(self, frequency): - """设置变频器频率""" - return self.set_inverter_frequency_via_relay(frequency) - - def control_inverter(self, action): - """控制变频器启停""" - return self.control_inverter_via_relay(action) - def check_upper_material_request(self): - """检查是否需要向上料斗要料""" - current_weight = self.read_transmitter_data(1) + """检查是否需要要料""" + current_weight = self.read_transmitter_data_via_relay(1) - # 如果读取失败,增加错误计数 if current_weight is None: self.upper_weight_error_count += 1 print(f"上料斗重量读取失败,错误计数: {self.upper_weight_error_count}") - - # 如果连续错误次数超过阈值,触发报警但不自动要料 if self.upper_weight_error_count >= self.max_error_count: - print("警告:上料斗重量传感器连续读取失败,请检查设备连接") - return False # 读取失败时不触发要料 + print("警告:上料斗传感器连续读取失败,请检查连接") + return False - # 重置错误计数 self.upper_weight_error_count = 0 - - if current_weight < self.min_required_weight: + # 判断是否需要要料:当前重量 < 目标重量 + 缓冲重量 + if current_weight < (self.single_batch_weight + self.min_required_weight): print("上料斗重量不足,通知搅拌楼要料") - self.move_upper_door_to_mixer() + self.request_material_from_mixing_building() # 请求搅拌楼下料 return True return False - def move_upper_door_to_mixer(self): - """移动上料斗到搅拌楼出砼口""" - print("移动上料斗到搅拌楼出砼口") + def request_material_from_mixing_building(self): + """ + 请求搅拌楼下料(待完善) + TODO: 与同事对接通信协议 + """ + print("发送要料请求至搅拌楼...") + self.return_upper_door_to_default() + # 这里需要与同事对接具体的通信方式 + # 可能是Modbus写寄存器、TCP通信、HTTP请求等 + pass + + def wait_for_mixing_building_material(self): + """ + 等待搅拌楼下料完成(待完善) + TODO: 与同事对接信号接收 + """ + print("等待搅拌楼下料完成...") + # 这里需要与同事对接具体的信号接收方式 + # 可能是Modbus读寄存器、TCP通信、HTTP请求等 + # 模拟等待 + time.sleep(5) + print("搅拌楼下料完成") + self.move_upper_door_over_lower() + return True + + def move_upper_door_over_lower(self): + """移动上料斗到下料斗上方""" + print("移动上料斗到下料斗上方") self.control_relay(self.DOOR_UPPER, 'open') - self.upper_door_position = 'to_mixer' + self.upper_door_position = 'over_lower' def return_upper_door(self): - """返回上料斗""" - print("上料斗返回原位") + """返回上料斗到搅拌楼""" + print("上料斗返回搅拌楼") + self.control_relay(self.DOOR_UPPER, 'close') + self.upper_door_position = 'returning' + + def return_upper_door_to_default(self): + """上料斗回到默认位置(搅拌楼下接料位置)""" + print("上料斗回到默认位置") self.control_relay(self.DOOR_UPPER, 'close') self.upper_door_position = 'default' def start_lower_feeding(self): - """开始下料过程""" + """开始分步下料""" if self.lower_feeding_stage != 0: print("下料已在进行中") return - print("开始三阶段下料过程") - self.lower_feeding_stage = 1 - self.feeding_stage_one() + # 检查关键设备是否可连接 + if not self._check_device_connectivity(): + print("关键设备连接失败,无法开始下料") + return + + print("开始分步下料过程") + self.lower_feeding_stage = 4 # 从等待模具车对齐开始 + self.upper_feeding_count = 0 + self.wait_for_vehicle_alignment() + + def _check_device_connectivity(self): + """检查关键设备连接状态""" + try: + # 检查网络继电器连接 + test_response = self.send_relay_command(self.read_status_command) + if not test_response: + print("网络继电器连接失败") + return False + + # 检查变频器连接 + if not self.relay_modbus_client.connect(): + print("无法连接到网络继电器Modbus服务") + return False + + # 尝试读取变频器一个寄存器(测试连接) + test_result = self.relay_modbus_client.read_holding_registers( + address=0x00, + count=1, + slave=self.inverter_config['slave_id'] + ) + + if isinstance(test_result, Exception): + print("变频器连接测试失败") + return False + + # 检查下料斗变送器连接 + test_weight = self.read_transmitter_data_via_relay(2) + if test_weight is None: + print("下料斗变送器连接失败") + return False + + self.relay_modbus_client.close() + return True + except Exception as e: + print(f"设备连接检查失败: {e}") + return False + + def wait_for_vehicle_alignment(self): + """等待模具车对齐""" + print("等待模具车对齐...") + self.lower_feeding_stage = 4 + + while self.lower_feeding_stage == 4 and self._running: + if self.vehicle_aligned: + print("模具车已对齐,开始下料") + self.lower_feeding_stage = 1 + self.feeding_stage_one() + break + time.sleep(self.alignment_check_interval) def feeding_stage_one(self): - """第一阶段下料""" - print("开始第一阶段下料 (1/3)") - # 设置变频器频率 (假设第一阶段频率为30Hz) - self.set_inverter_frequency(30.0) - self.control_inverter('start') + """第一阶段下料(2.5吨)""" + print("开始第一阶段下料 (1/2)") + self.upper_feeding_count = 1 + self.set_inverter_frequency_via_relay(self.frequencies[0]) + self.control_inverter_via_relay('start') - # 控制出砼门 self.control_relay(self.DOOR_LOWER_1, 'open') self.control_relay(self.DOOR_LOWER_2, 'open') - # 监控下料过程 start_time = time.time() - initial_weight = self.read_transmitter_data(2) - - # 如果初始重量读取失败,使用默认值或取消操作 + initial_weight = self.read_transmitter_data_via_relay(2) if initial_weight is None: - print("无法获取初始重量,取消下料操作") - self.finish_feeding() + print("无法获取初始重量,取消下料") + self.finish_current_batch() return - target_weight = initial_weight + 33.3 # 假设每阶段下料1/3 + target_weight = initial_weight + self.single_batch_weight while self.lower_feeding_stage == 1: - current_weight = self.read_transmitter_data(2) - # 如果读取失败,增加错误计数并检查是否超过阈值 + current_weight = self.read_transmitter_data_via_relay(2) if current_weight is None: self.lower_weight_error_count += 1 if self.lower_weight_error_count >= self.max_error_count: - print("下料斗重量传感器连续读取失败,停止下料") - self.finish_feeding() + print("下料斗传感器连续读取失败,停止下料") + self.finish_current_batch() return else: - self.lower_weight_error_count = 0 # 重置错误计数 + self.lower_weight_error_count = 0 - # 检查是否达到目标重量或超时 if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: self.lower_feeding_stage = 2 self.feeding_stage_two() break - time.sleep(2) # 每2秒读取一次 + time.sleep(2) def feeding_stage_two(self): - """第二阶段下料""" - print("开始第二阶段下料 (2/3)") - # 调整变频器频率 (假设第二阶段频率为40Hz) - self.set_inverter_frequency(40.0) + """第二阶段下料(剩余2.5吨)""" + print("开始第二阶段下料 (2/2)") + self.upper_feeding_count = 2 + self.set_inverter_frequency_via_relay(self.frequencies[1]) start_time = time.time() - initial_weight = self.read_transmitter_data(2) - - # 如果初始重量读取失败,使用默认值或取消操作 + initial_weight = self.read_transmitter_data_via_relay(2) if initial_weight is None: - print("无法获取初始重量,取消下料操作") - self.finish_feeding() + print("无法获取初始重量,取消下料") + self.finish_current_batch() return - target_weight = initial_weight + 33.3 + target_weight = initial_weight + self.single_batch_weight while self.lower_feeding_stage == 2: - current_weight = self.read_transmitter_data(2) - # 如果读取失败,增加错误计数并检查是否超过阈值 + current_weight = self.read_transmitter_data_via_relay(2) if current_weight is None: self.lower_weight_error_count += 1 if self.lower_weight_error_count >= self.max_error_count: - print("下料斗重量传感器连续读取失败,停止下料") - self.finish_feeding() + print("下料斗传感器连续读取失败,停止下料") + self.finish_current_batch() return else: - self.lower_weight_error_count = 0 # 重置错误计数 + self.lower_weight_error_count = 0 if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: self.lower_feeding_stage = 3 - self.feeding_stage_three() + self.finish_current_batch() break - time.sleep(2) # 每2秒读取一次 + time.sleep(2) - def feeding_stage_three(self): - """第三阶段下料""" - print("开始第三阶段下料 (3/3)") - # 调整变频器频率 (假设第三阶段频率为50Hz) - self.set_inverter_frequency(50.0) - - start_time = time.time() - initial_weight = self.read_transmitter_data(2) - - # 如果初始重量读取失败,使用默认值或取消操作 - if initial_weight is None: - print("无法获取初始重量,取消下料操作") - self.finish_feeding() - return - - target_weight = initial_weight + 33.3 - - while self.lower_feeding_stage == 3: - current_weight = self.read_transmitter_data(2) - # 如果读取失败,增加错误计数并检查是否超过阈值 - if current_weight is None: - self.lower_weight_error_count += 1 - if self.lower_weight_error_count >= self.max_error_count: - print("下料斗重量传感器连续读取失败,停止下料") - self.finish_feeding() - return - else: - self.lower_weight_error_count = 0 # 重置错误计数 - - if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: - self.lower_feeding_stage = 0 - self.finish_feeding() - break - time.sleep(2) # 每2秒读取一次 - - def finish_feeding(self): - """完成下料""" - print("下料完成,关闭出砼门") - self.control_inverter('stop') + def finish_current_batch(self): + """完成当前批次下料""" + print("当前批次下料完成,关闭出砼门") + self.control_inverter_via_relay('stop') self.control_relay(self.DOOR_LOWER_1, 'close') self.control_relay(self.DOOR_LOWER_2, 'close') + self.upper_feeding_count = 0 + + # 检查上料斗剩余重量 + upper_weight = self.read_transmitter_data_via_relay(1) + if upper_weight is not None and upper_weight < (self.single_batch_weight + self.min_required_weight): + print("上料斗剩余重量不足,需要返回搅拌楼要料") + self.lower_feeding_stage = 3 # 等待上料 + self.return_upper_door() # 返回上料斗 + self.request_material_from_mixing_building() # 请求要料 + else: + print("上料斗仍有足够物料,等待下一个模具车") + self.lower_feeding_stage = 4 # 等待模具车对齐 + self.wait_for_vehicle_alignment() + + def finish_feeding_process(self): + """完成整个下料流程""" + print("整个下料流程完成") + self.lower_feeding_stage = 0 + self.upper_feeding_count = 0 + self.return_upper_door_to_default() # 上料斗回到默认位置 def handle_overflow_control(self, overflow_detected, door_opening_large): """处理溢料控制""" if overflow_detected and door_opening_large: print("检测到溢料且出砼门开口较大,调小出砼门") - # 通过重复开关控制角度 self.control_relay(self.DOOR_LOWER_1, 'close') time.sleep(0.1) self.control_relay(self.DOOR_LOWER_1, 'open') time.sleep(0.1) + def is_lower_door_open(self): + """检查出砼门是否打开""" + return self.lower_feeding_stage in [1, 2] # 只有在下料阶段才认为门是打开的 + def check_arch_blocking(self): - """检查是否需要破拱 (按需读取)""" + """检查是否需要破拱""" current_time = time.time() - # 检查上料斗 (按需读取) - upper_weight = self.read_transmitter_data(1) - if upper_weight is not None: # 只有在成功读取重量时才检查堵塞 - if (abs(upper_weight - self.last_upper_weight) < 0.1) and \ - (current_time - self.last_weight_time) > 10: - print("上料斗可能堵塞,启动破拱") - self.control_relay(self.BREAK_ARCH_UPPER, 'open') - time.sleep(2) - self.control_relay(self.BREAK_ARCH_UPPER, 'close') + # 检查下料斗破拱(只有在下料过程中才检查) + if self.lower_feeding_stage in [1, 2]: + lower_weight = self.read_transmitter_data_via_relay(2) + if lower_weight is not None: + # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) + if (abs(lower_weight - self.last_lower_weight) < 0.1) and \ + (current_time - self.last_weight_time) > 10: + print("下料斗可能堵塞,启动破拱") + self.control_relay(self.BREAK_ARCH_LOWER, 'open') + time.sleep(2) + self.control_relay(self.BREAK_ARCH_LOWER, 'close') - # 检查下料斗 (按需读取) - lower_weight = self.read_transmitter_data(2) - if lower_weight is not None: # 只有在成功读取重量时才检查堵塞 - if (abs(lower_weight - self.last_lower_weight) < 0.1) and \ - (current_time - self.last_weight_time) > 10: - print("下料斗可能堵塞,启动破拱") - self.control_relay(self.BREAK_ARCH_LOWER, 'open') - time.sleep(2) - self.control_relay(self.BREAK_ARCH_LOWER, 'close') + self.last_lower_weight = lower_weight - # 更新重量记录(只有在成功读取时才更新) - if upper_weight is not None: - self.last_upper_weight = upper_weight - if lower_weight is not None: - self.last_lower_weight = lower_weight + # 检查上料斗破拱(只有在上料斗给下料斗下料时才检查) + if (self.upper_door_position == 'over_lower' and + self.lower_feeding_stage in [1, 2] and + self.is_lower_door_open()): + upper_weight = self.read_transmitter_data_via_relay(1) + if upper_weight is not None: + # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) + if (abs(upper_weight - self.last_upper_weight) < 0.1) and \ + (current_time - self.last_weight_time) > 10: + print("上料斗可能堵塞,启动破拱") + self.control_relay(self.BREAK_ARCH_UPPER, 'open') + time.sleep(2) + self.control_relay(self.BREAK_ARCH_UPPER, 'close') - if upper_weight is not None or lower_weight is not None: + self.last_upper_weight = upper_weight + + # 更新最后读取时间 + if (self.read_transmitter_data_via_relay(1) is not None or + self.read_transmitter_data_via_relay(2) is not None): self.last_weight_time = current_time def monitor_system(self): """监控系统状态""" while self._running: try: - # 检查是否需要要料 self.check_upper_material_request() - - # 检查破拱 (按需读取) self.check_arch_blocking() - time.sleep(1) except Exception as e: print(f"监控线程错误: {e}") + def setup_camera_capture(self, camera_index=0): + """ + 设置摄像头捕获 + :param camera_index: USB摄像头索引或IP摄像头配置 + """ + try: + rtsp_url = f"rtsp://{self.camera_username}:{self.camera_password}@{self.camera_ip}:{self.camera_port}/streaming/channels/{self.camera_channel}01" + self.camera = cv2.VideoCapture(rtsp_url) + + if not self.camera.isOpened(): + print(f"无法打开网络摄像头: {rtsp_url}") + return False + print(f"网络摄像头初始化成功,地址: {rtsp_url}") + return True + except Exception as e: + print(f"摄像头设置失败: {e}") + return False + + def capture_current_frame(self): + """捕获当前帧并返回numpy数组""" + try: + if self.camera is None: + print("摄像头未初始化") + return None + + ret, frame = self.camera.read() + if ret: + return frame + else: + print("无法捕获图像帧") + return None + except Exception as e: + print(f"图像捕获失败: {e}") + return None + + def detect_overflow_from_image(self, image_array): + """通过图像检测是否溢料(接受numpy数组)""" + try: + # 检查模型是否已加载 + if self.overflow_model is None: + print("堆料检测模型未加载") + return False + + rois = load_global_rois(self.roi_file_path) + + if not rois: + print("没有有效的ROI配置") + return False + + if image_array is None: + print("输入图像为空") + return False + + crops = crop_and_resize(image_array, rois, 640) + for roi_resized, _ in crops: + final_class, _, _, _ = classify_image_weighted(roi_resized, self.overflow_model, threshold=0.4) + if "大堆料" in final_class or "浇筑满" in final_class: + return True + + return False + except Exception as e: + print(f"溢料检测失败: {e}") + return False + + def detect_vehicle_alignment(self, image_array): + """通过图像检测模具车是否对齐(接受numpy数组)""" + try: + # 检查模型是否已加载 + if self.alignment_model is None: + print("对齐检测模型未加载") + return False + + if image_array is None: + print("输入图像为空") + return False + + # 直接使用模型进行推理 + results = self.alignment_model(image_array) + pred_probs = results[0].probs.data.cpu().numpy().flatten() + + # 类别0: 未对齐, 类别1: 对齐 + class_id = int(pred_probs.argmax()) + confidence = float(pred_probs[class_id]) + + # 只有当对齐且置信度>95%时才认为对齐 + if class_id == 1 and confidence > 0.95: + return True + return False + except Exception as e: + print(f"对齐检测失败: {e}") + return False + + def get_current_door_angle(self, image_path): + """ + 通过视觉系统获取当前出砼门角度 + """ + try: + # 检查模型是否已加载 + if self.angle_model is None: + print("夹角检测模型未加载") + return None + + angle_deg, _ = predict_obb_best_angle( + model=self.angle_model, # 传递预加载的模型实例 + image_path=image_path + ) + return angle_deg + except Exception as e: + print(f"角度检测失败: {e}") + return None + + def adjust_door_angle(self, current_angle, target_angle): + """ + 根据当前角度和目标角度调整出砼门 + """ + angle_diff = abs(current_angle - target_angle) + + if angle_diff <= self.angle_tolerance: + print(f"角度已在目标范围内: {current_angle:.2f}°") + return True + + if current_angle > target_angle: + # 需要减小角度 - 关闭DO2 + print(f"角度 {current_angle:.2f}° 过大,调整至 {target_angle}°,关闭出砼门") + self.control_relay(self.DOOR_LOWER_2, 'close') + time.sleep(0.1) + self.control_relay(self.DOOR_LOWER_2, 'open') + return False + else: + # 需要增大角度 - 打开DO2 + print(f"角度 {current_angle:.2f}° 过小,调整至 {target_angle}°,打开出砼门") + self.control_relay(self.DOOR_LOWER_2, 'open') + time.sleep(0.1) + self.control_relay(self.DOOR_LOWER_2, 'close') + return False + + def alignment_check_loop(self): + """ + 模具车对齐检查循环 + """ + while self._running: + try: + # 只在需要检查对齐时才检查 + if self.lower_feeding_stage == 4: + current_frame = self.capture_current_frame() + if current_frame is not None: + self.vehicle_aligned = self.detect_vehicle_alignment(current_frame) + if self.vehicle_aligned: + print("检测到模具车对齐") + else: + print("模具车未对齐") + time.sleep(self.alignment_check_interval) + except Exception as e: + print(f"对齐检查循环错误: {e}") + time.sleep(self.alignment_check_interval) + + def visual_control_loop(self): + """ + 视觉控制主循环 + """ + while self._running and self.visual_control_enabled: + try: + current_frame = self.capture_current_frame() + if current_frame is None: + print("无法获取当前图像,跳过本次调整") + time.sleep(self.visual_check_interval) + continue + + # 检测是否溢料 + overflow = self.detect_overflow_from_image(current_frame) + + # 获取当前角度(需要临时文件) + temp_path = "temp_angle_image.jpg" + cv2.imwrite(temp_path, current_frame) + current_angle = self.get_current_door_angle(temp_path) + if os.path.exists(temp_path): + os.remove(temp_path) + + if current_angle is None: + print("无法获取当前角度,跳过本次调整") + time.sleep(self.visual_check_interval) + continue + + print(f"当前角度: {current_angle:.2f}°, 溢料状态: {overflow}") + + # 根据溢料状态和角度决定调整策略 + if overflow and current_angle > self.angle_threshold: + self.adjust_door_angle(current_angle, self.target_angle) + elif not overflow and current_angle < self.target_angle: + if current_angle < self.max_angle - self.angle_tolerance: + target = min(current_angle + 10, self.max_angle) + self.adjust_door_angle(current_angle, target) + elif overflow and current_angle <= self.angle_threshold: + print("溢料但角度合理,无需调整") + else: + print("角度状态正常,无需调整") + + self.last_angle = current_angle + time.sleep(self.visual_check_interval) + + except Exception as e: + print(f"视觉控制循环错误: {e}") + time.sleep(self.visual_check_interval) + + def start_visual_control(self): + """ + 启动视觉控制线程 + """ + if not self.visual_control_enabled: + print("视觉控制未启用") + return + + print("启动视觉控制线程") + self._visual_control_thread = threading.Thread( + target=self.visual_control_loop, + daemon=True + ) + self._visual_control_thread.start() + return self._visual_control_thread + + def start_alignment_check(self): + """ + 启动模具车对齐检查线程 + """ + print("启动模具车对齐检查线程") + self._alignment_check_thread = threading.Thread( + target=self.alignment_check_loop, + daemon=True + ) + self._alignment_check_thread.start() + return self._alignment_check_thread + def start(self): """启动系统""" if self._running: @@ -496,6 +905,12 @@ class FeedingControlSystem: self._running = False if self._monitor_thread is not None: self._monitor_thread.join() + if self._visual_control_thread is not None: + self._visual_control_thread.join() + if self._alignment_check_thread is not None: + self._alignment_check_thread.join() + if self.camera is not None: + self.camera.release() print("控制系统已停止") @@ -503,19 +918,53 @@ class FeedingControlSystem: if __name__ == "__main__": system = FeedingControlSystem(relay_host='192.168.0.18', relay_port=50000) + # 设置角度控制参数 + system.set_angle_parameters( + target_angle=20.0, + min_angle=10.0, + max_angle=80.0, + threshold=60.0 + ) + + # 设置下料参数 + system.set_feeding_parameters( + target_vehicle_weight=5000, # 5吨 + upper_buffer_weight=500, # 0.5吨缓冲 + single_batch_weight=2500 # 每次下2.5吨 + ) + + # 设置摄像头配置 + system.set_camera_config( + camera_type="ip", + ip="192.168.1.51", + port=554, + username="admin", + password="XJ123456", + channel=1 + ) + + # 初始化摄像头 + if not system.setup_camera_capture(): + print("摄像头初始化失败") + exit(1) + + # 加载所有模型 + if not system.load_all_models(): + print("模型加载失败") + exit(1) + # 启动系统监控 system.start() - # 模拟操作 + # 启动视觉控制 + system.start_visual_control() + + # 启动对齐检查 + system.start_alignment_check() + try: - # 检查是否需要要料 - system.check_upper_material_request() - - # 开始下料 - system.start_lower_feeding() - # 运行一段时间 - time.sleep(60) + time.sleep(300) # 运行5分钟 except KeyboardInterrupt: print("程序被中断") diff --git a/config/config.yaml b/config/config.yaml index ba5cc0b..55e84ed 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,24 +1,28 @@ # 网络配置 network: relay: + # 网络继电器(所有485设备通过此接口转发) host: "192.168.0.18" port: 50000 + +# Modbus设备配置(通过网络继电器访问的RTU设备) +modbus_devices: inverter: - host: "192.168.0.20" - port: 502 + # 变频器 slave_id: 1 + address: 1 # 设备地址(用于网络继电器转发识别) transmitters: upper: - host: "192.168.0.21" - port: 502 - slave_id: 1 - weight_register: 0 + # 上料斗变送器 + slave_id: 2 + address: 2 + weight_register: 1 register_count: 2 lower: - host: "192.168.0.22" - port: 502 - slave_id: 2 - weight_register: 0 + # 下料斗变送器 + slave_id: 3 + address: 3 + weight_register: 1 register_count: 2 # 系统参数 @@ -41,4 +45,4 @@ relay: door_lower_1: 1 # DO1 - 出砼门控制1 door_lower_2: 2 # DO2 - 出砼门控制2 break_arch_upper: 3 # DO3 - 上料斗破拱 - break_arch_lower: 4 # DO4 - 下料斗破拱 \ No newline at end of file + break_arch_lower: 4 # DO4 - 下料斗破拱 diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..e69de29 diff --git a/src/control/feeding_controller.py b/src/control/feeding_controller.py index 0b2f7a4..c9150c5 100644 --- a/src/control/feeding_controller.py +++ b/src/control/feeding_controller.py @@ -9,7 +9,6 @@ from .state_machine import FeedingStateMachine, FeedingState class FeedingController: """下料控制器""" - def __init__(self): self.logger = app_logger.getChild('FeedingController') self.state_machine = FeedingStateMachine() @@ -24,7 +23,7 @@ class FeedingController: # 下料参数 self.stage_frequencies = { - 1: config.get('feeding.stage1_frequency', 30.0), + 1: config.get('feeding.stage1_frequency', 30.0),#三阶段的频率 2: config.get('feeding.stage2_frequency', 40.0), 3: config.get('feeding.stage3_frequency', 50.0) } @@ -50,22 +49,24 @@ class FeedingController: relay_mapping = config.get('relay') self.relay.set_device_mapping(relay_mapping) - # 变频器 - inverter_config = config.get('network.inverter') + # 变频器 - 修改为使用新的配置结构 + inverter_config = config.get('modbus_devices.inverter') self.inverter = InverterController( - inverter_config['host'], - inverter_config['port'], - inverter_config['slave_id'] + relay_config['host'], # 使用网络继电器的host + relay_config['port'], # 使用网络继电器的port + inverter_config['slave_id'], + inverter_config['address'] # 添加设备地址参数 ) - # 变送器 - transmitters_config = config.get('network.transmitters') + # 变送器 - 修改为使用新的配置结构 + transmitters_config = config.get('modbus_devices.transmitters') self.upper_transmitter = TransmitterController( 'upper', - transmitters_config['upper']['host'], - transmitters_config['upper']['port'], - transmitters_config['upper']['slave_id'] + relay_config['host'], # 使用网络继电器的host + relay_config['port'], # 使用网络继电器的port + transmitters_config['upper']['slave_id'], + transmitters_config['upper']['address'] # 添加设备地址参数 ) self.upper_transmitter.set_config( transmitters_config['upper']['weight_register'], @@ -74,9 +75,10 @@ class FeedingController: self.lower_transmitter = TransmitterController( 'lower', - transmitters_config['lower']['host'], - transmitters_config['lower']['port'], - transmitters_config['lower']['slave_id'] + relay_config['host'], # 使用网络继电器的host + relay_config['port'], # 使用网络继电器的port + transmitters_config['lower']['slave_id'], + transmitters_config['lower']['address'] # 添加设备地址参数 ) self.lower_transmitter.set_config( transmitters_config['lower']['weight_register'], @@ -106,7 +108,8 @@ class FeedingController: self.error_count = 0 if current_weight < self.min_required_weight: - self.logger.info("上料斗重量不足,通知搅拌楼要料") + self.logger.info("上料斗重量不足,通知搅拌楼要料")#通知搅拌楼要料,调用彭琪的接口 + #一个通讯的函数,内容包括(要料数据) self.move_upper_door_to_mixer() return True return False @@ -182,7 +185,6 @@ class FeedingController: (time.time() - start_time) > self.timeout: self.state_machine.set_state(FeedingState.STAGE_TWO) break - time.sleep(2) # 每2秒读取一次 def execute_stage_two(self): diff --git a/src/divices/inverter.py b/src/divices/inverter.py index ca8e729..e4f788d 100644 --- a/src/divices/inverter.py +++ b/src/divices/inverter.py @@ -6,10 +6,11 @@ from src.utils.logger import app_logger class InverterController: """变频器控制器""" - def __init__(self, host: str, port: int, slave_id: int = 1): - self.host = host - self.port = port + def __init__(self, relay_host: str, relay_port: int, slave_id: int = 1, device_address: int = 1): + self.relay_host = relay_host + self.relay_port = relay_port self.slave_id = slave_id + self.device_address = device_address # 设备地址,用于网络继电器转发识别 self.client = None self.logger = app_logger.getChild('InverterController') @@ -23,8 +24,13 @@ class InverterController: def connect(self): """连接变频器""" try: - self.client = ModbusTcpClient(self.host, port=self.port) - return self.client.connect() + self.client = ModbusTcpClient(self.relay_host, port=self.relay_port) + connection_result = self.client.connect() + + # 这里可能需要添加设备地址的处理逻辑,具体取决于网络继电器的协议 + # 例如,可能需要发送一个初始化命令告诉继电器要与哪个设备通信 + + return connection_result except Exception as e: self.logger.error(f"连接变频器失败: {e}") return False diff --git a/src/divices/transmitter.py b/src/divices/transmitter.py index 2c4943b..70991a1 100644 --- a/src/divices/transmitter.py +++ b/src/divices/transmitter.py @@ -7,11 +7,12 @@ from src.utils.logger import app_logger class TransmitterController: """重量变送器控制器""" - def __init__(self, name: str, host: str, port: int, slave_id: int = 1): + def __init__(self, name: str, relay_host: str, relay_port: int, slave_id: int = 1, device_address: int = 1): self.name = name - self.host = host - self.port = port + self.relay_host = relay_host + self.relay_port = relay_port self.slave_id = slave_id + self.device_address = device_address # 设备地址,用于网络继电器转发识别 self.client = None self.logger = app_logger.getChild(f'TransmitterController.{name}') @@ -19,16 +20,15 @@ class TransmitterController: self.weight_register = 0 self.register_count = 2 - def set_config(self, weight_register: int, register_count: int): - """设置变送器配置""" - self.weight_register = weight_register - self.register_count = register_count - def connect(self): """连接变送器""" try: - self.client = ModbusTcpClient(self.host, port=self.port) - return self.client.connect() + self.client = ModbusTcpClient(self.relay_host, port=self.relay_port) + connection_result = self.client.connect() + + # 这里可能需要添加设备地址的处理逻辑,具体取决于网络继电器的协议 + + return connection_result except Exception as e: self.logger.error(f"连接变送器 {self.name} 失败: {e}") return False @@ -61,9 +61,12 @@ class TransmitterController: # 解析重量数据 if self.register_count == 2: - # 组合两个寄存器为32位浮点数 (大端序) + # 根据协议,重量数据从第三个寄存器开始,需要4个字节 + # 组合两个寄存器为32位整数 (大端序) + # 按照协议描述,应该是4个字节的数据: [reg0_high, reg0_low, reg1_high, reg1_low] weight_bytes = struct.pack('>HH', result.registers[0], result.registers[1]) - weight = struct.unpack('>f', weight_bytes)[0] + # 按照协议,从第4个字节开始是重量数据,即前4个字节就是重量数据 + weight = struct.unpack('>I', weight_bytes)[0] # 使用大端序无符号整数解析 elif self.register_count == 1: # 单个寄存器直接作为整数 weight = float(result.registers[0]) @@ -72,11 +75,16 @@ class TransmitterController: return None self.logger.debug(f"变送器 {self.name} 读取重量: {weight}kg") - return weight + return float(weight) except ModbusException as e: self.logger.error(f"变送器 {self.name} Modbus通信错误: {e}") return None except Exception as e: self.logger.error(f"变送器 {self.name} 数据解析错误: {e}") - return None \ No newline at end of file + return None + + def set_config(self, weight_register, register_count): + """设置重量读取相关的寄存器配置""" + self.weight_register = weight_register + self.register_count = register_count \ No newline at end of file diff --git a/src/logs/app.log b/src/logs/app.log index 01045bd..29f2558 100644 --- a/src/logs/app.log +++ b/src/logs/app.log @@ -118,3 +118,119 @@ 2025-09-13 10:36:49,725 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] 2025-09-13 10:36:49,725 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ 2025-09-13 10:36:49,725 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:07,373 - FeedingControl.FeedingController - INFO - 豸ʼ +2025-09-16 19:45:07,373 - FeedingControl.FeedingController - INFO - 豸ʼ +2025-09-16 19:45:11,736 - FeedingControl.MainSystem - INFO - ʼ +2025-09-16 19:45:11,736 - FeedingControl.MainSystem - INFO - ʼ +2025-09-16 19:45:12,696 - FeedingControl.MainSystem - INFO - ϵͳ +2025-09-16 19:45:12,696 - FeedingControl.MainSystem - INFO - ϵͳ +2025-09-16 19:45:15,710 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1 +2025-09-16 19:45:15,710 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1 +2025-09-16 19:45:18,711 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:18,711 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:21,725 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:21,725 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:25,741 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:25,741 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:25,741 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 2 +2025-09-16 19:45:25,741 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 2 +2025-09-16 19:45:28,752 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:28,752 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:31,765 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:45:31,765 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:45:31,765 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:31,765 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:35,782 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:35,782 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:35,782 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 3 +2025-09-16 19:45:35,782 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 3 +2025-09-16 19:45:35,782 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:45:35,782 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:45:38,797 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:38,797 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:41,810 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:45:41,810 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:45:41,810 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:41,810 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:45,835 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:45,835 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:45,835 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 4 +2025-09-16 19:45:45,835 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 4 +2025-09-16 19:45:45,835 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:45:45,835 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:45:48,845 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:48,845 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:51,859 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:45:51,859 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:45:51,859 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:51,859 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:45:55,885 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:55,885 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:55,885 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 5 +2025-09-16 19:45:55,885 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 5 +2025-09-16 19:45:55,885 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:45:55,885 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:45:58,890 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:45:58,890 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:01,900 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:01,900 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:01,900 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:01,900 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:05,921 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:05,921 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:05,921 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 6 +2025-09-16 19:46:05,921 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 6 +2025-09-16 19:46:05,921 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:46:05,921 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:46:08,926 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:08,926 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:11,936 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:11,936 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:11,936 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:11,936 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:15,950 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:15,950 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:15,950 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 7 +2025-09-16 19:46:15,950 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 7 +2025-09-16 19:46:15,950 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:46:15,950 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:46:18,958 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:18,958 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:21,965 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:21,965 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:21,965 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:21,965 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:25,987 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:25,987 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:25,987 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 8 +2025-09-16 19:46:25,987 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 8 +2025-09-16 19:46:25,987 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:46:25,987 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸 +2025-09-16 19:46:28,989 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:28,989 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:46:31,993 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:31,993 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502] +2025-09-16 19:46:31,993 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:31,993 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬ +2025-09-16 19:46:32,358 - FeedingControl.MainSystem - INFO - յź 2ֹͣϵͳ... +2025-09-16 19:46:32,358 - FeedingControl.MainSystem - INFO - յź 2ֹͣϵͳ... +2025-09-16 19:46:32,358 - FeedingControl.MainSystem - INFO - ֹͣϵͳ +2025-09-16 19:46:32,358 - FeedingControl.MainSystem - INFO - ֹͣϵͳ +2025-09-16 19:46:32,358 - FeedingControl.MainSystem - INFO - ϵͳֹͣ +2025-09-16 19:46:32,358 - FeedingControl.MainSystem - INFO - ϵͳֹͣ +2025-09-16 19:57:25,205 - FeedingControl.FeedingController - INFO - 豸ʼ +2025-09-16 19:57:25,205 - FeedingControl.FeedingController - INFO - 豸ʼ +2025-09-16 19:57:30,519 - FeedingControl.MainSystem - INFO - ʼ +2025-09-16 19:57:30,519 - FeedingControl.MainSystem - INFO - ʼ +2025-09-16 19:57:32,282 - FeedingControl.MainSystem - INFO - ϵͳ +2025-09-16 19:57:32,282 - FeedingControl.MainSystem - INFO - ϵͳ +2025-09-16 19:57:47,433 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1 +2025-09-16 19:57:47,433 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1 +2025-09-16 19:58:06,833 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:58:06,833 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502] +2025-09-16 19:58:12,731 - FeedingControl.MainSystem - INFO - յź 2ֹͣϵͳ... +2025-09-16 19:58:12,731 - FeedingControl.MainSystem - INFO - յź 2ֹͣϵͳ... +2025-09-16 19:58:12,731 - FeedingControl.MainSystem - INFO - ֹͣϵͳ +2025-09-16 19:58:12,731 - FeedingControl.MainSystem - INFO - ֹͣϵͳ +2025-09-16 19:58:12,731 - FeedingControl.MainSystem - INFO - ϵͳֹͣ +2025-09-16 19:58:12,731 - FeedingControl.MainSystem - INFO - ϵͳֹͣ diff --git a/src/vision/alig.pt b/src/vision/alig.pt new file mode 100644 index 0000000..9e3f2ac Binary files /dev/null and b/src/vision/alig.pt differ diff --git a/src/vision/anger_caculate.py b/src/vision/anger_caculate.py new file mode 100644 index 0000000..e299d83 --- /dev/null +++ b/src/vision/anger_caculate.py @@ -0,0 +1,84 @@ +import cv2 +import os +import numpy as np +from ultralytics import YOLO + +def predict_obb_best_angle(model=None, model_path=None, image_path=None, save_path=None): + """ + 输入: + model: 预加载的YOLO模型实例(可选) + model_path: YOLO 权重路径(当model为None时使用) + image_path: 图片路径 + save_path: 可选,保存带标注图像 + 输出: + angle_deg: 置信度最高两个框的主方向夹角(度),如果检测少于两个目标返回 None + annotated_img: 可视化图像 + """ + # 1. 使用预加载的模型或加载新模型 + if model is not None: + # 使用预加载的模型 + loaded_model = model + elif model_path is not None: + # 加载模型 + loaded_model = YOLO(model_path) + else: + raise ValueError("必须提供model或model_path参数") + + # 2. 读取图像 + img = cv2.imread(image_path) + if img is None: + print(f"无法读取图像: {image_path}") + return None, None + + # 3. 推理 OBB + results = loaded_model(img, save=False, imgsz=640, conf=0.5, mode='obb') + result = results[0] + + # 4. 可视化 + annotated_img = result.plot() + if save_path: + os.makedirs(os.path.dirname(save_path), exist_ok=True) + cv2.imwrite(save_path, annotated_img) + print(f"推理结果已保存至: {save_path}") + + # 5. 提取旋转角度和置信度 + boxes = result.obb + if boxes is None or len(boxes) < 2: + print("检测到少于两个目标,无法计算夹角。") + return None, annotated_img + + box_info = [] + for box in boxes: + conf = box.conf.cpu().numpy()[0] + cx, cy, w, h, r_rad = box.xywhr.cpu().numpy()[0] + direction = r_rad if w >= h else r_rad + np.pi/2 + direction = direction % np.pi + box_info.append((conf, direction)) + + # 6. 取置信度最高两个框 + box_info = sorted(box_info, key=lambda x: x[0], reverse=True) + dir1, dir2 = box_info[0][1], box_info[1][1] + + # 7. 计算夹角(最小夹角,0~90°) + diff = abs(dir1 - dir2) + diff = min(diff, np.pi - diff) + angle_deg = np.degrees(diff) + + print(f"置信度最高两个框主方向夹角: {angle_deg:.2f}°") + return angle_deg, annotated_img + + +# ------------------- 测试 ------------------- +# if __name__ == "__main__": +# weight_path = r'angle.pt' +# image_path = r"./test_image/3.jpg" +# save_path = "./inference_results/detected_3.jpg" +# +# #angle_deg, annotated_img = predict_obb_best_angle(weight_path, image_path, save_path) +# angle_deg,_ = predict_obb_best_angle(model_path=weight_path, image_path=image_path, save_path=save_path) +# annotated_img = None +# print(angle_deg) +# if annotated_img is not None: +# cv2.imshow("YOLO OBB Prediction", annotated_img) +# cv2.waitKey(0) +# cv2.destroyAllWindows() \ No newline at end of file diff --git a/src/vision/angle.pt b/src/vision/angle.pt new file mode 100644 index 0000000..7b5b32d Binary files /dev/null and b/src/vision/angle.pt differ diff --git a/src/vision/overflow.pt b/src/vision/overflow.pt new file mode 100644 index 0000000..3fd6d73 Binary files /dev/null and b/src/vision/overflow.pt differ diff --git a/src/vision/resize_main.py b/src/vision/resize_main.py new file mode 100644 index 0000000..f14e8e3 --- /dev/null +++ b/src/vision/resize_main.py @@ -0,0 +1,106 @@ +import os +import shutil +from pathlib import Path +from ultralytics import YOLO +import cv2 + +# --------------------------- +# ROI 裁剪函数 +# --------------------------- +def load_global_rois(txt_path): + """加载全局 ROI 坐标""" + rois = [] + if not os.path.exists(txt_path): + print(f"❌ ROI 文件不存在: {txt_path}") + return rois + with open(txt_path, 'r') as f: + for line in f: + line = line.strip() + if line: + try: + x, y, w, h = map(int, line.split(',')) + rois.append((x, y, w, h)) + print(f"📌 加载 ROI: (x={x}, y={y}, w={w}, h={h})") + except Exception as e: + print(f"⚠️ 无法解析 ROI 行: {line}, 错误: {e}") + return rois + +def crop_and_resize(img, rois, target_size=640): + """根据 ROI 裁剪并 resize""" + crops = [] + for i, (x, y, w, h) in enumerate(rois): + h_img, w_img = img.shape[:2] + if x < 0 or y < 0 or x + w > w_img or y + h > h_img: + print(f"⚠️ ROI 越界,跳过: {x},{y},{w},{h}") + continue + roi_img = img[y:y+h, x:x+w] + roi_resized = cv2.resize(roi_img, (target_size, target_size), interpolation=cv2.INTER_AREA) + crops.append((roi_resized, i)) + return crops + +# --------------------------- +# 分类函数 +# --------------------------- +def classify_and_save_images(model_path, input_folder, output_root, roi_file, target_size=640): + # 加载模型 + model = YOLO(model_path) + + # 确保输出根目录存在 + output_root = Path(output_root) + output_root.mkdir(parents=True, exist_ok=True) + + # 创建类别子文件夹 (class0 到 class4) + class_dirs = [] + for i in range(5): # 假设有5个类别 (0-4) + class_dir = output_root / f"class{i}" + class_dir.mkdir(exist_ok=True) + class_dirs.append(class_dir) + + # 加载 ROI + rois = load_global_rois(roi_file) + if len(rois) == 0: + print("❌ 没有有效 ROI,退出") + return + + # 遍历输入文件夹 + for img_path in Path(input_folder).glob("*.*"): + if img_path.suffix.lower() not in ['.jpg', '.jpeg', '.png', '.bmp', '.tif']: + continue + + try: + # 读取原图 + img = cv2.imread(str(img_path)) + if img is None: + print(f"❌ 无法读取图像: {img_path}") + continue + + # 根据 ROI 裁剪 + crops = crop_and_resize(img, rois, target_size) + + for roi_img, roi_idx in crops: + # YOLO 推理 + results = model(roi_img) + + pred = results[0].probs.data # 获取概率分布 + class_id = int(pred.argmax()) + + # 保存到对应类别文件夹 + suffix = f"_roi{roi_idx}" if len(crops) > 1 else "" + dst_path = class_dirs[class_id] / f"{img_path.stem}{suffix}{img_path.suffix}" + cv2.imwrite(dst_path, roi_img) # 保存裁剪后的 ROI 图像 + print(f"Processed {img_path.name}{suffix} -> Class {class_id}") + + except Exception as e: + print(f"Error processing {img_path.name}: {str(e)}") + +# --------------------------- +# 主程序 +# --------------------------- +if __name__ == "__main__": + model_path = r"overflow.pt" + input_folder = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/f6" + output_root = "/media/hx/04e879fa-d697-4b02-ac7e-a4148876ebb0/dataset/class111" + roi_file = "./roi_coordinates/1_rois.txt" # 训练时使用的 ROI 文件 + target_size = 640 + + classify_and_save_images(model_path, input_folder, output_root, roi_file, target_size) diff --git a/src/vision/resize_tuili_image_main.py b/src/vision/resize_tuili_image_main.py new file mode 100644 index 0000000..f9e95de --- /dev/null +++ b/src/vision/resize_tuili_image_main.py @@ -0,0 +1,184 @@ +import os +from pathlib import Path +import cv2 +import numpy as np +from ultralytics import YOLO + +# --------------------------- +# 类别映射 +# --------------------------- +CLASS_NAMES = { + 0: "未堆料", + 1: "小堆料", + 2: "大堆料", + 3: "未浇筑满", + 4: "浇筑满" +} + + +# --------------------------- +# 加载 ROI 列表 +# --------------------------- +def load_global_rois(txt_path): + rois = [] + if not os.path.exists(txt_path): + print(f"ROI 文件不存在: {txt_path}") + return rois + with open(txt_path, 'r') as f: + for line in f: + s = line.strip() + if s: + try: + x, y, w, h = map(int, s.split(',')) + rois.append((x, y, w, h)) + except Exception as e: + print(f"无法解析 ROI 行 '{s}': {e}") + return rois + + +# --------------------------- +# 裁剪并 resize ROI +# --------------------------- +def crop_and_resize(img, rois, target_size=640): + crops = [] + h_img, w_img = img.shape[:2] + for i, (x, y, w, h) in enumerate(rois): + if x < 0 or y < 0 or x + w > w_img or y + h > h_img: + continue + roi = img[y:y + h, x:x + w] + roi_resized = cv2.resize(roi, (target_size, target_size), interpolation=cv2.INTER_AREA) + crops.append((roi_resized, i)) + return crops + + +# --------------------------- +# class1/class2 加权判断 +# --------------------------- +def weighted_small_large(pred_probs, threshold=0.4, w1=0.3, w2=0.7): + p1 = float(pred_probs[1]) + p2 = float(pred_probs[2]) + total = p1 + p2 + if total > 0: + score = (w1 * p1 + w2 * p2) / total + else: + score = 0.0 + final_class = "大堆料" if score >= threshold else "小堆料" + return final_class, score, p1, p2 + + +# --------------------------- +# 单张图片推理函数 +# --------------------------- +def classify_image_weighted(image, model, threshold=0.4): + results = model(image) + pred_probs = results[0].probs.data.cpu().numpy().flatten() + class_id = int(pred_probs.argmax()) + confidence = float(pred_probs[class_id]) + class_name = CLASS_NAMES.get(class_id, f"未知类别({class_id})") + + # class1/class2 使用加权得分 + if class_id in [1, 2]: + final_class, score, p1, p2 = weighted_small_large(pred_probs, threshold=threshold) + else: + final_class = class_name + score = confidence + p1 = float(pred_probs[1]) + p2 = float(pred_probs[2]) + + return final_class, score, p1, p2 + + +# --------------------------- +# 实时视频流推理函数 +# --------------------------- +def real_time_inference(rtsp_url, model_path, roi_file, target_size=640, threshold=0.4): + """ + 从RTSP流实时推理 + :param rtsp_url: RTSP流URL + :param model_path: 模型路径 + :param roi_file: ROI文件路径 + :param target_size: 目标尺寸 + :param threshold: 分类阈值 + """ + # 加载模型 + model = YOLO(model_path) + + # 加载ROI + rois = load_global_rois(roi_file) + if not rois: + print("❌ 没有有效 ROI,退出") + return + + # 打开RTSP流 + cap = cv2.VideoCapture(rtsp_url) + + if not cap.isOpened(): + print(f"❌ 无法打开视频流: {rtsp_url}") + return + + print(f"✅ 成功连接到视频流: {rtsp_url}") + print("按 'q' 键退出,按 's' 键保存当前帧") + + frame_count = 0 + while True: + ret, frame = cap.read() + if not ret: + print("❌ 无法读取帧,可能连接已断开") + break + + frame_count += 1 + print(f"\n处理第 {frame_count} 帧") + + try: + # 裁剪并调整ROI + crops = crop_and_resize(frame, rois, target_size) + + for roi_resized, roi_idx in crops: + final_class, score, p1, p2 = classify_image_weighted(roi_resized, model, threshold=threshold) + + print(f"ROI {roi_idx} -> 类别: {final_class}, 加权分数: {score:.2f}, " + f"class1 置信度: {p1:.2f}, class2 置信度: {p2:.2f}") + + # 判断是否溢料 + if "大堆料" in final_class or "浇筑满" in final_class: + print(f"🚨 检测到溢料: ROI {roi_idx} - {final_class}") + + # 可视化(可选) + cv2.imshow(f'ROI {roi_idx}', roi_resized) + + # 显示原始帧 + cv2.imshow('Original Frame', frame) + + except Exception as e: + print(f"处理帧时出错: {e}") + continue + + # 键盘控制 + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): # 按q退出 + break + elif key == ord('s'): # 按s保存当前帧 + cv2.imwrite(f"frame_{frame_count}.jpg", frame) + print(f"保存帧到 frame_{frame_count}.jpg") + + # 清理资源 + cap.release() + cv2.destroyAllWindows() + print("✅ 视频流处理结束") + + +# --------------------------- +# 主函数 - 实时推理示例 +# --------------------------- +if __name__ == "__main__": + # RTSP流URL + rtsp_url = "rtsp://admin:XJ123456@192.168.1.51:554/streaming/channels/101" + + # 配置参数 + model_path = r"overflow.pt" + roi_file = r"./roi_coordinates/1_rois.txt" + target_size = 640 + threshold = 0.4 + + print("开始实时视频流推理...") + real_time_inference(rtsp_url, model_path, roi_file, target_size, threshold) diff --git a/src/vision/roi_coordinates/1_rois.txt b/src/vision/roi_coordinates/1_rois.txt new file mode 100644 index 0000000..bb8f71d --- /dev/null +++ b/src/vision/roi_coordinates/1_rois.txt @@ -0,0 +1 @@ +859,810,696,328 diff --git a/src/vision/test_image/1.jpg b/src/vision/test_image/1.jpg new file mode 100644 index 0000000..2882cd5 Binary files /dev/null and b/src/vision/test_image/1.jpg differ diff --git a/src/vision/test_image/2.jpg b/src/vision/test_image/2.jpg new file mode 100644 index 0000000..dcd5369 Binary files /dev/null and b/src/vision/test_image/2.jpg differ diff --git a/src/vision/test_image/3.jpg b/src/vision/test_image/3.jpg new file mode 100644 index 0000000..8df7feb Binary files /dev/null and b/src/vision/test_image/3.jpg differ diff --git a/tests/test_control/logs/app.log b/tests/test_control/logs/app.log index 561a8a9..3a185c3 100644 --- a/tests/test_control/logs/app.log +++ b/tests/test_control/logs/app.log @@ -1,3 +1,4 @@ 2025-09-12 19:40:24,636 - FeedingControl.FeedingController - INFO - 豸ʼ 2025-09-12 19:40:27,379 - FeedingControl.FeedingController - INFO - 豸ʼ 2025-09-12 19:40:31,216 - FeedingControl.FeedingController - INFO - 豸ʼ +2025-09-13 11:01:27,838 - FeedingControl.FeedingController - INFO - 豸ʼ diff --git a/tests/test_devices/logs/app.log b/tests/test_devices/logs/app.log index 68914f4..d6adecb 100644 --- a/tests/test_devices/logs/app.log +++ b/tests/test_devices/logs/app.log @@ -1 +1,5 @@ 2025-09-13 10:37:43,979 - FeedingControl.RelayController - INFO - Ƽ̵ door_upper (0) open +2025-09-13 11:03:31,681 - FeedingControl.RelayController - INFO - Ƽ̵ door_upper (0) open +2025-09-13 11:03:34,120 - FeedingControl.RelayController - ERROR - Чļ̵豸: invalid_device +2025-09-13 11:03:36,864 - FeedingControl.RelayController - ERROR - Чļ̵豸: invalid_device +2025-09-13 11:03:36,864 - FeedingControl.RelayController - INFO - Ƽ̵ door_upper (0) open