去年在筹备边缘AI项目时,我拿着技术方案反复纠结:模型训练好了,部署到端侧却总被环境问题、推理性能、硬件兼容按在地上摩擦。正好赶上IOTE物联网展,现场跑了一圈边缘AI/ML演示展台,发现大家解决的其实都是同一类问题——如何把训练好的模型顺畅地搬到边缘设备上实时跑起来。这篇文章就把我从展会现场看到的“边缘AI/ML演示”背后的技术链路拆解开,完整走一遍从环境搭建到模型部署、再到浏览器实时推流的实操流程。文章里的示例代码、配置和踩坑清单,都是可以照着复制、落地执行的,无论是物联网方向的新手,还是准备做边缘AI落地的工程师,都有直接参考价值。

1. 边缘AI/ML是什么,为什么展会现场都在谈

1.1 先理解“边缘”和“AI/ML”在这里指的是什么

边缘AI/ML,拆开看是两部分:边缘计算 + 机器学习(Machine Learning,ML)。传统做法是把摄像头、传感器采集的数据传到云服务器,在云端跑AI模型,再把结果返回终端。边缘AI则是把数据推理放到离数据源更近的地方——比如一台嵌入式的AI盒子、一块开发板、一台工业网关,甚至是手机芯片。

在IOTE展会现场,绝大多数边缘AI/ML演示的形态都是这样一套组合:一个摄像头采集画面,一块开发板或AI盒子执行目标检测/分类模型,显示器上实时显示带检测框的视频流。整个过程不依赖云端推理,网络断了也能跑。

这么做的好处很直接:

  • 低延迟:推理在本地完成,省去数据上传和返回的网络耗时。
  • 节省带宽:视频流不需要全部传云端,只上传结果或关键帧。
  • 数据隐私:敏感视频流不出本地,降低泄露风险。
  • 离线可用:断网环境下依然能正常检测、识别。
  • 成本可控:不需要长期占用云服务器GPU资源。

1.2 边缘AI与云端AI的区别

很多刚接触的人会问:既然云端算力那么强,为什么非要在边缘跑?

对比项 云端AI 边缘AI
算力 高,可弹性扩展 有限,依赖硬件平台
延迟 受网络影响,几十到几百毫秒 毫秒级,实时性强
带宽 数据量大时成本高 只需上传结果
隐私 数据需出域 数据本地闭环
网络依赖 强依赖 可断网运行
模型规模 可以很大 需要压缩、量化

实际工程项目中,云端AI和边缘AI并不是非此即彼的关系。更常见的架构是“端-边-云”协同:边缘AI盒子负责实时推理和初步过滤,云端负责模型重新训练、大规模数据分析和策略下放。这也是IOTE展会上很多厂商主推的解决方案方向。

1.3 边缘AI/ML的典型应用场景

展会现场演示的方案,其实覆盖了大量落地场景:

  • 工业质检:流水线上的产品外观缺陷检测,需要在几十毫秒内框出瑕疵。
  • 智慧安防:人员入侵检测、口罩佩戴识别、消防通道占用检测。
  • 智慧零售:客流统计、货架商品识别、热力区域分析。
  • 智慧交通:车辆识别、车牌检测、违章行为预警。
  • 农业物联网:作物病虫害识别、果园成熟度判断。

这些场景的共同特征是:现场环境复杂、实时性要求高、网络状况不一定好、对数据隐私有要求。边缘AI/ML恰好能覆盖。

2. 边缘AI/ML演示的典型组成与环境准备

2.1 一张图看懂边缘AI演示链路

不画复杂的架构图,用一行逻辑就能说明:

摄像头采集 -> 视频解码 -> 模型推理 -> 后处理 -> 结果推流到浏览器/云端

整套演示链路中,最核心的动作有两个:

  1. 把训练好的模型转换成边缘设备能高效运行的格式。
  2. 用推理框架在设备本地加载模型,完成实时推理。

2.2 硬件与软件环境准备

IOTE展会现场看到的边缘AI盒子多种多样,不同项目选型差异很大。文章以常见环境为例,重点演示思路,不绑定具体品牌。

