☰
YOLOv4模型转换OpenVINO部署实战:从Darknet到Intel平台推理优化
2026/10/18 12:49:48 网站建设 项目流程

很多人在darknet里训完YOLOv4,第一反应是拿OpenCV的DNN模块直接读权重跑推理。小规模验证没问题,可一旦要上边缘盒子、做到多路视频流实时处理,OpenCV那条路就露怯了:内存占用高、CPU利用率上不去、延迟抖动大。换个思路,用OpenVINO把darknet模型转换并部署到Intel平台上,往往能把推理延迟砍掉一半以上,吞吐量翻一倍还多。这篇文章就把我在实际项目里踩过、填过的坑都梳理一遍,从darknet权重一路走到OpenVINO推理,整个过程按可复现的方式拆开讲。

这个标题看着简单,其实是一条完整链路:darknet训练出的.weights和.cfg要先转成ONNX,再由OpenVINO的模型优化器(简称MO)转成IR格式,最后写推理代码。跟直接用OpenCV跑比起来,多绕了两道转换,但换来的是Intel CPU/核显上真正的硬件加速和可控的线程调度。适合正在做边缘计算部署、想把YOLOv4从"能跑"变成"跑得稳、跑得快"的开发者参考。

1. 整体设计与技术链路拆解

1.1 为什么选择OpenVINO而不是其他方案

先说明白一件事:YOLOv4这个模型,darknet训练出来的权重格式是自己的,OpenVINO不认,TensorRT也不认。所以不管往哪个推理后端走,第一步都是把darknet的weights转成中间格式。区别就在于中间格式选什么、后面怎么优化。

我自己对比过三条路:

方案优点缺点
OpenCV DNN直接读darknet权重零转换,代码简单不支持异步推理,CPU多核利用率低,算力浪费严重
ONNX->TensorRT在N卡上性能上限高需要NVIDIA显卡,边缘场景很多设备没有
Darknet->ONNX->OpenVINOIntel平台上兼容性好,支持动态batch和异步调用,CPU/核显通吃需要处理层兼容问题,转换链路里坑比较多

OpenVINO路线最适合两类场景:一类是已有的服务器或工控机本身就是Intel CPU,不想额外配显卡;另一类是项目需要跑在一个功耗受限的盒子上,比如无风扇的嵌入式设备。这个方案的优势不只是快,更在于它的推理引擎对Intel指令集做了深度优化,AVX2、AVX512能用上,多核调度也比你自己在代码里开几个线程盲目并行要科学得多。

1.2 理解YOLOv4的网络结构对转换的意义

转换模型之前,先要对YOLOv4的网络结构有个整体认识。YOLOv4的主干是CSPDarknet53,颈部是SPP加PANet,输出端是三个不同尺度的检测头。放到转换场景里,这些结构直接决定了你在转ONNX时会不会撞上"不支持的算子"。

常见的问题点:

  • CSP结构中的split操作,在部分ONNX导出脚本里会生成为Slice算子,后续转换时要确保OpenVINO支持这个版本的Slice。
  • Mish激活函数在darknet原版里是自定义实现,早期OpenVINO插件对Mish支持不完整,需要要么在导出ONNX时把它替换成等价的数学表达式,要么用支持Mish的新版本OpenVINO。
  • PANet的上采样用的是Resize+Nearest,这个没问题,但没有特殊配置的话,ONNX导出的坐标变换模式可能跟OpenVINO预期的对齐方式不一致,导致输出特征图偏移,最终检测框整体偏移。

所以,在动手转换之前,建议先把你自己的训练配置找出来:类别数是多少、输入分辨率是多少、anchors是哪一组。这些信息在之后配置解析代码时一个都不能少。

1.3 整体转换链路选型

结论先行:我最终用的链路是darknet权重 -> ONNX -> OpenVINO IR。

为什么不直接用某些仓库里现成的"一键转换脚本"?因为那些脚本大多是个人维护的,跟你的YOLOv4具体版本(比如用了CutMix、用了修改后的激活函数)不一定匹配,而且一旦跑挂了,排查成本远比你自己控制每一步要高。分步转换的好处是,每一步出问题都能定位到具体环节,这在工程上是值得的。

具体分两条路:

  1. 如果你还在训练阶段,建议在保存权重时直接用支持导出ONNX的darknet分支。
  2. 如果你手头只有训练好的.weights和.cfg,就用转换脚本先转成ONNX。

