python的工业过程控制场景模拟第一百零六篇:AGV故障重调算法,一台运输车故障,自动分配任务给其他空闲AGV。

发布时间:2026/11/16 2:25:40

python的工业过程控制场景模拟第一百零六篇:AGV故障重调算法,一台运输车故障,自动分配任务给其他空闲AGV。 AGV 故障重调度算法 —— 基于任务迁移与负载均衡的动态分配“那年汽车焊装车间主线 AGV 突然驱动器报错停在生产线正中间后面 6 台车全部堵死停线 27 分钟损失六位数。后来我们上线了故障重调度算法一台车故障系统在 200ms 内把任务‘无损迁移’给空闲 AGV产线再也没有因为单车故障停过线。”—— 哈尔滨工程大学《工业过程控制》课程核心思想延伸一、实际应用场景描述在汽车制造、液晶面板、医药冷链等高节拍生产场景中数十台 AGV 组成密集搬运网络单车故障极易引发系统性拥堵┌──────────────────────────────────────────────┐│ AGV 集群故障重调度系统 ││ ││ [上位机调度中枢] ││ │ 心跳监测 / 故障告警 / 任务重分配 ││ ▼ ││ ┌────────────────────────────┐ ││ │ 状态感知层 │ ││ │ ┌──────────────────────┐ │ ││ │ │ 1. 心跳包 (50ms) │ │ ││ │ │ (UDP广播) │ │ ││ │ └──────────────────────┘ │ ││ │ ┌──────────────────────┐ │ ││ │ │ 2. 故障码解析 │ │ ││ │ │ (驱动器/激光/IMU) │ │ ││ │ └──────────────────────┘ │ ││ │ ┌──────────────────────┐ │ ││ │ │ 3. 电量/位置/负载上报 │ │ ││ │ │ (周期性状态同步) │ │ ││ └────────────┬───────────────┘ ││ │ 全局状态快照 ││ ┌───────┴───────┐ ││ ▼ ▼ ││ ┌─────────┐ ┌─────────┐ ││ │ 故障检测器 │ │ 任务迁移管理器 │ ││ │ • 心跳超时 │ │ • 任务接管 │ ││ │ • 异常码 │ │ • 路径重规划 │ ││ │ • 通信中断 │ │ • 代价评估 │ ││ └────┬────┘ └────┬────┘ ││ │ 故障事件 │ 迁移方案 ││ ▼ ▼ ││ ┌────────────────────────────┐ ││ │ 动态分配优化引擎 │ ││ │ • 空闲AGV筛选 │ ││ │ • 代价函数: f(d, soc, load)│ ││ │ • 匈牙利算法/贪心匹配 │ ││ │ • 负载均衡约束 │ ││ └────────────┬───────────────┘ ││ │ 新分配指令 ││ ▼ ││ ┌────────────────────────────┐ ││ │ 物理世界 (高节拍产线) │ ││ │ 故障AGV (急停/抱闸) │ ││ │ 空闲AGV (待命/充电) │ ││ │ 在途任务 (半成品/原料) │ ││ │ ⏱️ 生产节拍 (≤2min/台) │ ││ └───────────────────────────┘ ││ ││ 核心: 故障检测 任务迁移 负载均衡 无感切换 │└──────────────────────────────────────────────┘传统人工介入 vs 故障重调度维度 传统人工介入呼叫维修 故障重调度算法响应时间 ❌ 5~30 分钟 ✅ 200ms 内自动完成产线影响 ❌ 整线/局部停线 ✅ 单点故障无感切换任务处理 ❌ 任务丢失/人工找回 ✅ 任务状态完整迁移负载分布 ❌ 剩余 AGV 过载 ✅ 全局负载重均衡恢复效率 ❌ 依赖人工经验 ✅ 算法最优分配二、引入痛点2.1 现场的真实困境场景 现场发生了什么 根因“主干道瘫痪” “一台 AGV 坏在路口后面全堵死” 无故障隔离与任务迁移“物料裸露” “半成品停在缓冲区防尘罩失效” 任务未接管状态丢失“抢修变乱修” “维修工不知道车在执行什么任务” 任务上下文未同步“忙闲不均” “剩余 AGV 有的跑冒烟有的闲死” 无负载均衡重分配“节拍崩盘” “2 分钟节拍拖到 15 分钟” 重调度延迟过高2.2 核心矛盾高节拍产线的容错不是“修车”而是“换车”。 传统思路等待维修而我们需要的是故障后的“任务热迁移”——在毫秒级内将故障 AGV 的任务状态、路径规划、物料信息完整转移给最优的空闲 AGV并保证全局负载均衡。2.3 我们要解决什么用一段精简的 Python 程序构建一个 AGV 故障重调度仿真系统实现1. 故障检测 —— 心跳超时 异常码解析2. 任务建模 —— 任务状态机待分配/执行中/挂起/完成3. 候选筛选 —— 空闲 AGV 电量/位置/负载约束4. 代价评估 —— 距离 电量 负载均衡加权5. 最优分配 —— 贪心匹配工程实用版6. 可视化 —— 故障时刻、迁移过程、负载变化三、核心逻辑讲解3.1 理论基础任务迁移与负载均衡本工具基于哈工程《工业过程控制》第四章“线性规划”、第十三章“最优控制”和第十五章“可靠性与容错控制”① 任务状态机模型┌─────────┐│ 待分配 │└────┬────┘│ 分配▼┌─────────┐ 故障 ┌─────────┐│ 执行中 ├──────────▶│ 挂起 │└────┬────┘ └────┬────┘│ 完成 │ 迁移▼ ▼┌─────────┐ ┌─────────┐│ 完成 │◀────────│ 接管执行 │└─────────┘ └─────────┘② 故障检测心跳机制定义心跳超时阈值 T_{timeout} \text{Fault} \begin{cases}1, \text{if } (t_{now} - t_{last\_heartbeat}) T_{timeout} \\0, \text{otherwise}\end{cases}③ 代价函数设计候选 AGV j 接替任务 i 的综合代价C_{ij} \alpha \cdot \frac{d_{ij}}{d_{max}} \beta \cdot (1 - \frac{SOC_j}{SOC_{max}}) \gamma \cdot \frac{L_j}{L_{max}}其中- d_{ij} 候选 AGV 到任务拾取点的距离- SOC_j 电量- L_j 当前负载正在执行的任务数- \alpha, \beta, \gamma 权重系数④ 负载均衡约束避免重调度后出现新的瓶颈L_j^{new} \le \overline{L} \sigma_L其中 \overline{L} 为平均负载 \sigma_L 为允许波动。3.2 重调度架构总览┌─────────────┐│ 心跳监测线程 ││ (50ms周期) │└──────┬──────┘│ 超时事件┌─────────▼─────────┐│ 故障仲裁器 ││ • 误报过滤 ││ • 故障等级判定 │└─────────┬─────────┘│ 故障AGV ID┌─────────▼─────────┐│ 任务上下文提取 ││ • 任务ID/类型 ││ • 拾取点/投放点 ││ • 当前状态/路径 │└─────────┬─────────┘│ 任务快照┌─────────▼─────────┐│ 候选AGV筛选 ││ • 状态IDLE ││ • SOC≥阈值 ││ • 可达性检查 │└─────────┬─────────┘│ 候选列表┌─────────▼─────────┐│ 代价评估与分配 ││ • 计算C_ij ││ • 贪心/匈牙利匹配 ││ • 负载均衡校验 │└─────────┬─────────┘│ 新分配指令▼┌─────────────┐│ 集群执行层 │└─────────────┘四、代码讲解面向对象设计4.1 类结构总览类名 职责 设计模式TaskState 任务状态枚举 枚举AGVState AGV 状态枚举 枚举Task 搬运任务实体 实体对象AGV AGV 本体实体 实体对象HeartbeatMonitor 心跳监测器 观察者模式FaultDetector 故障检测器 策略模式TaskMigrator 任务迁移管理器 中介者模式DispatchOptimizer 重调度优化器 模板方法FleetSimulator 集群仿真器聚合根 聚合根VisualizationEngine 可视化引擎 封装4.2 核心代码完整可运行完整源码约 560 行包含 10 个类、故障注入、任务迁移、负载均衡、可视化。以下为精简核心版完整代码可直接复制运行。detailssummary 完整源码点击展开/折叠/summaryAGV故障重调度算法 —— 基于任务迁移与负载均衡参考哈尔滨工程大学《工业过程控制》第四章线性规划与第十五章容错控制from dataclasses import dataclass, fieldfrom typing import List, Dict, Optional, Tuple, Setfrom enum import Enum, autoimport numpy as npimport matplotlib.pyplot as pltimport heapqimport timeimport randomfrom collections import defaultdict, dequefrom datetime import datetimeimport threading# # 1. 基础枚举与数据结构# class TaskState(Enum):任务状态枚举PENDING auto() # 待分配DISPATCHED auto() # 已分配未开始EXECUTING auto() # 执行中SUSPENDED auto() # 挂起故障MIGRATED auto() # 已迁移COMPLETED auto() # 完成FAILED auto() # 失败class AGVState(Enum):AGV状态枚举IDLE auto() # 空闲BUSY auto() # 执行任务CHARGING auto() # 充电FAULT auto() # 故障MAINTENANCE auto() # 维护OFFLINE auto() # 离线dataclassclass Position:二维位置 —— 值对象x: float 0.0y: float 0.0def distance_to(self, other) - float:return np.sqrt((self.x - other.x)**2 (self.y - other.y)**2)def to_tuple(self):return (self.x, self.y)dataclassclass Task:搬运任务 —— 实体对象task_id: strpickup: Positiondropoff: Positionpriority: int 1 # 1普通, 2高, 3紧急payload: float 0.0 # 载重(kg)created_at: float field(default_factorytime.time)# 运行时状态state: TaskState TaskState.PENDINGassigned_agv: Optional[str] Nonesuspended_at: Optional[float] Nonemigrated_from: Optional[str] Nonemigrated_to: Optional[str] Noneprogress: float 0.0 # 0~1def snapshot(self) - dict:生成任务快照用于迁移return {task_id: self.task_id,pickup: self.pickup.to_tuple(),dropoff: self.dropoff.to_tuple(),priority: self.priority,payload: self.payload,progress: self.progress,state: self.state.name}dataclassclass AGV:AGV实体 —— 实体对象agv_id: strposition: Position field(default_factoryPosition)state: AGVState AGVState.IDLEbattery: float 100.0 # 电量%max_payload: float 500.0 # 最大载重speed: float 1.5 # 最大速度 m/stasks: List[Task] field(default_factorylist)last_heartbeat: float field(default_factorytime.time)fault_code: int 0total_distance: float 0.0completed_tasks: int 0propertydef load(self) - int:当前负载任务数return len(self.tasks)propertydef is_available(self) - bool:是否可接收新任务return self.state AGVState.IDLE and self.battery 20def assign_task(self, task: Task) - bool:分配任务if not self.is_available:return Falseif task.payload self.max_payload:return Falsetask.assigned_agv self.agv_idtask.state TaskState.DISPATCHEDself.tasks.append(task)self.state AGVState.BUSYreturn Truedef take_over_task(self, task: Task, snapshot: dict) - bool:接管任务迁移if not self.is_available:return Falseif task.payload self.max_payload:return False# 恢复任务状态task.progress snapshot.get(progress, 0.0)task.state TaskState.EXECUTINGtask.migrated_to self.agv_idtask.migrated_from snapshot.get(assigned_agv)self.tasks.append(task)self.state AGVState.BUSYreturn Truedef update_heartbeat(self):更新心跳self.last_heartbeat time.time()def simulate_movement(self, dt: float) - bool:模拟运动简化if not self.tasks:return Falsetask self.tasks[0]# 简化根据progress移动if task.progress 0.5:# 前往拾取点target task.pickupelse:# 前往投放点target task.dropoffdist self.position.distance_to(target)step self.speed * dtif dist step:self.position targettask.progress 1.0 if task.progress 0.5 else 0.5else:# 线性插值ratio step / distself.position.x (target.x - self.position.x) * ratioself.position.y (target.y - self.position.y) * ratiotask.progress 0.1 * dt# 耗电self.battery - step * 0.01self.total_distance step# 任务完成if task.progress 1.0 and dist 0.1:task.state TaskState.COMPLETEDself.tasks.remove(task)self.completed_tasks 1if not self.tasks:self.state AGVState.IDLEreturn Truereturn False# # 2. 心跳与故障检测# class HeartbeatMonitor:心跳监测器 —— 观察者模式def __init__(self, timeout: float 0.2):self.timeout timeoutself.listeners []def add_listener(self, callback):self.listeners.append(callback)def check(self, agv: AGV) - bool:检查心跳now time.time()if now - agv.last_heartbeat self.timeout:fault_info {agv_id: agv.agv_id,fault_type: HEARTBEAT_TIMEOUT,timestamp: now,last_heartbeat: agv.last_heartbeat}for listener in self.listeners:listener(fault_info)return Falsereturn Trueclass FaultDetector:故障检测器 —— 策略模式def __init__(self):self.fault_handlers {HEARTBEAT_TIMEOUT: self._handle_heartbeat_timeout,LOW_BATTERY: self._handle_low_battery,DRIVE_FAULT: self._handle_drive_fault,SENSOR_FAULT: self._handle_sensor_fault}def detect(self, agv: AGV) - Optional[dict]:综合故障检测# 心跳已在HeartbeatMonitor中处理# 电量检测if agv.battery 10:return {agv_id: agv.agv_id,fault_type: LOW_BATTERY,timestamp: time.time(),battery: agv.battery}# 驱动器故障模拟if agv.fault_code ! 0:return {agv_id: agv.agv_id,fault_type: DRIVE_FAULT,timestamp: time.time(),fault_code: agv.fault_code}return Nonedef handle_fault(self, fault_info: dict, fleet: FleetSimulator):处理故障handler self.fault_handlers.get(fault_info[fault_type])if handler:handler(fault_info, fleet)def _handle_heartbeat_timeout(self, fault_info: dict, fleet: FleetSimulator):agv_id fault_info[agv_id]agv fleet.agvs.get(agv_id)if agv:agv.state AGVState.OFFLINEprint(f [{datetime.now().strftime(%H:%M:%S.%f)[:-3]}] fAGV {agv_id} 心跳超时触发重调度...)fleet.trigger_reschedule(agv_id)def _handle_low_battery(self, fault_info: dict, fleet: FleetSimulator):agv_id fault_info[agv_id]agv fleet.agvs.get(agv_id)if agv:agv.state AGVState.CHARGINGprint(f [{datetime.now().strftime(%H:%M:%S.%f)[:-3]}] fAGV {agv_id} 电量过低进入充电模式)def _handle_drive_fault(self, fault_info: dict, fleet: FleetSimulator):agv_id fault_info[agv_id]agv fleet.agvs.get(agv_id)if agv:agv.state AGVState.FAULTprint(f⚙️ [{datetime.now().strftime(%H:%M:%S.%f)[:-3]}] fAGV {agv_id} 驱动器故障代码: {fault_info.get(fault_code)})fleet.trigger_reschedule(agv_id)def _handle_sensor_fault(self, fault_info: dict, fleet: FleetSimulator):agv_id fault_info[agv_id]agv fleet.agvs.get(agv_id)if agv:agv.state AGVState.FAULTprint(f [{datetime.now().strftime(%H:%M:%S.%f)[:-3]}] fAGV {agv_id} 传感器故障)# # 3. 任务迁移管理器# class TaskMigrator:任务迁移管理器 —— 中介者模式def __init__(self, optimizer: DispatchOptimizer):self.optimizer optimizerself.migration_log []def migrate_task(self, failed_agv_id: str, task: Task,fleet: FleetSimulator) - Optional[str]:执行任务迁移print(f 开始迁移任务 {task.task_id} (来自 AGV {failed_agv_id}))# 1. 生成任务快照snapshot task.snapshot()task.state TaskState.SUSPENDEDtask.suspended_at time.time()# 2. 筛选候选AGVcandidates self.optimizer.find_candidates(task, fleet.agvs)if not candidates:print(f ❌ 无可用AGV接管任务 {task.task_id})task.state TaskState.FAILEDreturn None# 3. 评估代价并选择最优best_agv_id self.optimizer.select_best(candidates, task, fleet.agvs)if not best_agv_id:print(f ❌ 代价评估失败任务 {task.task_id} 无法分配)task.state TaskState.FAILEDreturn None# 4. 执行迁移best_agv fleet.agvs[best_agv_id]if best_agv.take_over_task(task, snapshot):task.state TaskState.MIGRATEDmigration_record {timestamp: time.time(),task_id: task.task_id,from_agv: failed_agv_id,to_agv: best_agv_id,snapshot: snapshot}self.migration_log.append(migration_record)print(f ✅ 任务 {task.task_id} 已迁移: fAGV {failed_agv_id} → AGV {best_agv_id})return best_agv_idreturn Nonedef get_migration_stats(self) - dict:获取迁移统计if not self.migration_log:return {}total len(self.migration_log)avg_time np.mean([m[timestamp] for m in self.migration_log])return {total_migrations: total,avg_migration_time: avg_time,migrations: self.migration_log}# # 4. 重调度优化器# class DispatchOptimizer:重调度优化器 —— 模板方法def __init__(self, alpha1.0, beta0.5, gamma0.3):self.alpha alpha # 距离权重self.beta beta # 电量权重self.gamma gamma # 负载权重def find_candidates(self, task: Task, agvs: Dict[str, AGV]) - List[str]:筛选候选AGVcandidates []for agv in agvs.values():# 可用性检查if not agv.is_available:continue# 载重检查if task.payload agv.max_payload:continue# 电量检查考虑往返距离dist_to_pickup agv.position.distance_to(task.pickup)dist_pickup_to_dropoff task.pickup.distance_to(task.dropoff)total_dist dist_to_pickup dist_pickup_to_dropoffrequired_soc total_dist * 0.02 # 简化2%/100mif agv.battery required_soc 20: # 保留20%余量continuecandidates.append(agv.agv_id)return candidatesdef calculate_cost(self, agv_id: str, task: Task, agvs: Dict[str, AGV]) - float:计算分配代价agv agvs[agv_id]# 距离代价dist agv.position.distance_to(task.pickup)max_dist 100.0 # 归一化基准dist_cost min(dist / max_dist, 1.0)# 电量代价电量越低代价越高soc_cost 1.0 - (agv.battery / 100.0)# 负载代价avg_load np.mean([a.load for a in agvs.values() if a.load 0])load_cost agv.load / max(avg_load, 1.0) if avg_load 0 else 0# 综合代价total_cost (self.alpha * dist_cost self.beta * soc_cost self.gamma * load_cost)return total_costdef select_best(self, candidates: List[str], task: Task,agvs: Dict[str, AGV]) - Optional[str]:选择最优AGV贪心算法if not candidates:return Nonebest_agv_id Nonemin_cost float(inf)for agv_id in candidates:cost self.calculate_cost(agv_id, task, agvs)if cost min_cost:min_cost costbest_agv_id agv_idreturn best_agv_iddef balance_load(self, agvs: Dict[str, AGV]) - Dict[str, float]:负载均衡评估loads {agv_id: agv.load for agv_id, agv in agvs.items()}avg_load np.mean(list(loads.values())) if loads else 0std_load np.std(list(loads.values())) if loads else 0return {avg_load: avg_load,std_load: std_load,load_distribution: loads}# # 5. 集群仿真器聚合根# class FleetSimulator:AGV集群仿真器 —— 聚合根def __init__(self, num_agvs: int 8):self.agvs: Dict[str, AGV] {}self.tasks: Dict[str, Task] {}self.heartbeat_monitor HeartbeatMonitor(timeout0.2)self.fault_detector FaultDetector()利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
延伸阅读

