Lecture 03 学习讲义
正文
工具
按问题查找
Lecture 03可视化与实际输出 · 实现代码

可视化与实际输出 · 实现代码

独立阅读

并入相关学习路径

固定框匹配、PR/AP、NMS误删相邻对象反例

展开完整实现代码
def svg_document(width, height, fragments):
    return (f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" '
            f'viewBox="0 0 {width} {height}"><rect width="100%" height="100%" fill="#f7f8fa"/>'
            '<style>text{font-family:Arial,sans-serif;fill:#162235} .title{font-size:22px;font-weight:bold}'
            '.label{font-size:15px}.small{font-size:13px}.axis{stroke:#738095;stroke-width:1}</style>'
            + "".join(fragments) + "</svg>")


def svg_text(x, y, value, cls="label", extra=""):
    return f'<text x="{x}" y="{y}" class="{cls}" {extra}>{escape(str(value))}</text>'


geometry = [svg_text(32, 36, "Two GT boxes / four scored predictions", "title"),
            svg_text(32, 62, "Continuous xyxy coordinates; dashed = GT; solid = prediction", "label")]
for value in range(9):
    x, y = 80 + value * 55, 100 + value * 55
    geometry.append(f'<path d="M {x} 100 V 540 M 80 {y} H 520" stroke="#e0e4e9"/>')
    geometry += [svg_text(x - 4, 566, value, "small"), svg_text(55, y + 5, value, "small")]
geometry += [svg_text(293, 592, "x coordinate"), svg_text(20, 327, "y", "label")]
for i, b in enumerate(GT):
    x1, y1, x2, y2 = b
    geometry.append(f'<rect x="{80 + x1 * 55}" y="{100 + y1 * 55}" width="{(x2 - x1) * 55}" '
                    f'height="{(y2 - y1) * 55}" fill="none" stroke="#111827" stroke-width="4" stroke-dasharray="9 5"/>')
    geometry.append(svg_text(83 + x1 * 55, 88, f"GT{i+1}"))
colors = ["#156a9c", "#d16a20", "#188164", "#845ec2"]
for i, b in enumerate(PRED):
    x1, y1, x2, y2 = b
    geometry.append(f'<rect x="{80 + x1 * 55}" y="{100 + y1 * 55}" width="{(x2 - x1) * 55}" '
                    f'height="{(y2 - y1) * 55}" fill="none" stroke="{colors[i]}" stroke-width="2"/>')
    geometry.append(f'<line x1="585" x2="615" y1="{128+i*50}" y2="{128+i*50}" stroke="{colors[i]}" stroke-width="4"/>')
    geometry.append(svg_text(627, 134 + i * 50, f"P{i+1} | score {SCORES[i]:.2f}"))
    geometry.append(svg_text(627, 153 + i * 50, f"IoU(GT1,GT2) = {iou_matrix[i,0]:.3f}, {iou_matrix[i,1]:.3f}", "small"))
geometry += [svg_text(585, 372, "AP50: TP, FP, TP, FP"),
             svg_text(585, 399, "P2 overlaps GT1, already used."),
             svg_text(585, 444, "AP75: TP, FP, FP, FP"),
             svg_text(585, 471, "P3 IoU = 2/3 < 0.75."),
             svg_text(585, 527, "The axes include the distant FP.", "small")]
(OUT / "geometry.svg").write_text(svg_document(955, 615, geometry), encoding="utf-8")

pr = [svg_text(28, 36, "All-points AP integrates the precision envelope", "title"),
      svg_text(28, 62, "Original predictions, before NMS | solid: envelope; dots: raw ranked precision", "label")]
