🦁 动物园观光车自动导览系统
📖 项目概述
这是一个基于计算机视觉、深度学习和自然语言处理的智能动物园观光车导览系统。系统通过识别动物、规划路线、语音讲解,为游客提供沉浸式的智能导览体验。
🌍 实际应用场景
场景描述
在大型野生动物园,观光车沿着固定路线行驶,游客希望:
- 自动识别沿途动物并讲解
- 按最佳顺序游览各个景点
- 了解动物习性和保护知识
- 获得个性化导览服务
痛点分析
痛点 影响 信息缺失 游客看得到动物但不了解 路线混乱 不知道下一个景点是哪里 讲解单调 传统广播内容千篇一律 错过景点 容易错过隐蔽的观景点 语言障碍 外语游客听不懂中文讲解
🧠 核心逻辑讲解
┌─────────────────────────────────────────────────────────────────────────────┐ │ 系统工作流程 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 摄像头 │ -> │ 动物检测 │ -> │ 景点匹配 │ -> │ 语音讲解 │ │ │ │ 采集 │ │ YOLOv8 │ │ 导航引擎 │ │ TTS引擎 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ ↑ ↑ ↑ ↑ │ │ └──────────────┴──────────────┴──────────────┘ │ │ 主循环处理 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 路线规划 │ -> │ 距离计算 │ -> │ 状态管理 │ -> │ 用户交互 │ │ │ │ A*算法 │ │ 视觉测距 │ │ 状态机 │ │ 界面显示 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
景点停靠判定逻辑: ┌─────────────────────────────────────────────────────────────────────────────┐ │ 1. 检测到动物类别 │ │ ↓ │ │ 2. 匹配景点数据库 │ │ ↓ │ │ 3. 计算车辆与景点距离 │ │ ↓ │ │ 4. 距离 < 停靠阈值? │ │ ↓是 ↓否 │ │ 5. 触发停靠 继续行驶 │ │ ↓ │ │ 6. 播放讲解 + 显示信息 │ │ ↓ │ │ 7. 等待设定时间/游客确认 │ │ ↓ │ │ 8. 继续下一景点 │ └─────────────────────────────────────────────────────────────────────────────┘
关键技术点
- 多目标检测:YOLOv8同时检测多种动物
- 语义理解:将视觉识别映射到景点知识
- 路径规划:A*算法计算最优游览路线
- 语音合成:TTS生成自然流畅的讲解
- 状态机管理:处理导览全流程
📁 项目结构
zoo_tour_guide/ │ ├── main.py # 主程序入口 ├── config.py # 配置文件 ├── requirements.txt # 依赖包 ├── README.md # 项目说明 │ ├── modules/ # 功能模块 │ ├── init.py │ ├── camera.py # 摄像头管理 │ ├── animal_detector.py # 动物检测器 │ ├── tour_navigator.py # 导览导航器 │ ├── speech_engine.py # 语音引擎 │ ├── guide_display.py # 导览显示 │ └── route_planner.py # 路线规划器 │ ├── data/ # 数据文件 │ ├── animal_db.json # 动物知识库 │ ├── tour_route.json # 导览路线 │ └── voice/ # 语音文件 │ ├── models/ # 预训练模型 │ └── yolo_v8n_animals.pt # 动物检测模型 │ └── utils/ # 工具函数 ├── init.py ├── distance.py # 距离计算 ├── state_machine.py # 状态机 └── helpers.py # 辅助函数
💻 核心代码实现
- 配置文件 (config.py)
""" 动物园观光车自动导览系统 - 配置文件 包含系统所有可调参数 """
from dataclasses import dataclass, field from typing import List, Dict, Tuple, Optional import json
@dataclass class CameraConfig: """摄像头配置""" device_id: int = 0 # 摄像头设备ID width: int = 1920 # 分辨率宽度 height: int = 1080 # 分辨率高度 fps: int = 30 # 帧率 buffer_size: int = 5 # 帧缓冲大小
@dataclass class DetectionConfig: """目标检测配置""" model_path: str = "models/yolo_v8n_animals.pt" # 模型路径 conf_threshold: float = 0.6 # 置信度阈值 iou_threshold: float = 0.45 # IOU阈值 max_detections: int = 20 # 最大检测数量
# 动物类别映射 (COCO + 自定义)
animal_classes: Dict[int, str] = field(default_factory=lambda: {
15: "bird", 16: "cat", 17: "dog", 18: "horse", 19: "sheep",
20: "cow", 21: "elephant", 22: "bear", 23: "zebra", 24: "giraffe",
25: "backpack", 26: "umbrella", 27: "handbag", 28: "tie",
29: "suitcase", 30: "frisbee", 31: "skis", 32: "snowboard",
33: "sports ball", 34: "kite", 35: "baseball bat", 36: "baseball glove",
37: "skateboard", 38: "surfboard", 39: "tennis racket", 40: "bottle",
41: "wine glass", 42: "cup", 43: "fork", 44: "knife", 45: "spoon",
46: "bowl", 47: "banana", 48: "apple", 49: "sandwich", 50: "orange",
51: "broccoli", 52: "carrot", 53: "hot dog", 54: "pizza", 55: "donut",
56: "cake", 57: "chair", 58: "couch", 59: "potted plant", 60: "bed",
61: "dining table", 62: "toilet", 63: "tv", 64: "laptop", 65: "mouse",
66: "remote", 67: "keyboard", 68: "cell phone", 69: "microwave",
70: "oven", 71: "toaster", 72: "sink", 73: "refrigerator", 74: "book",
75: "clock", 76: "vase", 77: "scissors", 78: "teddy bear", 79: "hair drier",
80: "toothbrush",
# 自定义动物类别 (从80开始)
81: "lion", 82: "tiger", 83: "leopard", 84: "wolf", 85: "fox",
86: "monkey", 87: "panda", 88: "koala", 89: "hippo", 90: "rhino",
91: "crocodile", 92: "snake", 93: "turtle", 94: "frog", 95: "deer",
96: "buffalo", 97: "pig", 98: "chicken", 99: "duck", 100: "peacock"
})
@dataclass class TourConfig: """导览配置""" # 停靠参数 stop_distance_threshold: float = 5.0 # 停靠距离阈值(米) min_stop_duration: float = 15.0 # 最小停靠时间(秒) max_stop_duration: float = 30.0 # 最大停靠时间(秒)
# 路线配置
default_route: str = "safari_tour" # 默认路线
auto_advance: bool = True # 自动前往下一站
allow_revisit: bool = True # 允许重复访问
# 语音配置
language: str = "zh-CN" # 语言
speech_rate: int = 150 # 语速
volume: float = 0.9 # 音量
enable_background_music: bool = True # 背景音乐
@dataclass class DisplayConfig: """显示配置""" show_detection_boxes: bool = True # 显示检测框 show_distance_info: bool = True # 显示距离信息 show_route_map: bool = True # 显示路线图 show_animal_info: bool = True # 显示动物信息 theme_color: Tuple[int, int, int] = (34, 139, 34) # 主题色(森林绿) highlight_color: Tuple[int, int, int] = (255, 215, 0) # 高亮色(金色)
@dataclass class SystemConfig: """系统总配置""" camera: CameraConfig = CameraConfig() detection: DetectionConfig = DetectionConfig() tour: TourConfig = TourConfig() display: DisplayConfig = DisplayConfig() debug_mode: bool = False log_level: str = "INFO"
全局配置实例
CONFIG = SystemConfig()
def load_config_from_file(filepath: str) -> SystemConfig: """ 从JSON文件加载配置
Args:
filepath: 配置文件路径
Returns:
SystemConfig: 配置对象
"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
config_data = json.load(f)
# 解析并创建配置对象
# 这里简化实现,实际应完整解析
return CONFIG
except FileNotFoundError:
print(f"[警告] 配置文件不存在: {filepath},使用默认配置")
return CONFIG
except json.JSONDecodeError as e:
print(f"[错误] 配置文件解析失败: {e}")
return CONFIG
2. 动物知识库 (data/animal_db.json)
{ "animals": { "lion": { "id": "lion", "chinese_name": "狮子", "scientific_name": "Panthera leo", "category": "哺乳动物", "habitat": "非洲草原、亚洲部分地区", "diet": "肉食性", "description": "狮子是猫科动物中体型最大的一种,被称为'草原之王'。雄狮有浓密的鬃毛,是力量和地位的象征。", "fun_facts": [ "狮子是唯一群居的大型猫科动物", "雌狮负责大部分狩猎工作", "狮子一天可以睡16-20个小时", "狮子的吼叫声可以传播8公里远" ], "conservation_status": "易危(VU)", "voice_script": "各位游客大家好,现在我们看到的是草原之王——狮子。狮子是唯一群居的大型猫科动物,雄狮威武的鬃毛不仅是美丽的装饰,更是力量和健康的象征。雌狮才是真正的狩猎高手,它们团结协作,能捕食比自己大几倍的猎物。请大家注意观察,狮子一天中有大部分时间都在休息,这是它们的生存策略。" }, "elephant": { "id": "elephant", "chinese_name": "大象", "scientific_name": "Elephas maximus", "category": "哺乳动物", "habitat": "热带和亚热带地区", "diet": "草食性", "description": "大象是陆地上最大的动物,以其长长的鼻子和大大的耳朵而闻名。它们是高度智能的动物,有着丰富的情感和复杂的社会结构。", "fun_facts": [ "大象的鼻子有4万条肌肉", "大象可以记住30年以上的事情", "象群由最年长的雌性领导", "大象会为死去的同伴举行'葬礼'" ], "conservation_status": "濒危(EN)", "voice_script": "现在我们来到大象展区。大象是陆地上最大的动物,它们的鼻子堪称自然界最精密的工具,拥有四万条肌肉,可以完成抓取、吸水、交流等各种复杂动作。大象拥有惊人的记忆力,能记住几十年前的水源位置。更重要的是,大象是非常重感情的动物,它们会为死去的同伴举行特殊的仪式。请大家轻声细语,尊重这些温柔的巨人。" }, "giraffe": { "id": "giraffe", "chinese_name": "长颈鹿", "scientific_name": "Giraffa camelopardalis", "category": "哺乳动物", "habitat": "非洲稀树草原", "diet": "草食性", "description": "长颈鹿是世界上最高的陆地动物,成年个体身高可达5-5.5米。它们独特的长脖子和斑纹使它们成为最容易识别的动物之一。", "fun_facts": [ "长颈鹿的舌头长达50厘米,是蓝紫色的", "长颈鹿每天只需睡30分钟到2小时", "小长颈鹿出生时有1.8米高", "长颈鹿的心跳每分钟可达170次" ], "conservation_status": "易危(VU)", "voice_script": "看,那是优雅的长颈鹿!它们是世界上最高的陆地动物,站立时肩高就有3米多。长颈鹿的长脖子虽然看起来很重,但实际上非常轻盈,骨骼是中空的。最有意思的是它们的舌头——长达50厘米的蓝紫色舌头,专门用来卷取树叶。由于身高优势,长颈鹿喝水时必须劈开双腿,这在动物界可是独一无二的姿势呢!" }, "panda": { "id": "panda", "chinese_name": "大熊猫", "scientific_name": "Ailuropoda melanoleuca", "category": "哺乳动物", "habitat": "中国四川、陕西、甘肃山区", "diet": "主要食用竹子", "description": "大熊猫是中国特有的珍稀动物,以其黑白相间的毛色和憨态可掬的外表深受全世界人民的喜爱。虽然属于食肉目,但99%的食物都是竹子。", "fun_facts": [ "大熊猫每天要吃12-38公斤竹子", "刚出生的熊猫幼崽只有老鼠大小", "大熊猫有'活化石'之称,存在了800万年", "野生大熊猫的寿命约20年" ], "conservation_status": "易危(VU)", "voice_script": "现在我们看到的是国宝大熊猫!别看它们圆滚滚的样子很笨重,其实大熊猫是爬树高手,也是游泳健将。最令人惊讶的是,它们虽然属于食肉目,却几乎99%的食物都是竹子。一只成年大熊猫每天要花14个小时进食,吃掉12到38公斤的竹子。由于消化竹子的效率很低,它们必须大量进食才能维持能量。请大家欣赏这些黑白精灵的可爱模样!" }, "tiger": { "id": "tiger", "chinese_name": "老虎", "scientific_name": "Panthera tigris", "category": "哺乳动物", "habitat": "亚洲各地森林、草原", "diet": "肉食性", "description": "老虎是最大的猫科动物,以其橙黄色皮毛上的黑色条纹而著称。每只老虎的条纹图案都是独一无二的,就像人类的指纹一样。", "fun_facts": [ "老虎的条纹不仅在毛发上,皮肤也有", "老虎可以跳跃10米远、3米高", "东北虎是最大的虎亚种,体重可达300公斤", "老虎是出色的游泳健将" ], "conservation_status": "濒危(EN)", "voice_script": "威风凛凛的老虎来了!作为最大的猫科动物,老虎的每一道条纹都是独一无二的,就像人类的指纹。老虎是顶级的捕食者,拥有强大的爆发力和敏锐的感官。令人惊讶的是,老虎还是出色的游泳健将,经常在水中消暑和捕猎。不过现在全球野生老虎的数量已经不足4000只,它们正面临着栖息地丧失和偷猎的严重威胁。让我们珍惜与这些美丽生灵相遇的机会。" }, "monkey": { "id": "monkey", "chinese_name": "猴子", "scientific_name": "Various species", "category": "哺乳动物", "habitat": "全球热带、亚热带森林", "diet": "杂食性", "description": "猴子是灵长类动物的代表,以其聪明伶俐和灵活敏捷而著称。不同种类的猴子有着不同的社会结构和生活方式。", "fun_facts": [ "有些猴子会使用工具获取食物", "猴子的手指和人类一样有指纹", "卷尾猴会用石头砸开坚果", "日本猕猴会泡温泉取暖" ], "conservation_status": "因种而异", "voice_script": "活泼可爱的猴子们!作为我们的近亲,猴子展现出了惊人的智慧和社交能力。您可以看到它们灵活的身手,有的在树枝间荡秋千,有的在互相梳理毛发,这是它们建立社会联系的重要方式。最令人称奇的是,一些猴子种类已经学会了使用工具,比如用石头砸开坚果,用树枝钓取白蚁。观察它们的行为,我们会发现很多与人类相似的地方,这让我们更加意识到保护这些智慧生命的重要性。" } }, "routes": { "safari_tour": { "id": "safari_tour", "name": "经典非洲草原游", "description": "体验非洲大草原的壮美,观赏狮子、大象、长颈鹿等标志性动物", "stops": [ {"id": "entrance", "name": "园区入口", "duration": 0}, {"id": "lion_territory", "name": "狮子领地", "duration": 20}, {"id": "elephant_valley", "name": "大象谷", "duration": 20}, {"id": "giraffe_savanna", "name": "长颈鹿草原", "duration": 20}, {"id": "primate_forest", "name": "灵长类森林", "duration": 20}, {"id": "tiger_mountain", "name": "虎山", "duration": 20}, {"id": "panda_garden", "name": "熊猫馆", "duration": 20}, {"id": "exit", "name": "出口", "duration": 0} ] }, "family_tour": { "id": "family_tour", "name": "亲子欢乐游", "description": "适合家庭游客,节奏较慢,注重趣味性和教育性", "stops": [ {"id": "entrance", "name": "园区入口", "duration": 0}, {"id": "panda_garden", "name": "熊猫馆", "duration": 25}, {"id": "monkey_playground", "name": "猴山游乐场", "duration": 25}, {"id": "bird_paradise", "name": "鸟类天堂", "duration": 20}, {"id": "children_zoo", "name": "儿童动物园", "duration": 30}, {"id": "exit", "name": "出口", "duration": 0} ] }, "photography_tour": { "id": "photography_tour", "name": "摄影专线游", "description": "专为摄影爱好者设计,停留时间较长,光线角度更佳", "stops": [ {"id": "entrance", "name": "园区入口", "duration": 0}, {"id": "golden_hour_lions", "name": "狮子黄金时刻", "duration": 35}, {"id": "elephant_portrait", "name": "大象肖像", "duration": 35}, {"id": "giraffe_closeup", "name": "长颈鹿特写", "duration": 35}, {"id": "wildlife_action", "name": "野生动物动态", "duration": 40}, {"id": "exit", "name": "出口", "duration": 0} ] } } }
- 摄像头管理模块 (modules/camera.py)
""" 摄像头管理模块 负责观光车视频流的采集和管理 """
import cv2 import threading import time from queue import Queue from typing import Optional, Tuple, Generator from config import CONFIG
class CameraManager: """ 摄像头管理器
功能:
- 初始化观光车前置摄像头
- 提供帧读取接口
- 支持多线程采集
- 处理视频流异常
"""
def __init__(self, config: CONFIG.camera.__class__ = None):
"""
初始化摄像头管理器
Args:
config: 摄像头配置对象
"""
self.config = config or CONFIG.camera
self.cap: Optional[cv2.VideoCapture] = None
self.is_running = False
self.frame_queue = Queue(maxsize=self.config.buffer_size)
self._lock = threading.Lock()
self._error_count = 0
self._max_errors = 10
def initialize(self) -> bool:
"""
初始化摄像头设备
Returns:
bool: 初始化是否成功
"""
try:
print(f"[信息] 正在初始化摄像头设备 {self.config.device_id}...")
self.cap = cv2.VideoCapture(self.config.device_id)
if not self.cap.isOpened():
print(f"[错误] 无法打开摄像头设备 {self.config.device_id}")
print("[提示] 请检查:")
print(" 1. 摄像头是否正确连接")
print(" 2. 是否被其他程序占用")
print(" 3. 驱动程序是否正常安装")
return False
# 设置摄像头参数
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.config.width)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.config.height)
self.cap.set(cv2.CAP_PROP_FPS, self.config.fps)
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 减少缓冲延迟
# 验证设置
actual_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
actual_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
actual_fps = self.cap.get(cv2.CAP_PROP_FPS)
print(f"[信息] 摄像头初始化成功")
print(f" 分辨率: {actual_width}x{actual_height}")
print(f" 帧率: {actual_fps:.1f} FPS")
print(f" 缓冲大小: {self.config.buffer_size} 帧")
return True
except Exception as e:
print(f"[错误] 摄像头初始化失败: {e}")
return False
def start_capture(self):
"""
启动后台线程进行连续帧采集
使用双缓冲策略确保实时性
"""
if self.cap is None:
raise RuntimeError("摄像头未初始化")
self.is_running = True
self.capture_thread = threading.Thread(target=self._capture_loop)
self.capture_thread.daemon = True
self.capture_thread.start()
print("[信息] 后台帧采集线程已启动")
def _capture_loop(self):
"""
后台帧采集循环
使用时间戳确保帧率稳定
"""
target_interval = 1.0 / self.config.fps
last_capture_time = time.time()
while self.is_running:
try:
current_time = time.time()
elapsed = current_time - last_capture_time
# 控制采集速率
if elapsed < target_interval:
time.sleep(target_interval - elapsed)
# 清空缓冲区,获取最新帧
while self.cap.grab():
pass
ret, frame = self.cap.retrieve()
if ret:
# 非阻塞方式放入队列
if not self.frame_queue.full():
self.frame_queue.put({
'frame': frame.copy(),
'timestamp': time.time(),
'frame_number': self._error_count
})
self._error_count = 0
else:
self._error_count += 1
if self._error_count >= self._max_errors:
print("[错误] 连续帧读取失败,尝试重新初始化...")
self._try_reinitialize()
last_capture_time = time.time()
except Exception as e:
print(f"[警告] 帧采集异常: {e}")
self._error_count += 1
time.sleep(0.1)
def _try_reinitialize(self):
"""
尝试重新初始化摄像头
"""
try:
self.cap.release()
time.sleep(1.0)
self.cap = cv2.VideoCapture(self.config.device_id)
if self.cap.isOpened():
print("[信息] 摄像头重新初始化成功")
self._error_count = 0
else:
print("[错误] 摄像头重新初始化失败")
except Exception as e:
print(f"[错误] 重新初始化失败: {e}")
def read_frame(self, timeout: float = 1.0) -> Optional[dict]:
"""
读取一帧图像及其元数据
Args:
timeout: 等待超时时间(秒)
Returns:
Optional[dict]: 包含帧数据和元数据的字典,失败返回None
"""
try:
return self.frame_queue.get(timeout=timeout)
except:
return None
def read_frame_direct(self) -> Tuple[bool, Optional[cv2.Mat]]:
"""
直接从摄像头读取帧(同步方式,低延迟)
Returns:
tuple: (成功标志, 帧图像)
"""
if self.cap is None:
return False, None
# 清空缓冲区
while self.cap.grab():
pass
return self.cap.retrieve()
def get_latest_frame(self) -> Optional[dict]:
"""
获取最新帧(不阻塞)
Returns:
Optional[dict]: 最新帧数据
"""
frames = []
while not self.frame_queue.empty():
try:
frames.append(self.frame_queue.get_nowait())
except:
break
if frames:
return frames[-1]
return None
def release(self):
"""
释放摄像头资源
确保线程安全关闭
"""
print("[信息] 正在释放摄像头资源...")
self.is_running = False
if hasattr(self, 'capture_thread') and self.capture_thread.is_alive():
self.capture_thread.join(timeout=2.0)
if self.capture_thread.is_alive():
print("[警告] 采集线程未能正常结束")
if self.cap is not None:
self.cap.release()
self.cap = None
# 清空队列
while not self.frame_queue.empty():
try:
self.frame_queue.get_nowait()
except:
break
print("[信息] 摄像头资源已释放")
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!