设置帧率方法_Adaptive Performance:高品质画面呈现与流畅稳定帧率的二者兼得

Adaptive Performance是Unity为移动设备推出的功能,旨在平衡游戏性能和画面质量。通过与三星合作,该技术能实时分析硬件状态,动态调整CPU和GPU性能级别,提供更稳定、高效的帧率,延长设备运行时间。《Megacity》项目展示了Adaptive Performance在复杂场景中的应用,帮助开发者应对不同设备性能挑战。
部署运行你感兴趣的模型镜像

在GDC 2019上,Unity在Keynote主题演讲中介绍了Adaptive Performance自适应性能功能。Adaptive Performance预览版和《Megacity》移动端示例项目已经发布,开发者可以开始尝试它的功能。

本文将介绍Adaptive Performance自适应性能功能的详细信息,以及如何将其应用到项目中。

游戏性能与画面呈现的平衡

与PC或主机游戏不同,利用移动设备的完整性能要求对游戏有着精妙的平衡,这样才能使游戏既画面精美又运行流畅。

透支设备的性能会很快降低游戏性能,对硬件产生过大的负担,从而损害电池寿命并产生不稳定的性能。对于开发者而言,考虑到从低端到高端目标设备的庞大设备范围,这个问题会更加棘手。

目前有不同的方法可以解决该问题,其中二个主要方法是:

  • 尝试让游戏在所有目标硬件都有最好的运行效果,这意味着要牺牲图形保真度和帧率。

  • 尝试预测硬件行为,但准确测量硬件趋势的选择并不多,所以该方法很难实现。

Adaptive Performance

Adaptive Performance提供了一种更好的方法来管理游戏在设备上的实时热量和性能情况,使开发者能够主动的即时调整游戏性能和质量设置,利用好硬件性能,并避免过度使用设备性能。

Adaptive Performance可以获得更为可预测的帧率,减少热量积累,实现更长的运行时间和更加舒适的玩家体验,同时保持电池寿命。

对开发者而言,Adaptive Performance会对硬件进行全新的深度分析,使用新工具让游戏更加动态而灵活,在移动设备运行游戏时,给玩家提供最流畅和最优质的体验。

Adaptive Performance会给开发者提供操作系统的常用控制功能,例如:何时运行在高速时钟运转下,避免节流时要调整什么部分。

与三星合作改进Adaptive Performance

我们与全球最大的Android移动设备制造商三星进行合作,从而让Adaptive Performance解决方案走向成熟。

Adaptive Performance自适应性能功能基于三星的GameSDK构建,它会首先适用于Samsung Galaxy系列设备,例如:Samsung Galaxy S10和Galaxy Fold,未来该功能会支持更多Samsung Galaxy设备。

运行结果

下面的图表,我们在Unity GDC 2019 Keynote主题演讲上进行了展示,说明了当《Megacity》项目在全新Samsung Galaxy S10设备上运行时,Adaptive Performance有助于提供稳定的高帧率效果。

8b3435abe4b3fc93eb090ff3ddd12795.png

我们看见蓝线代表添加了Adaptive Performance后,与未添加功能的红线对比,《Megacity》演示项目运行在30 fps的状态下的时间更长,也更加稳定。

使用《Megacity》展示Adaptive Performance

《Megacity》项目是一个包含数百万个实体的未来主义风格交互式城市,它展示了Unity如何在当今移动硬件上运行极为复杂的项目。

《Megacity》展示Unity面向数据技术栈DOTS的最新功能,包括:实体组件系统ECS,Native Collections本地工具组件,C# Job System和Burst编译器。

《Megacity》项目是展示Adaptive Performance的正确选择,它为我们提供了灵活而动态地适应游戏,主动地利用好硬件性能。Adaptive Performance在开发时考虑到了可扩展性,适用于构建《Megacity》基础的DOTS功能。

《Megacity》项目的移动版本有450万个网格渲染器,20万个建筑部分,10万个音频源,以及超过600万个实体,它是展示Adaptive Performance自适应性能功能理想的选择。

对《Megacity》的处理方式

通过Unity资源包管理器安装Adaptive Performance后,在向设备构建时,Unity会自动添加Samsung GameSDK子系统到项目中。

在运行时期间,Unity会在支持设备上创建并启动Adaptive Performance Manager管理器,该功能会提供移动设备热量状态的反馈。

开发者可以在运行时期间从Adaptive Performance Manager订阅事件或查询信息,从而实时对不同情况做出反应,否则,它只会在Console控制台报告状态。

例如:开发者可以使用我们提供的API,创建在设备上对热量趋势和事件做出反应的应用程序,这可确保较长时间的稳定帧率,同时在节流开始前避免发生热量节流。

f88c9c70bf4fe6f656fbf3aeefd87096.png

在《Megacity》的Adaptive Performance示例实现中,我们使用了以下方法进行优化帧率:

  • 从较低CPU和GPU级别开始,逐渐提高它们的级别,从而消除瓶颈情况,这样可以保持较低的电量消耗。

  • 如果发现设备即将发生节流情况,我们可以调整质量设置以减少热量负载,并且决定降低LOD层级。

  • 一旦即将发生节流时,我们也会减少目标帧率。

  • 当达到目标帧率且温度正在下降时,我们会提高LOD层级,提高目标帧率,再次减少CPU和GPU级别。

这些功能可以让你的游戏在一段时间内实现更流畅的性能。通过密切关注设备的热量趋势,开发者可以动态的调整性能设置,避免节流情况。

了解如何实现这种效果,请下载《Megacity》移动示例项目:

http://megacity.unity3d.com/MegaCity_Mobile_AdaptivePerformance_Release.zip

Adaptive Performance Manager

该资源包的核心是Unity在启动时创建的Adaptive Performance Manager管理器,它允许开发者轻松访问和订阅热量和性能事件。

下面的示例代码展示了如何在MonoBehaviour的Start函数使用IAdaptivePerformance接口来访问Adaptive Performance Manager管理器。

private IAdaptivePerformance ap = null; 

void Start() 

    ap = Holder.instance; 

}

热量事件

当移动设备的热量状态发生变化,Unity就会发送热量事件,重要的状态有:即将发生节流的时候和节流情况已经发生的时候。

在下面的示例代码中,我们会订阅ThermalEvents,以减少或提高lodBias,这有助于减少GPU负载。

using UnityEngine; 

using UnityEngine.Mobile.AdaptivePerformance; 

public class AdaptiveLOD : MonoBehaviour 

    private IAdaptivePerformance ap = null; 

    void Start() { 

        if (Holder.instance == null) 

            return; 

        ap = Holder.instance; 

        if (!ap.active) 

            return; 

        QualitySettings.lodBias = 1; 

        ap.ThermalEvent += OnThermalEvent; 

    } 

    void OnThermalEvent(object obj, ThermalEventArgs ev) { 

        switch (ev.warningLevel) { 

            case PerformanceWarningLevel.NoWarning: 

                QualitySettings.lodBias = 1; 

                break; 

            case PerformanceWarningLevel.ThrottlingImminent: 

                QualitySettings.lodBias = 0.75f; 

                break; 

            case PerformanceWarningLevel.Throttling: 

                QualitySettings.lodBias = 0.5f; 

                break; 

        } 

    } 

}

如果我们减少lodBias到数值1以下,它会在很多情况下产生视觉影响,发生LOD对象突然改变的情况,但如果对游戏体验没有要求的话,这是减少图形负载的简单方法。

如果想做出更细致的决策来调整游戏图形和行为的处理方法,瓶颈事件会非常实用。

CPU和GPU性能级别

移动设备的CPU和GPU占了功耗的很大一部分,特别是在运行游戏时。

CPU内核和GPU运行在最大时钟速度时的效率较低,运行在高时钟速度下容易使移动设备温度过高,此时操作系统会限制CPU和GPU的频率来降低设备温度。

我们可以使用以下属性,通过限制最大允许时钟速度避免发生这种情况:

  • IAdaptivePerformance.cpuLevel

  • IAdaptivePerformance.gpuLevel

应用程序可以基于对当前性能要求的信息来配置这二个属性,并且基于实际情况决定应该提高还是降低级别。

  • 应用程序是否在之前帧达到目标帧率? 

  • 应用程序是位于游戏内场景还是菜单位置?

  • 接下来是否有性能消耗较大的场景?

  • 即将发生的事件是否利用大量CPU或GPU性能?

  • 是否展示不需要高CPU或GPU级别的广告?

