Compare commits
13 Commits
e6cd3658d0
...
ui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4a5a0a584 | ||
| 520b8818cd | |||
|
|
5ecc0f2bf5 | ||
|
|
809d07256a | ||
| 1d0d6d0b9f | |||
|
|
aaa795e00d | ||
|
|
316710a24c | ||
| 9db83deab6 | |||
| 6337567ddf | |||
| d0e74f49d0 | |||
|
|
33b88145bd | ||
|
|
a9e5181bce | ||
|
|
84c77ae459 |
2
.idea/huojv.iml
generated
2
.idea/huojv.iml
generated
@@ -2,7 +2,7 @@
|
|||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$" />
|
||||||
<orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.12 virtualenv at C:\Users\Administrator\Downloads\huojv\huojv\.venv" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,5 +3,5 @@
|
|||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="D:\CONDA\anaconda3" />
|
<option name="sdkName" value="D:\CONDA\anaconda3" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 virtualenv at C:\Users\Administrator\Downloads\huojv\huojv\.venv" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
30
config.json
30
config.json
@@ -1,33 +1,43 @@
|
|||||||
{
|
{
|
||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
"name": "电脑2",
|
"name": "服务器",
|
||||||
"serial_port": "COM12",
|
"serial_port": "COM17",
|
||||||
"serial_baudrate": 9600,
|
"serial_baudrate": 9600,
|
||||||
"camera_index": 1,
|
"camera_index": 1,
|
||||||
"camera_width": 1920,
|
"camera_width": 1920,
|
||||||
"camera_height": 1080,
|
"camera_height": 1080,
|
||||||
"move_velocity": 315,
|
"move_velocity": 470,
|
||||||
|
"active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2号机",
|
||||||
|
"serial_port": "COM14",
|
||||||
|
"serial_baudrate": 9600,
|
||||||
|
"camera_index": 2,
|
||||||
|
"camera_width": 1920,
|
||||||
|
"camera_height": 1080,
|
||||||
|
"move_velocity": 360,
|
||||||
"active": false
|
"active": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "服务器",
|
"name": "3号机",
|
||||||
"serial_port": "COM11",
|
"serial_port": "COM16",
|
||||||
"serial_baudrate": 9600,
|
"serial_baudrate": 9600,
|
||||||
"camera_index": 0,
|
"camera_index": 0,
|
||||||
"camera_width": 1920,
|
"camera_width": 1920,
|
||||||
"camera_height": 1080,
|
"camera_height": 1080,
|
||||||
"move_velocity": 470,
|
"move_velocity": 360,
|
||||||
"active": true
|
"active": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"display": {
|
"display": {
|
||||||
"preview_width": 1920,
|
"preview_width": 1920,
|
||||||
"preview_height": 1080,
|
"preview_height": 1080,
|
||||||
"preview_columns": 1,
|
"preview_columns": 2,
|
||||||
"preview_rows": 1,
|
"preview_rows": 2,
|
||||||
"show_preview": true,
|
"show_preview": true,
|
||||||
"preview_multi_window": false,
|
"preview_multi_window": false,
|
||||||
"preview_use_all_groups": false
|
"preview_use_all_groups": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,13 +214,28 @@ class ConfigGUI:
|
|||||||
|
|
||||||
# 确保串口下拉框列表与当前值一致
|
# 确保串口下拉框列表与当前值一致
|
||||||
if group and 'serial_port' in group:
|
if group and 'serial_port' in group:
|
||||||
# 如果当前串口不在选项里,追加
|
|
||||||
values = list(self.serial_port_cb.cget('values')) if self.serial_port_cb.cget('values') else []
|
values = list(self.serial_port_cb.cget('values')) if self.serial_port_cb.cget('values') else []
|
||||||
port_value = group['serial_port']
|
port_value = group['serial_port']
|
||||||
if port_value not in values:
|
|
||||||
values.append(port_value)
|
# 如果当前值不在列表中,尝试通过设备名匹配
|
||||||
self.serial_port_cb['values'] = values
|
matched = False
|
||||||
self.serial_port_cb.set(port_value)
|
if hasattr(self, 'port_display_map'):
|
||||||
|
for display_text, device in self.port_display_map.items():
|
||||||
|
if device == port_value:
|
||||||
|
if display_text not in values:
|
||||||
|
values.append(display_text)
|
||||||
|
self.serial_port_cb.set(display_text)
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not matched:
|
||||||
|
# 如果找不到匹配,直接添加原始值
|
||||||
|
if port_value not in values:
|
||||||
|
values.append(port_value)
|
||||||
|
if not hasattr(self, 'port_display_map'):
|
||||||
|
self.port_display_map = {}
|
||||||
|
self.port_display_map[port_value] = port_value
|
||||||
|
self.serial_port_cb.set(port_value)
|
||||||
|
|
||||||
def scan_cameras(self, max_index: int = 10):
|
def scan_cameras(self, max_index: int = 10):
|
||||||
"""扫描系统可用的采集卡索引,并填充下拉框"""
|
"""扫描系统可用的采集卡索引,并填充下拉框"""
|
||||||
@@ -302,23 +317,59 @@ class ConfigGUI:
|
|||||||
self.camera_index_cb.set(found[0])
|
self.camera_index_cb.set(found[0])
|
||||||
|
|
||||||
def scan_ports(self):
|
def scan_ports(self):
|
||||||
"""扫描系统可用的串口,并填充下拉框"""
|
"""扫描系统可用的串口,并填充下拉框(显示设备描述)"""
|
||||||
|
from utils.device_scanner import scan_serial_ports_with_info
|
||||||
|
|
||||||
found_real = []
|
found_real = []
|
||||||
|
port_display_map = {} # 显示文本 -> 实际设备名
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ports = serial.tools.list_ports.comports()
|
ports_info = scan_serial_ports_with_info()
|
||||||
found_real = [port.device for port in ports]
|
for port_info in ports_info:
|
||||||
# 按端口名排序
|
device = port_info['device']
|
||||||
found_real.sort(key=lambda x: int(x.replace('COM', '')) if x.replace('COM', '').isdigit() else 999)
|
description = port_info.get('description', '')
|
||||||
|
hwid = port_info.get('hwid', '')
|
||||||
|
|
||||||
|
# 显示格式:COM3 (设备描述) [HWID]
|
||||||
|
if description:
|
||||||
|
display_text = f"{device} ({description})"
|
||||||
|
else:
|
||||||
|
display_text = device
|
||||||
|
|
||||||
|
# 如果有HWID,添加到显示中(较短版本)
|
||||||
|
if hwid:
|
||||||
|
# 提取VID/PID部分(如果存在)
|
||||||
|
if 'VID_' in hwid and 'PID_' in hwid:
|
||||||
|
import re
|
||||||
|
vid_match = re.search(r'VID_([0-9A-F]+)', hwid)
|
||||||
|
pid_match = re.search(r'PID_([0-9A-F]+)', hwid)
|
||||||
|
if vid_match and pid_match:
|
||||||
|
display_text += f" [{vid_match.group(1)}:{pid_match.group(1)}]"
|
||||||
|
|
||||||
|
found_real.append(display_text)
|
||||||
|
port_display_map[display_text] = device
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"扫描串口错误: {e}")
|
print(f"扫描串口错误: {e}")
|
||||||
|
# 回退到简单扫描
|
||||||
|
try:
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
found_real = [port.device for port in ports]
|
||||||
|
found_real.sort(key=lambda x: int(x.replace('COM', '')) if x.replace('COM', '').isdigit() else 999)
|
||||||
|
port_display_map = {p: p for p in found_real}
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# 如果没有发现实际端口,使用默认端口列表
|
# 如果没有发现实际端口,使用默认端口列表
|
||||||
if not found_real:
|
if not found_real:
|
||||||
found = ["COM1", "COM2", "COM3", "COM4", "COM5", "COM6"] # 默认给一些常见端口
|
found = ["COM1", "COM2", "COM3", "COM4", "COM5", "COM6"]
|
||||||
|
port_display_map = {p: p for p in found}
|
||||||
messagebox.showwarning("警告", "未发现可用串口设备,已添加常用默认选项")
|
messagebox.showwarning("警告", "未发现可用串口设备,已添加常用默认选项")
|
||||||
else:
|
else:
|
||||||
found = found_real
|
found = found_real
|
||||||
messagebox.showinfo("扫描完成", f"发现可用串口: {', '.join(found)}")
|
messagebox.showinfo("扫描完成", f"发现 {len(found)} 个串口设备\n\n设备信息已包含描述和硬件ID")
|
||||||
|
|
||||||
|
# 保存映射关系,用于后续获取实际设备名
|
||||||
|
self.port_display_map = port_display_map
|
||||||
|
|
||||||
self.serial_port_cb['values'] = found
|
self.serial_port_cb['values'] = found
|
||||||
# 若当前无选择,则选择第一项
|
# 若当前无选择,则选择第一项
|
||||||
@@ -390,6 +441,11 @@ class ConfigGUI:
|
|||||||
else:
|
else:
|
||||||
# 字符串字段
|
# 字符串字段
|
||||||
value = value_str if value_str else group.get(key, '')
|
value = value_str if value_str else group.get(key, '')
|
||||||
|
# 特殊处理串口:从显示文本中提取实际设备名
|
||||||
|
if key == 'serial_port' and hasattr(self, 'port_display_map'):
|
||||||
|
# 如果值是显示文本,转换为实际设备名
|
||||||
|
if value in self.port_display_map:
|
||||||
|
value = self.port_display_map[value]
|
||||||
|
|
||||||
group[key] = value
|
group[key] = value
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -467,6 +523,11 @@ class ConfigGUI:
|
|||||||
else:
|
else:
|
||||||
# 字符串字段
|
# 字符串字段
|
||||||
value = value_str if value_str else group.get(key, '')
|
value = value_str if value_str else group.get(key, '')
|
||||||
|
# 特殊处理串口:从显示文本中提取实际设备名
|
||||||
|
if key == 'serial_port' and hasattr(self, 'port_display_map'):
|
||||||
|
# 如果值是显示文本,转换为实际设备名
|
||||||
|
if value in self.port_display_map:
|
||||||
|
value = self.port_display_map[value]
|
||||||
|
|
||||||
group[key] = value
|
group[key] = value
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -500,7 +561,7 @@ class ConfigGUI:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def start_program(self):
|
def start_program(self):
|
||||||
"""启动单个配置组的主程序"""
|
"""启动单个配置组的主程序并自动弹出采集卡预览窗口"""
|
||||||
# 保存配置(静默)
|
# 保存配置(静默)
|
||||||
if not self.save_config_silent():
|
if not self.save_config_silent():
|
||||||
messagebox.showerror("错误", "配置保存失败")
|
messagebox.showerror("错误", "配置保存失败")
|
||||||
@@ -527,11 +588,13 @@ class ConfigGUI:
|
|||||||
], creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0)
|
], creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0)
|
||||||
|
|
||||||
messagebox.showinfo("成功", f"已启动配置组: {active_group['name']}\n\n请在控制台查看运行状态")
|
messagebox.showinfo("成功", f"已启动配置组: {active_group['name']}\n\n请在控制台查看运行状态")
|
||||||
|
# 成功后弹出预览
|
||||||
|
self.start_preview()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("错误", f"启动失败: {e}")
|
messagebox.showerror("错误", f"启动失败: {e}")
|
||||||
|
|
||||||
def start_multi_program(self):
|
def start_multi_program(self):
|
||||||
"""启动多个配置组的主程序"""
|
"""启动多个配置组的主程序并自动弹出采集卡预览窗口"""
|
||||||
# 保存配置(静默)
|
# 保存配置(静默)
|
||||||
if not self.save_config_silent():
|
if not self.save_config_silent():
|
||||||
messagebox.showerror("错误", "配置保存失败")
|
messagebox.showerror("错误", "配置保存失败")
|
||||||
@@ -547,6 +610,8 @@ class ConfigGUI:
|
|||||||
], creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0)
|
], creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0)
|
||||||
|
|
||||||
messagebox.showinfo("提示", "多配置组启动器已打开\n\n请在控制台中选择要启动的配置组")
|
messagebox.showinfo("提示", "多配置组启动器已打开\n\n请在控制台中选择要启动的配置组")
|
||||||
|
# 成功后弹出预览
|
||||||
|
self.start_preview()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("错误", f"启动失败: {e}")
|
messagebox.showerror("错误", f"启动失败: {e}")
|
||||||
|
|
||||||
@@ -557,4 +622,3 @@ class ConfigGUI:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = ConfigGUI()
|
app = ConfigGUI()
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
|
|||||||
146
main.py
146
main.py
@@ -62,6 +62,18 @@ rw = (632, 378)
|
|||||||
v = active_group['move_velocity']
|
v = active_group['move_velocity']
|
||||||
|
|
||||||
def yolo_shibie(im_PIL, detections, model):
|
def yolo_shibie(im_PIL, detections, model):
|
||||||
|
detections = {
|
||||||
|
'center': None,
|
||||||
|
'next': None,
|
||||||
|
'npc1': None,
|
||||||
|
'npc2': None,
|
||||||
|
'npc3': None,
|
||||||
|
'npc4': None,
|
||||||
|
'boss': None,
|
||||||
|
'daojv': [],
|
||||||
|
'gw': [],
|
||||||
|
'zhaozi': None
|
||||||
|
}
|
||||||
results = model(im_PIL) # 目标检测
|
results = model(im_PIL) # 目标检测
|
||||||
for result in results:
|
for result in results:
|
||||||
for i in range(len(result.boxes.xyxy)):
|
for i in range(len(result.boxes.xyxy)):
|
||||||
@@ -186,7 +198,7 @@ while True:
|
|||||||
continue
|
continue
|
||||||
detections = yolo_shibie(im_opencv[1], detections, model)
|
detections = yolo_shibie(im_opencv[1], detections, model)
|
||||||
|
|
||||||
if shizi.tuwai(im_opencv[0]): # 进图算法
|
if shizi.tuwai(im_opencv[0]) or yolo_shibie(im_opencv[1], model0)['npc1'] is not None or yolo_shibie(im_opencv[1], model0)['npc2'] is not None or yolo_shibie(im_opencv[1], model0)['npc3'] is not None or yolo_shibie(im_opencv[1], model0)['npc4'] is not None: # 进图算法 # 进图算法
|
||||||
im_opencv = get_image.get_frame() # [RGB,PIL]
|
im_opencv = get_image.get_frame() # [RGB,PIL]
|
||||||
if im_opencv is None:
|
if im_opencv is None:
|
||||||
print("⚠️ 无法获取图像帧,跳过本次循环")
|
print("⚠️ 无法获取图像帧,跳过本次循环")
|
||||||
@@ -213,8 +225,12 @@ while True:
|
|||||||
move_to(rw, mb)
|
move_to(rw, mb)
|
||||||
continue
|
continue
|
||||||
elif detections['npc4'] is not None:
|
elif detections['npc4'] is not None:
|
||||||
if sq(detections['npc4'], rw) < 50:
|
npc4_pos = detections['npc4']
|
||||||
print("离npc4很近 直接进入")
|
current_distance = sq(npc4_pos, rw)
|
||||||
|
|
||||||
|
# 如果已经非常接近,直接进入
|
||||||
|
if current_distance < 30:
|
||||||
|
print(f"离npc4很近 (距离={current_distance:.1f}),直接进入")
|
||||||
keyboard.send_data("DD")
|
keyboard.send_data("DD")
|
||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
keyboard.release()
|
keyboard.release()
|
||||||
@@ -227,18 +243,132 @@ while True:
|
|||||||
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
|
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
else:
|
|
||||||
print("离npc4有点远 点击进入")
|
# 循环靠近npc4,直到足够接近或达到最大尝试次数
|
||||||
move_to(rw, detections['npc4'])
|
print(f"开始靠近npc4,当前距离={current_distance:.1f}")
|
||||||
|
max_attempts = 15 # 最大尝试次数
|
||||||
|
attempt = 0
|
||||||
|
last_distance = current_distance
|
||||||
|
stuck_count = 0 # 卡住计数器
|
||||||
|
|
||||||
|
while attempt < max_attempts:
|
||||||
|
attempt += 1
|
||||||
|
|
||||||
|
# 重新获取当前位置和npc4位置
|
||||||
|
im_opencv = get_image.get_frame()
|
||||||
|
if im_opencv is None:
|
||||||
|
print("⚠️ 无法获取图像帧")
|
||||||
|
break
|
||||||
|
|
||||||
|
detections_temp = {
|
||||||
|
'center': None, 'next': None,
|
||||||
|
'npc1': None, 'npc2': None, 'npc3': None, 'npc4': None,
|
||||||
|
'boss': None, 'zhaozi': None,
|
||||||
|
'daojv': [], 'gw': []
|
||||||
|
}
|
||||||
|
detections_temp = yolo_shibie(im_opencv[1], detections_temp, model0)
|
||||||
|
|
||||||
|
# 如果npc4丢失,重新检测
|
||||||
|
if detections_temp['npc4'] is None:
|
||||||
|
print(f"⚠️ npc4丢失,重新检测 (尝试 {attempt}/{max_attempts})")
|
||||||
|
time.sleep(0.5)
|
||||||
|
continue
|
||||||
|
|
||||||
|
npc4_pos = detections_temp['npc4']
|
||||||
|
current_distance = sq(npc4_pos, rw)
|
||||||
|
|
||||||
|
print(f" 尝试 {attempt}/{max_attempts}: 距离npc4={current_distance:.1f}")
|
||||||
|
|
||||||
|
# 如果已经足够接近,退出循环
|
||||||
|
if current_distance < 35:
|
||||||
|
print(f"✅ 已足够接近npc4 (距离={current_distance:.1f})")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 检测是否卡住(距离不再减小)
|
||||||
|
if abs(current_distance - last_distance) < 5:
|
||||||
|
stuck_count += 1
|
||||||
|
if stuck_count >= 3:
|
||||||
|
print(f"⚠️ 检测到卡住,尝试微调移动")
|
||||||
|
# 尝试向npc4方向微调
|
||||||
|
dx = npc4_pos[0] - rw[0]
|
||||||
|
dy = npc4_pos[1] - rw[1]
|
||||||
|
# 计算方向向量,但只移动一小段距离
|
||||||
|
step_size = 50 # 小步移动
|
||||||
|
distance_to_move = min(step_size, current_distance * 0.3)
|
||||||
|
if abs(dx) > abs(dy):
|
||||||
|
# 主要横向移动
|
||||||
|
target_x = rw[0] + int(dx / abs(dx) * distance_to_move) if dx != 0 else rw[0]
|
||||||
|
target_y = rw[1]
|
||||||
|
else:
|
||||||
|
# 主要纵向移动
|
||||||
|
target_x = rw[0]
|
||||||
|
target_y = rw[1] + int(dy / abs(dy) * distance_to_move) if dy != 0 else rw[1]
|
||||||
|
mb = (target_x, target_y)
|
||||||
|
move_to(rw, mb)
|
||||||
|
time.sleep(0.3)
|
||||||
|
stuck_count = 0
|
||||||
|
else:
|
||||||
|
# 正常移动,但使用小步长
|
||||||
|
step_size = min(80, current_distance * 0.4) # 移动距离为目标距离的40%,最多80像素
|
||||||
|
dx = npc4_pos[0] - rw[0]
|
||||||
|
dy = npc4_pos[1] - rw[1]
|
||||||
|
# 归一化方向向量
|
||||||
|
if abs(dx) > 0.1 or abs(dy) > 0.1:
|
||||||
|
direction_mag = math.sqrt(dx*dx + dy*dy)
|
||||||
|
target_x = rw[0] + int(dx / direction_mag * step_size)
|
||||||
|
target_y = rw[1] + int(dy / direction_mag * step_size)
|
||||||
|
mb = (target_x, target_y)
|
||||||
|
move_to(rw, mb)
|
||||||
|
time.sleep(0.2) # 短暂等待,让角色移动
|
||||||
|
else:
|
||||||
|
# 距离在减小,正常移动
|
||||||
|
stuck_count = 0
|
||||||
|
step_size = min(100, current_distance * 0.5) # 移动距离为目标距离的50%,最多100像素
|
||||||
|
dx = npc4_pos[0] - rw[0]
|
||||||
|
dy = npc4_pos[1] - rw[1]
|
||||||
|
# 归一化方向向量
|
||||||
|
if abs(dx) > 0.1 or abs(dy) > 0.1:
|
||||||
|
direction_mag = math.sqrt(dx*dx + dy*dy)
|
||||||
|
target_x = rw[0] + int(dx / direction_mag * step_size)
|
||||||
|
target_y = rw[1] + int(dy / direction_mag * step_size)
|
||||||
|
mb = (target_x, target_y)
|
||||||
|
move_to(rw, mb)
|
||||||
|
time.sleep(0.2) # 短暂等待,让角色移动
|
||||||
|
|
||||||
|
last_distance = current_distance
|
||||||
|
|
||||||
|
# 短暂延迟,让角色位置更新
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
# 移动完成后,检查是否可以进入
|
||||||
|
final_distance = sq(npc4_pos, rw)
|
||||||
|
print(f"靠近完成,最终距离={final_distance:.1f}")
|
||||||
|
|
||||||
|
if final_distance < 50:
|
||||||
|
print("距离足够近,尝试进入")
|
||||||
|
keyboard.send_data("DD")
|
||||||
|
time.sleep(0.15)
|
||||||
|
keyboard.release()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
im_opencv = get_image.get_frame() # [RGB,PIL]
|
im_opencv = get_image.get_frame()
|
||||||
if im_opencv is None:
|
if im_opencv is None:
|
||||||
print("⚠️ 无法获取图像帧")
|
print("⚠️ 无法获取图像帧")
|
||||||
continue
|
continue
|
||||||
if shizi.daoying(im_opencv[0]):
|
if shizi.daoying(im_opencv[0]):
|
||||||
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
|
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
else:
|
||||||
|
print(f"⚠️ 未能足够接近npc4 (距离={final_distance:.1f}),尝试点击进入")
|
||||||
|
move_to(rw, npc4_pos)
|
||||||
|
time.sleep(1)
|
||||||
|
im_opencv = get_image.get_frame()
|
||||||
|
if im_opencv is None:
|
||||||
|
print("⚠️ 无法获取图像帧")
|
||||||
|
continue
|
||||||
|
if shizi.daoying(im_opencv[0]):
|
||||||
|
mouse_gui.send_data_absolute(rw[0], rw[1] - 110, may=1)
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
elif shizi.tiaozhan(im_opencv[0]): # 开启挑战
|
elif shizi.tiaozhan(im_opencv[0]): # 开启挑战
|
||||||
print('进入塔4')
|
print('进入塔4')
|
||||||
mouse_gui.send_data_absolute(left + 1100, top + 600, may=1)
|
mouse_gui.send_data_absolute(left + 1100, top + 600, may=1)
|
||||||
|
|||||||
183
main_single.py
183
main_single.py
@@ -69,28 +69,42 @@ def run_automation_for_group(group_index):
|
|||||||
v = group['move_velocity'] # 从配置读取移动速度
|
v = group['move_velocity'] # 从配置读取移动速度
|
||||||
|
|
||||||
def yolo_shibie(im_PIL, detections, model):
|
def yolo_shibie(im_PIL, detections, model):
|
||||||
results = model(im_PIL)
|
try:
|
||||||
for result in results:
|
results = model(im_PIL)
|
||||||
for i in range(len(result.boxes.xyxy)):
|
for result in results:
|
||||||
left, top, right, bottom = result.boxes.xyxy[i]
|
if result.boxes is None or len(result.boxes.xyxy) == 0:
|
||||||
scalar_tensor = result.boxes.cls[i]
|
continue
|
||||||
value = scalar_tensor.item()
|
for i in range(len(result.boxes.xyxy)):
|
||||||
label = result.names[int(value)]
|
try:
|
||||||
if label == 'center' or label == 'next' or label == 'boss' or label == 'zhaozi':
|
left = float(result.boxes.xyxy[i][0])
|
||||||
player_x = int(left + (right - left) / 2)
|
top = float(result.boxes.xyxy[i][1])
|
||||||
player_y = int(top + (bottom - top) / 2) + 30
|
right = float(result.boxes.xyxy[i][2])
|
||||||
RW = [player_x, player_y]
|
bottom = float(result.boxes.xyxy[i][3])
|
||||||
detections[label] = RW
|
cls_id = int(result.boxes.cls[i])
|
||||||
elif label == 'daojv' or label == 'gw':
|
label = result.names[cls_id]
|
||||||
player_x = int(left + (right - left) / 2)
|
|
||||||
player_y = int(top + (bottom - top) / 2) + 30
|
if label == 'center' or label == 'next' or label == 'boss' or label == 'zhaozi':
|
||||||
RW = [player_x, player_y]
|
player_x = int(left + (right - left) / 2) + 3
|
||||||
detections[label].append(RW)
|
player_y = int(top + (bottom - top) / 2) + 40
|
||||||
elif label == 'npc1' or label == 'npc2' or label == 'npc3' or label == 'npc4':
|
RW = [player_x, player_y]
|
||||||
player_x = int(left + (right - left) / 2)
|
detections[label] = RW
|
||||||
player_y = int(bottom) + 30
|
elif label == 'daojv' or label == 'gw':
|
||||||
RW = [player_x, player_y]
|
player_x = int(left + (right - left) / 2) + 3
|
||||||
detections[label] = RW
|
player_y = int(top + (bottom - top) / 2) + 40
|
||||||
|
RW = [player_x, player_y]
|
||||||
|
if label not in detections:
|
||||||
|
detections[label] = []
|
||||||
|
detections[label].append(RW)
|
||||||
|
elif label == 'npc1' or label == 'npc2' or label == 'npc3' or label == 'npc4':
|
||||||
|
player_x = int(left + (right - left) / 2)
|
||||||
|
player_y = int(bottom) + 30
|
||||||
|
RW = [player_x, player_y]
|
||||||
|
detections[label] = RW
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 处理检测框时出错: {e}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ YOLO检测出错: {e}")
|
||||||
return detections
|
return detections
|
||||||
|
|
||||||
def sq(p1, p2):
|
def sq(p1, p2):
|
||||||
@@ -119,25 +133,76 @@ def run_automation_for_group(group_index):
|
|||||||
best_point = p
|
best_point = p
|
||||||
return best_point
|
return best_point
|
||||||
|
|
||||||
|
def safe_keyboard_send(data, max_retries=3):
|
||||||
|
"""安全的键盘发送函数,带重试机制"""
|
||||||
|
nonlocal keyboard
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
keyboard.send_data(data)
|
||||||
|
return True
|
||||||
|
except (serial.serialutil.SerialException, PermissionError, OSError) as e:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
print(f"⚠️ 键盘发送失败 (尝试 {attempt + 1}/{max_retries}): {e}")
|
||||||
|
time.sleep(0.1 * (attempt + 1)) # 递增延迟
|
||||||
|
# 尝试重新初始化串口
|
||||||
|
try:
|
||||||
|
if serial.ser and serial.ser.is_open:
|
||||||
|
serial.ser.close()
|
||||||
|
time.sleep(0.2)
|
||||||
|
init_mouse_keyboard(group)
|
||||||
|
# 重新创建keyboard对象
|
||||||
|
keyboard = ch9329Comm.keyboard.DataComm()
|
||||||
|
except Exception as init_e:
|
||||||
|
print(f"⚠️ 重新初始化串口失败: {init_e}")
|
||||||
|
else:
|
||||||
|
print(f"❌ 键盘发送失败,已重试 {max_retries} 次: {e}")
|
||||||
|
raise
|
||||||
|
return False
|
||||||
|
|
||||||
|
def safe_keyboard_release(max_retries=3):
|
||||||
|
"""安全的键盘释放函数,带重试机制"""
|
||||||
|
nonlocal keyboard
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
keyboard.release()
|
||||||
|
return True
|
||||||
|
except (serial.serialutil.SerialException, PermissionError, OSError) as e:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
time.sleep(0.1 * (attempt + 1))
|
||||||
|
# 尝试重新初始化串口
|
||||||
|
try:
|
||||||
|
if serial.ser and serial.ser.is_open:
|
||||||
|
serial.ser.close()
|
||||||
|
time.sleep(0.2)
|
||||||
|
init_mouse_keyboard(group)
|
||||||
|
keyboard = ch9329Comm.keyboard.DataComm()
|
||||||
|
except Exception as init_e:
|
||||||
|
print(f"⚠️ 重新初始化串口失败: {init_e}")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ 键盘释放失败: {e}")
|
||||||
|
# 释放失败不算致命错误,继续执行
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
def move_randomly(rw, k):
|
def move_randomly(rw, k):
|
||||||
k = k % 4
|
k = k % 4
|
||||||
suiji_t = float(random.randint(10, 13) / 10)
|
suiji_t = float(random.randint(10, 13) / 10)
|
||||||
if k == 0:
|
if k == 0:
|
||||||
keyboard.send_data("66")
|
if safe_keyboard_send("66"):
|
||||||
time.sleep(suiji_t)
|
time.sleep(suiji_t)
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
elif k == 1:
|
elif k == 1:
|
||||||
keyboard.send_data("88")
|
if safe_keyboard_send("88"):
|
||||||
time.sleep(suiji_t)
|
time.sleep(suiji_t)
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
elif k == 2:
|
elif k == 2:
|
||||||
keyboard.send_data("44")
|
if safe_keyboard_send("44"):
|
||||||
time.sleep(suiji_t)
|
time.sleep(suiji_t)
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
elif k == 3:
|
elif k == 3:
|
||||||
keyboard.send_data("22")
|
if safe_keyboard_send("22"):
|
||||||
time.sleep(suiji_t)
|
time.sleep(suiji_t)
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
return k + 1
|
return k + 1
|
||||||
|
|
||||||
def move_to(rw, mb):
|
def move_to(rw, mb):
|
||||||
@@ -145,22 +210,22 @@ def run_automation_for_group(group_index):
|
|||||||
nonlocal v
|
nonlocal v
|
||||||
v = group['move_velocity']
|
v = group['move_velocity']
|
||||||
if rw[0] >= mb[0]:
|
if rw[0] >= mb[0]:
|
||||||
keyboard.send_data("44")
|
if safe_keyboard_send("44"):
|
||||||
time.sleep(float(abs(rw[0] - mb[0]) / v))
|
time.sleep(float(abs(rw[0] - mb[0]) / v))
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
else:
|
else:
|
||||||
keyboard.send_data("66")
|
if safe_keyboard_send("66"):
|
||||||
time.sleep(float(abs(rw[0] - mb[0]) / v))
|
time.sleep(float(abs(rw[0] - mb[0]) / v))
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
|
|
||||||
if rw[1] >= mb[1]:
|
if rw[1] >= mb[1]:
|
||||||
keyboard.send_data("88")
|
if safe_keyboard_send("88"):
|
||||||
time.sleep(float(abs(rw[1] - mb[1]) / v))
|
time.sleep(float(abs(rw[1] - mb[1]) / v))
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
else:
|
else:
|
||||||
keyboard.send_data("22")
|
if safe_keyboard_send("22"):
|
||||||
time.sleep(float(abs(rw[1] - mb[1]) / v))
|
time.sleep(float(abs(rw[1] - mb[1]) / v))
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
|
|
||||||
# 主循环
|
# 主循环
|
||||||
print(f"🔄 配置组 {group['name']} 开始自动化循环...")
|
print(f"🔄 配置组 {group['name']} 开始自动化循环...")
|
||||||
@@ -211,9 +276,9 @@ def run_automation_for_group(group_index):
|
|||||||
elif detections['npc4'] is not None:
|
elif detections['npc4'] is not None:
|
||||||
if sq(detections['npc4'], rw) < 50:
|
if sq(detections['npc4'], rw) < 50:
|
||||||
print(f"[{group['name']}] 离npc4很近 直接进入")
|
print(f"[{group['name']}] 离npc4很近 直接进入")
|
||||||
keyboard.send_data("DD")
|
if safe_keyboard_send("DD"):
|
||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
im_opencv = get_image.get_frame()
|
im_opencv = get_image.get_frame()
|
||||||
if im_opencv and shizi.daoying(im_opencv[0]):
|
if im_opencv and shizi.daoying(im_opencv[0]):
|
||||||
@@ -257,9 +322,9 @@ def run_automation_for_group(group_index):
|
|||||||
if len(detections['daojv']) != 0:
|
if len(detections['daojv']) != 0:
|
||||||
move_to(rw, process_points(detections['daojv']))
|
move_to(rw, process_points(detections['daojv']))
|
||||||
for i in range(3 + len(detections['daojv'])):
|
for i in range(3 + len(detections['daojv'])):
|
||||||
keyboard.send_data("AA")
|
if safe_keyboard_send("AA"):
|
||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
continue
|
continue
|
||||||
if shizi.tuichu(im_opencv[0]) and detections['next'] is None and len(detections['daojv']) == 0 and len(detections['gw']) == 0 and boss_pd:
|
if shizi.tuichu(im_opencv[0]) and detections['next'] is None and len(detections['daojv']) == 0 and len(detections['gw']) == 0 and boss_pd:
|
||||||
print(f"[{group['name']}] 识别到可以退出挑战!!!!!!!!!!!!!!!!!!")
|
print(f"[{group['name']}] 识别到可以退出挑战!!!!!!!!!!!!!!!!!!")
|
||||||
@@ -314,9 +379,9 @@ def run_automation_for_group(group_index):
|
|||||||
panduan = True
|
panduan = True
|
||||||
move_to(rw, detections['next'])
|
move_to(rw, detections['next'])
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
keyboard.send_data("DD")
|
if safe_keyboard_send("DD"):
|
||||||
time.sleep(0.15)
|
time.sleep(0.15)
|
||||||
keyboard.release()
|
safe_keyboard_release()
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
k = move_randomly(rw, k)
|
k = move_randomly(rw, k)
|
||||||
@@ -334,6 +399,14 @@ def run_automation_for_group(group_index):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 清理资源
|
||||||
|
try:
|
||||||
|
if serial.ser and serial.ser.is_open:
|
||||||
|
serial.ser.close()
|
||||||
|
print(f"✅ 配置组 {group['name']} 串口已关闭")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 关闭串口时出错: {e}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
group_index = int(sys.argv[1])
|
group_index = int(sys.argv[1])
|
||||||
|
|||||||
197
utils/device_scanner.py
Normal file
197
utils/device_scanner.py
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
"""
|
||||||
|
设备扫描工具 - 通过唯一标识符而非索引来识别设备
|
||||||
|
解决重启后设备序号混乱的问题
|
||||||
|
"""
|
||||||
|
import cv2
|
||||||
|
import serial.tools.list_ports
|
||||||
|
import warnings
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
|
||||||
|
def get_camera_info(cam_index: int) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
获取采集卡的详细信息(包括设备名称)
|
||||||
|
:param cam_index: 采集卡索引
|
||||||
|
:return: 包含设备信息的字典,如果失败返回None
|
||||||
|
"""
|
||||||
|
old_stderr = sys.stderr
|
||||||
|
suppressed_output = io.StringIO()
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stderr = suppressed_output
|
||||||
|
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
|
||||||
|
# 尝试多种后端
|
||||||
|
backends = [
|
||||||
|
(cam_index, cv2.CAP_DSHOW),
|
||||||
|
(cam_index, cv2.CAP_ANY),
|
||||||
|
(cam_index, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
cap = None
|
||||||
|
for idx, backend in backends:
|
||||||
|
try:
|
||||||
|
if backend is not None:
|
||||||
|
cap = cv2.VideoCapture(idx, backend)
|
||||||
|
else:
|
||||||
|
cap = cv2.VideoCapture(idx)
|
||||||
|
|
||||||
|
if cap.isOpened():
|
||||||
|
ret, frame = cap.read()
|
||||||
|
if ret and frame is not None:
|
||||||
|
# 尝试获取设备名称(不同后端可能支持不同)
|
||||||
|
device_name = None
|
||||||
|
backend_name = None
|
||||||
|
|
||||||
|
if backend == cv2.CAP_DSHOW:
|
||||||
|
backend_name = "DirectShow"
|
||||||
|
# DirectShow可能支持获取设备名称
|
||||||
|
try:
|
||||||
|
# 某些情况下可以通过属性获取
|
||||||
|
pass # OpenCV限制,无法直接获取设备名称
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
elif backend == cv2.CAP_ANY:
|
||||||
|
backend_name = "Any"
|
||||||
|
else:
|
||||||
|
backend_name = "Default"
|
||||||
|
|
||||||
|
# 获取分辨率信息作为辅助标识
|
||||||
|
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
|
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||||
|
|
||||||
|
cap.release()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'index': cam_index,
|
||||||
|
'backend': backend_name,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'name': f"采集卡-{cam_index}",
|
||||||
|
'available': True
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
if cap:
|
||||||
|
cap.release()
|
||||||
|
cap = None
|
||||||
|
except Exception:
|
||||||
|
if cap:
|
||||||
|
try:
|
||||||
|
cap.release()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
cap = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
sys.stderr = old_stderr
|
||||||
|
|
||||||
|
def scan_cameras_with_info(max_index: int = 10) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
扫描所有可用采集卡,返回详细信息列表
|
||||||
|
:param max_index: 最大扫描索引
|
||||||
|
:return: 采集卡信息列表
|
||||||
|
"""
|
||||||
|
cameras = []
|
||||||
|
for i in range(max_index + 1):
|
||||||
|
info = get_camera_info(i)
|
||||||
|
if info:
|
||||||
|
cameras.append(info)
|
||||||
|
return cameras
|
||||||
|
|
||||||
|
def get_serial_port_info(port_name: str) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
获取串口的详细信息(包括硬件ID和描述)
|
||||||
|
:param port_name: 串口名称(如 "COM3")
|
||||||
|
:return: 包含串口信息的字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
for port in ports:
|
||||||
|
if port.device == port_name:
|
||||||
|
return {
|
||||||
|
'device': port.device,
|
||||||
|
'description': port.description,
|
||||||
|
'hwid': port.hwid, # 硬件ID(唯一标识)
|
||||||
|
'vid': port.vid if hasattr(port, 'vid') else None,
|
||||||
|
'pid': port.pid if hasattr(port, 'pid') else None,
|
||||||
|
'serial_number': port.serial_number if hasattr(port, 'serial_number') else None,
|
||||||
|
'manufacturer': port.manufacturer if hasattr(port, 'manufacturer') else None,
|
||||||
|
'name': port.description or port.device
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 获取串口信息失败: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def scan_serial_ports_with_info() -> List[Dict]:
|
||||||
|
"""
|
||||||
|
扫描所有可用串口,返回详细信息列表
|
||||||
|
:return: 串口信息列表
|
||||||
|
"""
|
||||||
|
ports_info = []
|
||||||
|
try:
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
for port in ports:
|
||||||
|
info = {
|
||||||
|
'device': port.device,
|
||||||
|
'description': port.description,
|
||||||
|
'hwid': port.hwid,
|
||||||
|
'vid': port.vid if hasattr(port, 'vid') else None,
|
||||||
|
'pid': port.pid if hasattr(port, 'pid') else None,
|
||||||
|
'serial_number': port.serial_number if hasattr(port, 'serial_number') else None,
|
||||||
|
'manufacturer': port.manufacturer if hasattr(port, 'manufacturer') else None,
|
||||||
|
'name': port.description or port.device
|
||||||
|
}
|
||||||
|
ports_info.append(info)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 扫描串口失败: {e}")
|
||||||
|
|
||||||
|
# 按设备名排序
|
||||||
|
ports_info.sort(key=lambda x: int(x['device'].replace('COM', '')) if x['device'].replace('COM', '').isdigit() else 999)
|
||||||
|
return ports_info
|
||||||
|
|
||||||
|
def find_camera_by_index(cameras: List[Dict], index: int) -> Optional[Dict]:
|
||||||
|
"""通过索引查找采集卡"""
|
||||||
|
for cam in cameras:
|
||||||
|
if cam['index'] == index:
|
||||||
|
return cam
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_serial_by_hwid(ports: List[Dict], hwid: str) -> Optional[Dict]:
|
||||||
|
"""通过硬件ID查找串口(最可靠)"""
|
||||||
|
for port in ports:
|
||||||
|
if port['hwid'] == hwid:
|
||||||
|
return port
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_serial_by_device(ports: List[Dict], device: str) -> Optional[Dict]:
|
||||||
|
"""通过设备名查找串口(兼容旧配置)"""
|
||||||
|
for port in ports:
|
||||||
|
if port['device'] == device:
|
||||||
|
return port
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 测试代码
|
||||||
|
print("=" * 60)
|
||||||
|
print("采集卡扫描:")
|
||||||
|
print("=" * 60)
|
||||||
|
cameras = scan_cameras_with_info()
|
||||||
|
for cam in cameras:
|
||||||
|
print(f" 索引 {cam['index']}: {cam['name']} ({cam['width']}x{cam['height']})")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("串口扫描:")
|
||||||
|
print("=" * 60)
|
||||||
|
ports = scan_serial_ports_with_info()
|
||||||
|
for port in ports:
|
||||||
|
print(f" {port['device']}: {port['name']}")
|
||||||
|
print(f" 硬件ID: {port['hwid']}")
|
||||||
|
if port['vid'] and port['pid']:
|
||||||
|
print(f" VID/PID: {port['vid']:04X}/{port['pid']:04X}")
|
||||||
|
|
||||||
@@ -10,18 +10,32 @@ mouse = None
|
|||||||
def init_mouse_keyboard(config_group):
|
def init_mouse_keyboard(config_group):
|
||||||
"""初始化鼠标和串口"""
|
"""初始化鼠标和串口"""
|
||||||
global serial, mouse
|
global serial, mouse
|
||||||
|
from utils.logger import logger
|
||||||
|
|
||||||
# 初始化串口
|
# 初始化串口
|
||||||
serial.ser = serial.Serial(
|
try:
|
||||||
config_group['serial_port'],
|
logger.info(f"🔧 正在打开串口: {config_group['serial_port']} @ {config_group['serial_baudrate']}")
|
||||||
config_group['serial_baudrate']
|
serial.ser = serial.Serial(
|
||||||
)
|
config_group['serial_port'],
|
||||||
|
config_group['serial_baudrate'],
|
||||||
|
timeout=1
|
||||||
|
)
|
||||||
|
logger.info(f"✅ 串口已打开: {config_group['serial_port']} @ {config_group['serial_baudrate']}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ 串口打开失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
# 初始化鼠标
|
# 初始化鼠标
|
||||||
mouse = ch9329Comm.mouse.DataComm(
|
try:
|
||||||
config_group['camera_width'],
|
logger.info(f"🔧 正在初始化鼠标: {config_group['camera_width']}x{config_group['camera_height']}")
|
||||||
config_group['camera_height']
|
mouse = ch9329Comm.mouse.DataComm(
|
||||||
)
|
config_group['camera_width'],
|
||||||
print(f"✅ 串口已打开: {config_group['serial_port']} {config_group['serial_baudrate']}")
|
config_group['camera_height']
|
||||||
print(f"✅ 鼠标已初始化: {config_group['camera_width']}x{config_group['camera_height']}")
|
)
|
||||||
|
logger.info(f"✅ 鼠标已初始化: {config_group['camera_width']}x{config_group['camera_height']}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ 鼠标初始化失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
def bezier_point(t, p0, p1, p2, p3):
|
def bezier_point(t, p0, p1, p2, p3):
|
||||||
"""计算三次贝塞尔曲线上的点"""
|
"""计算三次贝塞尔曲线上的点"""
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def tuwai(image):
|
|||||||
img_bytes = img_encoded.tobytes()
|
img_bytes = img_encoded.tobytes()
|
||||||
result = ocr.classification(img_bytes)
|
result = ocr.classification(img_bytes)
|
||||||
print(result)
|
print(result)
|
||||||
if result == "tap" or result=='tqp' or result=='top':
|
if result == "tap" or result=='tqp' or result=='top' or result=='tp':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|||||||
56
yolo_test.py
56
yolo_test.py
@@ -161,6 +161,9 @@ def yolo_shibie(im_PIL, detections, model, enhance_enabled=False, enhance_params
|
|||||||
|
|
||||||
# ✅ 提取检测信息
|
# ✅ 提取检测信息
|
||||||
if result.boxes is not None and len(result.boxes.xyxy) > 0:
|
if result.boxes is not None and len(result.boxes.xyxy) > 0:
|
||||||
|
# 用于存储多个候选npc4(如果检测到多个)
|
||||||
|
npc4_candidates = []
|
||||||
|
|
||||||
for i in range(len(result.boxes.xyxy)):
|
for i in range(len(result.boxes.xyxy)):
|
||||||
try:
|
try:
|
||||||
left = float(result.boxes.xyxy[i][0])
|
left = float(result.boxes.xyxy[i][0])
|
||||||
@@ -170,10 +173,36 @@ def yolo_shibie(im_PIL, detections, model, enhance_enabled=False, enhance_params
|
|||||||
cls_id = int(result.boxes.cls[i])
|
cls_id = int(result.boxes.cls[i])
|
||||||
label = result.names[cls_id]
|
label = result.names[cls_id]
|
||||||
|
|
||||||
if label in ['center', 'next', 'npc1', 'npc2', 'npc3', 'npc4', 'boss', 'zhaozi']:
|
# 获取置信度(如果可用)
|
||||||
|
confidence = float(result.boxes.conf[i]) if hasattr(result.boxes, 'conf') and len(result.boxes.conf) > i else 1.0
|
||||||
|
|
||||||
|
# npc1-npc4 使用底部位置(与main.py保持一致)
|
||||||
|
if label in ['npc1', 'npc2', 'npc3', 'npc4']:
|
||||||
|
player_x = int(left + (right - left) / 2)
|
||||||
|
player_y = int(bottom) + 30 # 使用底部位置,与main.py保持一致
|
||||||
|
position = [player_x, player_y]
|
||||||
|
|
||||||
|
# 特殊处理npc4:如果检测到多个,收集所有候选
|
||||||
|
if label == 'npc4':
|
||||||
|
npc4_candidates.append({
|
||||||
|
'position': position,
|
||||||
|
'confidence': confidence,
|
||||||
|
'box': [left, top, right, bottom],
|
||||||
|
'area': (right - left) * (bottom - top) # 检测框面积
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# npc1-npc3直接赋值(如果已经有值,保留置信度更高的)
|
||||||
|
if detections[label] is None or (hasattr(result.boxes, 'conf') and
|
||||||
|
confidence > 0.5):
|
||||||
|
detections[label] = position
|
||||||
|
|
||||||
|
# 其他目标使用中心点
|
||||||
|
elif label in ['center', 'next', 'boss', 'zhaozi']:
|
||||||
player_x = int(left + (right - left) / 2) + 3
|
player_x = int(left + (right - left) / 2) + 3
|
||||||
player_y = int(top + (bottom - top) / 2) + 40
|
player_y = int(top + (bottom - top) / 2) + 40
|
||||||
detections[label] = [player_x, player_y]
|
detections[label] = [player_x, player_y]
|
||||||
|
|
||||||
|
# 道具和怪物可以多个
|
||||||
elif label in ['daojv', 'gw']:
|
elif label in ['daojv', 'gw']:
|
||||||
player_x = int(left + (right - left) / 2) + 3
|
player_x = int(left + (right - left) / 2) + 3
|
||||||
player_y = int(top + (bottom - top) / 2) + 40
|
player_y = int(top + (bottom - top) / 2) + 40
|
||||||
@@ -181,10 +210,35 @@ def yolo_shibie(im_PIL, detections, model, enhance_enabled=False, enhance_params
|
|||||||
if label not in detections:
|
if label not in detections:
|
||||||
detections[label] = []
|
detections[label] = []
|
||||||
detections[label].append([player_x, player_y])
|
detections[label].append([player_x, player_y])
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ 处理检测框时出错: {e}")
|
print(f"⚠️ 处理检测框时出错: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 处理npc4:如果检测到多个,选择最合适的
|
||||||
|
if npc4_candidates:
|
||||||
|
# 按置信度排序,选择置信度最高的
|
||||||
|
npc4_candidates.sort(key=lambda x: x['confidence'], reverse=True)
|
||||||
|
|
||||||
|
# 选择最佳候选(置信度最高且面积合理)
|
||||||
|
best_npc4 = None
|
||||||
|
for candidate in npc4_candidates:
|
||||||
|
# 置信度阈值:至少0.3(可根据实际情况调整)
|
||||||
|
if candidate['confidence'] >= 0.3:
|
||||||
|
# 检查检测框面积是否合理(避免过小的误检)
|
||||||
|
area = candidate['area']
|
||||||
|
if area > 100: # 最小面积阈值
|
||||||
|
best_npc4 = candidate
|
||||||
|
break
|
||||||
|
|
||||||
|
if best_npc4:
|
||||||
|
detections['npc4'] = best_npc4['position']
|
||||||
|
# 可选:输出调试信息
|
||||||
|
# print(f"✅ 检测到npc4: 位置={best_npc4['position']}, 置信度={best_npc4['confidence']:.2f}")
|
||||||
|
elif len(npc4_candidates) == 1:
|
||||||
|
# 如果只有一个候选,即使置信度较低也使用
|
||||||
|
detections['npc4'] = npc4_candidates[0]['position']
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ YOLO检测出错: {e}")
|
print(f"⚠️ YOLO检测出错: {e}")
|
||||||
|
|
||||||
|
|||||||
227
yolotest2.py
Normal file
227
yolotest2.py
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
"""
|
||||||
|
从main.py提取的YOLO识别测试文件
|
||||||
|
使用与main.py相同的识别逻辑
|
||||||
|
"""
|
||||||
|
import cv2
|
||||||
|
from utils.get_image import GetImage
|
||||||
|
from ultralytics import YOLO
|
||||||
|
from config import config_manager
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 检查模型文件是否存在
|
||||||
|
model_path = r"best.pt"
|
||||||
|
model0_path = r"best0.pt"
|
||||||
|
|
||||||
|
if not os.path.exists(model_path):
|
||||||
|
print(f"❌ 模型文件不存在: {model_path}")
|
||||||
|
exit(1)
|
||||||
|
if not os.path.exists(model0_path):
|
||||||
|
print(f"❌ 模型文件不存在: {model0_path}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 加载YOLO模型(与main.py保持一致)
|
||||||
|
try:
|
||||||
|
model = YOLO(model_path).to('cuda')
|
||||||
|
model0 = YOLO(model0_path).to('cuda')
|
||||||
|
print(f"✅ 模型加载成功: {model_path}")
|
||||||
|
print(f"✅ 模型加载成功: {model0_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 模型加载失败: {e}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def yolo_shibie(im_PIL, detections, model):
|
||||||
|
"""
|
||||||
|
YOLO识别函数(与main.py中的实现完全一致)
|
||||||
|
:param im_PIL: PIL图像对象
|
||||||
|
:param detections: 检测结果字典
|
||||||
|
:param model: YOLO模型
|
||||||
|
:return: 更新后的detections字典
|
||||||
|
"""
|
||||||
|
results = model(im_PIL) # 目标检测
|
||||||
|
for result in results:
|
||||||
|
for i in range(len(result.boxes.xyxy)):
|
||||||
|
left, top, right, bottom = result.boxes.xyxy[i]
|
||||||
|
scalar_tensor = result.boxes.cls[i]
|
||||||
|
value = scalar_tensor.item()
|
||||||
|
label = result.names[int(value)]
|
||||||
|
if label == 'center' or label == 'next' or label == 'boss' or label == 'zhaozi':
|
||||||
|
player_x = int(left + (right - left) / 2)
|
||||||
|
player_y = int(top + (bottom - top) / 2) + 30
|
||||||
|
RW = [player_x, player_y]
|
||||||
|
detections[label] = RW
|
||||||
|
elif label == 'daojv' or label == 'gw':
|
||||||
|
player_x = int(left + (right - left) / 2)
|
||||||
|
player_y = int(top + (bottom - top) / 2) + 30
|
||||||
|
RW = [player_x, player_y]
|
||||||
|
detections[label].append(RW)
|
||||||
|
elif label == 'npc1' or label == 'npc2' or label == 'npc3' or label == 'npc4':
|
||||||
|
player_x = int(left + (right - left) / 2)
|
||||||
|
player_y = int(bottom) + 30
|
||||||
|
RW = [player_x, player_y]
|
||||||
|
detections[label] = RW
|
||||||
|
return detections
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("="*60)
|
||||||
|
print("YOLO识别测试(main.py逻辑)")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
# 从配置加载采集卡设置
|
||||||
|
active_group = config_manager.get_active_group()
|
||||||
|
|
||||||
|
if active_group is None:
|
||||||
|
print("⚠️ 没有活动的配置组,使用默认设置")
|
||||||
|
print("提示: 可以运行 python gui_config.py 设置配置")
|
||||||
|
cam_index = 0
|
||||||
|
width = 1920
|
||||||
|
height = 1080
|
||||||
|
use_model = model # 默认使用model
|
||||||
|
else:
|
||||||
|
print(f"📋 使用配置组: {active_group['name']}")
|
||||||
|
cam_index = active_group['camera_index']
|
||||||
|
width = active_group['camera_width']
|
||||||
|
height = active_group['camera_height']
|
||||||
|
use_model = model0 # 城镇中使用model0
|
||||||
|
print(f" 使用模型: model0 (best0.pt) - 用于城镇识别")
|
||||||
|
|
||||||
|
print(f" 采集卡索引: {cam_index}")
|
||||||
|
print(f" 分辨率: {width}x{height}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 初始化采集卡
|
||||||
|
print("🔧 正在初始化采集卡...")
|
||||||
|
get_image = GetImage(
|
||||||
|
cam_index=cam_index,
|
||||||
|
width=width,
|
||||||
|
height=height
|
||||||
|
)
|
||||||
|
|
||||||
|
if get_image.cap is None:
|
||||||
|
print("❌ 采集卡初始化失败")
|
||||||
|
print("请检查:")
|
||||||
|
print("1. 采集卡是否正确连接")
|
||||||
|
print("2. 采集卡索引是否正确")
|
||||||
|
print("3. 采集卡驱动是否安装")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("✅ 采集卡初始化成功")
|
||||||
|
print("\n快捷键:")
|
||||||
|
print(" 'q' 或 ESC - 退出")
|
||||||
|
print(" 'm' - 切换模型 (model/model0)")
|
||||||
|
print(" 'd' - 显示/隐藏检测信息")
|
||||||
|
print()
|
||||||
|
|
||||||
|
try:
|
||||||
|
frame_count = 0
|
||||||
|
show_detections = True # 是否显示检测信息
|
||||||
|
current_model = use_model # 当前使用的模型
|
||||||
|
current_model_name = "model0" if use_model == model0 else "model"
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# 获取帧
|
||||||
|
frame_data = get_image.get_frame()
|
||||||
|
|
||||||
|
if frame_data is None:
|
||||||
|
print("⚠️ 无法获取帧,跳过...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# frame_data 是 [im_opencv_rgb, im_PIL] 格式
|
||||||
|
im_opencv_rgb, im_PIL = frame_data
|
||||||
|
|
||||||
|
if im_PIL is None:
|
||||||
|
print("⚠️ PIL图像为空,跳过...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 初始化检测结果字典
|
||||||
|
detections = {
|
||||||
|
'center': None, 'next': None,
|
||||||
|
'npc1': None, 'npc2': None, 'npc3': None, 'npc4': None,
|
||||||
|
'boss': None, 'zhaozi': None,
|
||||||
|
'daojv': [], 'gw': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 执行YOLO检测(使用main.py的逻辑)
|
||||||
|
detections = yolo_shibie(im_PIL, detections, current_model)
|
||||||
|
|
||||||
|
# 获取绘制好框的图像用于显示
|
||||||
|
try:
|
||||||
|
results = current_model(im_PIL)
|
||||||
|
result = results[0]
|
||||||
|
frame_with_boxes_rgb = result.plot()
|
||||||
|
frame_with_boxes_bgr = cv2.cvtColor(frame_with_boxes_rgb, cv2.COLOR_RGB2BGR)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 绘制检测框失败: {e}")
|
||||||
|
frame_with_boxes_bgr = cv2.cvtColor(im_opencv_rgb, cv2.COLOR_RGB2BGR)
|
||||||
|
|
||||||
|
# 在图像上显示检测信息
|
||||||
|
if show_detections:
|
||||||
|
# 显示模型名称
|
||||||
|
cv2.putText(frame_with_boxes_bgr, f"Model: {current_model_name}",
|
||||||
|
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||||
|
|
||||||
|
# 显示检测到的目标
|
||||||
|
y_offset = 60
|
||||||
|
detected_items = []
|
||||||
|
for key, value in detections.items():
|
||||||
|
if value is not None and value != []:
|
||||||
|
if key in ['daojv', 'gw']:
|
||||||
|
detected_items.append(f"{key}: {len(value)}个")
|
||||||
|
else:
|
||||||
|
detected_items.append(f"{key}: {value}")
|
||||||
|
|
||||||
|
if detected_items:
|
||||||
|
text = f"Detected: {', '.join(detected_items[:5])}" # 最多显示5个
|
||||||
|
if len(detected_items) > 5:
|
||||||
|
text += f" ... (+{len(detected_items)-5})"
|
||||||
|
cv2.putText(frame_with_boxes_bgr, text,
|
||||||
|
(10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
|
||||||
|
|
||||||
|
# 显示图像
|
||||||
|
cv2.imshow("YOLO Detection (main.py logic)", frame_with_boxes_bgr)
|
||||||
|
|
||||||
|
# 检查按键
|
||||||
|
key = cv2.waitKey(1) & 0xFF
|
||||||
|
if key in [27, ord('q'), ord('Q')]:
|
||||||
|
print("\n用户退出")
|
||||||
|
break
|
||||||
|
elif key == ord('m') or key == ord('M'):
|
||||||
|
# 切换模型
|
||||||
|
if current_model == model:
|
||||||
|
current_model = model0
|
||||||
|
current_model_name = "model0"
|
||||||
|
else:
|
||||||
|
current_model = model
|
||||||
|
current_model_name = "model"
|
||||||
|
print(f"切换模型: {current_model_name}")
|
||||||
|
elif key == ord('d') or key == ord('D'):
|
||||||
|
show_detections = not show_detections
|
||||||
|
print(f"显示检测信息: {'开启' if show_detections else '关闭'}")
|
||||||
|
|
||||||
|
frame_count += 1
|
||||||
|
if frame_count % 30 == 0: # 每30帧打印一次
|
||||||
|
print(f"📊 已处理 {frame_count} 帧 (模型: {current_model_name})")
|
||||||
|
# 打印有检测到的目标
|
||||||
|
detected_items = {k: v for k, v in detections.items() if v is not None and v != []}
|
||||||
|
if detected_items:
|
||||||
|
print(f" 检测到: {detected_items}")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n用户中断测试")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 测试过程中发生错误: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
# 清理资源
|
||||||
|
get_image.release()
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
print("🔚 测试结束")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user