做了多年数字标牌(Signage),我早就习惯了一个状态:客户永远在提需求,播放盒的硬件永远是那几款老配置。早几年大家关心的只是“能不能解码4K”,后来开始问“能不能做远程发布”,再后来,几乎每一个项目方都会加一句:“能不能顺便统计一下客流?能不能判断广告有没有人看?”
这类需求放在云端做其实不难,但广告机分布在商场、门店、电梯,网络环境参差不齐,一断网AI就变废铁。把推理放到本地,又面临一个老问题:播放器的CPU本来就要负责解码和渲染,再跑模型很容易直接把播放卡死。我们最终选了一条比较冷门但实际效果很好的路:给Signage Player加上Intel AI VPU支持,用VPU承担AI推理,把CPU和GPU资源全部留给播放主流程。这篇文章把整个改造过程、码出来的坑、以及最终的调优结果都记录下来,给同样想搞边缘AI播放器的同行一个参考。
1. 项目背景与方案选型
1.1 客户真实需求与播放器现有压力
客户的原始需求很朴素:两万块屏,分布在全国几十个城市的连锁门店,希望广告屏能自动识别屏幕前是否有人、停留多久,并把这些数据回传总部。同时,如果检测到屏幕前出现多人围观,自动切换为互动广告。注意,这里的人脸检测只需要知道“有没有人、大概几个人”,不需要识别人脸身份,这样也躲开了大量隐私合规问题。
播放器端的现状是:CPU是一颗老款的x86低功耗处理器,内存4GB,之前跑一个Windows播放客户端,负责视频循环播放、图片轮播、远程指令接收和屏幕亮度策略。负载已经不小,如果直接在CPU上跑目标检测模型,从经验上看播放进程很容易出现丢帧,严重时还会导致系统看门狗重启。
所以整个方案的第一原则是:AI推理不能抢占播放主进程的CPU资源,至少不能长时间占满一个核心。
1.2 为什么选Intel AI VPU而不是GPU或纯CPU
一开始考虑过小GPU,例如NVIDIA Jetson,但被成本和功耗卡住了。播放器本身的硬件是客户已经批量采购的x86盒子,不能整个换掉。外接独立GPU又带了供电和散热问题,一个广告屏现场根本没有空间放一个带风扇的独立显卡。
Intel AI VPU在当时的定位正好补了这个空档。我们测试过第一款方案是Movidius Myriad X,通过USB 3.0连接,整板功耗只有1W到2W,峰值算力可以跑到1 TOPS左右。不要小看这个算力,对一个经过裁剪的人体检测模型来说,已经足够做到每秒10帧以上的推理。而播放器只需要每秒取一帧做检测,完全够用。
更重要的是,Intel的OpenVINO工具链对这类VPU支持非常完善,模型从TensorFlow或PyTorch转成IR格式后,调用方式基本一致。这意味着我们可以先在自己的开发机上用普通CPU调试,最后切换设备类型到VPU就行,不需要单独写一套推理代码。
1.3 整体方案架构与数据流设计
改造后的Signage Player实际上跑了两条并行业务线:
- 播放链:视频源解码、画面渲染、窗口合成、广告计划调度,这一条线保持原样。
- 推理链:从播放链的帧数据中定期抽帧,经过预处理后交给VPU推理,推理结果写回共享内存,再由播放控制模块决定是否切换广告内容。
这两条线之间不能直接耦合。我们最初的设计是播放器每帧都调用AI接口,结果一旦模型初始化变慢,整个播放循环都被阻塞。最终改成播放链和推理链通过生产者消费者队列交互,播放线程只把帧指针塞进队列后立即返回,推理线程拿到帧后做格式转换和推理。队列长度固定为3,满了就丢帧,绝不阻塞播放线程。
这样的数据流设计直接决定了整个系统在压力下的表现:VPU忙的时候最多丢掉一些检测帧,但广告画面不会卡。对数字标牌场景来说,“画面流畅”比“每一帧都检测”重要得多。
2. 硬件环境与开发环境搭建
2.1 必要的硬件清单与选型建议
我在这个项目里实际使用的硬件如下:
| 部件 | 型号/规格 | 用途 |
|---|---|---|
| 播放器主板 | 低功耗x86工控板,四核CPU,支持VT-x | 承载系统与播放客户端 |
| VPU加速卡 | Intel Movidius Myriad X 神经计算棒2代 | 接USB 3.0,承担AI推理 |
| 内存 | 4GB DDR4 | 系统运行 |
| 存储 | 64GB SSD | 系统与广告素材 |
| 显示器 | 1080P商用屏 | 输出广告画面 |
如果你打算在新项目里选型,注意VPU尽量接在USB 3.0接口上,USB 2.0虽然也能用,但传输帧数据的时间明显变长,推理吞吐会下降20%左右。另外,部分工控板的USB口供电不稳,神经计算棒工作一段时间后会掉线,最好通过带供电的HUB连接,或者选带独立供电的USB口。
2.2 OpenVINO工具链安装与版本陷阱
OpenVINO是连接应用和VPU的桥梁。这个项目用的版本是2022.3 LTS,之所以没用更新的版本,是因为后续几个版本对Myriad X的支持策略有调整,在旧设备上出现过兼容性问题。如果你也是老VPU,建议优先选择带LTS标记的稳定版本。
安装过程不复杂,但有一个坑必须提醒:不要直接 pip install openvino 就完事,因为Python的openvino包默认不带VPU插件。正确做法是安装完整的OpenVINO Runtime,并确认开放式。
在Ubuntu下,比较稳的安装方式:
wget https://storage.openvinotoolkit.org/repositories/openvino/packages/2022.3.0/linux/l_openvino_toolkit_ubuntu20_2022.3.0_1.tgz tar -xzf l_openvino_toolkit_ubuntu20_2022.3.0_1.tgz cd l_openvino_toolkit_ubuntu20_2022.3.0_1 sudo ./install_dependencies/install_openvino_dependencies.sh source /opt/intel/openvino_2022/setupvars.sh安装完成后,先用官方工具检测设备是否可用,这一步能提前暴露90%的环境问题:
python3 -c "from openvino.runtime import Core; c = Core(); print(c.available_devices)"如果输出里出现 GPU、CPU,但没有 MYRIAD,说明插件没装全,或者VPU设备没被系统识别。先查设备节点:
lsusb | grep 03e7这里的03e7是Intel VPU的USB Vendor ID,如果看不到,先检查USB线缆和供电,而不是急着重装驱动。
2.3 模型转换与部署格式选择
播放器上的模型必须转成OpenVINO IR格式,不能直接跑TensorFlow的pb文件或PyTorch的pt文件。转换过程可以在开发机上完成,产出的.xml和.bin文件再拷贝到播放器。
我用的原始模型是一个针对边缘设备优化过的人体检测模型,输入分辨率是416x416。转成IR的主要命令如下:
python3 /opt/intel/openvino_2022/tools/model_tools/downloader.py --name person-detection-0200 python3 /opt/intel/openvino_2022/deployment_tools/tools/model_downloader/converter.py --name person-detection-0200这个模型是从Open Model Zoo下载的,好处是已经适配过VPU,输出格式明确,省去自己解析的功夫。如果你要用自己训练的YOLO模型,可以参考YOLO官方提供的OpenVINO导出脚本,转换后要注意输出层的解析方式。
模型转换中的一个核心参数是--data_type=FP16。VPU对FP32模型支持不友好,FP16不仅推理速度快,内存占用也会少一半。实测同样一个模型,FP16比FP32在Myriad X上快接近40%。所以如果你发现VPU推理很慢,先别怀疑算力,检查一下模型是不是FP16格式。
3. 核心实现:播放器如何与VPU协同工作
3.1 播放线程与推理线程的解耦逻辑
这部分是整个项目的核心,也是最容易翻车的地方。很多第一次做AI播放器的人会犯同一个错误:在视频渲染的回调里直接做同步推理,结果模型一次推理要80毫秒,播放器帧率直接从60帧掉到12帧。
我的做法是建一个只保存帧引用的循环缓冲,播放线程在每次渲染前拷贝一帧当前画面,置入缓冲区,推理线程从这个缓冲里取帧,再做缩放、格式转换和推理。
伪代码如下:
import queue import threading frame_queue = queue.Queue(maxsize=3) def player_render_callback(frame): if not frame_queue.full(): frame_queue.put(frame.copy()) # 播放逻辑继续执行,不等待推理结果 def inference_worker(): while True: frame = frame_queue.get() resized = preprocess(frame) # resize到416x416,BGR转RGB result = compiled_model([resized])[output_layer] postprocess(result)注意队列满了之后直接丢帧,这是有意为之。在播放器场景里,检测结果本身有延迟完全没关系,我们最终关心的是一段时间内的人流量趋势,而不是精确到每一帧的检测框。
3.2 从OpenVINO Runtime创建VPU推理会话
模型加载到VPU上的代码比较简单,但有些参数会直接影响稳定性。
from openvino.runtime import Core core = Core() model = core.read_model("person-detection-0200.xml") compiled_model = core.compile_model(model, "MYRIAD", config={})这里没有指定任何config参数,实际上有几个建议加上:
"VPU_POWER" : 0,表示不强制VPU高性能,而是保持低功耗稳定运行。"VPU_NUMBER_OF_SHAVES" : 1,如果模型较小,使用1个SHAVE可以减少内存和发热。"VPU_FORCE_RESET" : "YES",在设备长时间运行后强制复位,避免死锁。
如果你发现连续跑几天后VPU响应变慢,可以考虑在每次启动客户端时执行一次设备复位,通过OpenVINO的set_property接口:
core.set_property("MYRIAD", {"VPU_FORCE_RESET": "YES"})这个操作对长时间无人值守的播放器非常重要,广告机不可能三天两头有人去现场重启设备。
3.3 播放器画面抽帧的高效姿势
播放器用的底层库是FFmpeg加自研渲染引擎。抽帧不是在渲染之后从GPU回读,而是在解码环节用av_frame_clone拿一份帧数据,不需要经过屏幕采集,效率高很多。
AVFrame *cloned_frame = av_frame_clone(frame); // 把 cloned_frame 送入推理线程从解码环节抽帧的好处是即使在多窗口显示的时候,画面没有被屏幕合成覆盖,得到的是真实播放内容。如果从HDMI输出端回采,还需要额外处理HDCP等加密问题,得不偿失。
抽帧频率一般设置为每秒1帧,或者每解码30帧抽1帧。人流量统计用1秒1帧已经足够,过高反而会让VPU排队拥堵,造成检测延迟积累。
3.4 推理结果如何影响播放策略
在传统播放器里,广告计划的切换要么按时间表,要么按遥控器指令。加入AI之后,我们加了一个“热力切换”逻辑:当连续3秒检测到屏幕前的人数超过设定阈值,就执行广告切换;当人数长时间为0,则回到默认广告列表。
实现上不复杂,推理线程只是把人数计数写入一个结构体:
struct ai_result { int people_count; uint64_t timestamp_ms; };播放控制线程每500ms读取一次这个计数,再叠加一个低通滤波,防止瞬时抖动。这里有个细节:必须加迟滞阈值。比如设定人数大于2时切换,人数小于1时才切回,否则人数在2和1之间来回跳,广告会频繁切换,非常影响体验。
3.5 多路视频与多VPU的扩展方式
有些使用场景需要一台播放器同时管理两个屏幕,每个屏各有独立广告位。此时一个VPU的算力可能不够。
我们做的扩展方案是给机器插两个神经计算棒,OpenVINO会把它们枚举成MYRIAD.1和MYRIAD.2。两个VPU各自绑定一路视频源,互不干扰。
compiled_model_1 = core.compile_model(model, "MYRIAD.1") compiled_model_2 = core.compile_model(model, "MYRIAD.2")注意,不是所有主板多个USB口都能同时稳定挂VPU,实测一些USB控制器会把两个VPU分配在同一条总线上,导致总带宽不够。选主板时尽量挑支持USB控制器分离的型号,最好两个口分别来自不同控制器。
4. 性能测试结果与调优实录
4.1 不同推理配置下的性能对比
我们在实际播放1080P视频的同时,对以下三种配置做了对比测试:
| 配置 | CPU占用率 | 推理帧率 | 播放丢帧情况 | 整机功耗 |
|---|---|---|---|---|
| 纯CPU推理(Intel HD核显) | 46% | 8 FPS | 偶发掉帧 | 24W |
| VPU推理,FP32模型 | 18% | 12 FPS | 无 | 19W |
| VPU推理,FP16模型 | 17% | 18 FPS | 无 | 19W |
从结果看,VPU最大的收益不是把推理帧率提高了多少,而是把CPU占用率降下来了。纯CPU推理时,播放进程和检测进程互相抢资源,丢帧不可避免;而VPU接入后,CPU占用率降到合理区间,播放画面始终稳定。
功耗方面,VPU方案比纯CPU方案还低了5W左右,因为CPU不再满负荷跑模型,整体发热也小了。
4.2 长时间稳定性与温度表现
数字标牌设备在商场环境中经常7x24小时运行,稳定性优先级高于单次的推理性能。我们做了一个72小时压力测试:循环播放4K视频,同时持续执行人群检测,每2小时记录一次设备温度和VPU状态。
测试中发现,VPU表面温度稳定在55到60摄氏度之间,没有过热保护。真正的问题是内存泄漏。播放器主进程每处理1000帧会增加约2MB内存,48小时后内存占用从400MB涨到了700MB,虽然还不至于崩溃,但对长期运行来说是个隐患。
排查后定位到是推理结果后处理时,我们用来保存检测框的容器在单例类中不断累加,没有清空。修正后,连续运行72小时内存曲线平稳。
4.3 与云端推理方案的成本对比
做这个项目之前,客户也咨询过纯云端方案:摄像头或播放器把视频帧上传到云服务器,识别完再返回结果。简单一算就否掉了:
| 项目 | 云端方案 | 边缘VPU方案 |
|---|---|---|
| 网络依赖 | 断网即失效 | 完全离线可用 |
| 单次识别延迟 | 200-500ms | 50-80ms |
| 每月带宽成本 | 每点位约30元 | 0元 |
| 数据隐私 | 视频内容经过第三方 | 本地处理,不出设备 |
虽然云端方案的识别精度通常更高,但在数字标牌这种长尾分布的场景,网络的不可靠性会直接让广告互动变成零。VPU方案把识别延迟压到100毫秒以内,现场互动基本无感。
4.4 一个意外收获:VPU空闲时的资源复用
接入VPU之后,我们还发现一个额外好处:广告机在非营业时间(比如商场打烊后)完全没有人流,此时VPU几乎全空闲。于是我们在播放器后台加了一个低优先级任务,利用这段空闲时间对播放器本地存储的广告素材做质量分析,比如检测有没有模糊图像、字幕超边界,然后生成报告上传给运维中心。
这个功能原本需要专门的质检工具在办公室完成,现在直接用现场播放器的闲置算力就把巡检做了。VPU不忙的时候干点杂活,不仅不增加成本,还节省了运维流程。
5. 常见问题与排查方案
5.1 VPU设备无法识别
现象:lsusb看不到03e7设备,或者OpenVINO返回Not found。
排查步骤:
- 先换一个USB口,尽量从机箱后置USB口连接。
- 查看系统日志:
dmesg | tail -50,如果看到powered down字样,多半是供电不足。 - 如果使用USB HUB,换成带独立供电的HUB。
- 在Windows设备管理器里看有没有黄色感叹号的未知设备,如果有,下载Intel官方驱动手动安装。
我们遇到过最离奇的情况是主板BIOS设置了USB节能模式,设备在系统空闲时被自动挂起,导致推理偶尔超时。进BIOS关闭USB自动挂起后问题彻底解决。
5.2 模型转换后推理结果全为空
现象:OpenVINO能正常推理,但没有检测框输出。
常见原因是原始模型预处理方式不对。比如CRNN还是YOLO,不同模型的输入格式要求差异很大。我们使用的person-detection-0200模型要求输入是BGR顺序,U8类型;而另一个模型要求RGB顺序。在代码里写死一种格式就会出问题。
排查办法是先用Open Model Zoo附带的测试脚本跑同一个模型,确认设备没问题,再逐段对比自己的预处理代码。
5.3 推理线程偶发死锁
现象:播放器运行正常,但AI功能停止更新,过一段时间后自动恢复,或必须重启。
这个问题的元凶往往是VPU的固件在长时间运行后进入异常状态。最初我以为是代码死锁,后来通过定时打印线程栈发现线程卡在了InferRequest.start_async()的等待调用里。
解决办法是周期性检查推理结果的时间戳,如果超过5秒没有新结果,就调用core.set_property("MYRIAD", {"VPU_FORCE_RESET": "YES"})强制复位设备,并重新compile一次模型。把这个操作封装成自动恢复逻辑后,死锁问题再也没有出现。
5.4 常见错误速查表
| 错误信息 | 可能原因 | 解决方案 |
|---|---|---|
Can not init Myriad device | 供电不足或固件损坏 | 更换USB口/重刷固件 |
Failed to create InferencePlugin | OpenVINO版本与VPU插件不匹配 | 换成LTS版本并补装插件 |
GetSupportedConfig failed | 设备被其它进程占用 | 重启播放器,或检查是否有旧进程残留 |
Network output shape is not expected | 模型输出解析代码错误 | 核对模型输出层shape |
Inference took 2000ms | 模型FP32或VPU老化 | 重新导出FP16模型 |
6. 最后的一点个人体会
这个项目从立项到落地大概用了两个月,真正写业务代码的时间不多,大量时间花在调试VPU和播放器之间的资源竞争上。如果让我重新做一次,我会在一开始就把“AI推理独立线程 + 缓冲队列 + 自动复位”这三个框架性的东西定下来,而不是先写模型推理再补播放器适配。
另外,Intel VPU并不是万能的,它对模型规模的限制很明显。一旦你的模型要求检测类别超过20个,或者输入分辨率超过640x640,Myriad X就会显得有些吃力。如果你的项目对模型的复杂度要求更高,现在更合理的选择可能是Intel新一代集成NPU的处理器,或者Arc系列独立显卡。但思路是通用的:播放器主业务和AI推理必须分层,边缘设备上的资源永远要优先保播放的稳定。
数字标牌的未来一定不只是“播放”,而是“感知”。VPU这类低功耗推理单元让普通广告机也能具备边缘AI能力,而且不用大改硬件。希望这篇文章能帮到正在折腾同样问题的朋友。