public void EnterMenu(){  

    if (!ap.active) 

        return;  

    // 在菜单设置低CPU和GPU级别

    ap.cpuLevel = 0; 

    ap.gpuLevel = 0; 

    // 设置低目标FPS

    Application.targetFrameRate = 15; 

public void ExitMenu(){  

    // 在回到游戏时,设置较高CPU和GPU级别

    ap.cpuLevel = ap.maxCpuPerformanceLevel; 

    ap.gpuLevel = ap.maxGpuPerformanceLevel; 

}

性能瓶颈的警告系统

在Adaptive Performance Manager管理器中,我们可以订阅接收性能瓶颈事件,了解游戏是CPU绑定,GPU绑定还是“帧率绑定”。

帧率绑定意味着游戏受到了Application.targetFrameRate的限制,这种情况下应用程序应该考虑降低性能要求。

GPU Framtime Driver帧率控制工具会运行在后台,管理瓶颈决策,开发者可以通过Adaptive Performance Manager管理器查询信息,它会监视上一帧GPU使用的硬件时间。

目前,CPU时间通过累计Unity内部子系统所用时间来计算。根据游戏和实际情况,开发者可以让工具根据热量状态变化,在游戏处于CPU绑定时或GPU绑定时使用不同的应对方法。

void OnBottleneckChange(object obj, PerformanceBottleneckChangeEventArgs ev) { 

    switch (ev.bottleneck) { 

        case PerformanceBottleneck.TargetFrameRate: 

            if (ap.cpuLevel > 0) {

                ap.cpuLevel--;

            }

           if (ap.gpuLevel > 0) {

                ap.gpuLevel--;

            }

            break; 

        case PerformanceBottleneck.GPU: 

            if (ap.gpuLevel < ap.maxGpuPerformanceLevel) {

                ap.gpuLevel++;

            }

            break; 

        case PerformanceBottleneck.CPU: 

            if (ap.cpuLevel < ap.maxCpuPerformanceLevel) {

                ap.cpuLevel++;

            }

            break; 

    } 

}

对游戏进行优化的方法很多,上述示例和《Megacity》项目的示例只提供了一些优化的建议,游戏的优化归根结底取决于最适合特定游戏的方法。

Adaptive Performance更多信息,请访问帮助文档:

https://docs.unity3d.com/Packages/com.unity.mobile.adaptiveperformance@latest

未来发展

现在仅仅只是开始,我们会继续开发Adaptive Performance自适应性能功能,添加更多功能并支持更多设备。

当前资源包提供一个底层API,而我们已经在开发基于组件的高层次API,它兼容DOTS,能够在Unity项目轻松适应性能。

Adaptive Performance预览版已通过Unity资源包管理器面向Unity 2019.1 Beta发布,了解相关最新信息,以及其他开发者的使用方法,请访问Unity官方论坛:

https://forum.unity.com/threads/adaptive-performance-package.652306/

推荐阅读

  • 面向数据技术栈DOTS之ECS实体组件系统

  • Unity GDC 2019 Keynote精彩回顾

  • 3款创作游戏的五星工具资源包

  • 使用Tilemap创作等距2D环境

  • 达哥课堂|使用Shader Graph制作飘动的巨龙

  • 使用摄像机和着色器让AI从视觉检测玩家

Unite Shanghai 2019

5月10日-12日上海,Unite大会强势回归。技术门票正在热销中,购票即获指定Asset Store资源商店精品21款资源的5折优惠券。

  • 了解Training Day开发者训练营课程内容,请点击此处。

  • 了解部分技术演讲内容,请点击此处。

  • H5游戏开发圆桌会议正在报名,请点击此处。

购票请访问:Unite2019.youkuaiyun.com

79c679e2b6ada15db66e5488bd365d0c.png

点击“阅读原文”访问Unity Connect

您可能感兴趣的与本文相关的镜像

Wan2.2-I2V-A14B

Wan2.2-I2V-A14B

图生视频
Wan2.2

Wan2.2是由通义万相开源高效文本到视频生成模型,是有​50亿参数的轻量级视频生成模型,专为快速内容创作优化。支持480P视频生成,具备优秀的时序连贯性和运动推理能力

做mujoco原生渲染#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 人形机器人仿真完整演示 - 场景设置修复版 """ import sys import os import time import json import numpy as np import matplotlib.pyplot as plt from typing import Dict, List, Tuple import threading import argparse import mujoco # 导入自定义模块 from mujoco_simulation import HumanoidRobot from advanced_control import ( IntelligentDecisionSystem, PathPlanner, EnergyOptimizer, AdaptiveController, EnvironmentState, RobotState, TaskType, TerrainType ) class HumanoidDemo: """人形机器人仿真演示类""" def __init__(self): """初始化演示系统""" print("🤖 人形机器人仿真演示系统") print("=" * 60) # 创建人形机器人 self.robot = HumanoidRobot() # 创建高级控制模块 self.decision_system = IntelligentDecisionSystem() self.path_planner = PathPlanner() self.energy_optimizer = EnergyOptimizer() self.adaptive_controller = AdaptiveController() # 演示配置 self.demo_config = { 'duration': 30.0, 'enable_ai': True, 'enable_optimization': True, 'enable_adaptation': True, 'record_data': True, 'save_video': True } # 演示数据 self.demo_data = { 'timestamps': [], 'robot_states': [], 'environment_states': [], 'decisions': [], 'energy_consumption': [], 'performance_metrics': [] } print("✅ 演示系统初始化完成") def setup_demo_scenario(self, scenario_type: str = "comprehensive"): """设置演示场景""" scenarios = { "comprehensive": self._setup_comprehensive_scenario, "walking": self._setup_walking_scenario, "obstacle": self._setup_obstacle_scenario, "terrain": self._setup_terrain_scenario, "interference": self._setup_interference_scenario } if scenario_type in scenarios: print(f"🎬 设置 {scenario_type} 演示场景...") scenarios[scenario_type]() else: print(f"⚠️ 未知场景类型: {scenario_type},使用综合场景") self._setup_comprehensive_scenario() def _setup_comprehensive_scenario(self): """设置综合演示场景""" self.scenario_timeline = [ [0, 5, "walking", "平地行走"], [5, 10, "obstacle", "动态避障"], [10, 15, "terrain", "斜坡行走"], [15, 20, "terrain", "楼梯攀爬"], [20, 25, "interference", "风力干扰"], [25, 30, "walking", "恢复行走"] ] self.environment_config = { 'obstacles': [ {'position': [2, 0, 0.5], 'radius': 0.3, 'type': 'static'}, {'position': [4, 1, 0.3], 'radius': 0.3, 'type': 'dynamic'}, {'position': [6, -1, 0.4], 'radius': 0.2, 'type': 'moving'} ], 'terrain_sequence': ['flat', 'slope', 'stairs', 'sand', 'flat'], 'wind_patterns': [ {'time': [20, 25], 'force': [50, 0, 0], 'type': 'gust'}, {'time': [25, 30], 'force': [20, 10, 0], 'type': 'steady'} ], 'light_patterns': [ {'time': [22, 24], 'intensity': 0.2, 'type': 'dim'}, {'time': [24, 26], 'intensity': 0.9, 'type': 'bright'} ] } def _setup_walking_scenario(self): """设置行走演示场景""" self.scenario_timeline = [ [0, 10, "walking", "基础行走"], [10, 20, "walking", "快速行走"], [20, 30, "walking", "慢速行走"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [], 'light_patterns': [] } # 添加缺失的场景设置方法 def _setup_obstacle_scenario(self): """设置避障演示场景""" self.scenario_timeline = [ [0, 10, "obstacle", "静态障碍物"], [10, 20, "obstacle", "动态障碍物"], [20, 30, "obstacle", "复杂障碍物"] ] self.environment_config = { 'obstacles': [ {'position': [1, 0, 0.5], 'radius': 0.3, 'type': 'static'}, {'position': [3, 0, 0.3], 'radius': 0.2, 'type': 'dynamic'}, {'position': [5, 0, 0.4], 'radius': 0.25, 'type': 'moving'} ], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [], 'light_patterns': [] } def _setup_terrain_scenario(self): """设置地形演示场景""" self.scenario_timeline = [ [0, 6, "terrain", "平地"], [6, 12, "terrain", "斜坡"], [12, 18, "terrain", "楼梯"], [18, 24, "terrain", "沙地"], [24, 30, "terrain", "平地"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat', 'slope', 'stairs', 'sand', 'flat'], 'wind_patterns': [], 'light_patterns': [] } def _setup_interference_scenario(self): """设置干扰演示场景""" self.scenario_timeline = [ [0, 10, "walking", "正常行走"], [10, 20, "interference", "风力干扰"], [20, 30, "interference", "光照干扰"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [ {'time': [10, 20], 'force': [80, 0, 0], 'type': 'strong_wind'} ], 'light_patterns': [ {'time': [20, 30], 'intensity': 0.1, 'type': 'low_light'} ] } # 添加 get_current_scenario_state 方法 def get_current_scenario_state(self, current_time: float) -> Dict: """获取当前场景状态""" current_task = "idle" task_description = "待机" # 确定当前任务 for start_time, end_time, task, description in self.scenario_timeline: if start_time <= current_time < end_time: current_task = task task_description = description break # 确定当前地形 terrain_index = min(int(current_time / 6), len(self.environment_config['terrain_sequence']) - 1) current_terrain = self.environment_config['terrain_sequence'][terrain_index] # 计算当前风力 current_wind = np.zeros(3) for wind_pattern in self.environment_config['wind_patterns']: if wind_pattern['time'][0] <= current_time < wind_pattern['time'][1]: current_wind = np.array(wind_pattern['force']) break # 计算当前光照 current_light = 1.0 for light_pattern in self.environment_config['light_patterns']: if light_pattern['time'][0] <= current_time < light_pattern['time'][1]: current_light = light_pattern['intensity'] break return { 'task': current_task, 'description': task_description, 'terrain': current_terrain, 'wind': current_wind, 'light': current_light, 'obstacles': self.environment_config['obstacles'].copy(), 'time': current_time } def run_demo(self): """运行演示""" print(f"🚀 开始演示,持续时间: {self.demo_config['duration']}秒") print("=" * 60) try: start_time = time.time() while time.time() - start_time < self.demo_config['duration']: current_time = time.time() - start_time # 获取当前场景状态 scenario_state = self.get_current_scenario_state(current_time) # 打印状态信息 print(f"⏱️ {current_time:5.1f}s | " f"任务: {scenario_state['description']:8s} | " f"地形: {scenario_state['terrain']:6s}") # 模拟时间步进 time.sleep(0.1) print("\n✅ 演示完成!") except KeyboardInterrupt: print("\n⏹️ 演示被用户中断") def main(): """主函数""" parser = argparse.ArgumentParser(description='人形机器人仿真演示') parser.add_argument('--scenario', type=str, default='comprehensive', choices=['comprehensive', 'walking', 'obstacle', 'terrain', 'interference'], help='演示场景类型') parser.add_argument('--duration', type=float, default=30.0, help='演示持续时间(秒)') args = parser.parse_args() # 创建演示实例 demo = HumanoidDemo() demo.demo_config['duration'] = args.duration # 设置场景 demo.setup_demo_scenario(args.scenario) # 运行演示 demo.run_demo() if __name__ == "__main__": main()
07-29
修正代码#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 人形机器人仿真演示 - MuJoCo原生可视化 """ import sys import os import time import json import numpy as np import matplotlib.pyplot as plt from typing import Dict, List, Tuple, Optional import threading import argparse import mujoco import glfw from mujoco import viewer import imageio from datetime import datetime # 导入自定义模块 try: from mujoco_simulation import HumanoidRobot from advanced_control import ( IntelligentDecisionSystem, PathPlanner, EnergyOptimizer, AdaptiveController, EnvironmentState, RobotState, TaskType, TerrainType ) except ImportError: # 创建模拟类以避免导入错误 class HumanoidRobot: def __init__(self): self.model = mujoco.MjModel.from_xml_string(""" <mujoco> <worldbody> <light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/> <camera name="fixed" pos="0 -3 1" xyaxes="1 0 0 0 1 2"/> <body name="floor" pos="0 0 0"> <geom size="10 10 0.1" type="box" rgba=".8 .9 .8 1"/> </body> <body name="torso" pos="0 0 1.2"> <joint type="free"/> <geom size="0.2" type="sphere" rgba="1 0 0 1"/> <body name="head" pos="0 0 0.2"> <geom size="0.15" type="sphere" rgba="0 0 1 1"/> </body> <body name="left_arm" pos="0.3 0 0"> <joint type="hinge" axis="0 1 0"/> <geom size="0.08" fromto="0 0 0 0.4 0 0" type="capsule" rgba="0 1 0 1"/> </body> <body name="right_arm" pos="-0.3 0 0"> <joint type="hinge" axis="0 1 0"/> <geom size="0.08" fromto="0 0 0 -0.4 0 0" type="capsule" rgba="0 1 0 1"/> </body> <body name="left_leg" pos="0.15 0 -0.3"> <joint type="hinge" axis="0 1 0"/> <geom size="0.08" fromto="0 0 0 0 -0.4 0" type="capsule" rgba="0 1 0 1"/> </body> <body name="right_leg" pos="-0.15 0 -0.3"> <joint type="hinge" axis="0 1 0"/> <geom size="0.08" fromto="0 0 0 0 -0.4 0" type="capsule" rgba="0 1 0 1"/> </body> </body> </worldbody> </mujoco> """) self.data = mujoco.MjData(self.model) self.position = np.array([0.0, 0.0, 1.0]) self.velocity = np.zeros(3) self.orientation = np.array([1.0, 0.0, 0.0, 0.0]) # 设置初始状态 mujoco.mj_resetData(self.model, self.data) self.data.qpos[3:7] = [1, 0, 0, 0] # 四元数方向 class IntelligentDecisionSystem: pass class MuJoCoVisualizer: """MuJoCo原生可视化系统""" def __init__(self, model, data, width=1200, height=800): self.model = model self.data = data self.width = width self.height = height self.viewer = None self.camera = "fixed" # 默认摄像头 self.camera_modes = ["fixed", "track", "free"] self.current_camera_mode = 0 self.recording = False self.video_writer = None self.video_frames = [] self.scene_state = {} # 初始化摄像头位置 self.cameras = { "fixed": {"type": "fixed", "pos": [0, -3, 1], "xyaxes": [1, 0, 0, 0, 1, 2]}, "track": {"type": "track", "distance": 3.0, "elevation": -20, "azimuth": 45}, "free": {"type": "free", "distance": 5.0, "lookat": [0, 0, 1.2], "elevation": -20, "azimuth": 90} } def init_viewer(self): def __init__(self, model, data, width=1200, height=800): self.model = model self.data = data self.width = width self.height = height self.viewer = None self.camera = "fixed" # 默认摄像头 self.camera_modes = ["fixed", "track", "free"] self.current_camera_mode = 0 self.recording = False self.video_writer = None self.video_frames = [] self.scene_state = {} # 初始化摄像头位置 self.cameras = { "fixed": {"type": "fixed", "pos": [0, -3, 1], "xyaxes": [1, 0, 0, 0, 1, 2]}, "track": {"type": "track", "distance": 3.0, "elevation": -20, "azimuth": 45}, "free": {"type": "free", "distance": 5.0, "lookat": [0, 0, 1.2], "elevation": -20, "azimuth": 90} } def init_viewer(self): """初始化MuJoCo查看器""" # 创建必要的选项对象 opt = mujoco.MjvOption() pert = mujoco.MjvPerturb() scn = mujoco.MjvScene(self.model, maxgeom=1000) # 正确初始化查看器 self.viewer = viewer.Handle( width=self.width, height=self.height, opt=opt, pert=pert, user_scn=scn ) # 设置模型和数据 self.viewer.model = self.model self.viewer.data = self.data # 设置初始摄像头 self._update_camera() # 设置可视化选项 self.viewer.vopt.flags = [ mujoco.mjtVisFlag.mjVIS_JOINT, mujoco.mjtVisFlag.mjVIS_CONTACTPOINT, mujoco.mjtVisFlag.mjVIS_CONTACTFORCE ] # 设置初始视图 self._update_camera() def _update_camera(self): """更新摄像头设置""" mode = self.camera_modes[self.current_camera_mode] cam_settings = self.cameras[mode] if mode == "fixed": self.viewer.cam.type = mujoco.mjtCamera.mjCAMERA_FIXED self.viewer.cam.fixedcamid = mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_CAMERA, "fixed") elif mode == "track": self.viewer.cam.type = mujoco.mjtCamera.mjCAMERA_TRACKING self.viewer.cam.trackbodyid = mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_BODY, "torso") self.viewer.cam.distance = cam_settings["distance"] self.viewer.cam.elevation = cam_settings["elevation"] self.viewer.cam.azimuth = cam_settings["azimuth"] elif mode == "free": self.viewer.cam.type = mujoco.mjtCamera.mjCAMERA_FREE self.viewer.cam.lookat[:] = cam_settings["lookat"] self.viewer.cam.distance = cam_settings["distance"] self.viewer.cam.elevation = cam_settings["elevation"] self.viewer.cam.azimuth = cam_settings["azimuth"] def toggle_camera(self): """切换摄像头模式""" self.current_camera_mode = (self.current_camera_mode + 1) % len(self.camera_modes) self._update_camera() print(f"摄像头模式切换为: {self.camera_modes[self.current_camera_mode]}") def render(self, render_state=True): """渲染当前帧""" if not self.viewer: return # 更新场景状态 self.scene_state.update({ 'task': render_state.get('task', 'idle'), 'description': render_state.get('description', '待机'), 'terrain': render_state.get('terrain', 'flat'), 'time': render_state.get('time', 0.0), 'energy': render_state.get('energy', 0.0) }) # 渲染场景 self.viewer.render() # 录制视频 if self.recording: frame = np.empty((self.height, self.width, 3), dtype=np.uint8) self.viewer.read_pixels(frame, depth=None) self.video_frames.append(frame.copy()) def start_recording(self): """开始录制视频""" self.recording = True self.video_frames = [] print("⏺️ 开始录制视频...") def stop_recording(self): """停止录制并保存视频""" if not self.recording or len(self.video_frames) == 0: return False self.recording = False timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"simulation_{timestamp}.mp4" try: with imageio.get_writer(filename, fps=30) as writer: for frame in self.video_frames: writer.append_data(frame) print(f"✅ 视频已保存到: {filename}") self.video_frames = [] return True except Exception as e: print(f"⚠️ 保存视频失败: {e}") return False def close(self): """关闭查看器""" if self.viewer: self.viewer.close() self.viewer = None class HumanoidDemo: """人形机器人仿真演示类""" def __init__(self): """初始化演示系统""" print("🤖 人形机器人仿真演示系统") print("=" * 60) # 创建人形机器人 self.robot = self._create_robot() # 创建高级控制模块 self.decision_system = IntelligentDecisionSystem() self.path_planner = PathPlanner() self.energy_optimizer = EnergyOptimizer() self.adaptive_controller = AdaptiveController() # 创建MuJoCo可视化系统 self.visualizer = MuJoCoVisualizer(self.robot.model, self.robot.data) self.visualizer.init_viewer() # 演示配置 self.demo_config = { 'duration': 30.0, 'enable_ai': True, 'enable_optimization': True, 'enable_adaptation': True, 'record_data': True, 'save_video': False } # 演示数据 self.demo_data = { 'timestamps': [], 'robot_states': [], 'environment_states': [], 'decisions': [], 'energy_consumption': [0.0], 'performance_metrics': [{'stability': 1.0, 'efficiency': 1.0}] } # 运行状态 self.is_running = False self.current_time = 0.0 self.paused = False self.key_actions = { glfw.KEY_SPACE: 'pause', glfw.KEY_R: 'reset', glfw.KEY_C: 'change_camera', glfw.KEY_S: 'save_data', glfw.KEY_V: 'toggle_recording' } print("✅ 演示系统初始化完成") def _create_robot(self): """创建机器人实例""" try: return HumanoidRobot() except Exception as e: print(f"⚠️ 创建机器人时出错: {e}") print("⚠️ 使用模拟机器人替代") return HumanoidRobot() @property def energy_consumption(self): return self.demo_data['energy_consumption'] @property def performance_metrics(self): return self.demo_data['performance_metrics'] def setup_demo_scenario(self, scenario_type: str = "comprehensive"): """设置演示场景""" scenarios = { "comprehensive": self._setup_comprehensive_scenario, "walking": self._setup_walking_scenario, "obstacle": self._setup_obstacle_scenario, "terrain": self._setup_terrain_scenario, "interference": self._setup_interference_scenario } if scenario_type in scenarios: print(f"🎬 设置 {scenario_type} 演示场景...") scenarios[scenario_type]() else: print(f"⚠️ 未知场景类型: {scenario_type},使用综合场景") self._setup_comprehensive_scenario() def _setup_comprehensive_scenario(self): """设置综合演示场景""" self.scenario_timeline = [ [0, 5, "walking", "平地行走"], [5, 10, "obstacle", "动态避障"], [10, 15, "terrain", "斜坡行走"], [15, 20, "terrain", "楼梯攀爬"], [20, 25, "interference", "风力干扰"], [25, 30, "walking", "恢复行走"] ] self.environment_config = { 'obstacles': [ {'position': [2, 0, 0.5], 'radius': 0.3, 'type': 'static'}, {'position': [4, 1, 0.3], 'radius': 0.3, 'type': 'dynamic'}, {'position': [6, -1, 0.4], 'radius': 0.2, 'type': 'moving'} ], 'terrain_sequence': ['flat', 'slope', 'stairs', 'sand', 'flat'], 'wind_patterns': [ {'time': [20, 25], 'force': [50, 0, 0], 'type': 'gust'}, {'time': [25, 30], 'force': [20, 10, 0], 'type': 'steady'} ], 'light_patterns': [ {'time': [22, 24], 'intensity': 0.2, 'type': 'dim'}, {'time': [24, 26], 'intensity': 0.9, 'type': 'bright'} ] } # 添加地形几何体 self._add_terrain_geoms() def _add_terrain_geoms(self): """根据场景添加地形几何体""" # 添加斜坡 slope_height = 0.5 self.robot.model.geom('floor').size = [10, 10, 0.1] # 重置地板 # 添加斜坡几何体 slope_pos = [5, 0, slope_height / 2] slope_size = [5, 10, slope_height / 2] slope_quat = [1, 0, 0, 0] # 四元数 self.robot.model.body_add('world', name='slope') self.robot.model.geom_add( body='slope', name='slope_geom', type='box', size=slope_size, pos=slope_pos, quat=slope_quat, rgba=[0.7, 0.7, 0.9, 1] ) # 添加楼梯 stair_positions = [ [7.0, 0, 0.1], [7.5, 0, 0.3], [8.0, 0, 0.5], [8.5, 0, 0.7], [9.0, 0, 0.9] ] for i, pos in enumerate(stair_positions): self.robot.model.body_add('world', name=f'stair_{i}') self.robot.model.geom_add( body=f'stair_{i}', name=f'stair_geom_{i}', type='box', size=[0.25, 2.0, 0.1], pos=pos, rgba=[0.8, 0.7, 0.6, 1] ) # 重新初始化数据 mujoco.mj_resetData(self.robot.model, self.robot.data) def _setup_walking_scenario(self): """设置行走演示场景""" self.scenario_timeline = [ [0, 10, "walking", "基础行走"], [10, 20, "walking", "快速行走"], [20, 30, "walking", "慢速行走"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [], 'light_patterns': [] } def _setup_obstacle_scenario(self): """设置避障演示场景""" self.scenario_timeline = [ [0, 10, "obstacle", "静态障碍物"], [10, 20, "obstacle", "动态障碍物"], [20, 30, "obstacle", "复杂障碍物"] ] self.environment_config = { 'obstacles': [ {'position': [1, 0, 0.5], 'radius': 0.3, 'type': 'static'}, {'position': [3, 0, 0.3], 'radius': 0.2, 'type': 'dynamic'}, {'position': [5, 0, 0.4], 'radius': 0.25, 'type': 'moving'} ], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [], 'light_patterns': [] } # 添加障碍物几何体 for i, obs in enumerate(self.environment_config['obstacles']): self.robot.model.body_add('world', name=f'obstacle_{i}') self.robot.model.geom_add( body=f'obstacle_{i}', name=f'obs_geom_{i}', type='sphere', size=[obs['radius']], pos=obs['position'], rgba=[0.9, 0.5, 0.5, 1] if obs['type'] == 'static' else [0.5, 0.7, 0.9, 1] if obs['type'] == 'dynamic' else [0.7, 0.5, 0.9, 1] ) mujoco.mj_resetData(self.robot.model, self.robot.data) def _setup_terrain_scenario(self): """设置地形演示场景""" self.scenario_timeline = [ [0, 6, "terrain", "平地"], [6, 12, "terrain", "斜坡"], [12, 18, "terrain", "楼梯"], [18, 24, "terrain", "沙地"], [24, 30, "terrain", "平地"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat', 'slope', 'stairs', 'sand', 'flat'], 'wind_patterns': [], 'light_patterns': [] } self._add_terrain_geoms() def _setup_interference_scenario(self): """设置干扰演示场景""" self.scenario_timeline = [ [0, 10, "walking", "正常行走"], [10, 20, "interference", "风力干扰"], [20, 30, "interference", "光照干扰"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [ {'time': [10, 20], 'force': [80, 0, 0], 'type': 'strong_wind'} ], 'light_patterns': [ {'time': [20, 30], 'intensity': 0.1, 'type': 'low_light'} ] } def get_current_scenario_state(self, current_time: float) -> Dict: """获取当前场景状态""" current_task = "idle" task_description = "待机" # 确定当前任务 for start_time, end_time, task, description in self.scenario_timeline: if start_time <= current_time < end_time: current_task = task task_description = description break # 确定当前地形 terrain_index = min(int(current_time / 6), len(self.environment_config['terrain_sequence']) - 1) current_terrain = self.environment_config['terrain_sequence'][terrain_index] # 计算当前风力 current_wind = np.zeros(3) for wind_pattern in self.environment_config['wind_patterns']: if wind_pattern['time'][0] <= current_time < wind_pattern['time'][1]: current_wind = np.array(wind_pattern['force']) break # 计算当前光照 current_light = 1.0 for light_pattern in self.environment_config['light_patterns']: if light_pattern['time'][0] <= current_time < light_pattern['time'][1]: current_light = light_pattern['intensity'] break return { 'task': current_task, 'description': task_description, 'terrain': current_terrain, 'wind': current_wind, 'light': current_light, 'obstacles': self.environment_config['obstacles'].copy(), 'time': current_time } def update_robot_state(self): """更新机器人状态""" # 模拟机器人行走 if self.get_current_scenario_state(self.current_time)['task'] == 'walking': # 简单的前进运动 self.robot.data.qpos[0] = self.current_time * 0.5 # x位置 self.robot.data.qvel[0] = 0.5 # x速度 # 添加腿部摆动动画 leg_angle = 30 * np.sin(self.current_time * 5) arm_angle = 20 * np.sin(self.current_time * 3) # 左腿关节 left_leg_joint = mujoco.mj_name2id(self.robot.model, mujoco.mjtObj.mjOBJ_JOINT, "left_leg_joint") if left_leg_joint != -1: self.robot.data.qpos[left_leg_joint] = np.deg2rad(leg_angle) # 右腿关节 right_leg_joint = mujoco.mj_name2id(self.robot.model, mujoco.mjtObj.mjOBJ_JOINT, "right_leg_joint") if right_leg_joint != -1: self.robot.data.qpos[right_leg_joint] = np.deg2rad(-leg_angle) # 左臂关节 left_arm_joint = mujoco.mj_name2id(self.robot.model, mujoco.mjtObj.mjOBJ_JOINT, "left_arm_joint") if left_arm_joint != -1: self.robot.data.qpos[left_arm_joint] = np.deg2rad(arm_angle) # 右臂关节 right_arm_joint = mujoco.mj_name2id(self.robot.model, mujoco.mjtObj.mjOBJ_JOINT, "right_arm_joint") if right_arm_joint != -1: self.robot.data.qpos[right_arm_joint] = np.deg2rad(-arm_angle) # 应用风力 wind_force = self.get_current_scenario_state(self.current_time)['wind'] if np.linalg.norm(wind_force) > 0.1: torso_id = mujoco.mj_name2id(self.robot.model, mujoco.mjtObj.mjOBJ_BODY, "torso") if torso_id != -1: self.robot.data.xfrc_applied[torso_id, 0] = wind_force[0] self.robot.data.xfrc_applied[torso_id, 1] = wind_force[1] self.robot.data.xfrc_applied[torso_id, 2] = wind_force[2] # 前向动力学计算 mujoco.mj_forward(self.robot.model, self.robot.data) def change_camera_mode(self): """切换摄像头视角""" self.visualizer.toggle_camera() def record_data(self, current_time): """记录演示数据""" self.demo_data['timestamps'].append(current_time) # 模拟能量消耗 - 根据地形和任务类型变化 energy_factor = 1.0 terrain = self.get_current_scenario_state(current_time)['terrain'] if terrain == 'slope': energy_factor = 1.5 elif terrain == 'stairs': energy_factor = 1.8 elif terrain == 'sand': energy_factor = 2.0 new_energy = self.energy_consumption[-1] + 0.1 * energy_factor self.energy_consumption.append(new_energy) # 模拟性能指标 stability = 1.0 - np.random.uniform(0, 0.1) efficiency = 1.0 - np.random.uniform(0, 0.05) self.performance_metrics.append({ 'stability': max(0.1, stability), 'efficiency': max(0.1, efficiency) }) def save_performance_data(self): """保存性能数据""" filename = f"performance_data_{time.strftime('%Y%m%d_%H%M%S')}.json" try: with open(filename, 'w') as f: json.dump({ 'timestamps': self.demo_data['timestamps'], 'energy_consumption': self.demo_data['energy_consumption'], 'performance_metrics': self.demo_data['performance_metrics'] }, f, indent=2) print(f"✅ 性能数据已保存到 {filename}") return True except Exception as e: print(f"⚠️ 保存性能数据失败: {e}") return False def handle_events(self): """处理GLFW事件""" if not self.visualizer.viewer: return True # 检查窗口是否关闭 if glfw.window_should_close(self.visualizer.viewer.window): return False # 处理键盘事件 for key, action in self.key_actions.items(): if glfw.get_key(self.visualizer.viewer.window, key) == glfw.PRESS: return action return True def run_demo(self): """运行演示""" print(f"🚀 开始演示,持续时间: {self.demo_config['duration']}秒") print("=" * 60) print("控制说明:") print(" 空格键: 暂停/继续") print(" R键: 重置演示") print(" C键: 切换摄像头视角") print(" S键: 保存性能数据") print(" V键: 开始/停止录制视频") print(" ESC: 退出程序") self.is_running = True start_time = time.time() last_frame_time = start_time try: while self.current_time < self.demo_config['duration']: # 处理事件 event_result = self.handle_events() if event_result == False: # 窗口关闭 break elif event_result == "pause": self.paused = not self.paused print(f"⏸️ {'已暂停' if self.paused else '继续运行'}") elif event_result == "reset": start_time = time.time() self.current_time = 0.0 self.demo_data = { 'timestamps': [], 'energy_consumption': [0.0], 'performance_metrics': [{'stability': 1.0, 'efficiency': 1.0}] } print("🔄 演示已重置") elif event_result == "change_camera": self.change_camera_mode() elif event_result == "save_data": self.save_performance_data() elif event_result == "toggle_recording": if self.visualizer.recording: self.visualizer.stop_recording() else: self.visualizer.start_recording() if self.paused: time.sleep(0.1) continue # 更新当前时间 current_real_time = time.time() self.current_time = current_real_time - start_time # 控制帧率 (约30FPS) if current_real_time - last_frame_time < 0.033: continue last_frame_time = current_real_time # 更新机器人状态 self.update_robot_state() # 记录数据 if self.demo_config['record_data']: self.record_data(self.current_time) # 获取当前场景状态 scenario_state = self.get_current_scenario_state(self.current_time) render_state = { 'task': scenario_state['task'], 'description': scenario_state['description'], 'terrain': scenario_state['terrain'], 'time': self.current_time, 'energy': self.energy_consumption[-1] } # 渲染可视化界面 self.visualizer.render(render_state) # 添加场景信息覆盖 self._add_info_overlay(scenario_state) self.is_running = False print("\n✅ 演示完成!") # 自动保存性能数据 if self.demo_config['record_data']: self.save_performance_data() # 停止录制 if self.visualizer.recording: self.visualizer.stop_recording() except Exception as e: self.is_running = False print(f"\n⛔ 演示出错: {e}") traceback.print_exc() # 出错时保存当前数据 if self.demo_config['record_data']: print("⚠️ 尝试保存当前性能数据...") self.save_performance_data() finally: self.visualizer.close() def _add_info_overlay(self, scenario_state): """添加信息覆盖层""" if not self.visualizer.viewer: return # 左上角信息 self.visualizer.viewer.add_overlay( mujoco.mjtGridPos.mjGRID_TOPLEFT, "场景", f"{scenario_state['description']}" ) self.visualizer.viewer.add_overlay( mujoco.mjtGridPos.mjGRID_TOPLEFT, "地形", f"{scenario_state['terrain']}" ) self.visualizer.viewer.add_overlay( mujoco.mjtGridPos.mjGRID_TOPLEFT, "时间", f"{self.current_time:.1f}s / {self.demo_config['duration']:.1f}s" ) self.visualizer.viewer.add_overlay( mujoco.mjtGridPos.mjGRID_TOPLEFT, "能量消耗", f"{self.energy_consumption[-1]:.2f} J" ) self.visualizer.viewer.add_overlay( mujoco.mjtGridPos.mjGRID_TOPLEFT, "控制模式", f"{'AI控制' if self.demo_config['enable_ai'] else '手动控制'}" ) # 右上角状态 status = "运行中" if self.is_running else "已停止" self.visualizer.viewer.add_overlay( mujoco.mjtGridPos.mjGRID_TOPRIGHT, "状态", f"{status}{' (已暂停)' if self.paused else ''}" ) # 录制状态 if self.visualizer.recording: self.visualizer.viewer.add_overlay( mujoco.mjtGridPos.mjGRID_TOPRIGHT, "录制", "⏺️ 录制中..." ) def main(): """主函数""" parser = argparse.ArgumentParser(description='人形机器人仿真演示') parser.add_argument('--scenario', type=str, default='comprehensive', choices=['comprehensive', 'walking', 'obstacle', 'terrain', 'interference'], help='演示场景类型') parser.add_argument('--duration', type=float, default=30.0, help='演示持续时间(秒)') args = parser.parse_args() # 创建演示实例 demo = HumanoidDemo() demo.demo_config['duration'] = args.duration # 设置场景 demo.setup_demo_scenario(args.scenario) # 运行演示 demo.run_demo() if __name__ == "__main__": main()
07-29
针对以上问题,修正代码#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 人形机器人仿真演示 - 修复OpenGL初始化问题 """ import sys import os import time import json import numpy as np import matplotlib.pyplot as plt from typing import Dict, List, Tuple, Optional import threading import argparse import mujoco import pygame from pygame.locals import * import math import cv2 from PIL import Image import io import traceback import subprocess import ctypes # 检查OpenGL支持并尝试修复 def fix_opengl_issues(): """尝试解决OpenGL初始化问题""" print("🛠️ 尝试解决OpenGL初始化问题...") # 尝试设置不同的渲染后端 backends = ['glfw', 'osmesa', 'egl'] for backend in backends: try: os.environ['MUJOCO_GL'] = backend print(f" 尝试使用 {backend} 渲染后端...") # 测试OpenGL加载 test_model = mujoco.MjModel.from_xml_string('<mujoco/>') test_data = mujoco.MjData(test_model) mujoco.mj_forward(test_model, test_data) print(f"✅ 使用 {backend} 后端成功!") return True except Exception as e: print(f"⚠️ {backend} 后端失败: {e}") # 尝试加载OpenGL库 try: print(" 尝试直接加载OpenGL库...") opengl_libs = [ 'libGL.so.1', # Linux '/usr/lib/x86_64-linux-gnu/libGL.so.1', # Ubuntu 'opengl32.dll', # Windows '/System/Library/Frameworks/OpenGL.framework/OpenGL' # macOS ] for lib in opengl_libs: try: ctypes.CDLL(lib) print(f"✅ 成功加载 {lib}") return True except Exception as e: print(f"⚠️ 加载 {lib} 失败: {e}") except Exception as e: print(f"⚠️ OpenGL库加载失败: {e}") return False # 导入自定义模块 try: from mujoco_simulation import HumanoidRobot from advanced_control import ( IntelligentDecisionSystem, PathPlanner, EnergyOptimizer, AdaptiveController, EnvironmentState, RobotState, TaskType, TerrainType ) except ImportError: # 创建模拟类以避免导入错误 class HumanoidRobot: def __init__(self): self.model = None self.data = None self.position = np.array([0.0, 0.0, 1.0]) self.velocity = np.zeros(3) self.orientation = np.array([1.0, 0.0, 0.0, 0.0]) class IntelligentDecisionSystem: pass class VisualizationSystem: """可视化系统 - 用于2D和3D渲染""" def __init__(self, model: Optional[mujoco.MjModel] = None, width: int = 1200, height: int = 800): """初始化可视化系统""" pygame.init() self.screen = pygame.display.set_mode((width, height)) pygame.display.set_caption("人形机器人仿真演示") self.clock = pygame.time.Clock() self.font = pygame.font.SysFont('SimHei', 20) self.title_font = pygame.font.SysFont('SimHei', 28, bold=True) self.terrain_colors = { 'flat': (180, 200, 180), 'slope': (160, 180, 200), 'stairs': (200, 180, 160), 'sand': (220, 200, 150), 'grass': (150, 200, 150) } self.obstacle_colors = { 'static': (200, 100, 100), 'dynamic': (100, 150, 200), 'moving': (150, 100, 200) } self.robot_img = self._create_robot_image() # 尝试修复OpenGL问题 self.opengl_fixed = fix_opengl_issues() # 初始化MuJoCo渲染组件 self.camera = mujoco.MjvCamera() self.opt = mujoco.MjvOption() self.scene = None self.context = None if model is not None and self.opengl_fixed: try: # 尝试创建渲染上下文 self.context = mujoco.MjrContext(model, mujoco.mjtFontScale.mjFONTSCALE_150) self.scene = mujoco.MjvScene(model=model, maxgeom=1000) print("✅ 成功初始化渲染上下文") # 设置相机位置 self.camera.distance = 5.0 self.camera.elevation = -20 self.camera.azimuth = 90 except Exception as e: print(f"⚠️ 渲染上下文初始化失败: {e}") self.scene = None self.context = None else: print("⚠️ 无法修复OpenGL问题,禁用3D渲染") def _create_robot_image(self): """创建机器人图像""" surface = pygame.Surface((50, 80), pygame.SRCALPHA) # 绘制头部 pygame.draw.circle(surface, (100, 150, 255), (25, 15), 10) # 绘制身体 pygame.draw.rect(surface, (100, 200, 100), (15, 25, 20, 30)) # 绘制腿部 pygame.draw.line(surface, (0, 0, 0), (20, 55), (15, 75), 3) pygame.draw.line(surface, (0, 0, 0), (30, 55), (35, 75), 3) # 绘制手臂 pygame.draw.line(surface, (0, 0, 0), (15, 35), (5, 50), 3) pygame.draw.line(surface, (0, 0, 0), (35, 35), (45, 50), 3) return surface def render_2d(self, demo, current_time: float): """渲染2D可视化界面""" self.screen.fill((240, 240, 245)) # 获取当前场景状态 scenario_state = demo.get_current_scenario_state(current_time) # 绘制标题 title = self.title_font.render("人形机器人仿真演示系统", True, (30, 30, 100)) self.screen.blit(title, (20, 15)) # 绘制状态面板 self._render_status_panel(demo, scenario_state, current_time) # 绘制场景可视化 self._render_scenario_visualization(demo, scenario_state, current_time) # 绘制3D渲染窗口 self._render_3d_view(demo) # 绘制性能图表 self._render_performance_charts(demo) # 绘制控制说明 self._render_instructions() pygame.display.flip() def _render_status_panel(self, demo, scenario_state: Dict, current_time: float): """渲染状态面板""" # 状态面板背景 pygame.draw.rect(self.screen, (250, 250, 255), (20, 70, 450, 180), 0, 10) pygame.draw.rect(self.screen, (200, 200, 220), (20, 70, 450, 180), 2, 10) # 场景信息 scene_text = self.font.render(f"场景: {scenario_state['description']}", True, (30, 30, 30)) self.screen.blit(scene_text, (40, 90)) # 地形信息 terrain_text = self.font.render(f"地形: {scenario_state['terrain']}", True, (30, 30, 30)) self.screen.blit(terrain_text, (40, 120)) # 时间信息 time_text = self.font.render(f"时间: {current_time:.1f}s / {demo.demo_config['duration']:.1f}s", True, (30, 30, 30)) self.screen.blit(time_text, (40, 150)) # 能量消耗 energy_text = self.font.render( f"能量消耗: {demo.energy_consumption[-1] if demo.energy_consumption else 0:.2f} J", True, (30, 30, 30)) self.screen.blit(energy_text, (40, 180)) # 控制模式 mode_text = self.font.render(f"控制模式: {'AI控制' if demo.demo_config['enable_ai'] else '手动控制'}", True, (30, 30, 30)) self.screen.blit(mode_text, (40, 210)) # 渲染状态 render_status = "可用" if self.scene and self.context else "不可用" render_text = self.font.render(f"3D渲染: {render_status}", True, (30, 30, 30)) self.screen.blit(render_text, (250, 180)) # 状态指示器 status_color = (100, 200, 100) if demo.is_running else (200, 100, 100) pygame.draw.circle(self.screen, status_color, (400, 100), 10) status_text = self.font.render("运行中" if demo.is_running else "已停止", True, (30, 30, 30)) self.screen.blit(status_text, (415, 95)) # 暂停状态 if demo.paused: pause_text = self.font.render("已暂停", True, (200, 100, 50)) self.screen.blit(pause_text, (400, 130)) def _render_scenario_visualization(self, demo, scenario_state: Dict, current_time: float): """渲染场景可视化""" # 场景可视化背景 pygame.draw.rect(self.screen, (250, 250, 255), (20, 270, 450, 300), 0, 10) pygame.draw.rect(self.screen, (200, 200, 220), (20, 270, 450, 300), 2, 10) # 绘制地形 terrain_color = self.terrain_colors.get(scenario_state['terrain'], (180, 180, 180)) pygame.draw.rect(self.screen, terrain_color, (50, 350, 350, 100)) # 绘制地形特征 if scenario_state['terrain'] == 'slope': for i in range(10): pygame.draw.line(self.screen, (150, 150, 150), (50 + i * 35, 350), (50 + i * 35, 450 - i * 3), 2) elif scenario_state['terrain'] == 'stairs': for i in range(5): pygame.draw.rect(self.screen, (180, 160, 140), (50 + i * 70, 450 - i * 20, 70, 20 + i * 20)) elif scenario_state['terrain'] == 'sand': for i in range(20): x = 50 + np.random.randint(0, 350) y = 350 + np.random.randint(0, 100) pygame.draw.circle(self.screen, (210, 190, 150), (x, y), 3) # 绘制障碍物 for obs in scenario_state['obstacles']: color = self.obstacle_colors.get(obs['type'], (150, 150, 150)) x = 50 + (obs['position'][0] / 10) * 350 y = 400 - (obs['position'][1] / 2) * 50 radius = obs['radius'] * 50 pygame.draw.circle(self.screen, color, (int(x), int(y)), int(radius)) # 绘制机器人 robot_x = 50 + (min(current_time / demo.demo_config['duration'], 1.0) * 350) robot_y = 400 self.screen.blit(self.robot_img, (robot_x - 25, robot_y - 40)) # 绘制风力效果 if np.linalg.norm(scenario_state['wind']) > 0.1: wind_dir = scenario_state['wind'] / np.linalg.norm(scenario_state['wind']) for i in range(5): offset = i * 20 pygame.draw.line(self.screen, (100, 150, 255), (robot_x + 30 + offset, robot_y - 20), (robot_x + 50 + offset + wind_dir[0] * 10, robot_y - 20 + wind_dir[1] * 10), 2) pygame.draw.polygon(self.screen, (100, 150, 255), [ (robot_x + 50 + offset + wind_dir[0] * 10, robot_y - 20 + wind_dir[1] * 10), (robot_x + 45 + offset + wind_dir[0] * 15, robot_y - 25 + wind_dir[1] * 15), (robot_x + 45 + offset + wind_dir[0] * 15, robot_y - 15 + wind_dir[1] * 15) ]) # 场景标题 scene_title = self.font.render(f"当前场景: {scenario_state['description']}", True, (30, 30, 100)) self.screen.blit(scene_title, (40, 290)) def _render_3d_view(self, demo): """渲染3D视图""" # 3D视图背景 pygame.draw.rect(self.screen, (250, 250, 255), (500, 70, 680, 300), 0, 10) pygame.draw.rect(self.screen, (200, 200, 220), (500, 70, 680, 300), 2, 10) # 添加标题 title = self.font.render("机器人3D视图", True, (30, 30, 100)) self.screen.blit(title, (510, 80)) # 检查渲染组件是否可用 if self.scene is None or self.context is None or demo.robot.model is None: error_text = self.font.render("3D渲染不可用: 无法初始化OpenGL", True, (255, 0, 0)) self.screen.blit(error_text, (550, 150)) return try: # 更新3D场景 mujoco.mjv_updateScene( demo.robot.model, demo.robot.data, self.opt, None, self.camera, mujoco.mjtCatBit.mjCAT_ALL, self.scene) # 渲染到内存缓冲区 width, height = 680, 300 buffer = np.zeros((height, width, 3), dtype=np.uint8) viewport = mujoco.MjrRect(0, 0, width, height) mujoco.mjr_render(viewport, self.scene, self.context) # 获取渲染图像 mujoco.mjr_readPixels(buffer, None, viewport, self.context) # 转换为Pygame表面 img = Image.fromarray(buffer, 'RGB') img = img.transpose(Image.FLIP_TOP_BOTTOM) img_bytes = img.tobytes() py_image = pygame.image.fromstring(img_bytes, (width, height), 'RGB') # 绘制到屏幕 self.screen.blit(py_image, (500, 70)) except Exception as e: # 显示错误信息 error_text = self.font.render(f"3D渲染错误: {str(e)}", True, (255, 0, 0)) self.screen.blit(error_text, (510, 150)) def _render_performance_charts(self, demo): """渲染性能图表""" # 性能面板背景 pygame.draw.rect(self.screen, (250, 250, 255), (500, 390, 680, 180), 0, 10) pygame.draw.rect(self.screen, (200, 200, 220), (500, 390, 680, 180), 2, 10) # 标题 title = self.font.render("性能指标", True, (30, 30, 100)) self.screen.blit(title, (510, 400)) # 绘制能量消耗图表 if len(demo.energy_consumption) > 1: points = [] max_energy = max(demo.energy_consumption) if max(demo.energy_consumption) > 0 else 1 for i, val in enumerate(demo.energy_consumption): x = 520 + (i / len(demo.energy_consumption)) * 300 y = 550 - (val / max_energy) * 100 points.append((x, y)) if len(points) > 1: pygame.draw.lines(self.screen, (100, 150, 255), False, points, 2) # 绘制稳定性图表 if len(demo.performance_metrics) > 1: points = [] max_stability = max([m['stability'] for m in demo.performance_metrics]) or 1 for i, metric in enumerate(demo.performance_metrics): x = 850 + (i / len(demo.performance_metrics)) * 300 y = 550 - (metric['stability'] / max_stability) * 100 points.append((x, y)) if len(points) > 1: pygame.draw.lines(self.screen, (255, 150, 100), False, points, 2) # 图表标签 energy_label = self.font.render("能量消耗", True, (100, 150, 255)) self.screen.blit(energy_label, (520, 560)) stability_label = self.font.render("稳定性指标", True, (255, 150, 100)) self.screen.blit(stability_label, (850, 560)) def _render_instructions(self): """渲染控制说明""" # 控制说明背景 pygame.draw.rect(self.screen, (250, 250, 255), (20, 580, 1160, 100), 0, 10) pygame.draw.rect(self.screen, (200, 200, 220), (20, 580, 1160, 100), 2, 10) # 控制说明 instructions = [ "ESC: 退出程序", "空格键: 暂停/继续", "R键: 重置演示", "↑↓←→: 控制机器人移动", "C键: 切换摄像头视角" ] for i, text in enumerate(instructions): inst_text = self.font.render(text, True, (80, 80, 180)) self.screen.blit(inst_text, (40 + i * 220, 600)) def handle_events(self): """处理Pygame事件""" for event in pygame.event.get(): if event.type == QUIT: return False elif event.type == KEYDOWN: if event.key == K_ESCAPE: return False if event.key == K_SPACE: return "pause" elif event.key == K_r: return "reset" elif event.key == K_UP: return "move_forward" elif event.key == K_DOWN: return "move_backward" elif event.key == K_LEFT: return "move_left" elif event.key == K_RIGHT: return "move_right" elif event.key == K_c: return "change_camera" return True class HumanoidDemo: """人形机器人仿真演示类""" def __init__(self): """初始化演示系统""" print("🤖 人形机器人仿真演示系统") print("=" * 60) # 创建人形机器人 self.robot = self._create_robot() # 创建高级控制模块 self.decision_system = IntelligentDecisionSystem() self.path_planner = PathPlanner() self.energy_optimizer = EnergyOptimizer() self.adaptive_controller = AdaptiveController() # 创建可视化系统 - 传递机器人模型 self.visualization = VisualizationSystem( self.robot.model if hasattr(self.robot, 'model') else None ) # 演示配置 self.demo_config = { 'duration': 30.0, 'enable_ai': True, 'enable_optimization': True, 'enable_adaptation': True, 'record_data': True, 'save_video': False } # 演示数据 self.demo_data = { 'timestamps': [], 'robot_states': [], 'environment_states': [], 'decisions': [], 'energy_consumption': [0.0], 'performance_metrics': [{'stability': 1.0, 'efficiency': 1.0}] } # 运行状态 self.is_running = False self.current_time = 0.0 self.paused = False self.camera_mode = 0 # 0: 默认, 1: 跟随, 2: 第一人称 print("✅ 演示系统初始化完成") def _create_robot(self): """创建机器人实例,处理可能的错误""" try: return HumanoidRobot() except Exception as e: print(f"⚠️ 创建机器人时出错: {e}") print("⚠️ 使用模拟机器人替代") return HumanoidRobot() # 使用之前定义的模拟类 @property def energy_consumption(self): return self.demo_data['energy_consumption'] @property def performance_metrics(self): return self.demo_data['performance_metrics'] def setup_demo_scenario(self, scenario_type: str = "comprehensive"): """设置演示场景""" scenarios = { "comprehensive": self._setup_comprehensive_scenario, "walking": self._setup_walking_scenario, "obstacle": self._setup_obstacle_scenario, "terrain": self._setup_terrain_scenario, "interference": self._setup_interference_scenario } if scenario_type in scenarios: print(f"🎬 设置 {scenario_type} 演示场景...") scenarios[scenario_type]() else: print(f"⚠️ 未知场景类型: {scenario_type},使用综合场景") self._setup_comprehensive_scenario() def _setup_comprehensive_scenario(self): """设置综合演示场景""" self.scenario_timeline = [ [0, 5, "walking", "平地行走"], [5, 10, "obstacle", "动态避障"], [10, 15, "terrain", "斜坡行走"], [15, 20, "terrain", "楼梯攀爬"], [20, 25, "interference", "风力干扰"], [25, 30, "walking", "恢复行走"] ] self.environment_config = { 'obstacles': [ {'position': [2, 0, 0.5], 'radius': 0.3, 'type': 'static'}, {'position': [4, 1, 0.3], 'radius': 0.3, 'type': 'dynamic'}, {'position': [6, -1, 0.4], 'radius': 0.2, 'type': 'moving'} ], 'terrain_sequence': ['flat', 'slope', 'stairs', 'sand', 'flat'], 'wind_patterns': [ {'time': [20, 25], 'force': [50, 0, 0], 'type': 'gust'}, {'time': [25, 30], 'force': [20, 10, 0], 'type': 'steady'} ], 'light_patterns': [ {'time': [22, 24], 'intensity': 0.2, 'type': 'dim'}, {'time': [24, 26], 'intensity': 0.9, 'type': 'bright'} ] } def _setup_walking_scenario(self): """设置行走演示场景""" self.scenario_timeline = [ [0, 10, "walking", "基础行走"], [10, 20, "walking", "快速行走"], [20, 30, "walking", "慢速行走"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [], 'light_patterns': [] } def _setup_obstacle_scenario(self): """设置避障演示场景""" self.scenario_timeline = [ [0, 10, "obstacle", "静态障碍物"], [10, 20, "obstacle", "动态障碍物"], [20, 30, "obstacle", "复杂障碍物"] ] self.environment_config = { 'obstacles': [ {'position': [1, 0, 0.5], 'radius': 0.3, 'type': 'static'}, {'position': [3, 0, 0.3], 'radius': 0.2, 'type': 'dynamic'}, {'position': [5, 0, 0.4], 'radius': 0.25, 'type': 'moving'} ], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [], 'light_patterns': [] } def _setup_terrain_scenario(self): """设置地形演示场景""" self.scenario_timeline = [ [0, 6, "terrain", "平地"], [6, 12, "terrain", "斜坡"], [12, 18, "terrain", "楼梯"], [18, 24, "terrain", "沙地"], [24, 30, "terrain", "平地"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat', 'slope', 'stairs', 'sand', 'flat'], 'wind_patterns': [], 'light_patterns': [] } def _setup_interference_scenario(self): """设置干扰演示场景""" self.scenario_timeline = [ [0, 10, "walking", "正常行走"], [10, 20, "interference", "风力干扰"], [20, 30, "interference", "光照干扰"] ] self.environment_config = { 'obstacles': [], 'terrain_sequence': ['flat'] * 3, 'wind_patterns': [ {'time': [10, 20], 'force': [80, 0, 0], 'type': 'strong_wind'} ], 'light_patterns': [ {'time': [20, 30], 'intensity': 0.1, 'type': 'low_light'} ] } def get_current_scenario_state(self, current_time: float) -> Dict: """获取当前场景状态""" current_task = "idle" task_description = "待机" # 确定当前任务 for start_time, end_time, task, description in self.scenario_timeline: if start_time <= current_time < end_time: current_task = task task_description = description break # 确定当前地形 terrain_index = min(int(current_time / 6), len(self.environment_config['terrain_sequence']) - 1) current_terrain = self.environment_config['terrain_sequence'][terrain_index] # 计算当前风力 current_wind = np.zeros(3) for wind_pattern in self.environment_config['wind_patterns']: if wind_pattern['time'][0] <= current_time < wind_pattern['time'][1]: current_wind = np.array(wind_pattern['force']) break # 计算当前光照 current_light = 1.0 for light_pattern in self.environment_config['light_patterns']: if light_pattern['time'][0] <= current_time < light_pattern['time'][1]: current_light = light_pattern['intensity'] break return { 'task': current_task, 'description': task_description, 'terrain': current_terrain, 'wind': current_wind, 'light': current_light, 'obstacles': self.environment_config['obstacles'].copy(), 'time': current_time } def update_robot_state(self): """更新机器人状态""" # 模拟机器人控制逻辑 # 这里应该调用实际的机器人控制模块 pass def change_camera_mode(self): """切换摄像头视角""" self.camera_mode = (self.camera_mode + 1) % 3 if self.visualization.camera: if self.camera_mode == 0: # 默认视角 self.visualization.camera.distance = 5.0 self.visualization.camera.elevation = -20 self.visualization.camera.azimuth = 90 elif self.camera_mode == 1: # 跟随视角 self.visualization.camera.distance = 3.0 self.visualization.camera.elevation = -10 self.visualization.camera.azimuth = 45 elif self.camera_mode == 2: # 第一人称视角 self.visualization.camera.distance = 1.5 self.visualization.camera.elevation = 0 self.visualization.camera.azimuth = 0 def record_data(self, current_time): """记录演示数据""" self.demo_data['timestamps'].append(current_time) # 模拟能量消耗 - 根据地形和任务类型变化 energy_factor = 1.0 if self.get_current_scenario_state(current_time)['terrain'] == 'slope': energy_factor = 1.5 elif self.get_current_scenario_state(current_time)['terrain'] == 'stairs': energy_factor = 1.8 elif self.get_current_scenario_state(current_time)['terrain'] == 'sand': energy_factor = 2.0 new_energy = self.energy_consumption[-1] + 0.1 * energy_factor self.energy_consumption.append(new_energy) # 模拟性能指标 stability = 1.0 - np.random.uniform(0, 0.1) efficiency = 1.0 - np.random.uniform(0, 0.05) self.performance_metrics.append({ 'stability': max(0.1, stability), 'efficiency': max(0.1, efficiency) }) def run_demo(self): """运行演示""" print(f"🚀 开始演示,持续时间: {self.demo_config['duration']}秒") print("=" * 60) self.is_running = True start_time = time.time() try: while self.current_time < self.demo_config['duration']: # 处理事件 event_result = self.visualization.handle_events() if event_result == False: break elif event_result == "pause": self.paused = not self.paused elif event_result == "reset": start_time = time.time() self.current_time = 0.0 self.demo_data = { 'timestamps': [], 'energy_consumption': [0.0], 'performance_metrics': [{'stability': 1.0, 'efficiency': 1.0}] } elif event_result == "change_camera": self.change_camera_mode() if self.paused: time.sleep(0.1) continue # 更新当前时间 self.current_time = time.time() - start_time # 更新机器人状态 self.update_robot_state() # 记录数据 if self.demo_config['record_data']: self.record_data(self.current_time) # 渲染可视化界面 self.visualization.render_2d(self, self.current_time) # 控制帧率 self.visualization.clock.tick(30) self.is_running = False print("\n✅ 演示完成!") except Exception as e: self.is_running = False print(f"\n⛔ 演示出错: {e}") traceback.print_exc() finally: pygame.quit() def main(): """主函数""" parser = argparse.ArgumentParser(description='人形机器人仿真演示') parser.add_argument('--scenario', type=str, default='comprehensive', choices=['comprehensive', 'walking', 'obstacle', 'terrain', 'interference'], help='演示场景类型') parser.add_argument('--duration', type=float, default=30.0, help='演示持续时间(秒)') args = parser.parse_args() # 创建演示实例 demo = HumanoidDemo() demo.demo_config['duration'] = args.duration # 设置场景 demo.setup_demo_scenario(args.scenario) # 运行演示 demo.run_demo() if __name__ == "__main__": main()
07-29
import cv2 import numpy as np import time import os import argparse import json from datetime import datetime class VideoTester: def __init__(self, video_source, output_dir='results'): """ 初始化视频测试系统 参数: video_source: 视频文件路径或摄像头索引 output_dir: 结果输出目录 """ # 视频源处理 if isinstance(video_source, int) or video_source.isdigit(): self.cap = cv2.VideoCapture(int(video_source)) self.source_type = 'camera' else: if not os.path.exists(video_source): raise FileNotFoundError(f"视频文件不存在: {video_source}") self.cap = cv2.VideoCapture(video_source) self.source_type = 'file' # 验证视频源 if not self.cap.isOpened(): raise RuntimeError("无法打开视频源") # 获取视频属性 self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) self.fps = self.cap.get(cv2.CAP_PROP_FPS) self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(f"视频源: {video_source}") print(f"分辨率: {self.frame_width}x{self.frame_height}") print(f"帧率: {self.fps:.2f} FPS") print(f"总帧数: {self.total_frames if self.total_frames > 0 else '实时视频'}") # 创建输出目录 os.makedirs(output_dir, exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") self.output_prefix = os.path.join(output_dir, f"test_{timestamp}") # 处理参数 self.min_radius = 5 self.max_radius = 100 self.detection_params = { 'blur_size': (9, 9), 'threshold_block': 11, 'threshold_c': 2, 'min_area': 30, 'max_area': 3000 } # 性能跟踪 self.frame_count = 0 self.processing_times = [] self.detection_counts = [] self.start_time = time.time() # 结果记录 self.results = { 'source': video_source, 'source_type': self.source_type, 'resolution': [self.frame_width, self.frame_height], 'fps': self.fps, 'total_frames': self.total_frames, 'detection_params': self.detection_params, 'frames': [] } def detect_objects(self, frame): """ 检测图像中的圆形物体 返回: 位置列表[(x, y, radius), ...] """ start_time = time.perf_counter() # 预处理 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, self.detection_params['blur_size'], 2) # 自适应阈值 thresh = cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, self.detection_params['threshold_block'], self.detection_params['threshold_c'] ) # 形态学操作 kernel = np.ones((3, 3), np.uint8) cleaned = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) # 轮廓检测 contours, _ = cv2.findContours(cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) positions = [] for cnt in contours: area = cv2.contourArea(cnt) if area < self.detection_params['min_area'] or area > self.detection_params['max_area']: continue (x, y), radius = cv2.minEnclosingCircle(cnt) radius = int(radius) if self.min_radius <= radius <= self.max_radius: # 圆形度计算 perimeter = cv2.arcLength(cnt, True) if perimeter > 0: circularity = 4 * np.pi * area / (perimeter ** 2) if circularity > 0.7: positions.append((int(x), int(y), radius, circularity)) # 性能统计 proc_time = time.perf_counter() - start_time self.processing_times.append(proc_time) return positions def visualize_results(self, frame, positions, show_info=True): """ 可视化检测结果 """ display = frame.copy() # 绘制检测到的物体 for (x, y, r, circularity) in positions: color = (0, 255, 0) # 绿色: 检测通过 if circularity < 0.8: color = (0, 255, 255) # 黄色: 中等圆形度 if circularity < 0.7: color = (0, 0, 255) # 红色: 圆形度不足 cv2.circle(display, (x, y), r, color, 2) cv2.circle(display, (x, y), 2, (255, 0, 0), 2) cv2.putText(display, f"{circularity:.2f}", (x+10, y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 200), 1) if show_info: # 实时性能信息 elapsed = time.time() - self.start_time current_fps = self.frame_count / elapsed if elapsed > 0 else 0 # 处理时间统计 avg_proc_time = np.mean(self.processing_times[-50:]) if self.processing_times else 0 # 信息覆盖层 info_bar = np.zeros((60, display.shape[1], 3), dtype=np.uint8) # 基本视频信息 cv2.putText(info_bar, f"Frame: {self.frame_count}/{self.total_frames}", (10, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) cv2.putText(info_bar, f"FPS: {current_fps:.1f} (Source: {self.fps:.1f})", (10, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) # 检测信息 cv2.putText(info_bar, f"Objects: {len(positions)}", (250, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.putText(info_bar, f"Proc Time: {avg_proc_time*1000:.1f}ms", (250, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) # 参数信息 cv2.putText(info_bar, f"Threshold: {self.detection_params['threshold_block']}/{self.detection_params['threshold_c']}", (450, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 0), 1) cv2.putText(info_bar, f"Area: {self.detection_params['min_area']}-{self.detection_params['max_area']}", (450, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 0), 1) # 键盘提示 cv2.putText(info_bar, "[S]Save [Q]Quit [+]Blur [-]Blur [A]Area+ [Z]Area-", (display.shape[1]//2 - 180, 55), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 100, 255), 1) # 合并信息条 display = np.vstack([info_bar, display]) return display def save_results(self, frame, positions): """保存当前帧的检测结果""" frame_data = { 'frame_number': self.frame_count, 'timestamp': time.time() - self.start_time, 'objects_count': len(positions), 'objects': [] } for i, (x, y, r, circularity) in enumerate(positions): frame_data['objects'].append({ 'id': i, 'x': x, 'y': y, 'radius': r, 'circularity': circularity }) self.results['frames'].append(frame_data) # 保存关键帧图像 if positions: cv2.imwrite(f"{self.output_prefix}_frame_{self.frame_count}.jpg", frame) def run_test(self): """执行视频测试主循环""" print("\n=== 开始视频测试 ===") print("控制指令:") print(" Q - 退出测试") print(" S - 保存当前结果") print(" + - 增加模糊级别") print(" - - 减少模糊级别") print(" A - 增加最小面积") print(" Z - 减少最小面积") print(" D - 切换调试信息显示") debug_info = True # 默认显示调试信息 while True: ret, frame = self.cap.read() if not ret: if self.source_type == 'file': print("\n视频播放完毕") else: print("\n无法从摄像头获取帧") break # 处理帧 positions = self.detect_objects(frame) self.detection_counts.append(len(positions)) # 可视化 display = self.visualize_results(frame, positions, debug_info) cv2.imshow("Video Test - Object Detection", display) # 保存结果 self.save_results(frame, positions) # 更新帧计数 self.frame_count += 1 # 处理键盘输入 key = cv2.waitKey(1) & 0xFF if key == ord('q'): print("用户终止测试") break elif key == ord('s'): self.save_final_results() print(f"结果已保存到: {self.output_prefix}_results.json") elif key == ord('+'): self.detection_params['threshold_block'] = min(51, self.detection_params['threshold_block'] + 2) print(f"阈值块大小增加至: {self.detection_params['threshold_block']}") elif key == ord('-'): self.detection_params['threshold_block'] = max(3, self.detection_params['threshold_block'] - 2) print(f"阈值块大小减少至: {self.detection_params['threshold_block']}") elif key == ord('a'): self.detection_params['min_area'] = min(1000, self.detection_params['min_area'] + 10) print(f"最小面积增加至: {self.detection_params['min_area']}") elif key == ord('z'): self.detection_params['min_area'] = max(10, self.detection_params['min_area'] - 10) print(f"最小面积减少至: {self.detection_params['min_area']}") elif key == ord('d'): debug_info = not debug_info print(f"调试信息: {'开启' if debug_info else '关闭'}") # 清理资源 self.cap.release() cv2.destroyAllWindows() self.save_final_results() self.generate_report() print("\n=== 测试完成 ===") print(f"处理帧数: {self.frame_count}") print(f"平均处理时间: {np.mean(self.processing_times)*1000:.1f}ms") print(f"平均检测对象数: {np.mean(self.detection_counts):.1f}") print(f"结果文件: {self.output_prefix}_results.json") print(f"测试报告: {self.output_prefix}_report.txt") def save_final_results(self): """保存最终结果到JSON文件""" # 添加性能统计 self.results['performance'] = { 'total_processed_frames': self.frame_count, 'average_processing_time': np.mean(self.processing_times) if self.processing_times else 0, 'max_processing_time': np.max(self.processing_times) if self.processing_times else 0, 'min_processing_time': np.min(self.processing_times) if self.processing_times else 0, 'average_objects_per_frame': np.mean(self.detection_counts) if self.detection_counts else 0 } with open(f"{self.output_prefix}_results.json", 'w') as f: json.dump(self.results, f, indent=2) def generate_report(self): """生成文本格式测试报告""" report = f"=== 视频测试报告 ===\n\n" report += f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" report += f"视频源: {self.results['source']} ({self.results['source_type']})\n" report += f"分辨率: {self.results['resolution'][0]}x{self.results['resolution'][1]}\n" report += f"帧率: {self.results['fps']:.2f} FPS\n" report += f"总帧数: {self.results['total_frames']}\n" report += f"处理帧数: {self.results['performance']['total_processed_frames']}\n\n" report += f"检测参数:\n" report += f" 模糊大小: {self.detection_params['blur_size']}\n" report += f" 阈值块大小: {self.detection_params['threshold_block']}\n" report += f" 阈值常数: {self.detection_params['threshold_c']}\n" report += f" 最小面积: {self.detection_params['min_area']}\n" report += f" 最大面积: {self.detection_params['max_area']}\n" report += f" 最小半径: {self.min_radius}\n" report += f" 最大半径: {self.max_radius}\n\n" report += f"性能统计:\n" report += f" 平均处理时间: {self.results['performance']['average_processing_time']*1000:.1f} ms\n" report += f" 最大处理时间: {self.results['performance']['max_processing_time']*1000:.1f} ms\n" report += f" 最小处理时间: {self.results['performance']['min_processing_time']*1000:.1f} ms\n" report += f" 平均每帧对象数: {self.results['performance']['average_objects_per_frame']:.1f}\n" with open(f"{self.output_prefix}_report.txt", 'w') as f: f.write(report) if __name__ == "__main__": # 命令行参数解析 parser = argparse.ArgumentParser(description='视频测试系统') parser.add_argument('source', help='视频文件路径或摄像头索引 (0, 1, ...)') parser.add_argument('-o', '--output', default='results', help='输出目录') args = parser.parse_args() try: tester = VideoTester(args.source, args.output) tester.run_test() except Exception as e: print(f"错误: {str(e)}") exit(1)优化
10-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值