for panel, key in enumerate(["AP50", "AP75"]):
    result = baseline[key]
    left, top, width, height = 80 + panel * 460, 108, 340, 290
    for tick in [0, .25, .5, .75, 1]:
        x, y = left + tick * width, top + (1 - tick) * height
        pr += [f'<path d="M {x} {top} V {top+height} M {left} {y} H {left+width}" stroke="#dbe1e7"/>',
               svg_text(x - 8, top + height + 26, f"{tick:g}", "small"),
               svg_text(left - 35, y + 4, f"{tick:g}", "small")]
    mrec, env = np.array(result["envelope_recall"]), np.array(result["precision_envelope"])
    intervals = np.flatnonzero(np.diff(mrec) > 0)
    for interval_i, j in enumerate(intervals):
        x0, x1 = left + mrec[j] * width, left + mrec[j + 1] * width
        y = top + (1 - env[j + 1]) * height
        pr.append(f'<rect x="{x0}" y="{y}" width="{x1-x0}" height="{top+height-y}" fill="#cce4ec"/>')
        pr.append(f'<path d="M {x0} {y} H {x1}" stroke="#136b87" stroke-width="4"/>')
        if interval_i + 1 < len(intervals):
            next_y = top + (1 - env[intervals[interval_i + 1] + 1]) * height
            pr.append(f'<path d="M {x1} {y} V {next_y}" stroke="#136b87" stroke-width="3"/>')
    for i, (r, p) in enumerate(zip(result["recall"], result["precision"])):
        x, y = left + r * width, top + (1 - p) * height
        pr += [f'<circle cx="{x}" cy="{y}" r="5" fill="#cc662c"/>',
               svg_text(x + (10 if x < left + width else -26), y + (18 if i in (0, 3) else -9), f"P{i+1}", "small")]
    pr += [svg_text(left + 108, 94, f"{key} = {result['ap']:.6f}"),
           svg_text(left + 130, 451, "Recall"), svg_text(left - 38, 85, "Precision", "small")]
pr += [svg_text(80, 489, "AP50 = (1/2)*1 + (1/2)*(2/3) = 5/6"),
       svg_text(540, 489, "AP75 = (1/2)*1 + (1/2)*0 = 1/2"),
       svg_text(80, 529, "Repeated recall adds no area; the interval from 0.5 to 1 uses precision 2/3.", "small")]
(OUT / "precision_recall.svg").write_text(svg_document(970, 556, pr), encoding="utf-8")

nms = [svg_text(28, 36, "The same NMS rule can remove a duplicate or a real object", "title"),
       svg_text(28, 63, "Class-aware NMS: suppress same-class boxes only when IoU > 0.50", "label")]
for panel, (title, boxes, mutual, before, after) in enumerate([
        ("A. Duplicate predictions for one object", PRED[:2], 9/11, 5/6, 1.0),
        ("B. Two adjacent real objects", NEIGHBOR_GT, .6, 1.0, .5)]):
    left = 40 + panel * 485
    nms.append(svg_text(left, 110, title))
    for i, b in enumerate(boxes):
        x1, y1, x2, y2 = b
        nms.append(f'<rect x="{left+25+x1*105}" y="{145+y1*85}" width="{(x2-x1)*105}" '
                   f'height="{(y2-y1)*85}" fill="none" stroke="{colors[i]}" stroke-width="4" '
                   + ('stroke-dasharray="8 5"' if i == 1 else '') + '/>')
    nms += [svg_text(left + 25, 345, f"Mutual IoU = {mutual:.3f} > 0.50"),
            svg_text(left + 25, 373, "Blue P1 retained; dashed orange P2 removed."),
            svg_text(left + 25, 410, f"AP50: {before:.6f} -> {after:.6f}")]
    if panel == 0:
        nms += [svg_text(left + 25, 442, "P2 was a duplicate FP; full-scene AP improves.", "small"),
                svg_text(left + 25, 465, "P3 and P4 remain in the original scene.", "small")]
    else:
        nms += [svg_text(left + 25, 442, "P2 was a TP; recall falls from 1.0 to 0.5.", "small"),
                svg_text(left + 25, 465, "Changing only NMS threshold to 0.70 keeps both.", "small")]
(OUT / "nms_tradeoff.svg").write_text(svg_document(1000, 505, nms), encoding="utf-8")

# Rich display is optional; SVG files are created above without IPython.
try:
    from IPython.display import SVG, display
    for figure in ["geometry.svg", "precision_recall.svg", "nms_tradeoff.svg"]:
        display(SVG(filename=str(OUT / figure)))
except ImportError:
    print("SVG files saved; open them in a browser for viewing.")