更多相关文章

2026/11/8 12:49:19

React 底层原理与大型应用架构实践:流量上来前要补哪些防线

React 底层原理与大型应用架构实践:流量上来前要补哪些防线说明:本文用高频更新场景解释背压与诊断方法。任何频率、时延或容量数值都只作配置示例,需以目标设备和实际负载测试调整。1. 大促零点的告警蜂鸣:秒杀页面 P99 渲染耗时…

2026/11/8 18:44:50

PyCharm高效开发指南:从配置到实战技巧

1. PyCharm:Python开发者的生产力倍增器作为JetBrains旗下专为Python设计的集成开发环境,PyCharm已经成为全球数百万开发者的首选工具。我第一次接触PyCharm是在2015年接手一个Django项目时,当时就被它的智能代码补全和强大的调试功能所震撼。…

2026/11/12 12:45:53

SpringBoot与微信小程序结合的刷题系统开发实践

1. 项目概述:当SpringBoot遇上微信小程序的刷题革命 去年帮朋友改造培训机构题库系统时,我首次尝试将SpringBoot后端与微信小程序前端结合。原本预计两周的工作量,最终只用了5天就完成了核心功能上线。这种技术组合带来的开发效率提升让我印象…

2026/11/16 2:20:45

嘉祥电工证培训学校怎么报名不被坑?官方报名入口在这里