两条路最后汇合到同一个地方:拿到一个像样的yolov4.onnx文件,再用OpenVINO的mo工具去转IR。

2. 转换实操:从Darknet权重到OpenVINO IR

2.1 第一步:把darknet权重转成ONNX

网上能找到不少转ONNX的开源脚本,主流做法是基于某个darknet fork加上ONNX导出支持。不管用哪个,核心都是解析darknet的weights文件和cfg文件,按层重建网络结构,再走一遍前向把权重填进去。

这里有一个非常值得注意的细节:weights文件里存的是float32的裸权重,一层一层按顺序排列的,并没有层名字。脚本解析顺序全靠cfg文件里的layer顺序。如果你改过cfg,比如调了filter数量,那weights文件的布局就会变化,脚本如果没同步改,转换出来就是"张冠李戴"。所以转完ONNX后,一定先跑一次ONNX Runtime推理对比一下darknet原版输出的检测框,确保转换没有把权重搞错位。

以某个常见的转换脚本为例,操作大概是:

python darknet_to_onnx.py --cfg yolov4.cfg --weights yolov4.weights --output yolov4.onnx

有个小建议,如果脚本允许设置输入尺寸,建议直接设成你部署时实际要用的尺寸。比如你计划用608x608输入,就不要用默认的416x416去转,省得后面再改IR的输入shape,多一次插值误差。

2.2 第二步:从ONNX转OpenVINO IR

拿到了yolov4.onnx,接着用OpenVINO的模型优化器来转IR文件。

这里要提醒一句,OpenVINO的版本不同,mo工具所在路径和命令参数会有差异。较新的版本中,mo.py已经整合成ovc,但很多旧项目还是用mo.py。我以常见的mo.py为例,命令大概长这样:

cd /opt/intel/openvino_2021/deployment_tools/model_optimizer python3 mo.py --input_model yolov4.onnx --input_shape [1,3,608,608] --data_type FP32 --output_dir ./ir_model

这里我把数据格式指定为FP32。如果后续要做INT8量化,可以先转FP32,再用OpenVINO的Post-training Optimization Tool(POT)去做量化,不建议一开始就直接转INT8,因为低精度量化需要校准数据集,用POT流程更规范。

还有一个参数值得注意:如果你是老版本OpenVINO,建议加上--disable_fusing,或者至少确认剪枝融合后输出层没有被合并掉。YOLO这种多输出的模型,输出节点是三个卷积层,如果融合逻辑把它跟前面的卷积合并了,后面后处理解析起来会很别扭。

转完后会生成三个文件:xml是网络结构,bin是权重,mapping是节点映射。后处理代码解析输出层时,要用到mapping里的原始节点名称。

2.3 验证IR文件正确性

转换完不要急着写代码,先验证一下IR文件的输出跟原模型能不能对上。我通常用一个笨办法:用相同输入图片,分别用darknet原版跑一次前向,再用OpenVINO的benchmark跑一次,对比三路输出特征图的均值和方差。

benchmark_app -m ./ir_model/yolov4.xml -i test.jpg -d CPU

如果你看到检测结果跟原版差别很大,别急着调后处理,先用输出特征图对比来排除模型转换问题。这个思路我后面在排查章节还会展开讲。

3. 推理代码实现与后处理细节

3.1 初始化推理引擎

OpenVINO的推理代码,核心就几步:创建Core对象、读取网络、设置输入输出、配置推理设备。

用Python示例:

from openvino.inference_engine import IECore ie = IECore() net = ie.read_network(model='yolov4.xml', weights='yolov4.bin') exec_net = ie.load_network(network=net, device_name='CPU') input_blob = next(iter(net.input_info)) output_blobs = list(net.outputs.keys()) # 调整输入尺寸 net.input_info[input_blob].precision = 'FP32'

值得多说一句的是设备选择。很多人以为OpenVINO只能跑CPU,其实它还支持GPU(核显)和VPU。实测下来,在大部分Intel CPU上,CPU插件是综合性最好的选择:稳定、兼容性高、多核优化到位。核显GPU在带宽充裕时峰值性能更高,但功耗和驱动问题多一些,不是所有工控机都适合。

3.2 YOLOv4输出张量解析

