文章目录
- 一、效果
- 二、简介
- 三、环境准备
-
- 3.1、安装依赖
- [3.2、YOLOv8 类别说明](#3.2、YOLOv8 类别说明)
- 四、核心解析
-
- 4.1、检测目标过滤
- [4.2、类型映射(适配你 Vue 前端)](#4.2、类型映射(适配你 Vue 前端))
- [4.3、3D 坐标映射](#4.3、3D 坐标映射)
- 4.4、性能优化
- 五、使用方法
- 六、完整源码
与我之前写的这篇文章呼应:【Web】使用Vue3开发3D游戏(九)纹理视觉效果
一、效果
1.1、原视频
路况
1.2、效果视频
路况识别效果
1.3、静图效果

二、简介
在行车记录仪路况分析、道路障碍物检测、智能驾驶仿真场景中,行人、车辆识别是最基础也是最核心的能力。本文基于 YOLOv8 实现本地 MP4 视频实时检测,精准区分行人、轿车、摩托车、货车、巴士等交通参与者,同时适配 Vue3 + PlayCanvas 3D 前端 数据协议,输出标准化障碍物类型,可直接对接 3D 场景做障碍物映射,非常适合做仿真项目、机器狗路况感知、Web3D 可视化开发。
三、环境准备
3.1、安装依赖
运行
bash
pip install ultralytics opencv-python websockets
- ultralytics:YOLOv8 官方库,一行加载模型、推理
- opencv-python:视频读取、画面绘制、编码推流
- websockets:向前端实时推送视频流 + 障碍物数据
3.2、YOLOv8 类别说明
YOLOv8 预训练模型自带 80 个类别,路况常用关键类别:
| 类别 | ID 对应物体 |
|---|---|
| 0 | 行人 person |
| 1 | 自行车 |
| 2 | 轿车car |
| 3 | 摩托车 |
| 5 | 巴士公交车 |
| 7 | 卡车货车 |
业务规则:所有车辆统一归类为 car 类型 = 1,行人固定 type=0,完美适配你 Vue3 前端 mapTypeToModel 映射规则:
js
const typeMap = {
0: 'person',
1: 'car'
};
四、核心解析
- 只检测行人 + 全部车辆,过滤无关物体
- 绘制矩形框标记目标
- 适配前端协议:人type=0,所有车type=1
- WebSocket 同时推送 视频画面 + 3D 障碍物坐标
- 优化帧率、控制 CPU 占用,不卡顿服务器
- 固定端口 9090,兼容你现有 Vue 前端连接
4.1、检测目标过滤
只保留路况有效目标:
py
if cls_id not in [0, 1, 2, 3, 5, 7]:
continue
排除猫、狗、椅子等无关物体,减少无效计算、降低 CPU。
4.2、类型映射(适配你 Vue 前端)
py
if cls_id == 0:
obj_type = 0 # 行人 → person
else:
obj_type = 1 # 所有车辆 → car
完美对应前端:
js
const typeMap = {0:'person', 1:'car'}
4.3、3D 坐标映射
根据画面中目标左右位置归一化,映射为 3D 场景左右偏移,前后固定,实现:
视频里人 / 车往左 → 3D 障碍物往左
视频里人 / 车往右 → 3D 障碍物往右
4.4、性能优化
- 压缩视频分辨率推理,大幅降 CPU
- 控制检测间隔,不无限死循环占用资源
- 异常连接捕获,不抛崩溃错误
- 视频播放完毕自动循环,无需手动重启
五、使用方法
把行车记录仪路况视频命名为 test.mp4,放在同目录
安装依赖后直接运行:
bash
python3 mp4_websocket_stream.py
Vue3 前端直接连接 ws://ip:9090,自动接收:
实时摄像头画面
行人 / 车辆 3D 障碍物数据,自动渲染对应模型
适配场景
- Web3D 智能驾驶仿真
- 机器狗 / 无人车路况感知可视化
- 行车记录仪视频 AI 分析
- PlayCanvas + Vue3 前后端联合开发
- 路口行人车辆检测统计
六、完整源码
py
import asyncio
import websockets
import json
import base64
import cv2
import threading
import time
from ultralytics import YOLO
# ==================== 配置 ====================
VIDEO_PATH = "./test.mp4"
WEBSOCKET_HOST = "0.0.0.0"
WEBSOCKET_PORT = 9090
JPEG_QUALITY = 60
MOVE_SCALE = 2.5
DETECT_INTERVAL = 0.05
# ==============================================
latest_frame_bytes = None
obstacle_list = []
# 加载YOLO模型
model = YOLO("yolov8n.pt")
def video_loop():
global latest_frame_bytes, obstacle_list
cap = cv2.VideoCapture(VIDEO_PATH)
fps = cap.get(cv2.CAP_PROP_FPS) or 25
while True:
ret, frame = cap.read()
if not ret:
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
continue
# 降低分辨率,省CPU
small_frame = cv2.resize(frame, (640, 360))
h, w = small_frame.shape[:2]
obs = []
# YOLO推理
results = model(small_frame, conf=0.5, verbose=False, imgsz=320)
for result in results:
for box in result.boxes:
cls_id = int(box.cls[0])
# 只检测 人 + 车
if cls_id not in [0,1,2,3,5,7]:
continue
# 画框
x1, y1, x2, y2 = map(int, box.xyxy[0])
cv2.rectangle(small_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 计算3D坐标
cx = (x1 + x2) / 2
norm_x = (cx - w/2) / (w/2)
# ==========================
# 前端要求:人=0,车=1
# ==========================
if cls_id == 0:
obj_type = 0 # 人
else:
obj_type = 1 # 所有车辆统一为 car
# 你的正确坐标,完全不动!
obs.append({
"track_id": int(box.id[0]) if box.id is not None else 100,
"type": obj_type,
"x": 10,
"y": -norm_x * MOVE_SCALE,
"z": 5.0,
"width": 0.6,
"height": 1.7,
"length": 0.6,
"heading": 0.0
})
obstacle_list = obs
# 修复这里!cv2.imencode
ok, jpeg = cv2.imencode('.jpg', small_frame, [cv2.IMWRITE_JPEG_QUALITY, JPEG_QUALITY])
if ok:
latest_frame_bytes = jpeg.tobytes()
time.sleep(DETECT_INTERVAL)
async def send_client(websocket):
global latest_frame_bytes, obstacle_list
try:
while True:
if latest_frame_bytes:
b64 = base64.b64encode(latest_frame_bytes).decode()
await websocket.send(json.dumps({"msg": {"data": b64}}))
await websocket.send(json.dumps({"msg": {"objs": obstacle_list}}))
await asyncio.sleep(0.05)
except:
pass
async def handle_conn(websocket):
try:
await send_client(websocket)
except:
pass
async def main():
threading.Thread(target=video_loop, daemon=True).start()
async with websockets.serve(handle_conn, WEBSOCKET_HOST, WEBSOCKET_PORT):
print("✅ 启动成功:人=0,车=1,端口9090正常!")
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())