1. 项目缘起当“限行”遇上“边缘智能”最近在做一个挺有意思的社区项目起因是我们这片老城区道路窄、车流大早晚高峰堵得水泄不通。为了解决这个问题街道办想尝试一种更精细化的“道路空间配给”管理说白了就是在特定时段对特定路段根据车牌尾号或者车辆类型比如只允许新能源车进入进行动态限行。传统的做法要么是立个牌子靠自觉要么是安排交警值守成本高、灵活性差还容易引发纠纷。正好手头有几块吃灰的英伟达 Jetson Nano 开发板这玩意儿号称“边缘AI计算盒子”功耗低、算力够天生就是为这种需要实时图像处理、本地决策的场景设计的。我就琢磨着能不能用它来搞一套低成本、可部署、能实时分析的智能限行控制系统核心思路很简单在路口部署摄像头Jetson Nano 实时分析视频流识别车牌、判断车辆类型然后根据预设的规则比如当前时段允许的尾号控制道闸或信号灯甚至联动电子显示屏进行提示。这个想法听起来不复杂但真做起来从硬件选型、环境配置、模型部署到系统集成每一步都有不少门道。网上关于 Jetson Nano 跑 YOLO 做目标检测的教程很多但大多停留在“跑通Demo”阶段真正要把它变成一个7x24小时稳定运行的“路侧智能终端”需要考虑的细节远超想象。接下来我就把从零搭建这套系统的完整过程、踩过的坑以及一些优化心得毫无保留地分享出来。2. 硬件选型与系统准备不只是“点亮”开发板很多人拿到 Jetson Nano 的第一步就是照着官方教程刷机、点亮这没错但为了项目稳定有些前置选择必须想清楚。2.1 Jetson Nano 的版本与供电陷阱Jetson Nano 主要有两个版本开发者套件Developer Kit和模块Module。我们项目用的是最常见的开发者套件 B01 版。这里第一个坑就是供电。官方推荐使用 5V/4A 的 Micro-USB 电源但如果你接上了摄像头、USB 网卡或者其他外设4A 可能只是勉强够用。我在实测中发现当系统满负载运行 YOLO 推理时如果电源质量稍差或线缆有损耗很容易触发欠压保护导致开发板重启这在路侧控制场景中是灾难性的。注意务必使用优质、线径粗的 5V/4A 电源适配器并建议在电源输入端并联一个大电容如 4700uF/10V作为储能缓冲能有效避免因瞬间电流需求过大导致的电压跌落。如果条件允许直接使用 5.5/2.1mm 的桶形插座供电需要焊接或使用转接头其接触电阻和承载电流能力通常优于 Micro-USB。2.2 系统镜像与基础环境配置刷机本身不复杂从英伟达官网下载 SD 卡镜像我用的 JetPack 4.6.1对应 Ubuntu 18.04 和 CUDA 10.2用 Etcher 写入至少 32GB 的 UHS-I 速度以上的 TF 卡即可。首次启动会进行系统初始化。这里的关键在于初始配置的优化。首先不要使用图形化界面。我们的设备最终要无头无显示器运行图形桌面GNOME会白白占用几百MB内存和不少CPU资源。在首次启动配置时选择“Console”模式只使用命令行界面。内存分配也需要注意Jetson Nano 的 4GB 内存是 CPU 和 GPU 共享的。默认分配可能不适合深度学习。我们可以通过编辑/boot/extlinux/extlinux.conf文件在对应内核启动行的末尾修改mem参数。我通常设置为mem3072M给 GPU 预留约 1GB 的共享内存具体可以根据模型大小调整。# 示例在对应启动行找到类似下面的内容并修改 APPEND ${cbootargs} quiet root/dev/mmcblk0p1 rw rootwait rootfstypeext4 consolettyS0,115200n8 consoletty0 fbconmap:0 mem3072M其次务必换源并安装必要工具。Ubuntu 18.04 的官方源速度慢换成国内阿里或清华源。然后安装一批项目管理必备的工具sudo apt update sudo apt install -y htop tmux git curl wget vim python3-pip python3-devtmux尤其重要它允许你在 SSH 会话中创建持久化的终端窗口即使网络断开任务也在后台运行这对于远程部署和调试至关重要。2.3 散热与物理部署考量Jetson Nano 在满载时发热量不小主动散热风扇是必须的。市面上有很多配套的散热风扇套件。我建议选择带温控功能的可以根据芯片温度自动调节转速平衡噪音和散热效果。物理部署上需要考虑防水防尘的工业外壳。我们用的是亚克力定制外壳并加了硅胶密封圈成本不高。摄像头选用普通的 USB 网络摄像头即可注意要支持 MJPEG 或 YUYV 格式以减少 CPU 解码压力。如果夜间也需要工作则需要考虑带红外补光或低照度性能好的摄像头。3. 深度学习环境搭建在 ARM 架构上驯服 YOLO这是整个项目的核心也是最容易卡住的地方。Jetson Nano 是 ARM64 架构很多在 x86 平台上一键安装的包在这里都需要从源码编译。3.1 安装 PyTorch 与 TorchVision英伟达为 JetPack 提供了适配好的 PyTorch 轮子whl文件这是最稳妥的方式。首先去英伟达官网论坛或开发者中心找到与你 JetPack 版本CUDA 10.2匹配的 PyTorch 版本。例如 JetPack 4.6.1 对应的是 PyTorch 1.10.0。下载对应的.whl文件后用 pip 安装pip3 install numpy torch-1.10.0-cp36-cp36m-linux_aarch64.whl安装 TorchVision 则相对麻烦需要从源码编译因为要确保和 PyTorch 版本匹配。先安装一堆编译依赖sudo apt install -y libjpeg-dev zlib1g-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev libopenblas-dev liblapack-dev然后下载对应版本的 TorchVision 源码如 v0.11.1 for PyTorch 1.10.0进行编译安装git clone --branch v0.11.1 https://github.com/pytorch/vision torchvision cd torchvision export BUILD_VERSION0.11.1 python3 setup.py install --user这个过程可能需要十几分钟。完成后在 Python 中导入torch和torchvision测试是否成功并检查torch.cuda.is_available()是否为 True。3.2 OpenCV 的编译与“加速”系统自带的 OpenCV 通常版本较旧且不支持 GPU 加速。我们需要从源码编译支持 CUDA 和 GStreamer用于高效视频流处理的 OpenCV。这是一项耗时可能超过1小时但收益巨大的工作。首先安装海量的依赖库sudo apt install -y build-essential cmake git unzip pkg-config libjpeg-dev libtiff-dev libpng-dev libavcodec-dev libavformat-dev libswscale-dev libgtk2.0-dev libcanberra-gtk-module libxvidcore-dev libx264-dev libgtk-3-dev libblas-dev liblapack-dev gfortran libhdf5-dev libopenblas-dev libatlas-base-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev然后下载 OpenCV 和 OpenCV Contrib 源码我选用的是 4.5.5 版本比较稳定。使用 CMake 进行配置时关键是要开启 CUDA、GStreamer 和 Python3 绑定cd opencv-4.5.5 mkdir build cd build cmake -D CMAKE_BUILD_TYPERELEASE \ -D CMAKE_INSTALL_PREFIX/usr/local \ -D OPENCV_EXTRA_MODULES_PATH../../opencv_contrib-4.5.5/modules \ -D WITH_CUDAON \ -D WITH_CUDNNON \ -D OPENCV_DNN_CUDAON \ -D ENABLE_FAST_MATHON \ -D CUDA_FAST_MATHON \ -D WITH_GSTREAMERON \ -D WITH_LIBV4LON \ -D BUILD_opencv_python3ON \ -D PYTHON3_EXECUTABLE/usr/bin/python3 \ -D PYTHON3_INCLUDE_DIR/usr/include/python3.6 \ -D PYTHON3_LIBRARY/usr/lib/aarch64-linux-gnu/libpython3.6m.so \ -D PYTHON3_NUMPY_INCLUDE_DIRS/home/nvidia/.local/lib/python3.6/site-packages/numpy/core/include \ -D BUILD_EXAMPLESOFF \ -D BUILD_TESTSOFF \ -D BUILD_PERF_TESTSOFF ..配置完成后执行make -j4Jetson Nano 是四核所以用4个线程并行编译。这个过程极其漫长可以去喝杯咖啡。编译安装完成后需要将 OpenCV 的库路径添加到环境变量。3.3 YOLO 模型的选择与转换YOLO 版本众多从 v5 到 v11还有各种变体。在 Jetson Nano 上选择模型的核心原则是在精度和速度之间寻找平衡并且模型必须能被 TensorRT 高效加速。YOLOv5s / YOLOv8n 是很好的起点模型小速度较快。但原生的 PyTorch.pt模型需要转换为 TensorRT 的.engine格式才能发挥最大性能。YOLOv11 / YOLOv26 这些是社区或研究机构提出的最新版本可能带来了更高的精度或更优的结构。但关键在于其官方仓库是否提供了 TensorRT 导出支持。很多新模型初期只提供 PyTorch 实现在 ARM 平台部署的难度和不确定性会大大增加。我们的项目最终选择了YOLOv8n因为它生态成熟有详尽的 TensorRT 导出教程且 Nano 对其支持度很好。部署流程如下训练/获取模型在性能更强的 x86 服务器上使用车牌和车辆类型数据集训练 YOLOv8n 模型得到best.pt文件。你也可以使用官方预训练模型进行微调。导出为 ONNX使用 Ultralytics 的export.py脚本将.pt模型导出为.onnx格式。这里需要指定动态维度以支持不同分辨率的输入。yolo export modelbest.pt formatonnx dynamicTrue转换为 TensorRT Engine这是最核心的加速步骤。使用 TensorRT 的trtexec工具JetPack 已自带进行转换。/usr/src/tensorrt/bin/trtexec --onnxbest.onnx --saveEnginebest.engine --fp16 --workspace1024参数--fp16表示使用半精度浮点数能大幅提升速度且精度损失可接受。--workspace设置了 GPU 内存的临时工作空间大小如果转换复杂模型时失败可以尝试增大此值。将生成的best.engine文件拷贝到 Jetson Nano 上我们的推理引擎就准备好了。相比于直接运行 PyTorch 模型TensorRT 引擎通常能有 3-8 倍的速度提升。4. 实时视频处理管道构建从摄像头到推理结果有了模型下一步就是构建一个高效、稳定的视频流处理循环。这里的目标是低延迟、高吞吐并且要能处理丢帧、断流等异常情况。4.1 使用 GStreamer 构建高效流水线OpenCV 的cv2.VideoCapture(0)虽然简单但效率不高延迟大。对于 USB 摄像头使用 GStreamer 管道是专业选择。它能将视频采集、解码、格式转换、缩放等步骤在底层用流水线并行处理。一个典型的 GStreamer 管道字符串如下gst_str ( v4l2src device/dev/video0 ! image/jpeg, width1280, height720, framerate30/1 ! jpegdec ! videoconvert ! video/x-raw, formatBGR ! appsink droptrue syncfalse ) cap cv2.VideoCapture(gst_str, cv2.CAP_GSTREAMER)这个管道从/dev/video0设备采集 MJPEG 格式的 1280x72030fps 视频流进行 JPEG 解码转换为 BGR 格式最后送入 OpenCV。droptrue和syncfalse参数告诉管道在应用端处理不及时时主动丢帧而不是阻塞这对于维持实时性至关重要。4.2 多线程推理与结果处理单线程模式下程序会“采集一帧 - 推理一帧 - 显示/处理结果”推理过程的阻塞会导致采集卡顿帧率上不去。我们必须采用生产者-消费者模型使用多线程或多进程解耦采集和推理。import threading import queue import time class VideoStream: def __init__(self, gst_pipeline, queue_size2): self.cap cv2.VideoCapture(gst_pipeline, cv2.CAP_GSTREAMER) self.q queue.Queue(maxsizequeue_size) # 设置一个很小的队列避免延迟累积 self.stopped False def start(self): t threading.Thread(targetself.update, args()) t.daemon True t.start() return self def update(self): while not self.stopped: if not self.q.full(): # 队列未满时才读帧 ret, frame self.cap.read() if ret: self.q.put(frame) else: self.stop() else: time.sleep(0.01) # 队列已满短暂休眠 def read(self): return self.q.get() def stop(self): self.stopped True self.cap.release() # 在主线程中从 stream.read() 获取帧送入另一个推理线程进行处理。推理线程则从队列中取帧预处理缩放、归一化送入 TensorRT 引擎进行推理后处理非极大值抑制 NMS最后得到车牌坐标、号码和车辆类型信息。这里队列大小queue_size设置为 2 或 3 就够了目的是缓冲偶尔的推理波动而不是存储大量历史帧否则会导致处理延迟越来越高。4.3 车牌识别OCR的集成YOLO 检测出车牌区域后需要对其进行光学字符识别OCR。在资源受限的 Jetson Nano 上我们同样需要轻量级的方案。PaddleOCR是一个优秀的开源选择它提供了精度不错的轻量级模型。我们可以使用其提供的ch_ppocr_mobile_v2.0识别模型该模型对中文车牌支持良好。部署时同样建议将 PaddlePaddle 模型转换为 TensorRT 或使用 ONNX Runtime 进行推理以提升速度。将裁剪出的车牌图像经过灰度化、二值化等简单预处理后送入 OCR 模型即可得到识别出的车牌字符串。5. 业务逻辑与控制输出让AI“说话”和“行动”识别出车牌和车辆类型只是第一步核心是根据这些信息做出决策并执行控制。5.1 规则引擎的设计我们需要一个灵活可配的规则引擎。规则可以存储在简单的 JSON 配置文件中例如{ rules: [ { time_range: [07:00, 09:00], weekdays: [1, 2, 3, 4, 5], allowed_tail_numbers: [1, 3, 5, 7, 9], allowed_vehicle_types: [新能源], action: open_barrier }, { time_range: [17:00, 19:00], weekdays: [1, 2, 3, 4, 5], allowed_tail_numbers: [0, 2, 4, 6, 8], allowed_vehicle_types: [新能源], action: open_barrier } ], default_action: close_barrier_and_alert }程序在初始化时加载此规则。每识别到一辆车就获取当前时间、星期几然后遍历规则列表找到第一条匹配的规则执行对应的动作action。如果没有匹配规则则执行默认动作。5.2 控制接口的实现控制输出通常通过 Jetson Nano 的 GPIO 引脚或网络通信实现。GPIO 控制道闸/信号灯这是最直接的方式。Jetson Nano 的 40-pin 扩展接口兼容树莓派。我们可以使用Jetson.GPIO库需单独安装来控制其中某个引脚输出高电平或低电平这个信号可以连接继电器模块进而控制道闸电机的升降或信号灯的亮灭。import Jetson.GPIO as GPIO CHANNEL 18 GPIO.setmode(GPIO.BOARD) GPIO.setup(CHANNEL, GPIO.OUT) # 允许通行 GPIO.output(CHANNEL, GPIO.HIGH) time.sleep(2) # 保持开启2秒 GPIO.output(CHANNEL, GPIO.LOW)网络通信如果控制设备如智能道闸、交通信号机支持网络协议如 HTTP API、MQTT、Modbus TCP则可以通过 Python 的requests或paho-mqtt库发送控制指令。这种方式更灵活适合复杂系统集成。例如通过 MQTT 发布一条消息到traffic/intersection_a/barrier主题内容为{command: open, duration: 5}。5.3 人机交互与日志记录系统需要反馈。我们可以在路口架设一个小型 LED 显示屏通过串口或网络控制显示“允许通行”、“车牌尾号X限行”等信息。同时所有识别记录、控制动作和系统状态都必须被可靠地记录下来。日志记录建议采用logging模块配置为同时输出到控制台和文件并设置日志轮转避免单个文件过大。关键信息如时间戳、车牌号、识别置信度、匹配规则、执行动作等都应记录在案便于事后核查和系统分析。import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[logging.FileHandler(traffic_control.log), logging.StreamHandler()]) logger logging.getLogger(__name__) logger.info(fVehicle detected. Plate: {plate_number}, Type: {vehicle_type}. Action: {action_taken}.)6. 系统集成、优化与稳定性保障将各个模块拼装起来并让它在真实环境中稳定运行是最后的挑战。6.1 服务化与自启动我们不能总在 SSH 终端里运行一个 Python 脚本。需要将程序封装成一个系统服务Systemd Service。创建一个服务文件/etc/systemd/system/road-rationing.service[Unit] DescriptionReal-time Road Space Rationing Control Service Afternetwork.target [Service] Typesimple Usernvidia WorkingDirectory/home/nvidia/road_rationing ExecStart/usr/bin/python3 /home/nvidia/road_rationing/main.py Restarton-failure RestartSec5s [Install] WantedBymulti-user.target这样系统启动后该服务会自动运行并且如果程序意外崩溃会在5秒后自动重启。通过sudo systemctl start/stop/status road-rationing可以方便地管理。6.2 性能监控与看门狗我们需要知道系统运行是否健康。可以编写一个简单的监控脚本定期检查GPU 和 CPU 使用率使用tegrastats工具或读取/proc文件系统。内存使用情况。推理帧率FPS在程序中计算平均帧率。摄像头流是否中断通过判断视频捕获对象是否成功读帧。这些信息可以通过 HTTP 接口暴露出来方便远程监控。更进一步可以设置一个“看门狗”线程如果检测到关键指标异常如超过1分钟没有推理出结果则尝试重启视频捕获模块甚至重启整个服务。6.3 应对极端情况与模型更新光照与天气变化模型在训练时应包含不同光照逆光、夜间、天气雨、雾下的数据。在实际部署中如果摄像头支持可以启用自动曝光、自动白平衡等。对于夜间必须依赖红外补光摄像头。车牌污损与遮挡这是 OCR 的难点。除了在预处理阶段尝试增强对比度在业务逻辑上对于低置信度的识别结果可以结合“同一车辆连续多帧识别”的结果进行投票决策或者触发人工复核流程如拍照上传后台。模型更新当需要更新识别模型时不能直接替换正在使用的.engine文件。应采用“蓝绿部署”思路将新模型文件以不同名称如best_v2.engine部署然后通过向服务发送信号如SIGUSR1或修改配置文件让服务动态加载新模型实现热更新避免服务中断。从一块小小的 Jetson Nano 开发板到一套能真正在路口执行智能限行控制的系统中间跨越的不仅是代码更是对嵌入式AI部署全链路的深入理解。这套方案的成本远低于传统的工控机服务器方案并且在响应速度和数据隐私数据在本地处理上更有优势。当然它仍然是一个原型系统要投入大规模市政应用还需要在硬件可靠性工业级器件、通信安全、系统冗余等方面做大量工作。但对于社区、园区、停车场等中小规模场景这无疑是一个高性价比且极具启发性的尝试。