这是整个部署流程里最容易写错的地方。YOLOv4的输出是三个尺度,每个尺度的特征图尺寸不同,但输出张量的长度等于 3 * (4 + 1 + num_classes),其中3是每个网格的anchor数量。

以COCO的80类模型为例,每个anchor的预测向量长度是85。如果用608输入,三个输出头的shape分别是:

  • 76 x 76 x 255
  • 38 x 38 x 255
  • 19 x 19 x 255

255 = 3 x 85。这三个输出头分别负责小目标、中目标、大目标。

后处理解析的完整逻辑:

  1. 将输出的caffe格式或onnx格式的NDArray摆正,确保维度是[N, C, H, W]和[N, H, W, C]的正确对应,通常OpenVINO输出是NCHW,要先转成NHWC方便按网格处理。
  2. 对每个尺度的每个网格点,取出3个anchor对应的预测向量。
  3. 对每个预测向量,用sigmoid激活得到objectness、class probabilities和box坐标偏移。
  4. 结合anchor尺寸和网格坐标,还原出相对于输入图像的边框坐标。
  5. 把三个尺度的所有候选框汇总,执行非极大值抑制(NMS)。

这一步最容易出事的地方是坐标还原。YOLOv4在darknet里的坐标定义是中心点坐标加宽高,而OpenVINO输出里,网络最后几层如果不做解码,拿到的还是原始偏移量,需要自己加网格坐标和anchor换算。

我贴一段核心的解析代码思路:

def parse_yolo_output(pred, anchors, num_classes, input_w, input_h): # pred shape: [N, C, H, W],先转成 [N, H, W, C] pred = np.transpose(pred, (0, 2, 3, 1)) grid_h, grid_w = pred.shape[1:3] pred = pred.reshape(-1, grid_h, grid_w, len(anchors), 5 + num_classes) boxes = [] for row in range(grid_h): for col in range(grid_w): for a in range(len(anchors)): # 取出objectness、坐标、类别概率 ... # 计算中心x、y相对整张图的坐标 x = (col + sigmoid(tx)) / grid_w * input_w y = (row + sigmoid(ty)) / grid_h * input_h w = anchor_w * exp(tw) h = anchor_h * exp(th) # 保留objectness大于阈值的候选 ... return boxes

这段逻辑里sigmoid(..)、exp(..)要搞清楚哪些地方需要、哪些地方不需要。darknet在训练时输出的tx、ty是未经sigmoid的原始偏移,有些转换工具在导出ONNX时会把sigmoid写进图里,有些不会。判断方法很简单:拿一张已知目标的图片,输出结果如果框位置偏差但类别对了,多半是sigmoid没对上。

我在前面加的exp那一步也容易踩坑。darknet的YOLOv4训练时,宽高输出用的是exp。如果你把网络输出直接当真实宽高用,小目标检测会全面崩坏。

3.3 NMS策略与性能

NMS的写法直接影响端到端延迟。YOLOv4输出的候选框可能有上万个,如果全部参与NMS,CPU上会白白烧掉很多时间。

我习惯按以下顺序做:

  1. 先用objectness阈值(一般0.25~0.5)过滤掉大部分候选框。
  2. 再用类别分数阈值过滤一次。
  3. 最后按类别分别做NMS,IoU阈值一般在0.4~0.5。
  4. NMS实现用OpenCV的cv2.dnn.NMSBoxes即可,C++的话可以用标准库实现,注意按score降序排序后贪心抑制。

提一个性能经验:把解码头和NMS放到同一个循环里,而不是先全部解码存成Python list再做NMS。Python的list在候选框多的时候会拖慢速度,能用numpy批量操作就用numpy。

3.4 推理代码的完整骨架

完整推理流程骨架如下:

def infer_frame(exec_net, frame, input_blob, output_blobs): # 1. 预处理:resize到608x608,归一化到0~1,HWC转CHW,加batch维度 input_data = preprocess(frame) # 2. 推理 outputs = exec_net.infer(inputs={input_blob: input_data}) # 3. 解析三路输出 boxes = [] for i, output_name in enumerate(output_blobs): pred = outputs[output_name] boxes.extend(parse_yolo_output(pred, anchor_groups[i], num_classes, 608, 608)) # 4. NMS final_boxes = nms(boxes, conf_threshold=0.25, iou_threshold=0.45) return final_boxes