嘉祥电工证培训学校怎么报名不被坑?官方报名入口在这里 你是不是也在为“不知道去哪报名怕被中介坑”发愁?现在考电工证,最怕的就是找错机构,结果花了不少钱,证书还没拿到手,甚至耽误了工作机会。别急,今天就带你了解【嘉祥电工证培训学校】的官方报名…

2026/11/16 2:20:45

赣榆特种工培训学校千万别踩坑怎么选

赣榆特种工培训学校千万别踩坑怎么选 不知道去哪报名怕被中介坑,现在考个特种作业操作证,找培训机构比找工作还难。特别是赣榆特种工培训学校,网上一搜一大把,但真正靠谱的寥寥无几。很多初次报考的人,一不小心就掉进“低价陷阱”,最后不仅浪费钱,连证…

2026/11/16 2:20:45

低压电工证真假辨别别被中介坑了,考前押题能帮你省心

低压电工证真假辨别别被中介坑了,考前押题能帮你省心 你是不是正准备考低压电工证,但不知道去哪报名,怕被中介坑?现在市面上培训机构五花八门,真假难辨,稍不留神就可能花冤枉钱拿不到真证。低压电工证真假辨别是很多人的痛点,但如果你掌握了正确的渠道…

2026/11/16 2:20:45

电工证哪个平台报销?学历不高怕报不上名?报名流程全在这里

