用Python玩转VR手柄:OpenXR库在MuJoCo遥操作中的实战应用
用Python玩转VR手柄OpenXR库在MuJoCo遥操作中的实战应用最近在机器人仿真和遥操作领域一个有趣的趋势是越来越多开发者开始利用消费级VR设备作为高性价比的输入工具。想象一下你不再需要昂贵的专业力反馈设备只用一台Pico或者Quest手柄就能在MuJoCo仿真环境中直观地操控机械臂进行数据采集或者模仿学习实验。这听起来像是未来实验室的配置但实际上凭借Python生态中的OpenXR库我们完全可以在自己的电脑上搭建这样一套系统。这篇文章就是为你准备的如果你是一名对机器人仿真、VR交互或者Python自动化感兴趣的开发者或研究者希望将虚拟现实的手柄数据无缝接入到物理仿真世界中那么接下来的内容会非常实用。我们将绕过那些晦涩的官方文档和零散的教程直接从实战角度出发构建一个从手柄数据读取、坐标转换到最终驱动MuJoCo模型的完整链路。你会发现整个过程虽然涉及多个技术栈但核心逻辑清晰并且充满了可以自定义和优化的空间。1. 搭建开发环境与OpenXR初探在开始写代码之前确保你的开发环境是正确且隔离的至关重要。不同于简单的脚本涉及硬件交互和图形渲染的库往往对依赖版本非常敏感。我的建议是从一开始就使用Conda来管理你的Python环境这能避免后续无数令人头疼的版本冲突问题。首先我们创建一个专用于此项目的虚拟环境。我选择Python 3.10因为它是一个在稳定性和新特性之间取得良好平衡的版本。conda create -n pyopenxr_mujoco python3.10 conda activate pyopenxr_mujoco接下来安装核心库pyopenxr。这是OpenXR API的Python绑定是我们与VR运行时如SteamVR通信的桥梁。直接使用pip安装即可。pip install pyopenxr注意pyopenxr是一个相对底层的封装它几乎一对一地映射了OpenXR的C API。这意味着它的使用方式会更接近系统编程你需要处理诸如ctypes指针、结构体等概念。不过别担心我们会将其封装成更友好的Python类。环境准备好后一个快速的连通性测试是必要的。最直接的方法是运行pyopenxr仓库中提供的示例。但这里有个更简单的技巧你可以先不克隆整个仓库而是写一个极简的脚本来检测OpenXR运行时是否正常工作。# test_openxr.py import xr try: # 尝试创建一个最简单的OpenXR实例 instance_create_info xr.InstanceCreateInfo( application_infoxr.ApplicationInfo( application_nameTest App, application_version1, engine_nameNo Engine, engine_version1, ), enabled_extension_names[], ) with xr.InstanceObject(create_infoinstance_create_info) as instance: print(fOpenXR实例创建成功运行时名称: {instance.runtime_name}) print(基本测试通过环境配置正确。) except Exception as e: print(fOpenXR初始化失败: {e}) print(请确保) print(1. 已安装SteamVR并确保其正在运行。) print(2. VR头显已连接并被SteamVR识别。) print(3. 对于Pico设备可能需要通过Pico串流助手或VD等软件连接。)运行这个脚本如果看到成功信息恭喜你最困难的一步已经迈过去了。如果失败通常的问题集中在VR运行时上。对于Windows用户确保SteamVR已安装并更新到最新版本。对于Pico 4这样的设备你需要通过官方串流软件如Pico Streaming Assistant或第三方工具如Virtual Desktop将头显与电脑连接并确保SteamVR能将其识别为活动设备。2. 深入OpenXR捕获手柄姿态与按钮事件当环境就绪我们便进入核心环节编写一个健壮的控制器数据读取类。原始文章中的代码给出了一个很好的起点但我们可以将其重构得更模块化、更易于理解和扩展。我们的目标是创建一个VRController类它不仅能稳定地获取左右手柄的位置Position、姿态Orientation用四元数表示还能实时响应按钮事件。首先理解OpenXR的工作流程是关键。它遵循一个典型的“实例-会话-动作”模型创建实例Instance连接OpenXR运行时。创建会话Session与图形系统如OpenGL和显示设备绑定。定义动作Action声明我们想获取的输入类型如“姿态”、“按钮点击”。绑定Binding将抽象的动作映射到具体设备的输入路径如/user/hand/right/input/grip/pose。循环Frame Loop在主循环中同步动作状态并查询数据。下面是我们重构后的控制器类核心部分。我将其拆解为初始化、动作设置和数据获取三个方法并添加了详细的注释。import ctypes import time import xr import numpy as np from dataclasses import dataclass from typing import Dict, Optional dataclass class ControllerState: 封装单个手柄的状态数据 position: np.ndarray # [x, y, z] rotation: np.ndarray # 四元数 [x, y, z, w] buttons: Dict[str, bool] # 按钮名称 - 按下状态 is_active: bool # 该手柄当前是否被追踪 class VRControllerManager: def __init__(self, action_set_namedefault_actions): self.context None self._left_controller_state ControllerState( positionnp.zeros(3), rotationnp.array([0., 0., 0., 1.]), # 单位四元数 buttons{}, is_activeFalse ) self._right_controller_state ControllerState( positionnp.zeros(3), rotationnp.array([0., 0., 0., 1.]), buttons{}, is_activeFalse ) self._init_openxr(action_set_name) def _init_openxr(self, action_set_name): 初始化OpenXR实例、会话和动作系统 # 1. 创建实例 instance_create_info xr.InstanceCreateInfo( application_infoxr.ApplicationInfo( application_nameMuJoCo Teleop, application_version1, engine_nameMuJoCo Engine, engine_version1, ), enabled_extension_names[xr.KHR_OPENGL_ENABLE_EXTENSION_NAME], ) self.context xr.ContextObject(create_infoinstance_create_info) # 2. 定义动作集Action Set self.action_set xr.create_action_set( instanceself.context.instance, create_infoxr.ActionCreateInfo( action_typexr.ActionType.POSE_INPUT, action_nameaction_set_name, localized_action_nameController Actions, ), ) # 3. 创建具体动作姿态和按钮 controller_paths (xr.Path * 2)( xr.string_to_path(self.context.instance, /user/hand/left), xr.string_to_path(self.context.instance, /user/hand/right), ) self._create_actions(controller_paths) self._suggest_bindings(controller_paths) def _create_actions(self, controller_paths): 创建姿态和按钮动作 # 姿态动作 - 用于获取手柄的位置和旋转 self.pose_action xr.create_action( action_setself.action_set, create_infoxr.ActionCreateInfo( action_typexr.ActionType.POSE_INPUT, action_namehand_pose, localized_action_nameHand Pose, subaction_pathscontroller_paths, ), ) # 为左右手分别创建动作空间Action Space这是定位所必需的 self.pose_spaces [ xr.create_action_space( sessionself.context.session, create_infoxr.ActionSpaceCreateInfo( actionself.pose_action, subaction_pathcontroller_paths[0], # 左手 ), ), xr.create_action_space( sessionself.context.session, create_infoxr.ActionSpaceCreateInfo( actionself.pose_action, subaction_pathcontroller_paths[1], # 右手 ), ), ] # 定义我们感兴趣的按钮。这里以“握柄键”(grip)和“触发器”(trigger)为例。 # OpenXR的路径是标准化的不同设备Pico, Vive, Index都遵循。 self.button_actions {} button_defs { grip: /input/squeeze/click, # 握柄键 trigger: /input/trigger/click, # 触发器 primary: /input/a/click, # A键 (右手) / X键 (左手) secondary: /input/b/click, # B键 (右手) / Y键 (左手) } for btn_name, path_suffix in button_defs.items(): action xr.create_action( action_setself.action_set, create_infoxr.ActionCreateInfo( action_typexr.ActionType.BOOLEAN_INPUT, action_namef{btn_name}_button, localized_action_namef{btn_name.capitalize()} Button, subaction_pathscontroller_paths, ), ) self.button_actions[btn_name] action在定义了动作之后我们需要告诉OpenXR这些动作对应到手柄上的哪个物理输入。这就是“建议绑定”Suggest Bindings。以下代码展示了如何为常见的“简单控制器”交互配置文件Profile建立绑定。这个配置文件被许多VR设备支持。def _suggest_bindings(self, controller_paths): 为动作建议硬件绑定 suggested_bindings [] hand_names [left, right] # 绑定姿态 for i, hand in enumerate(hand_names): binding xr.ActionSuggestedBinding( actionself.pose_action, bindingxr.string_to_path(self.context.instance, f/user/hand/{hand}/input/grip/pose), ) suggested_bindings.append(binding) # 绑定各个按钮 for btn_name, action in self.button_actions.items(): for hand in hand_names: # 注意按钮路径可能需要根据设备微调。这里是通用映射。 if btn_name primary: path f/user/hand/{hand}/input/x/click if hand left else f/user/hand/{hand}/input/a/click elif btn_name secondary: path f/user/hand/{hand}/input/y/click if hand left else f/user/hand/{hand}/input/b/click else: path f/user/hand/{hand}/input/{btn_name}/click binding xr.ActionSuggestedBinding( actionaction, bindingxr.string_to_path(self.context.instance, path), ) suggested_bindings.append(binding) # 提交绑定建议 xr.suggest_interaction_profile_bindings( instanceself.context.instance, suggested_bindingsxr.InteractionProfileSuggestedBinding( interaction_profilexr.string_to_path(self.context.instance, /interaction_profiles/khr/simple_controller), count_suggested_bindingslen(suggested_bindings), suggested_bindings(xr.ActionSuggestedBinding * len(suggested_bindings))(*suggested_bindings), ), )有了这些设置我们就可以在主循环中获取数据了。下面的update方法应该在独立的线程或你的主循环中定期调用。def update(self): 更新所有控制器状态。应在渲染循环中调用。 if not self.context or self.context.session_state ! xr.SessionState.FOCUSED: return # 同步动作状态 active_action_set xr.ActiveActionSet(action_setself.action_set, subaction_pathxr.NULL_PATH) xr.sync_actions( sessionself.context.session, sync_infoxr.ActionsSyncInfo( count_active_action_sets1, active_action_setsctypes.pointer(active_action_set), ), ) # 获取预测的显示时间用于查询该时刻的姿态 frame_state self.context.get_frame_state() predicted_display_time frame_state.predicted_display_time # 更新左右手数据 for hand_index, hand_name in enumerate([left, right]): self._update_controller_state(hand_index, hand_name, predicted_display_time) def _update_controller_state(self, hand_index, hand_name, time): 更新单个控制器的状态 state getattr(self, f_{hand_name}_controller_state) space self.pose_spaces[hand_index] # 1. 获取姿态 space_location xr.locate_space(spacespace, base_spaceself.context.space, timetime) if space_location.location_flags xr.SPACE_LOCATION_POSITION_VALID_BIT: state.is_active True pose space_location.pose # 位置 (单位米) state.position np.array([pose.position.x, pose.position.y, pose.position.z]) # 旋转 (四元数) state.rotation np.array([pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w]) else: state.is_active False # 2. 获取按钮状态 controller_path xr.string_to_path(self.context.instance, f/user/hand/{hand_name}) for btn_name, action in self.button_actions.items(): btn_state xr.get_action_state_boolean( sessionself.context.session, get_infoxr.ActionStateGetInfo(actionaction, subaction_pathcontroller_path), ) if btn_state.is_active: state.buttons[btn_name] btn_state.current_state else: state.buttons[btn_name] False # 提供便捷的属性访问 property def left_controller(self): return self._left_controller_state property def right_controller(self): return self._right_controller_state现在你可以在主程序中这样使用它controller_manager VRControllerManager() try: while True: controller_manager.update() left_state controller_manager.left_controller if left_state.is_active: print(f左手位置: {left_state.position}, 触发器按下: {left_state.buttons.get(trigger, False)}) time.sleep(0.02) # ~50Hz except KeyboardInterrupt: print(程序退出)这个类提供了清晰的数据接口和更好的错误处理是构建更复杂应用的基础。3. 坐标系的迷思从VR空间到MuJoCo世界从OpenXR获取的手柄数据直接扔进MuJoCo是行不通的。你会看到机械臂朝莫名其妙的方向运动或者旋转完全错乱。这是因为VR运行时如SteamVR、手柄自身和MuJoCo仿真环境使用了不同的坐标系约定。解决这个问题的过程本质上是一次三维空间中的“翻译”工作。首先我们需要明确各个坐标系的定义。通过打印手柄静止放在桌面时的数据并观察在MuJoCo中创建一个简单方块如geom typebox size0.05 0.05 0.05/时其默认朝向我们可以总结出以下规律坐标系X轴正向Y轴正向Z轴正向旋向性HandednessOpenXR/SteamVR右方上方后方左手系 (Y向上)Pico手柄自身手柄右侧手柄上方手柄前方通常与运行时一致MuJoCo (MJCF)前方左方上方右手系 (Z向上)关键概念左手系与右手系。伸出你的左手让食指指向X轴正方向右中指指向Y轴正方向上拇指指向Z轴正方向后这就是左手系。MuJoCo使用的右手系则是食指前中指左拇指上。坐标系转换的核心之一就是处理这个差异。因此从OpenXR坐标系转换到MuJoCo坐标系不仅需要重排坐标轴还可能涉及轴的反转和旋向性的改变。一个常见的转换关系是MuJoCo X (前) OpenXR -Z (后)MuJoCo Y (左) OpenXR -X (左)MuJoCo Z (上) OpenXR Y (上)这个转换可以用一个3x3的旋转矩阵R_convert来表示R_convert [[ 0, 0, -1], [-1, 0, 0], [ 0, 1, 0]]这意味着对于一个来自OpenXR的位置向量pos_openxr [x, y, z]其在MuJoCo中的坐标pos_mujoco可以通过矩阵乘法得到pos_mujoco R_convert pos_openxr然而事情还没完。位置转换相对简单姿态旋转的转换则更为复杂。旋转通常用四元数[x, y, z, w]表示。我们不能直接对四元数应用上述轴重排矩阵。标准的做法是将四元数转换为一个3x3的旋转矩阵R_openxr。将坐标系转换矩阵R_convert左乘这个旋转矩阵R_mujoco R_convert R_openxr。将新的旋转矩阵R_mujoco转换回四元数。但实践中我发现仅仅进行轴重排手柄的“向前”方向手柄指向在MuJoCo中可能并不符合直觉。例如你可能希望手柄的“扳机指向前方”这个姿势在MuJoCo中对应机械臂末端执行器如夹爪的“开口朝前”。这通常需要额外的固定旋转偏移来对齐本地坐标系。假设经过轴重排后手柄的本地坐标系即附着在手柄模型上的坐标系与MuJoCo中物体的本地坐标系仍然存在一个固定的旋转差。这个差值通常可以用绕某个轴旋转特定角度来描述。例如可能需要绕重排后的X轴旋转-90度再绕Z轴旋转90度。这个固定偏移可以用一个旋转矩阵R_offset表示。那么完整的从OpenXR姿态到MuJoCo物体姿态的转换可以合并为一个4x4的齐次变换矩阵T_mujoco的计算T_openxr [ R_openxr, pos_openxr ] # 4x4矩阵最后一行是[0,0,0,1] [ 0 , 1 ] # 构建轴重排矩阵扩充为4x4 M_axis_remap [ R_convert, [0,0,0] ] [ 0 , 1 ] # 构建固定偏移矩阵 M_fixed_offset [ R_offset, [0,0,0] ] [ 0 , 1 ] # 最终变换先进行轴重排再应用固定旋转偏移 T_mujoco M_fixed_offset M_axis_remap T_openxr下面是一个实现了完整转换的Python工具函数import numpy as np from scipy.spatial.transform import Rotation as R def transform_openxr_to_mujoco(pose_openxr): 将OpenXR姿态转换到MuJoCo坐标系。 参数: pose_openxr: 字典包含 position ([x,y,z]) 和 rotation ([x,y,z,w])。 返回: pos_mujoco: np.array, 转换后的位置 [x, y, z]。 quat_mujoco: np.array, 转换后的四元数 [w, x, y, z] (注意MuJoCo使用w在前格式)。 pos_o np.array(pose_openxr[position]) quat_o np.array(pose_openxr[rotation]) # [x, y, z, w] # 1. 轴重排矩阵 (左手系Y向上 - 右手系Z向上) # OpenXR: X右, Y上, Z后 # MuJoCo: X前, Y左, Z上 R_axis np.array([ [ 0, 0, -1], # MuJoCo X 来自 OpenXR -Z [-1, 0, 0], # MuJoCo Y 来自 OpenXR -X [ 0, 1, 0] # MuJoCo Z 来自 OpenXR Y ]) # 2. 固定旋转偏移 (用于对齐手柄本地坐标系与MuJoCo物体本地坐标系) # 这个偏移需要根据你的具体手柄和MuJoCo模型调整。以下是常见值 # 绕新X轴旋转-90度再绕新Z轴旋转90度 R_offset R.from_euler(xz, [-90, 90], degreesTrue).as_matrix() # 3. 组合旋转 R_openxr R.from_quat([quat_o[0], quat_o[1], quat_o[2], quat_o[3]]).as_matrix() R_combined R_offset R_axis R_openxr quat_combined R.from_matrix(R_combined).as_quat() # 返回 [x, y, z, w] # 4. 转换位置 pos_transformed R_axis pos_o # 注意位置转换不包含固定偏移R_offset # MuJoCo的四元数格式是 [w, x, y, z] quat_mujoco np.array([quat_combined[3], quat_combined[0], quat_combined[1], quat_combined[2]]) return pos_transformed, quat_mujoco这个函数是核心中的核心。你可能需要微调R_offset中的欧拉角直到手柄的移动和旋转在仿真中完全符合你的直觉。一个实用的调试方法是在MuJoCo中可视化一个带坐标轴的几何体然后用手柄控制它观察其运动是否与你的手部运动自然对应。4. 构建MuJoCo遥操作闭环系统当我们可以稳定获取手柄数据并正确转换坐标系后最后一步就是将其与MuJoCo仿真引擎连接起来形成一个实时闭环的遥操作系统。这个系统将包含数据流、仿真步进和可视化三个主要部分。首先确保安装了MuJoCo的Python接口。现在安装过程已经非常简单pip install mujoco我们设计一个TeleopSimulation类来管理整个闭环。它将做以下几件事加载MuJoCo模型例如一个机械臂。创建一个与OpenXR控制器通信的数据线程或异步任务。在每一个MuJoCo仿真步中获取最新的手柄数据将其转换为目标位姿。通过控制器如位置控制器、逆运动学IK计算关节指令驱动机械臂。实时渲染更新。这里我们假设你已经有一个MuJoCo模型文件.xml其中定义了一个名为“endeffector”的站点site我们将控制这个站点的位姿。import threading import queue import time import mujoco import mujoco.viewer import numpy as np class TeleopSimulation: def __init__(self, model_path, controller_manager): self.model mujoco.MjModel.from_xml_path(model_path) self.data mujoco.MjData(self.model) self.controller_manager controller_manager # 用于存储最新手柄数据的线程安全队列 self.pose_queue queue.Queue(maxsize1) # 控制循环运行标志 self.running False # 找到末端执行器站点的ID self.endeffector_site_id mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_SITE, endeffector) if self.endeffector_site_id -1: raise ValueError(在模型中未找到名为 endeffector 的站点。) def _controller_reader_thread(self): 独立线程持续读取手柄数据并放入队列 while self.running: self.controller_manager.update() left_state self.controller_manager.left_controller if left_state.is_active: # 打包数据位置和四元数 pose_data { position: left_state.position.copy(), rotation: left_state.rotation.copy(), # [x, y, z, w] trigger_pressed: left_state.buttons.get(trigger, False) } # 非阻塞方式更新队列只保留最新数据 if not self.pose_queue.empty(): try: self.pose_queue.get_nowait() except queue.Empty: pass self.pose_queue.put_nowait(pose_data) time.sleep(0.02) # 约50Hz读取 def _apply_pose_to_simulation(self, pose_data): 将转换后的位姿应用到MuJoCo模型 # 1. 坐标系转换 pos_mjc, quat_mjc transform_openxr_to_mujoco(pose_data) # 2. 直接设置站点位姿用于可视化或简单控制 # 注意这不会产生物理效果只是“瞬间移动”了站点。 self.data.site(self.endeffector_site_id).xpos[:] pos_mjc self.data.site(self.endeffector_site_id).xmat[:] mujoco.mju_quat2Mat(quat_mjc).flatten() # 3. (进阶) 使用逆运动学(IK)驱动机械臂 # 设置IK目标 self.data.mocap_pos[self.endeffector_site_id] pos_mjc self.data.mocap_quat[self.endeffector_site_id] quat_mjc # 调用MuJoCo的IK求解器需要模型中有相应的自由度 mujoco.mj_kinematics(self.model, self.data) # 或者使用更高级的控制器如操作空间PD控制器来计算关节力矩。 # 4. 处理按钮事件例如触发器控制夹爪开合 if pose_data[trigger_pressed]: # 例如设置夹爪执行器的控制信号 gripper_actuator_id mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_ACTUATOR, gripper) if gripper_actuator_id ! -1: self.data.ctrl[gripper_actuator_id] 1.0 # 闭合 else: if gripper_actuator_id ! -1: self.data.ctrl[gripper_actuator_id] -1.0 # 张开 def run(self): 启动遥操作仿真主循环 self.running True # 启动手柄数据读取线程 reader_thread threading.Thread(targetself._controller_reader_thread, daemonTrue) reader_thread.start() # 启动MuJoCo可视化器 with mujoco.viewer.launch_passive(self.model, self.data) as viewer: print(遥操作仿真已启动。移动左手柄控制机械臂按触发器控制夹爪。) print(在可视化窗口中按ESC退出。) # 主仿真循环 while viewer.is_running() and self.running: # 从队列获取最新手柄数据非阻塞 try: latest_pose self.pose_queue.get_nowait() self._apply_pose_to_simulation(latest_pose) except queue.Empty: pass # 没有新数据使用上一帧的数据继续 # 步进仿真 mujoco.mj_step(self.model, self.data) # 同步渲染 viewer.sync() # 控制仿真步进速率与手柄读取速率匹配 time.sleep(0.01) # ~100Hz仿真 self.running False reader_thread.join(timeout1.0) print(仿真结束。)最后将所有部分组合起来if __name__ __main__: # 1. 初始化VR控制器管理器 vr_manager VRControllerManager() # 2. 创建遥操作仿真实例 # 假设你的MuJoCo模型文件名为 robot_arm.xml sim TeleopSimulation(model_pathrobot_arm.xml, controller_managervr_manager) # 3. 运行 try: sim.run() except KeyboardInterrupt: print(用户中断。)运行这个程序你应该能看到MuJoCo的渲染窗口并且通过移动和旋转你的VR手柄可以控制仿真环境中的机械臂末端执行器进行相应的运动。按下手柄的触发器还可以触发夹爪的开合动作如果模型中有定义。在这个过程中你可能会遇到延迟、抖动或不稳定的问题。这通常源于几个方面OpenXR数据读取频率、坐标转换计算开销、MuJoCo仿真步进时间、以及网络传输如果使用了Socket。优化策略包括使用更高性能的数学库如numpy的向量化操作、调整各线程的休眠时间以达到最佳同步、或者在MuJoCo中使用更高效的控制器。但无论如何看到虚拟机械臂随着你的手真实舞动的那一刻所有的调试都是值得的。这为机器人模仿学习、远程操作训练乃至虚拟调试打开了一扇低成本、高沉浸感的大门。