这一套跑通后,基本就能在CPU上实时跑了。接下来谈性能调优,这才是OpenVINO部署的真正重头戏。

4. 常见问题与排查技巧实录

这一章节是整篇文章含金量最高的部分,全都是我在实际项目里一步步踩出来的经验,按问题频率从高到低排列。

4.1 转换后检测框整体偏移或完全没有检测结果

现象:模型转换成功,推理也能跑,但输出的框位置偏到了奇怪的地方,或者所有objectness都接近0。

排查顺序:

  1. 先确认ONNX阶段是否正确:用ONNX Runtime对比darknet原版的输出。如果这一步就对不上,问题在weights解析,不在OpenVINO。
  2. 确认输入预处理一致:YOLOv4要求输入0~1的float值,有些代码习惯除以255.0,有些转换脚本会把像素映射到-1~1,如果混用,检测结果基本全废。
  3. 确认三路输出的顺序:OpenVINO的output_blobs顺序是按照模型定义来的,不一定是大尺度在前。如果你的代码假设第一个输出是13x13,实际第一个是52x52,那anchors配错,检测框全乱。

4.2 conv层报Unsupported或Parse error

现象:在运行mo.py时,提示某个算子不支持,常见于Mish、LeakyReLU的某个变体、或者Slice层。

排查思路:

  • 升级OpenVINO版本。我最早用2020.x版本转YOLOv4,Mish不支持很常见。升级到2021.4之后,大部分兼容问题自然消失。
  • 如果必须用旧版本,就在导出ONNX时手动把Mish替换成x * tanh(ln(1 + e^x))等价表达式。
  • 检查ONNX的opset版本,我在某些环境里用opset 11导出的模型转起来失败,用opset 10反而正常。这个跟特定版本有关,可以多试。

4.3 推理延迟比预期高很多

现象:模型在darknet里推理大约50ms,转到OpenVINO后反而80ms。

排查方向:

  1. 确认有没有用错设备。默认device_name是CPU,但如果你机器有核显还开了GPU插件,有时候会单独指定GPU,反而更慢。
  2. 确认输入尺寸。如果转换IR时用了608x608,推理时却喂了1280x720的图,会额外多一次resize,而且openvino内部可能会把大图resize到608,这个开销有时候比推理本身还大。
  3. 检查线程数设置。可以通过配置CPU_THREADS_NUM控制线程数,但要注意,线程数设多高不代表更快,还看有没有超线程干扰。我实测四核八线程的机器上,设4个线程往往比设8个更快。
  4. 看绑核设置。在NUMA架构的服务器上,CPU绑核与不绑核吞吐差别很大,建议初跑时用OPENVINO_CPU_BIND_THREADS=YES启用绑核。

4.4 多路视频流场景下CPU占用异常高

现象:接4路视频流后,CPU直接飙满,延迟忽高忽低。

核心解法:用异步推理,不要用同步infer。OpenVINO提供async模式,允许你在当前帧推理的同时去做下一帧的预处理和上一帧的后处理,Pipeline重叠起来,吞吐量有质的提升。这个后面专门讲。

另外,将多个输入合并成一个batch推理,也是一个减少调度开销的思路。YOLOv4本身不是必须单帧处理,只要模型输入是[1,3,H,W],你改成[4,3,H,W]并做好动态batch支持,推理引擎会在内部合并计算。

4.5 输出张量的数值范围看起来不对

现象:从OpenVINO拿到的输出,数值普遍在几十到几百,而不是0~1的小数。

原因:darknet的YOLOv4在训练时用的是线性输出加sigmoid,但有些导出工具把最后的sigmoid层去掉了,导致你拿到的是线性值。这本身不是问题,后处理时记得对objectness和class probability做sigmoid即可。判断依据就是:当你的检测结果时好时坏、置信度普遍偏高时,多半是这个原因。

4.6 常见问题速查表

问题可能原因解决办法
转换时算子不支持OpenVINO版本太旧升级到2021.4+
输出层找不到mo工具融合了输出加--disable_fusing
检测框整体偏移坐标解码少加网格偏移检查decode逻辑
置信度全部接近1少了sigmoid后处理补sigmoid
推理耗时比darknet还高线程数设置不当或输入尺寸不一致调线程数、统一尺寸
IR转出来很大FP32且分辨率高按部署精度转FP16或量化
转INT8后掉点严重校准集太小用1000+张代表性图片做POT校准

