动物园观光车自动导览,按景点停靠,语音讲解,输出导览路线。

1 阅读13分钟

🦁 动物园观光车自动导览系统

📖 项目概述

这是一个基于计算机视觉、深度学习和自然语言处理的智能动物园观光车导览系统。系统通过识别动物、规划路线、语音讲解,为游客提供沉浸式的智能导览体验。

🌍 实际应用场景

场景描述

在大型野生动物园,观光车沿着固定路线行驶,游客希望:

  • 自动识别沿途动物并讲解
  • 按最佳顺序游览各个景点
  • 了解动物习性和保护知识
  • 获得个性化导览服务

痛点分析

痛点 影响 信息缺失 游客看得到动物但不了解 路线混乱 不知道下一个景点是哪里 讲解单调 传统广播内容千篇一律 错过景点 容易错过隐蔽的观景点 语言障碍 外语游客听不懂中文讲解

🧠 核心逻辑讲解

┌─────────────────────────────────────────────────────────────────────────────┐ │ 系统工作流程 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 摄像头 │ -> │ 动物检测 │ -> │ 景点匹配 │ -> │ 语音讲解 │ │ │ │ 采集 │ │ YOLOv8 │ │ 导航引擎 │ │ TTS引擎 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ ↑ ↑ ↑ ↑ │ │ └──────────────┴──────────────┴──────────────┘ │ │ 主循环处理 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 路线规划 │ -> │ 距离计算 │ -> │ 状态管理 │ -> │ 用户交互 │ │ │ │ A*算法 │ │ 视觉测距 │ │ 状态机 │ │ 界面显示 │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

景点停靠判定逻辑: ┌─────────────────────────────────────────────────────────────────────────────┐ │ 1. 检测到动物类别 │ │ ↓ │ │ 2. 匹配景点数据库 │ │ ↓ │ │ 3. 计算车辆与景点距离 │ │ ↓ │ │ 4. 距离 < 停靠阈值? │ │ ↓是 ↓否 │ │ 5. 触发停靠 继续行驶 │ │ ↓ │ │ 6. 播放讲解 + 显示信息 │ │ ↓ │ │ 7. 等待设定时间/游客确认 │ │ ↓ │ │ 8. 继续下一景点 │ └─────────────────────────────────────────────────────────────────────────────┘

关键技术点

  1. 多目标检测:YOLOv8同时检测多种动物
  2. 语义理解:将视觉识别映射到景点知识
  3. 路径规划:A*算法计算最优游览路线
  4. 语音合成:TTS生成自然流畅的讲解
  5. 状态机管理:处理导览全流程

📁 项目结构

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 # 辅助函数

💻 核心代码实现

  1. 配置文件 (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} ] } } }

  1. 摄像头管理模块 (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解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!