相关新闻

EasyAnimateV5-7b-zh-InP Web界面高级技巧:Batch生成、Seed固定复现、历史记录导出

EasyAnimateV5-7b-zh-InP Web界面高级技巧:Batch生成、Seed固定复现、历史记录导出

EasyAnimateV5-7b-zh-InP Web界面高级技巧:Batch生成、Seed固定复现、历史记录导出 掌握专业级图生视频工作流,提升创作效率与一致性 1. 从基础到精通的界面操作进阶 EasyAnimateV5-7b-zh-InP作为专业的图生视频模型,其Web界面提供了丰富的功…

2026/5/17 8:36:17 阅读更多 →
突破语言壁垒:XUnity.AutoTranslator实现Unity游戏实时翻译的全面解析

突破语言壁垒:XUnity.AutoTranslator实现Unity游戏实时翻译的全面解析

突破语言壁垒:XUnity.AutoTranslator实现Unity游戏实时翻译的全面解析 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 在全球化游戏市场中,语言障碍常常成为玩家体验优质外文游戏的…

2026/7/6 4:29:44 阅读更多 →
lychee-rerank-mm效果实测:4090上单次推理平均耗时<1.2s(含预处理)

lychee-rerank-mm效果实测:4090上单次推理平均耗时<1.2s(含预处理)