硬件侧:

  • 带NPU/GPU的边缘设备,例如NVIDIA Jetson系列、瑞芯微RK3588、算力盒子等。
  • USB摄像头或RTSP网络摄像头。
  • 显示器,用于展示推理画面。
  • 建议准备一块空的SD卡/移动硬盘,方便刷系统。

软件侧:

  • 操作系统:Ubuntu 20.04/22.04(或设备出厂自带的Linux系统)。
  • Python 3.8+,建议用3.10。
  • 推理框架:onnxruntime、OpenCV-Python、Flask或FastAPI用于推流。
  • 模型导出工具:ultralytics(YOLO系列训练/导出)。
  • 远程连接工具:SSH、VNC。

版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。安装基础依赖的命令如下:

# 建议先创建虚拟环境
python3 -m venv edge_ai_demo
source edge_ai_demo/bin/activate

# 安装基础依赖
pip install opencv-python
pip install onnxruntime
pip install flask
pip install numpy

如果边缘设备是ARM架构,onnxruntime需要安装对应ARM版本。可以在官网下载匹配平台的安装包,也可以尝试:

pip install onnxruntime

安装完成后在Python中验证:

import onnxruntime as ort
print(ort.__version__)
print(ort.get_available_providers())

2.3 演示项目目录结构

整个演示项目的目录结构如下,后续代码都按这个结构组织:

edge-ai-demo/
├── models/
│   └── yolov8n.onnx          # 转换好的ONNX模型
├── app.py                     # Flask推流主程序
├── detector.py                # 推理模块
├── requirements.txt           # 依赖清单
└── README.md                  # 项目说明

3. 核心原理:模型是如何从云端训练变成端侧实时推理的

3.1 模型训练与导出

在边缘设备上跑的模型,不是直接在端侧训练的(少数在线学习场景除外)。常规流程是先在云端/本地服务器上用GPU训练模型,训练好后导出为中间格式。

以YOLOv8目标检测为例,训练完成后导出ONNX格式:

yolo export model=yolov8n.pt format=onnx opset=12 simplify=True

导出时要注意几个关键点:

  • opset版本要和推理框架支持的版本匹配,不是越高越好。
  • simplify=True可以简化计算图,减少部分冗余算子。
  • 导出后可以用onnxruntime做一次正确性校验,防止模型导出后推理结果异常。

3.2 模型转换与量化

边缘设备算力有限,原始浮点模型往往太大、太慢。通常需要做量化和裁剪。

常见量化方式:

量化方式 说明 适用场景
FP16 半精度,显存/内存占用减半 NVIDIA Jetson等支持FP16的GPU
INT8 权重量化为8位整数,体积小速度快 大多数边缘NPU
动态量化 只量化权重,推理时动态计算 CPU推理

量化带来的收益是体积变小、速度变快,代价是精度有一定损失。现场演示时最常见的一个问题就是量化后小物体检测不出来了。所以演示前必须在目标场景数据上重新评估精度。

3.3 推理框架怎么选

不同硬件平台适配的推理框架不同。选错框架,性能天差地别。

  • NVIDIA Jetson:推荐TensorRT或onnxruntime-GPU。
  • 瑞芯微RK3588:推荐RKNN-Toolkit2,把ONNX/PyTorch模型转成rknn格式。
  • 纯CPU环境:推荐onnxruntime或OpenVINO(Intel平台)。
  • 手机/嵌入式:TFLite、NCNN、MNN。

选型原则是:优先选芯片厂商官方推荐的推理框架,因为NPU的底层指令集往往是私有的,通用框架不一定能调用NPU加速。

3.4 部署形态与性能优化

边缘设备上常见的部署形态有三种:

  1. 纯Python进程:开发简单,适合原型和演示,性能受GIL限制。
  2. Python + C++扩展:推理部分用C++实现,通过pybind11封装,兼顾开发和性能。
  3. 纯C++服务:性能最好,但开发周期长。