电工证哪个平台报销?学历不高怕报不上名?报名流程全在这里 你是不是也这样想, 学历不高怕报不上名 ?电工证是很多一线工人想要突破职业瓶颈的关键,但很多人担心自己文化程度不够,能不能报名、能不能通过?别担心,这篇文章会从 怎么报名…

2026/11/16 2:20:45

南阳市在那老电工证怎么转异地?内部消息教你避坑

南阳市在那老电工证怎么转异地?内部消息教你避坑 找工作被卡在“特种作业证”这道门槛?南阳市在那老电工证转异地这事,很多人根本不知道怎么操作,结果耽误了入职、耽误了机会。内部消息告诉你,现在南阳市很多企业都开始严格审查电工证,特别是异地转证的…

2026/11/15 0:02:49

电工证考试口诉怎么准备值不值得考

电工证考试口诉怎么准备值不值得考 实操考试心里没底怕挂科,这是大多数准备考电工证的人心里最真实的感受。尤其是面对【电工证考试口诉】环节,很多人不知道该怎么准备,甚至担心自己说不好就被淘汰。其实,只要掌握方法, 值不值得考…

2026/11/15 0:02:49

桑植县考电工证千万别踩坑!报名全流程教你避坑

桑植县考电工证千万别踩坑!报名全流程教你避坑 你是不是也遇到过这种情况:想考个电工证,但不知道从哪儿下手,怕被中介坑,怕花冤枉钱,更怕考了证还不能用?别急,这正是你该看这篇文章的原因。在桑植县考电工证,确实有很多人被坑过,但只要你知道流程、…

2026/11/16 0:00:11

黎城电工证查询报名费多少钱 内部消息告诉你怎么避开中介陷阱

黎城电工证查询报名费多少钱 内部消息告诉你怎么避开中介陷阱 你是不是也和我一样,想考电工证,但不知道去哪报名,怕被中介坑?最近网上有不少人问“黎城电工证查询报名费多少钱”,结果一查,信息五花八门,真假难辨。别担心,今天我就从一线从业者的角度…

2026/11/16 0:00:11

青岛崂山区电工证培训通过率揭秘:外省证书怎么转过来?

青岛崂山区电工证培训通过率揭秘:外省证书怎么转过来? 之前考的证在外省能不能转过来?这个问题困扰着很多在外地打工的电工,尤其是像南阳卧龙区的建筑工人,他们常年在工地作业,手上持有电工证是基本要求,但很多人因为工作流动性大,担心证书不能转到本…

2026/11/15 4:46:37

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/15 2:06:31

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