lychee-rerank-mm效果实测&#xff1a;4090上单次推理平均耗时<1.2s&#xff08;含预处理&#xff09; 1. 项目概述 lychee-rerank-mm是一个专为RTX 4090显卡优化的多模态重排序系统&#xff0c;基于Qwen2.5-VL多模态大模型和Lychee-rerank-mm专业重排序模型构建。这个系统…

2026/7/5 20:08:13 阅读更多 →

最新新闻

Playwright网络请求拦截与模拟实战:从原理到六大应用场景详解

Playwright网络请求拦截与模拟实战:从原理到六大应用场景详解

1. 项目概述与核心价值 如果你正在用 Playwright 做自动化测试或者数据抓取&#xff0c;有没有遇到过这样的场景&#xff1a;测试一个下单流程&#xff0c;但支付接口总是不稳定&#xff0c;导致你的测试脚本动不动就失败&#xff1b;或者你想测试前端页面在网络超时或服务器返…

2026/7/6 9:04:10 阅读更多 →
PyTorch LSTM 多变量多任务预测:3种损失函数加权策略对比与代码实现

PyTorch LSTM 多变量多任务预测:3种损失函数加权策略对比与代码实现

PyTorch LSTM 多变量多任务预测&#xff1a;3种损失函数加权策略对比与代码实现在时间序列预测领域&#xff0c;多任务学习正逐渐成为提升模型性能的关键技术。当我们需要同时预测多个相关变量时&#xff0c;传统的单任务学习方法往往难以捕捉变量间的复杂关系。本文将深入探讨…