5. 性能调优实测与量化方案

5.1 单帧延迟与吞吐量平衡

在讨论调优之前,先明确两个指标:延迟和吞吐。延迟是一帧从进去到出来的时间,吞吐是单位时间处理的帧数。OpenVINO的CPU插件有两种执行模式:延迟模式/吞吐模式。

  • 延迟模式适合交互式应用,比如你拿着摄像头看实时画面,不能有明显的跟手延迟。
  • 吞吐模式适合离线批量处理,比如把录制好的视频流逐帧分析,追求总处理时间短。

代码里设置方式如下:

config = {'PERF_COUNT': 'YES', 'CPU_THREADS_NUM': '4'} exec_net = ie.load_network(network=net, device_name='CPU', config=config)

实测中,608输入、80类模型,在一台普通i5处理器上,FP32单帧延迟大约30ms左右,通过设置线程数和绑核能压到20ms出头。如果只是做视频文件分析,开吞吐模式,多线程流水线跑起来,性能还能提升30%~50%。

5.2 FP16与INT8量化对比

OpenVINO支持FP16和INT8两种低精度推理。

FP16在CPU上不划算,因为CPU的原生FP16计算支持不如GPU,很多场景反而是先转成FP32再算,但这个转换有额外开销。所以结论:纯CPU部署,不一定选FP16,要看实测。

INT8就不一样了。使用POT量化,配合校准集,YOLOv4这类模型在CPU上通常能获得1.5~2倍的加速,代价是精度掉1~3个百分点。如果你的场景对检测精度要求苛刻,比如工业缺陷检测,建议谨慎使用INT8。如果只是做人员计数、车辆识别这类任务,INT8完全够用。

INT8量化的操作步骤:

  1. 准备一个校准数据集,200~1000张代表性图片,覆盖各种光照、角度。
  2. 用POT工具加载IR模型,跑校准。
  3. 产出量化后的IR模型,推理时直接用。
pot -m yolov4.xml -w yolov4.bin --quantize --metric --dataset ...

POT工具参数的细节取决于你的数据格式,但核心逻辑是:它会用校准集跑几次前向,并根据激活值的分布计算缩放因子。

5.3 异步推理流水线设计

这是性能优化里最立竿见影的一招。

同步推理的逻辑是:预处理->推理->后处理,做完一个才做下一个,CPU在推理阶段干等,后处理阶段推理引擎空闲,预处理阶段更是纯CPU计算。异步推理把这三个阶段做成流水线:

# 伪代码:异步流水线 request = exec_net.start_async(request_id=0, inputs={input_blob: input_data}) request.wait() outputs = request.outputs

更进一步,你还可以用多个request轮流执行,让CPU的多个核都忙起来:

  • request 0在推理第n帧
  • 主线程同时做第n+1帧的预处理
  • 上一帧的后处理可以在另一个线程里做

这样的三重流水线,在长视频流场景下,比同步模式能快一倍以上。这里最推荐的实践是:主线程负责读帧和预处理,worker线程负责推理,后处理线程负责解析。线程间用队列传递数据,Python里用queue.Queue,C++里用带锁的环形队列。

5.4 多路视频流的资源分配

多路视频流场景下,有两种策略:

第一种是每路单独一个推理请求。好处是隔离性好,一路卡住不影响其他路;坏处是线程上下文切换多,CPU利用率碎片化。

第二种是合并batch。把多路的帧拼成一个batch输入,一次推理多帧。好处是吞吐量高,坏处是延迟等于batch里所有帧的累计时间,而且其中一路断流会导致整个batch等它。

我的经验是:4路以下用单独请求,超过4路优先合并batch。具体还得看机器核数和模型规模,没有银弹。

5.5 实测数据参考

在一台主流配置的Intel CPU(6核12线程)上,YOLOv4 608输入、80类,OpenVINO FP32同步推理,延迟大约40ms;开启异步流水线后,吞吐量从25fps提升到35fps左右;INT8量化后再跑,性能又上一个台阶,但精度需要评估。

这个数据可以作为你的参考基准,但别盲目相信,每台机器的内存带宽、CPU型号都会影响结果。拿到模型后先跑一次benchmark再决定优化方向。

6. 部署环境与跨平台注意事项

6.1 Python推理环境的完整配置

