)
ONE----- yolov8 的模型输出YOLOv8 推理后,原始输出是一个张量,形状是:[1,84,8400]你可以这样理解:1 → batch size(一次推理一张图)8400 → 候选框的数量(模型在图上撒了 8400 个"探测点")84 → 每个框的信息 = 4个坐标 + 80个类别概率(COCO数据集有80类)from ultralyticsimportYOLOimportcv2# 加载模型(第一次会自动下载权重)model=YOLO("yolov8n.pt")# 推理一张图片results=model("bus.jpg")# results 是一个列表,每张图对应一个元素r=results[0]# 查看检测到的框print(r.boxes)# 打印每个检测框的信息forboxinr.boxes: cls_id=int(box.cls)# 类别编号,比如 0conf=float(box.conf)# 置信度,比如 0.92x1,y1,x2,y2=box.xyxy[0]# 像素坐标label=model.names[cls_id]# 类别名字,比如 "person"print(f"{label}: conf={conf:.2f}, box=[{x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f}]")简单的推理代码如上所示。1. 1 额外知识 NMS 非极大值抑制是怎么工作的TWO-----从一只猫开始,彻底搞懂 NMS(非极大值抑制)本文面向零基础读者,结合图像场景和完整代码,把 NMS 的每一行逻辑讲透。什么是 NMS,为什么需要它目标检测模型(比如 YOLO、Faster R-CNN)在推理时,不会只输出一个框,而是会同时输出几十甚至几百个候选框,每个框都带一个置信度分数。问题来了:图里明明只有一只猫,模型却给出了5个框,每个框的位置略有不同,分数各异。我们需要一种方法,从这5个框里挑出最好的那一个,把其余废框全部去掉。这个方法就是NMS(Non-Maximum Suppression,非极大值抑制)。完整代码defnms_from_scratch(boxes,scores,iou_threshold=0.45):""" boxes: [[x1,y1,x2,y2], ...] 像素坐标 scores: [0.92, 0.81, ...] 置信度 返回:保留的框的索引列表 """# 第1步:按置信度从高到低排序order=sorted(range(len(scores)),key=lambdai:scores[i],reverse=True)keep=[]whileorder:# 第2步:取出当前最高分的框,直接保留best=order[0]keep.append(best)order=order[1:]# 第3步:计算其余框与 best 框的 IoUremaining=[]foridxinorder:x1=max(boxes[best][0],boxes[idx][0])y1=max(boxes[best][1],boxes[idx][1])x2=min(boxes[best][2],boxes[idx][2])y2=min(boxes[best][3],boxes[idx][3])inter=max(0,x2-x1)*max(0,y2-y1)area_best=(boxes[best][2]-boxes[best][0])*(boxes[best][3]-boxes[best][1])area_idx=(boxes[idx][2]-boxes[idx][0])*(boxes[idx][3]-boxes[idx][1])union=area_best+area_idx-inter iou=inter/unionifunion0else0# 第4步:IoU 低于阈值才保留(与 best 不重叠)ifiou=iou_threshold:remaining.append(idx)order=remainingreturnkeep场景设定图中只有一只猫,模型输出了5个预测框:框置信度位置描述框A0.92把整只猫框住,框得很准框B0.81与框A高度重叠,稍小一点框C0.74比框A还大,把框A包进去了框D0.65只框住了猫的右半部分框E0.58只框住了猫的左下半身NMS 的目标:最终只保留框A,把其余4个全部丢掉。(是否能做到,取决于阈值设定,后面会讲。)第一个关键问题:IoU 怎么算IoU(Intersection over Union,交并比)是 NMS 的核心计算。它衡量两个框的重叠程度,数值在 0 到 1 之间,越大说明越重叠。计算公式:IoU = 交集面积 / 并集面积交集怎么算:x1=max(boxes[best][0],boxes[idx][0])# 交集左边界y1=max(boxes[best][1],boxes[idx][1])# 交集上边界x2=min(boxes[best][2],boxes[idx][2])# 交集右边界y2=min(boxes[best][3],boxes[idx][3])# 交集下边界inter=max(0,x2-x1)*max(0,y2-y1)为什么左上角用max,右下角用min?想象两段尺子:框A 握住 x=40 到 x=140,框B 握住 x=180 到 x=280。两人都覆盖到的区域,从"更晚进入"的那端开始(max),到"更早结束"的那端结束(min)。这才是两框的公共区域。为什么要max(0, ...)?如果两框完全不挨着,算出来的宽度或高度会是负数(比如 140-180=-40)。负数面积没有意义,max(0, -40) = 0表示"没有交集"。并集怎么算:union=area_best+area_idx-inter直接 A面积 + B面积 会把交集区域算两遍,所以要减去一次。就像两个朋友圈各有100人,共同好友30人,合并后是 100+100-30=170 人,不是200人。逐步走一遍 NMS 流程第1步:排序order=sorted(range(len(scores)),key=lambdai:scores[i],reverse=True)# order = [框A, 框B, 框C, 框D, 框E] 对应分数 [0.92, 0.81, 0.74, 0.65, 0.58]按置信度从高到低排好,后面每轮都从队列头部取框。第2步:第1轮 — 取出框Abest=order[0]# 框A,分数最高keep.append(best)# 无条件保留order=order[1:]# order = [框B, 框C, 框D, 框E]框A被放入keep,接下来用它和剩余4个框逐一比较 IoU。情况一:框A vs 框B(IoU = 0.71)→ 丢弃框B框B和框A几乎框住同一只猫,大量重叠。iou=0.71if0.71=0.45:# Falseremaining.append(框B)# 条件不满足,框B不被添加,相当于被丢弃IoU 超过阈值 → 说明是重复预测同一目标 → 丢弃。情况二:框A vs 框C(IoU = 0.63)→ 丢弃框C框C比框A更大,把框A几乎完全包住了。但面积大不代表正确,IoU=0.63 仍然超过阈值,同样丢弃。NMS 只看 IoU,不管谁的框更大。情况三:框A vs 框D(IoU = 0.26)→ 暂时保留框D框D只覆盖了猫的右半部分,与框A重叠不多。iou=0.26if0.26=0.45:# Trueremaining.append(框D)# 保留,进入下一轮IoU 低于阈值,NMS 认为两框描述的不是完全相同的区域,框D进入下一轮。情况四:框A vs 框E(IoU = 0.12)→ 暂时保留框E框E只框住了猫的左下半身,与框A重叠很少,同样保留进入下一轮。第3步:第2轮 — 取出框D第1轮结束后,order = [框D, 框E]。框D分数更高(0.65),取出放入 keep。best=框D keep.append(框D)order=[框E]然后框D和框E比较:两者分别在猫的右半身和左下半身,几乎不重叠,IoU≈0.08,框E继续保留。第4步:第3轮 — 取出框Ebest=框E keep.append(框E)order=[]# 队列为空,while 结束最终结果:keep = [框A, 框D, 框E],保留了3个框。暴露的问题:阈值决定一切图里明明只有一只猫,NMS 却保留了3个框。这不是 bug,而是阈值设得太大导致的。阈值 0.45 时,框D和框E与框A的重叠程度没有超过阈值,所以都留下来了。如果阈值改成 0.20:比较IoU结果框A vs 框B0.71丢弃框A vs 框C0.63丢弃框A vs 框D0.260.26 0.20 → 丢弃框A vs 框E0.280.28 0.20 → 丢弃最终只保留框A,完美匹配"只有1只猫"的事实。阈值选取原则:阈值越小 → 越严格 → 稍微重叠就压制,适合"单目标、不密集"的场景阈值越大 → 越宽松 → 允许更多框并存,适合多目标密集检测实际工程中,YOLO 系列默认阈值通常在 0.45~0.5,行人检测等密集场景可以低至 0.3。没有万能的阈值,需要根据任务调整。高频疑问解答Q:框B的位置比框A更准,为什么还是丢框B?NMS 不知道哪个框"更准"。它唯一的依据是分数:分数高 = 框更可信。框A的分数(0.92)高于框B(0.81),所以框A先被取出保留,框B只能被框A审判,没有机会反审判框A。这是 NMS 的设计权衡:简单、快速,但完全依赖分数的可靠性。如果模型给了一个烂框高分,NMS 就会选错,这是它本身的局限性。Q:IoU = 0 说明什么?两框完全不重叠,max(0, 负数) = 0,交集为零,IoU = 0。这种情况下框肯定被保留(0 ≤ 任何阈值)。Q:IoU = 1 说明什么?两框完全重合,交集等于并集,IoU = 1。这种情况下框肯定被丢弃(1 任何合理阈值)。总结NMS 的完整逻辑用一句话概括:每次取分数最高的框放入结果,然后把所有跟它高度重叠(IoU 阈值)的框全部丢掉,循环直到没有框为止。四个核心计算点:max求交集左上角,min求交集右下角 — 取两框同时覆盖的区域max(0, ...)— 防止不重叠时出现负数面积union = A + B - inter— 防止交集被算两次if iou = threshold才保留 — IoU 大说明重复,应该丢弃理解了这四点,NMS 的代码就完全透明了。如果有多的框 结果处理的方式THREE----YOLOv8 ultralytics 源码框架深度解析:root {–bg:#0f1117;–bg2:#161b27;–bg3:#1e2535;–border:#2a3347;–text:#c8d3e8;–text2:#7a8aaa;–text3:#4a5570;–blue:#4e9ef5;–blue-dim:#1a2f50;–teal:#34c990;–teal-dim:#0d2e22;–purple:#a78bfa;–purple-dim:#1e1640;–amber:#f59e42;–amber-dim:#2e1e08;–coral:#f47060;–green:#6bcb77;–green-dim:#0d2210;–white:#eef2ff;–code-bg:#111827;–code-text:#a0e8a0;}*{box-sizing:border-box;margin:0;padding:0;}body{background:var(–bg);color:var(–text);font-family:‘Noto Sans SC’,sans-serif;font-size:15px;line-height:1.8;max-width:860px;margin:0 auto;padding:60px 32px 120px;}h1{font-family:‘Noto Serif SC’,serif;font-size:2rem;font-weight:700;color:var(–white);margin:60px 0 20px;letter-spacing:-.02em;line-height:1.3;}h1:first-of-type{margin-top:0;}h2{font-family:‘Noto Serif SC’,serif;font-size:1.35rem;font-weight:700;color:var(–white);margin:48px 0 16px;padding-bottom:10px;border-bottom:1px solid var(–border);}h3{font-size:.8rem;font-weight:700;color:var(–blue);margin:32px 0 12px;letter-spacing:.04em;text-transform:uppercase;}p{color:var(–text);margin-bottom:16px;}strong{color:var(–white);font-weight:700;}code{font-family:‘JetBrains Mono’,monospace;font-size:.82em;background:var(–bg3);color:var(–blue);padding:2px 7px;border-radius:4px;border:1px solid var(–border);}.hero{border:1px solid var(–border);border-radius:12px;background:var(–bg2);padding:48px 40px 40px;margin-bottom:60px;position:relative;overflow:hidden;}.hero::before{content:‘’;position:absolute;top:-80px;right:-80px;width:300px;height:300px;border-radius:50%;background:radial-gradient(circle,rgba(78,158,245,.08) 0%,transparent 70%);pointer-events:none;}.hero-tag{display:inline-block;font-family:‘JetBrains Mono’,monospace;font-size:.72rem;color:var(–teal);background:var(–teal-dim);border:1px solid var(–teal);border-radius:4px;padding:3px 10px;margin-bottom:20px;letter-spacing:.08em;}.hero h1{font-size:2.4rem;margin:0 0 12px;background:linear-gradient(135deg,#fff 0%,var(–blue) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}.hero-sub{color:var(–text2);font-size:1rem;margin-bottom:28px;}.hero-meta{display:flex;gap:20px;flex-wrap:wrap;}.hero-meta span{font-family:‘JetBrains Mono’,monospace;font-size:.75rem;color:var(–text3);}.hero-meta span b{color:var(–text2);}.toc{background:var(–bg2);border:1px solid var(–border);border-radius:10px;padding:24px 28px;margin:40px 0;}.toc-title{font-family:‘JetBrains Mono’,monospace;font-size:.72rem;color:var(–text3);letter-spacing:.12em;text-transform:uppercase;margin-bottom:14px;}.toc ol{padding-left:20px;display:grid;grid-template-columns:1fr 1fr;gap:4px 32px;}.toc li{color:var(–text2);font-size:.88rem;padding:3px 0;}.toc li a{color:var(–blue);text-decoration:none;}.toc li a:hover{text-decoration:underline;}.dir-header{display:flex;align-items:stretch;border-radius:8px;overflow:hidden;margin:48px 0 20px;border:1px solid var(–border);}.dir-header-main{flex:1;padding:16px 22px;font-family:‘JetBrains Mono’,monospace;font-size:1.1rem;font-weight:600;color:var(–white);}.dir-header-tag{padding:16px 20px;font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;display:flex;align-items:center;font-family:‘JetBrains Mono’,monospace;}.dir-blue .dir-header-main{background:#0d1f3c;}.dir-blue .dir-header-tag{background:#1a3a6a;color:var(–blue);}.dir-teal .dir-header-main{background:#0a1f18;}.dir-teal .dir-header-tag{background:#0d2e22;color:var(–teal);}.dir-purple .dir-header-main{background:#130e2e;}.dir-purple .dir-header-tag{background:#1e1640;color:var(–purple);}.dir-amber .dir-header-main{background:#1c1000;}.dir-amber .dir-header-tag{background:#2e1e08;color:var(–amber);}.dir-blue2 .dir-header-main{background:#071830;}.dir-blue2 .dir-header-tag{background:#0e2848;color:#74b8ff;}.dir-green .dir-header-main{background:#081808;}.dir-green .dir-header-tag{background:#0d2210;color:var(–green);}.dir-gray .dir-header-main{background:#161616;}.dir-gray .dir-header-tag{background:#202020;color:#888;}.file-table{width:100%;border-collapse:collapse;margin:16px 0 24px;font-size:.88rem;}.file-table tr{border-bottom:1px solid var(–border);}.file-table tr:last-child{border-bottom:none;}.file-table td{padding:12px 14px;vertical-align:top;}.file-table td:first-child{font-family:‘JetBrains Mono’,monospace;font-size:.8rem;color:var(–blue);white-space:nowrap;background:var(–bg2);width:200px;border-right:1px solid var(–border);}.file-table td:last-child{color:var(–text);background:var(–bg);}.file-table tr:hover td{background:var(–bg3);}.code-block{background:var(–code-bg);border:1px solid var(–border);border-radius:8px;padding:20px 24px;margin:16px 0 24px;overflow-x:auto;}.code-block pre{font-family:‘JetBrains Mono’,monospace;font-size:.8rem;line-height:1.7;color:var(–code-text);white-space:pre;}.code-block .comment{color:#4a6a4a;}.code-block .keyword{color:#c792ea;}.code-block .string{color:#c3e88d;}.code-block .number{color:#f78c6c;}.code-block .fn{color:#82aaff;}.callout{border-radius:8px;overflow:hidden;margin:20px 0 28px;border:1px solid var(–border);}.callout-title{padding:10px 18px;font-size:.82rem;font-weight:700;letter-spacing:.04em;}.callout-body{padding:14px 18px;font-size:.88rem;line-height:1.8;}.callout-body li{margin-left:18px;color:var(–text);margin-bottom:4px;}.callout-blue .callout-title{background:#1a3a6a;color:var(–blue);}.callout-blue .callout-body{background:#0a1a30;}.callout-teal .callout-title{background:#0d2e22;color:var(–teal);}.callout-teal .callout-body{background:#071a14;}.callout-amber .callout-title{background:#2e1e08;color:var(–amber);}.callout-amber .callout-body{background:#180e00;}.callout-purple .callout-title{background:#1e1640;color:var(–purple);}.callout-purple .callout-body{background:#100a28;}.callout-green .callout-title{background:#0d2210;color:var(–green);}.callout-green .callout-body{background:#060e06;}.summary-table{width:100%;border-collapse:collapse;margin:16px 0 32px;font-size:.85rem;}.summary-table th{background:var(–bg3);color:var(–text2);font-weight:600;padding:10px 14px;text-align:left;border-bottom:2px solid var(–border);font-size:.78rem;letter-spacing:.05em;text-transform:uppercase;}.summary-table td{padding:10px 14px;border-bottom:1px solid var(–border);vertical-align:top;}.summary-table td:first-child{font-family:‘JetBrains Mono’,monospace;font-size:.8rem;color:var(–blue);}.summary-table tr:hover td{background:var(–bg2);}.badge-must{background:var(–teal-dim);color:var(–teal);padding:2px 8px;border-radius:4px;font-size:.75rem;font-weight:700;}.badge-later{background:var(–bg3);color:var(–text3);padding:2px 8px;border-radius:4px;font-size:.75rem;}.chain-table{width:100%;border-collapse:collapse;margin:16px 0 28px;font-size:.85rem;}.chain-table th{background:var(–bg3);color:var(–text2);padding:10px 14px;text-align:left;border-bottom:2px solid var(–border);font-size:.75rem;letter-spacing:.06em;text-transform:uppercase;}.chain-table td{padding:10px 14px;border-bottom:1px solid var(–border);}.chain-table td:first-child{color:var(–amber);font-weight:700;font-size:.82rem;white-space:nowrap;}.chain-table td:nth-child(2){font-family:‘JetBrains Mono’,monospace;font-size:.78rem;color:var(–blue);white-space:nowrap;}.chain-table tr:hover td{background:var(–bg2);}.task-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0 28px;}.task-card{background:var(–bg2);border:1px solid var(–border);border-radius:8px;padding:14px 16px;}.task-card .task-name{font-family:‘JetBrains Mono’,monospace;font-size:.8rem;color:var(–teal);font-weight:600;margin-bottom:6px;}.task-card .task-out{font-size:.82rem;color:var(–text2);}.task-card .task-use{font-size:.78rem;color:var(–text3);margin-top:4px