现场演示项目通常用Python快速实现。性能优化重点看这几块:

  • 输入分辨率:不是越大越好,要平衡精度和延迟。
  • 批处理大小:边缘设备通常batch=1。
  • 线程数:onnxruntime可以配置线程数,要和设备CPU核数匹配。
  • 解码和推理并行:用多线程让摄像头解码和模型推理同时进行。
  • 避免频繁内存拷贝:尽量复用输入输出缓冲区。

4. 完整实战:在边缘设备上跑通一个目标检测演示

这一节我们来实现一个完整的边缘AI/ML演示:摄像头采集画面,YOLOv8n模型在本地推理,检测画面中的人和物体,最后通过浏览器实时查看结果。

4.1 创建项目结构

先在边缘设备上创建项目目录:

mkdir -p edge-ai-demo/models
cd edge-ai-demo

4.2 导出ONNX模型

在训练服务器或本地电脑上执行:

pip install ultralytics

yolo export model=yolov8n.pt format=onnx opset=12 simplify=True

导出后将 yolov8n.onnx 文件拷贝到边缘设备的 edge-ai-demo/models/ 目录下。

如果没有预先训练的模型,也可以用YOLOv8官方预训练权重,或者在线下载一个ONNX版本的YOLO模型。

4.3 编写推理模块

文件路径: edge-ai-demo/detector.py

import cv2
import numpy as np
import onnxruntime as ort


class YOLOv8Detector:
    def __init__(self, onnx_path, conf_threshold=0.5, iou_threshold=0.45):
        self.session = ort.InferenceSession(
            onnx_path,
            providers=["CPUExecutionProvider"]
        )
        self.conf_threshold = conf_threshold
        self.iou_threshold = iou_threshold

        input_info = self.session.get_inputs()[0]
        self.input_name = input_info.name
        self.input_shape = input_info.shape
        self.input_h, self.input_w = int(self.input_shape[2]), int(self.input_shape[3])

        self.output_names = [o.name for o in self.session.get_outputs()]

    def preprocess(self, frame):
        img = cv2.resize(frame, (self.input_w, self.input_h))
        img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR -> RGB,HWC -> CHW
        img = np.ascontiguousarray(img, dtype=np.float32)
        img /= 255.0
        img = np.expand_dims(img, axis=0)
        return img

    def postprocess(self, outputs, frame_shape):
        # 以常见的1x84x8400输出为例
        preds = outputs[0][0]  # shape: [84, 8400]
        preds = preds.T         # shape: [8400, 84]
        boxes = []
        scores = []

        for pred in preds:
            class_scores = pred[4:]
            max_score = class_scores.max()
            if max_score < self.conf_threshold:
                continue
            cls_id = int(class_scores.argmax())
            cx, cy, w, h = pred[:4]
            x1 = cx - w / 2
            y1 = cy - h / 2
            x2 = cx + w / 2
            y2 = cy + h / 2
            boxes.append([x1, y1, x2, y2])
            scores.append(float(max_score))
            labels.append(cls_id)

        if not boxes:
            return []

        boxes = np.array(boxes)
        scores = np.array(scores)

        # 将缩放后的坐标映射回原图
        scale_x = frame_shape[1] / self.input_w
        scale_y = frame_shape[0] / self.input_h
        boxes[:, [0, 2]] *= scale_x
        boxes[:, [1, 3]] *= scale_y

        indices = cv2.dnn.NMSBoxes(
            boxes.tolist(), scores.tolist(), self.conf_threshold, self.iou_threshold
        )

        results = []
        for i in indices.flatten():
            x1, y1, x2, y2 = boxes[i].astype(int)
            results.append({
                "bbox": (x1, y1, x2, y2),
                "score": float(scores[i]),
                "class_id": int(labels[i]),
            })
        return results

    def detect(self, frame):
        import time
        input_tensor = self.preprocess(frame)
        outputs = self.session.run(self.output_names, {self.input_name: input_tensor})
        return self.postprocess(outputs, frame.shape)

如果设备支持CUDA或TensorRT,可以调整providers:

providers=[
    "TensorrtExecutionProvider",
    "CUDAExecutionProvider",
    "CPUExecutionProvider"
]

注意:实际能启用哪些provider,取决于onnxruntime的安装版本和设备驱动。

4.4 编写Flask推流主程序

文件路径: edge-ai-demo/app.py

from flask import Flask, Response, render_template_string
import cv2
import time
import threading
from detector import YOLOv8Detector

app = Flask(__name__)

MODEL_PATH = "models/yolov8n.onnx"
VIDEO_SOURCE = 0  # 0 表示USB摄像头;也可以填写RTSP地址

detector = YOLOv8Detector(MODEL_PATH, conf_threshold=0.4)
cap = cv2.VideoCapture(VIDEO_SOURCE)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

lock = threading.Lock()


def generate_frames():
    while True:
        ret, frame = cap.read()
        if not ret:
            break

        results = detector.detect(frame)

        for r in results:
            x1, y1, x2, y2 = r["bbox"]
            score = r["score"]
            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
            label = f"obj {score:.2f}"
            cv2.putText(
                frame, label, (x1, max(0, y1 - 10)),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2
            )

        ret, jpeg = cv2.imencode(".jpg", frame)
        if not ret:
            continue

        yield (
            b"--frame\r\n"
            b"Content-Type: image/jpeg\r\n\r\n" + jpeg.tobytes() + b"\r\n"
        )

        time.sleep(0.03)


@app.route("/video_feed")
def video_feed():
    return Response(
        generate_frames(),
        mimetype="multipart/x-mixed-replace; boundary=frame"
    )


@app.route("/")
def index():
    return render_template_string("""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Edge AI Demo</title>
        <meta charset="utf-8">
        <style>
            body { background: #1e1e1e; color: #eee; text-align: center;
                   font-family: Arial, sans-serif; padding-top: 40px; }
            img { max-width: 80%; border: 3px solid #333; border-radius: 8px; }
        </style>
    </head>
    <body>
        <h2>边缘AI/ML实时推理演示</h2>
        <p>浏览器实时查看摄像头推理结果</p>
        <img src="/video_feed">
    </body>
    </html>
    """)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080, debug=False, threaded=True)

4.5 运行与验证

启动服务:

cd edge-ai-demo
python app.py

终端输出预期:

 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080

浏览器访问:

http://<边缘设备IP>:8080

如果一切正常,页面上会出现实时视频流,画面中的人或者物会被绿色的框标出来。

5. 现场演示翻车的常见问题与排查思路

边缘AI演示最容易在“演示那一刻”出问题。我在展会现场也见过不少展台临时调模型、重启程序的场景。下面把高频问题列成一个排查清单,大家现场演示前可以逐项核对。

问题现象 常见原因 解决思路
onnxruntime加载模型失败 模型opset版本过高或包含不支持的算子 降低opset重新导出,或换用更新版推理框架
推理速度很慢,只有几帧每秒 模型太大或未使用NPU/GPU 换小模型、做量化、启用硬件加速provider
摄像头打不开 设备索引错误或权限不足 用 ls /dev/video* 确认摄像头编号,检查用户组权限
浏览器画面卡顿 推流线程阻塞在推理上 解码线程和推理线程分离,控制推流帧率
检测框位置偏移明显 预处理时坐标系没映射回原图 检查resize和坐标缩放逻辑
检测不到物体 置信度阈值太高或量化后精度下降 调低阈值,评估量化前后精度差异
部署在ARM设备上import onnxruntime报错 框架版本与Python/ARM架构不匹配 下载对应平台wheel包重新安装
服务可以启动但页面无法访问 防火墙未放行端口 开放8080端口,或临时关闭防火墙验证

针对“推理速度慢”这个问题,现场演示前可以用一个最小性能压测快速摸底:

# 查看设备CPU信息
lscpu

# 查看内存占用
free -h

# 看看有没有加载硬件加速模块
nvidia-smi  # NVIDIA平台
ls /dev | grep -i rknpu  # RK3588平台

