简介一套基于OpenCV DNN模块部署YOLOP全景驾驶感知网络的完整工程面向自动驾驶、智能交通方向的CV开发者与嵌入式部署工程师。YOLOP可同时完成交通目标检测、可驾驶区域分割与车道线检测三项视觉感知任务适用车载视觉感知、行车记录仪后处理等场景需要具备一定的OpenCV与深度学习基础。项目共15个文件、约28.58MB包含Python与C双版本源码、预训练ONNX模型、类别名称映射、测试图片及文档目录结构清晰便于对照学习。整套程序仅依赖OpenCV库即可运行省去PyTorch等深度学习框架的环境配置适合快速验证ONNX模型部署效果也便于按需改写成其他视觉任务。已有192人学习下载对希望理解轻量级多任务感知模型推理流程的读者有直接参考价值。1. 用OpenCV部署YOLOPCPU也能压住全景驾驶感知的三合一在很多人的认知里同时跑交通目标检测、可驾驶区域分割、车道线检测至少得在工控机上插一块独立显卡否则帧率一定会掉到没法看。但真实情况是YOLOP这个网络本身就是把三项视觉感知任务放进同一个Backbone和特征融合结构里推理时每个摄像头帧只需要一次前向传播。把它从PyTorch导出成ONNX后用OpenCV的DNN模块加载即使一台不带GPU的i5主机在640x640输入下也能跑到可用的帧率。这篇笔记讲的就是这条路怎么把YOLOP的ONNX塞进OpenCV怎么解析检测、可驾驶区域、车道线三路输出以及我踩过的几个部署翻车点。适合做驾驶辅助原型、路侧感知盒子、机器人巡线这类场景的工程师新手能照着跑通流程熟手可以拿走参数和排查思路。2. YOLOP的三个输出头与OpenCV部署选型先看懂模型再动手2.1 Backbone共享后三分叉YOLOP比串行方案快在哪YOLOP的全称是You Only Look Once for Panoptic Driving Perception核心思想很简单输入一帧图像经过一个共享的CSPDarknet Backbone和FPN结构最后分出三个支路。支路一是基于YOLOv5检测头的交通目标检测支路二是可驾驶区域分割支路三是车道线分割。三项任务共享了绝大部分卷积计算所以它比“先跑一个检测网络、再跑一个分割网络”的串行方案省掉接近一半的算力和内存带宽这对部署来说才是真正的价值。部署前需要把网络输出结构记清楚。输入是1x3x640x640的RGB图像输出在ONNX里通常表现为三个blob检测头输出所有尺度的预测框候选常见形状是1x25200x6其中25200来自80x80、40x40、20x20三个特征图上的锚点总数6列是中心点坐标、宽高、目标置信度和类别置信度可驾驶区域分割头输出1x2x208x208的通道图两个通道分别表示不可驾驶和可驾驶区域的logits车道线分割头输出1x4x208x208四个通道对应背景和三条车道线类别。搞清楚这三个shape后处理代码才不会写到一半不知道数据的物理含义。2.2 选择OpenCV DNN做部署的四个理由与一个边界我接过不少智能驾驶相关的落地项目团队最后选择OpenCV而不是继续背着PyTorch环境原因比较集中。其一依赖体积小运行时不需要再安装PyTorch和CUDA版本匹配的繁琐环境一个OpenCV动态库就把模型推理和全部图像处理管起来了。其二OpenCV的DNN模块自带ONNX解析器模型离线导出后运行时的结构都封在onnx文件里换模型就是换文件。其三OpenCV对CPU上的卷积算子做了不少SIMD优化在边缘设备上没有独立显卡也能跑。其四后续接摄像头、视频解码、绘制、推流全部是OpenCV自带的功能不会出现“模型用一套、图像处理用另一套库”的割裂。当然它也有边界。DNN模块只擅长前向推理不支持训练不支持动态输入维度对自定义算子的覆盖也不如ONNX Runtime完整。如果你的YOLOP导出时把一些太新的算子带进去OpenCV会直接报Unsupported ONNX op。所以需要从一开始就按OpenCV能消化的方式导模型这个约束必须在写导出脚本前就明确。部署方式运行时依赖CPU推理能效算子兼容度适合阶段PyTorch重环境易冲突中最高随版本快速跟进算法验证OpenCV DNN轻一个库搞定较好中等需主动规避新算子原型与量产评估TensorRT依赖CUDA设备低高但绑定NVIDIA硬件GPU设备量产2.3 导出ONNX的准备工作固定shape、降OpSet、关闭动态轴把YOLOP的PyTorch权重转成ONNX是部署流程里最容易忽略的一个环节。常见做法是在训练代码或推理代码里加一段导出逻辑用固定尺寸的dummy input导出不要开dynamic_axes并把opset_version降到11或12。原因很简单OpenCV的DNN模块对高版本opset里的新算子支持滞后动态维度会迫使模型在网络里插入更多shape相关的算子这两者都会显著增加加载失败的概率。我一般会在导出后先用ONNX Runtime跑一遍同一张图和PyTorch原模型对比输出确认数值基本一致再拿给OpenCV加载这样可以省掉后面一大半定位问题的时间。import torch # 示意这里按你的工程实际导入模型结构 from model import YOLOPModel model YOLOPModel().eval() dummy torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy, yolop.onnx, input_names[images], output_names[det_out, drive_seg, ll_seg], opset_version11, dynamic_axesNone, )这段代码里最关键的是opseset_version固定为11以及明确写出三个输出名。输出名会在OpenCV侧通过net.getUnconnectedOutLayersNames()拿到名字对不上后面解析数据就只能靠猜。dynamic_axes设置为None保证ONNX里的shape全部是常量OpenCV加载时能直接分配固定大小的blob。3. 用OpenCV加载ONNX并完成三项任务推理完整代码拆分3.1 环境准备OpenCV 4.2以上与ONNX读取的确认步骤先确认OpenCV版本。ONNX支持在OpenCV 4.2之后才逐步稳定如果算子报错频繁直接升到4.7以上的版本能省很多时间。安装命令很简单但要提醒一个很常见的坑opencv-python和opencv-contrib-python不要同时装这两个包都包含cv2同时装会发生文件互相覆盖最后import cv2时拿到哪个版本完全看运气。pip install opencv-python4.2 numpy python -c import cv2; print(cv2.__version__)在普通x86服务器上直接pip装就够了。如果是在树莓派或ARM板子上常见做法是用apt装发行版提供的python3-opencv或者走opencv cmake编译步骤编译时把DNN选项打开。用源码编译虽然慢但能把ffmpeg、GStreamer这些视频解码能力一起编译进去后面接USB摄像头或RTSP流会更顺。环境就绪后先用一行代码加载ONNX验证基础可用性net cv2.dnn.readNetFromONNX(yolop.onnx)这一步不报异常说明模型结构和opset都被OpenCV接受了接下来才值得继续写预处理和后处理。3.2 预处理blobFromImage里被忽略的RGB顺序和letterboxYOLOP训练时通常会先对输入做letterbox也就是把原始图像等比缩放到640x640剩余区域用灰色填充。OpenCV的blobFromImage本身只做resize不做等比缩放填充所以这一步要在调用blobFromImage之前自己完成。另外还有一个容易被忽略的细节OpenCV读的图像是BGR顺序而YOLOP在ImageNet预训练时用的是RGB颜色顺序反了会导致分割结果对不齐。import cv2 import numpy as np INPUT_H INPUT_W 640 def letterbox_frame(frame, dst_size(640, 640)): h, w frame.shape[:2] scale min(dst_size[0] / w, dst_size[1] / h) new_w, new_h int(round(w * scale)), int(round(h * scale)) resized cv2.resize(frame, (new_w, new_h), interpolationcv2.INTER_LINEAR) canvas np.full((dst_size[1], dst_size[0], 3), 114, dtypenp.uint8) x_off (dst_size[0] - new_w) // 2 y_off (dst_size[1] - new_h) // 2 canvas[y_off:y_off new_h, x_off:x_off new_w] resized return canvas, scale, x_off, y_off def preprocess(frame): canvas, _, _, _ letterbox_frame(frame) rgb cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB) blob cv2.dnn.blobFromImage( rgb, 1.0 / 255.0, (INPUT_W, INPUT_H), swapRBFalse, cropFalse ) return blobblobFromImage的第三个参数是目标尺寸这里的尺寸必须和导出ONNX时的输入尺寸一致。scale设为1/255是把像素值从0到255归一化到0到1如果导出时模型内部没有做归一化这个参数就不能省。swapRB设为False是因为前面已经手动做过BGR转RGB这里再转就重复了如果你想省一行代码可以不调用cvtColor直接给blobFromImage传原始BGR图并设swapRBTrue但我个人更倾向于把颜色转换放在明处避免后面排查时产生误解。注意如果你使用的导出脚本训练时没有用letterbox而是直接resize那么letterbox_frame这一段要整段去掉否则检测框的坐标全部会整体偏移一段距离。3.3 推理与三头输出解析从outNames到NMS解出检测框加载模型并推理的代码量不大核心是要正确拿到三个输出。net.forward传入getUnconnectedOutLayersNames()返回的名称列表OpenCV会按顺序返回输出blob顺序和导出时的output_names一致。返回的det_out、drive_seg、ll_seg都是numpy数组可以直接用numpy操作。net cv2.dnn.readNetFromONNX(yolop.onnx) # 如果设备支持CUDA可以解除下面两行的注释切换到GPU推理 # net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) # net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) out_names net.getUnconnectedOutLayersNames() print(output names:, out_names) net.setInput(preprocess(frame)) det_out, drive_seg, ll_seg net.forward(out_names) print(det:, det_out.shape, drive_seg:, drive_seg.shape, ll_seg:, ll_seg.shape)det_out的形状是1x25200x6六列分别是cx、cy、width、height、obj_conf、cls_conf。我这里的类别数是1如果你的模型来源或重新标注后类别数更多最后一维就是4加类别数再加1。检测头输出的坐标是相对640x640输入归一化到0到1的需要乘回输入尺寸才能得到检测框。为了减少NMS的计算量我习惯先按置信度过滤掉大部分候选框再做NMS。def decode_detections(det_out, conf_threshold0.3): preds det_out[0] boxes, scores [], [] for p in preds: obj_conf float(p[4]) cls_conf float(p[5]) conf obj_conf * cls_conf if conf conf_threshold: continue cx, cy, bw, bh p[:4] x1 (cx - bw / 2.0) * INPUT_W y1 (cy - bh / 2.0) * INPUT_H x2 (cx bw / 2.0) * INPUT_W y2 (cy bh / 2.0) * INPUT_H boxes.append([x1, y1, x2, y2]) scores.append(conf) if not boxes: return [], [] idx cv2.dnn.NMSBoxes(boxes, scores, conf_threshold, nms_threshold0.45) idx np.array(idx).reshape(-1) return [boxes[i] for i in idx], [scores[i] for i in idx]这里比较考验经验的是conf的计算方式。YOLOP的检测头训练时目标置信度和类别置信度是分开输出的直接用obj_conf作为最终置信度是很多部署脚本里的简化做法但如果类别数量大且存在相似类别最好乘上cls_conf。坐标还原时要注意因为前面做了letterbox真正要映射回原始原图需要减去x_off、y_off再除以scale。上面这段代码返回的是640x640坐标系下的框后续叠加到原图时要再做一次逆变换。3.4 可驾驶区域与车道线的argmax、插值与可视化分割输出要做的处理比检测简单两个分割头都是通道维度的分类结果。可驾驶区域是二分类直接在通道维度取argmax得到0或1的掩码车道线是四分类同样取argmax得到0到3的类别索引。掩码需要从208x208放大到原图尺寸这一步的插值方式有讲究二值语义掩码用最近邻插值不要用双线性插值否则车道线边缘会出现一圈半透明的过渡色视觉上像冒出了好几条线。def process_segmentation(drive_seg, ll_seg, frame_shape): # 可驾驶区域两通道取最大得到0/1掩码 drive np.argmax(drive_seg[0], axis0).astype(np.uint8) drive cv2.resize(drive, (frame_shape[1], frame_shape[0]), interpolationcv2.INTER_NEAREST) # 车道线四通道取最大0是背景1/2/3是三条线 lanes np.argmax(ll_seg[0], axis0).astype(np.uint8) lanes cv2.resize(lanes, (frame_shape[1], frame_shape[0]), interpolationcv2.INTER_NEAREST) overlay np.zeros((frame_shape[0], frame_shape[1], 3), dtypenp.uint8) overlay[drive 1] (0, 200, 0) # 可驾驶区域绿色 lane_colors {1: (0, 0, 255), 2: (0, 255, 255), 3: (255, 0, 0)} for cls_id, color in lane_colors.items(): overlay[lanes cls_id] color result cv2.addWeighted(frame, 0.7, overlay, 0.3, 0) return result, drive, lanesargmax直接作用于网络原始logits是安全的。softmax是单调函数对每个像素来说logits最大的通道就是概率最大的类别所以不需要额外接softmax就能拿到类别索引。如果你发现分割结果里有大量零星噪声区域可以在argmax之后做一次形态学开运算cv2.morphologyEx配cv2.MORPH_OPEN核取3x3对可驾驶区域和车道线都有效。但要注意核的大小不要超过5否则真的细窄车道线会被抹掉。4. OpenCV部署YOLOP常见问题排查算子报错、输出错位与部署翻车记录这一章写的是我实际部署中遇到最多的几类问题每一条都是现象、原因、解决按顺序给到可以直接当排查手册用。4.1 “No module named cv2”安装阶段的第一个坑现象在conda环境里执行pip install opencv-python成功但启动Python脚本时import cv2直接抛ModuleNotFoundError: No module named cv2。在VSCode里点运行没问题切到终端跑就报错。原因conda的base环境和Python虚拟环境混用pip安装时指向的site-packages与当前解释器不是同一份。最常见的是在conda环境里直接调用了系统的python或者相反。解决先确认解释器路径再统一用python -m pip安装。which python python -m pip install opencv-python4.2 -i https://pypi.org/simple python -c import cv2; print(cv2.__version__)这里用python -m pip而不是裸pip是为了保证包装进当前python对应的环境。如果在ARM板或嵌入式Linux上这条命令可能找不到预编译轮子就需要走opencv cmake编译步骤编译时需要注意磁盘空间预留OpenCV整个编译目录很容易超过5GB。这个坑虽然与模型无关但会浪费很多人一下午。4.2 Unsupported ONNX op算子兼容性的经典翻车现象cv2.dnn.readNetFromONNX(yolop.onnx)在执行到一半时报出形如Unsupported ONNX op: Resize或Unsupported ONNX op: GridSample的错误模型完全加载不了。原因YOLOP导出时采用的opset_version过高或者把某些后处理算子也一起编进了ONNX图。OpenCV的DNN模块支持的算子版本滞后于ONNX标准尤其是高版本Resize的坐标变换模式、GridSample这类较新的算子。解决先用onnx-simplifier压缩模型图再用onnx检查模型完整性。python -m onnxsim yolop.onnx yolop_sim.onnx python -c import onnx; onnx.checker.check_model(onnx.load(yolop_sim.onnx))如果simplifier无法解决就要回到导出源头将opset_version改为11并且只导出主干计算不要导出decode和NMS。我见过很多导出的ONNX里带着NonMaxSuppression算子这类算子在OpenCV里是不被支持的。可视化检查ONNX图结构可以用Netron加载后直接查看输出节点凡是出现NMS、GridSample、ROIAlign这类节点先回到PyTorch侧把它们从导出图里摘掉。4.3 VideoCapture拉流中断把读帧和推理拆到两个线程现象接上USB摄像头或RTSP流后程序前几十帧正常随后画面卡顿终端冒出一堆[ WARN ] frames timed outcv2.VideoCapture.read()开始返回False。原因读帧和模型推理在同一个线程里顺序执行推理耗时大于帧间隔时VideoCapture的内部缓冲被占满取帧超时。摄像头和网络流对这种阻塞都很敏感不是模型算错了而是数据流通环节堵住了。解决把读帧放到独立线程用一个容量为1的队列持续保存最新帧推理线程每次从队列里取最新帧就行。import threading import queue frame_queue queue.Queue(maxsize1) def capture_loop(cap): while True: ok, frame cap.read() if ok: if not frame_queue.empty(): try: # 把上一帧丢掉只保留最新帧 frame_queue.get_nowait() except queue.Empty: pass frame_queue.put(frame) cap cv2.VideoCapture(0) threading.Thread(targetcapture_loop, args(cap,), daemonTrue).start() while True: frame frame_queue.get() # 推理代码放在这里这个做法本质是丢帧策略适合驾驶感知这类对实时性要求高于完整性的场景。推理线程处理完当前帧后如果新的帧已经在队列里就立刻取走处理不会因为等待摄像头把老帧传完而浪费时间。实际效果是画面看起来帧率稳定日志里不再出现timed out。4.4 RGB顺序出错分割边界错位与车道线对不上的原因现象检测框大体能框住车辆但可驾驶区域掩码边缘和真实路沿总是错位车道线明显偏向道路一侧甚至出现在路牙子上。原因YOLOP训练时输入是RGB顺序而OpenCV读图得到的是BGR。如果在预处理代码里先调用了cvtColor转RGB又把blobFromImage的swapRB设为True等于做了两次通道交换模型看到的输入颜色和训练时不一致。颜色顺序错乱对检测的影响较小因为目标轮廓信息还在但对依赖颜色分布的分割任务影响非常大。解决统一为“手动转RGB swapRBFalse”或“不转RGB swapRBTrue”中的一种二选一不要混用。我自己的习惯是手动转RGB并显式把swapRB设为False因为代码可读性更好。快速验证方法是用同一帧分别跑两种预处理观察两个mask差异差异明显就说明颜色顺序有问题。4.5 findContours/contourArea的怪错掩码类型与返回值坑现象把np.argmax得到的mask传给cv2.findContours时报contourarea未定义标识符或者提示类型不匹配。用cv2.contourArea单独计算轮廓面积时收到奇怪的C异常。原因argmax返回的数组默认dtype是int64而OpenCV在Python侧的轮廓处理默认期望uint8类型。另外OpenCV 4.x的findContours返回值是两个OpenCV 3.x是三个不少迁移过来的老代码把返回值接错了。解决先转成uint8并把多类别mask压成二值图像再提取轮廓。mask np.argmax(drive_seg[0], axis0).astype(np.uint8) # 多类别mask转二值正类统一为255 mask[mask 0] 255 # OpenCV 4.x 返回两个值3.x 返回三个值 contours, _ cv2.findContours( mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) areas [cv2.contourArea(c) for c in contours]把正类像素统一改成255是因为轮廓提取只关注前景和背景两类如果保留1、2、3这样的多值标签每一类都会生成独立的轮廓可驾驶区域里会出现大量无意义的空洞。如果只想保留面积最大的可驾驶区域按areas排序取最大值再用cv2.fillPoly绘制即可。5. 把YOLOP部署调稳输入尺寸、线程策略与性能取舍5.1 要不要把输入从640降到320FPS与小目标之间的平衡降低模型输入分辨率是提高帧率最直接的手段但代价是检测小目标和远端目标的召回率下降。我做过一次对比同一个ONNX模型在相同CPU上640x640输入能跑到25帧左右降到320x320能跑到55帧以上但距离车头60米外的小轿车在很多帧里直接漏检。输入尺寸CPU推理帧率车辆检测可靠性分割边界质量适用场景640x64025帧左右高小目标保留好细腻x86盒子、GPU设备480x48035帧左右中高中等低功耗主板320x32055帧以上中低小目标易丢明显锯齿原型验证、无人机用320输入时建议把置信度阈值从0.3调到0.2因为图像缩小后模型输出的置信度整体会下降一些不调阈值会进一步放大漏检。分割掩码放大回原图时车道线边缘的锯齿会比较明显可以使用3x3的中值滤波做一次平滑但不要改成双线性插值会让车道线宽度失真。5.2 哪个输出头吃了最多时间用拆分量测定位瓶颈OpenCV的net.forward一次性返回三个输出很难直接测每个头各自耗时。但通过分析ONNX图中每个头的最后几层可以大概估算检测头要处理三个尺度的特征图并拼接分割头在208x208分辨率上有更多卷积通道。实际体验里如果总耗时20毫秒检测头和两个分割头大约各占一半。分割头拖慢速度的一个原因是208x208的特征图在整个head里要保持较高分辨率计算量随通道数线性增长。如果对分割边缘的精细度要求不高另一个常见做法是让模型输出后只对可驾驶区域做半分辨率处理也就是把seg输出从208x208降到104x104再做argmax再插值回原图。但要注意这个操作省的是后处理时间不是模型前向时间。真正想省前向时间只能从模型结构入手比如把分割头的通道数减半后重新微调这已经属于改动网络结构不在OpenCV部署范畴里展开了。5.3 25198个候选框如何过NMS先topK再NMS的惯例严格来说YOLOv5结构在640x640输入下产生的锚点候选是25200个。如果置信度阈值设得很低比如0.05可能有三四千个框会进入NMS而cv2.dnn.NMSBoxes的复杂度在候选框数量大时会明显上升成为推理后的第二瓶颈。我这里说的数值不一定精确对应当前YOLOP导出方式但量级是真实的。解决方法是先按分数取topK再做NMS。驾驶场景里一帧的交通目标数量通常不超过几十个取前2000个框再做NMS几乎不会丢检测。order np.argsort(-np.array(scores, dtypenp.float32))[:2000] cand_boxes [boxes[i] for i in order] cand_scores [scores[i] for i in order] idx cv2.dnn.NMSBoxes(cand_boxes, cand_scores, conf_threshold, nms_threshold0.45)参数方面conf_threshold建议设在0.25到0.4之间nms_threshold在0.4到0.5之间。前车互相重叠较多的行车记录仪视角nms_threshold取0.5可以降低漏检路侧固定视角下目标稀疏取0.4误检更少。这是我在多个交通感知项目里反复调过的经验值。5.4 车道线分支的有效阈值与和LSD算法的交叉验证车道线分支输出的是四个通道的logits直接argmax得到的类别中有一部分像素的置信度其实很低画出来就是分散的白噪点。常见的补救方法是在argmax后加一个最小置信度约束只有最大响应超过阈值的像素才被认为是车道线。logits ll_seg[0].transpose(1, 2, 0) # 变成(208,208,4) cls np.argmax(logits, axis2) max_prob np.max(logits, axis2) # 置信度低于0.4的像素视为背景 mask np.where((max_prob 0.4) (cls 0), cls, 0).astype(np.uint8)这里的0.4不是固定值夜间或逆光场景需要调低到0.3白天强烈日光下可以提到0.5。阈值调高会让线条更干净但也会断掉远端的车道线。为了验证车道线分支输出是否正确我会用同一帧调用LSD算法提取直线段然后统计落在上述mask范围内的线段占比比例超过0.5说明模型输出的车道线方向和场景几何基本一致。这个方法不需要标注数据作为部署后的快速验证非常有效。6. 部署完不急着上车一项验证指标和三段式检测6.1 用一个离线测试脚本记录三项指标模型能跑起来只是第一步部署结果能不能用于实际判断需要一个可重复的验证流程。我的做法是准备一段连续视频用离线脚本逐帧推理记录每帧的检测框数量、可驾驶区域像素面积、车道线像素面积以及当前帧率。metric_log [] for idx, frame in enumerate(clip_frames): t0 time.perf_counter() net.setInput(preprocess(frame)) det_out, drive_seg, ll_seg net.forward(out_names) boxes, _ decode_detections(det_out, conf_threshold0.3) drive np.argmax(drive_seg[0], axis0).astype(np.uint8) lanes np.argmax(ll_seg[0], axis0).astype(np.uint8) fps 1.0 / (time.perf_counter() - t0) metric_log.append(( idx, fps, len(boxes), int((drive 1).sum()), int((lanes 0).sum()), ))6.2 判断输出稳定的三个信号日志生成后重点看三个信号。一是检测框数量是否在相邻帧间剧烈抖动目标进出画面边界时抖动是正常的若在空旷直道上一会检出10辆车一会检出0辆说明预处理或阈值设置有问题。二是车道线像素面积是否在某段连续帧里骤降这种问题常出现在夜间或隧道入口的亮度突变处原因往往是置信度阈值把低响应像素全过滤了。三是可驾驶区域面积是否忽大忽小如果单帧内出现面积跳变去查那一帧的RGB顺序和letterbox参数是否被意外改动。我最早做这类部署时只盯着FPS结果模型在夜间一段视频里车道线输出从稳定几百像素掉到几乎为零不查日志根本发现不了。先把日志跑起来再谈优化这是我一贯的习惯。工程里最贵的不是模型推理时间而是问题出在哪里完全不知道的那些时间。希望这些能帮你在部署YOLOP时少走弯路也欢迎你在自己的项目里把这三个指标作为默认输出。本文还有配套的精品资源点击获取