2026/7/6 9:04:10 阅读更多 →
Python Selenium自动化校园网登录:从环境搭建到打包部署全攻略

Python Selenium自动化校园网登录:从环境搭建到打包部署全攻略

1. 项目概述与核心价值 校园网登录&#xff0c;尤其是像深澜Srun这类需要跳转认证页面的系统&#xff0c;对于需要长时间保持网络连接的用户来说&#xff0c;是个不大不小的痛点。手动打开浏览器、输入账号密码、点击登录&#xff0c;这套流程每天重复几次&#xff0c;不仅繁琐…

2026/7/6 9:04:10 阅读更多 →
Python自动化破解WIPO六宫格验证码:图像处理与网络请求实战

Python自动化破解WIPO六宫格验证码:图像处理与网络请求实战

1. 项目概述与核心痛点最近在做一个专利数据分析的小工具&#xff0c;需要从世界知识产权组织&#xff08;WIPO&#xff09;的专利数据库里批量抓取一些公开数据。本来以为用Python的requests库写个脚本&#xff0c;模拟登录后按部就班爬取就完事了&#xff0c;结果第一步就卡在…

2026/7/6 9:02:09 阅读更多 →
基于MCP协议为AI编程助手集成逆向分析能力:从静态反编译到动态Hook的完整实践