如果设备有NPU但onnxruntime没有启用,推理只会跑在CPU上,性能会差很多。这种情况必须先根据芯片平台切换到对应的推理框架。

6. 从Demo到项目落地的工程建议

IOTE展会上很多演示做得非常流畅,但从“能跑通的Demo”到“可维护的边缘AI/ML项目”,中间还隔着不少工程问题。

6.1 模型侧建议

  • 模型选型先小后大:优先用YOLOv8n、YOLOv5s这类轻量模型跑通链路,再根据精度需求逐步升档。
  • 导出后必须验证:模型转换格式后精度可能发生变化,要在真实场景数据上做回归验证。
  • 量化前先留评估集:不能只看单张图片效果,至少要准备100张左右覆盖典型场景的验证图。
  • 版本管理:训练权重、导出文件、推理代码要一一对应,建议用DVC或Git LFS管理大文件。

6.2 硬件侧建议

  • 摄像头稳定连接:USB摄像头长时间运行可能掉线,工业场景优先用RTSP网络摄像头。
  • 供电要稳:边缘盒子用独立电源,避免和电机等大功率设备共用电源。
  • 散热要够:连续推理时NPU/GPU温度会快速升高,高温降频直接导致帧率波动,演示现场尤其要注意。

6.3 工程架构建议

生产项目的代码结构要比Demo复杂得多,建议按模块拆分:

src/
├── capture/     # 视频流采集
├── inference/   # 模型推理封装
├── tracking/    # 目标跟踪
├── sink/        # 结果输出(MQTT/数据库/推流)
├── config/      # 配置文件
└── monitor/     # 运行监控

具体落地建议:

  • 配置外置:模型路径、摄像头地址、置信度阈值全部放到YAML或环境变量中,禁止硬编码。
  • 推理服务化:把推理逻辑封装成HTTP/gRPC服务或内部消息队列消费者,方便单独升级。
  • 结果结构化:检测结果统一转成JSON,包含时间戳、置信度、坐标、设备ID,便于后续分析。
  • 增加看门狗:边缘设备无人值守,主进程崩溃后要能自动拉起。
  • 日志分级:至少要区分info/warning/error,记录每一帧的处理耗时和检测结果数量。
  • 安全边界:边缘盒子上开启SSH时使用密钥登录,限制外网端口开放;涉及重要数据要提示根据隐私合规要求做评估。

现场演示时可能只需要一个浏览器页面,但上线后,考虑的就该是可靠性和可维护性了。

6.4 功耗与成本评估

边缘AI/ML项目落地时,功耗往往是被忽略的变量。同样跑一个YOLOv8n模型,不同硬件平台的功耗可能从5W到60W不等。实际选型时要综合评估:

  • 单路视频功耗需求。
  • 多路视频叠加后的总功耗。
  • 现场供电条件是否满足。
  • 散热和IP等级要求。

不要在展会现场只看帧率数值,要问清楚演示设备在不同负载下的功耗和温度表现。

7. 总结与下一步学习路线

围绕边缘AI/ML现场演示,本文完整梳理了从概念、环境准备到模型导出、ONNX推理、Flask推流整个闭环,也整理了演示现场常见的翻车点和排查清单。看完后,至少能完成这样三件事:第一,理解边缘AI/ML和云端AI的边界;第二,把训练好的模型转换后在本地设备跑起来;第三,通过浏览器实时查看推理结果。

如果你正准备入门边缘AI部署,下一步可以沿着这个路径继续深入:

  1. 换一个真实业务数据集,训练一个自己的目标检测模型。
  2. 尝试把模型量化为INT8,对比量化前后精度和速度的差异。
  3. 根据手上设备的芯片平台,学习对应的官方推理框架。
  4. 把Demo里的Flask推流替换成MQTT结果上报,让检测数据进入后端系统。
  5. 尝试多路视频流并发推理,评估边缘设备的性能上限。

真正到了项目落地阶段,你会慢慢发现,边缘AI比拼的不只是模型精度,更是软硬件协同能力和工程细节。希望这篇教程能帮你把第一块基石垫稳。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