推荐用OpenVINO自带的Python环境,或者用pip安装openvino包,版本号跟进官方节奏。部署时最稳妥的方式是使用Docker镜像,这样OpenVINO运行时和依赖库都能锁定版本,不会出现开发机跑得好好的、部署机环境对不上的情况。

一个典型的Dockerfile思路:

FROM openvino/ubuntu20_runtime:latest COPY ./ir_model /models COPY ./app.py /app WORKDIR /app CMD ["python3", "app.py"]

这种镜像里已经装好了OpenVINO运行时,直接就能跑IR模型。

6.2 C++与Python的取舍

Python开发速度快,调优方便,但如果你追求极致性能,C++是更好的选择。OpenVINO的C++ API在推理调用上的额外开销更小,尤其在高频小目标场景下,Python的GIL和numpy拷贝会成为瓶颈。

我的习惯是:原型验证用Python,正式部署框架搭好后,把推理核心用C++重写,或者用pybind11把C++封装成Python模块。如果你不是C++重度用户,这个建议可以忽略,因为大多数业务场景下Python的OpenVINO性能已经够用了。

6.3 模型文件的版本管理

这是一个很容易被忽略但非常重要的工程细节。darknet权重、ONNX、IR文件,每一级转换产物都应该和转换环境版本一起记录。比如你用一个特定版本的转换脚本转出了ONNX,这个脚本是在哪个commit上跑的?openvino是什么版本?datatype是多少?如果不记录,半年后模型要重转,所有人都会抓瞎。

我的做法是为每个模型单独建一个目录,放一份"转换记录.txt",写清楚环境版本、命令参数、输入尺寸、anchors、依赖脚本链接。成本很低,收益极高。

7. 从单机部署到服务化扩展

7.1 把推理封装成HTTP服务

单机部署终究是给本机进程用的,如果多个业务模块都需要调用检测能力,最好封装成服务。

我常用FastAPI来包一层,把OpenVINO推理封装成一个异步接口。请求过来时,把图片作为bytes传入,服务端负责解码、预处理、推理、后处理,返回检测结果的JSON。

@app.post("/detect") async def detect_image(file: UploadFile): img_bytes = await file.read() img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) boxes = infer_frame(exec_net, img) return {"boxes": boxes.tolist()}

服务化的核心价值是解耦:业务端不用关心模型怎么部署,只关心拿到检测结果。模型升级、量化、推理优化都收敛在服务内部。

7.2 多模型共存的资源管理

实际项目里,一个服务可能同时跑目标检测、分类、跟踪多个模型。OpenVINO允许多个网络并存,但要控制总线程数,避免互相抢CPU。建议为每个模型单独设置CPU_THREADS_NUM,并且总线程数不要超过物理核心数太多。

这个坑我切切实实踩过:同时跑YOLOv4和一个小的分类网络,没有限制线程数,结果两个模型互相抢资源,整体吞吐反而腰斩。

7.3 什么时候需要换推理引擎

最后说一个你可能不爱听但很实在的话题:OpenVINO不是万能的。如果你的部署平台不是Intel,或者你需要在N卡上达到极致性能,TensorRT可能是更好的选择。如果你的模型结构非常新颖、包含很多自定义算子,OpenVINO转换链路可能需要花不少精力去适配,这时候可以评估一下ONNX Runtime+EP的组合。

选型依据排序应该是:

  1. 部署目标平台是什么
  2. 模型结构复杂性
  3. 延迟和吞吐的硬性要求
  4. 团队长期的维护成本

把OpenVINO作为默认选项没毛病,它在Intel平台上覆盖面最广、资料最多、社区活跃,但保持开放的选型心态,不被单一工具绑架,是做部署优化的基本素养。

写在最后的一点体会

项目做多了会发现,模型部署的坑,绝大多数不在推理引擎本身,而在转换链路上的各种"隐式约定":anchors的顺序、输出的排列方式、后处理里要不要sigmoid,这些细节错一个,结果就是鸟枪打飞机——全偏。吃亏吃多了,我现在每拿到一个模型,第一件事不是跑推理,而是先花半天时间把网络结构、输出节点、anchors、预处理方式全部列成文档,再动手写代码。这个过程看起来耽误时间,实际是节省时间。希望这篇关于YOLOv4配合OpenVINO的部署实录,能帮你把这条路走得更顺一点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询