基于MCP协议为AI编程助手集成逆向分析能力:从静态反编译到动态Hook的完整实践

1. 项目概述&#xff1a;当AI编程助手学会“拆解”程序 想象一下&#xff0c;你正在分析一个加密的Android应用&#xff0c;试图找到它的网络通信密钥。传统流程是&#xff1a;打开IDA Pro反汇编引擎库&#xff0c;在十六进制视图中搜索可疑字符串&#xff1b;同时启动Frida&am…

2026/7/6 9:00:06 阅读更多 →
AI模型测试:从确定性断言到统计判定的工程实践

AI模型测试:从确定性断言到统计判定的工程实践

1. 项目概述&#xff1a;当AI的“不确定性”撞上测试的“确定性” 干了这么多年测试&#xff0c;从功能、性能到安全&#xff0c;感觉什么大风大浪都见过了。但最近两年&#xff0c;随着公司业务里AI驱动的功能越来越多&#xff0c;比如智能客服的意图识别、内容平台的个性化推…

2026/7/6 9:00:06 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性&#xff1a;5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域&#xff0c;单元测试是保证代码质量的重要环节。当应用涉及数据库操作时&#xff0c;测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南&#xff1a;用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南&#xff1a;告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况&#xff1a;下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools&#xff1a;5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱&#xff0c;支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里&#xff0c;参与了关于混合后量子密码学的讨论&#xff0c;应付端点攻击找茬的人&#xff0c;还参与留言板讨论后&#xff0c;发现“威胁模型”对多数人仍是陌生概念&#xff0c;且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”&#xff1a;我理解的渗透测试到底是什么&#xff1f;每次看到新闻里说某个大公司的数据被“黑”了&#xff0c;或者某个网站被攻击导致服务瘫痪&#xff0c;你是不是和我一样&#xff0c;心里会冒出两个念头&#xff1a;一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