{"version":2,"relationshipURLBase":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.html","relationshipResolution":"Resolve relative relationship URLs against relationshipURLBase; complete records and absolute URLs are in blocks.","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","url":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.html","markdown":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.md","pages":[{"id":"reading-opening","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","unit":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","unitId":"notebook-blocks/e4240ba681bd49f3871529d076ed4b97","chapter":"course","number":1}],"aliases":[{"oldId":"b-8e4b889b6be5c6093d3d3283","targetId":"b-8e4b889b6be5c6093d3d3283","page":"reading-opening","operation":"move","beforeSHA256":"9027342eb8430f11699811edb871aeb2e37b28e9f874a2aa48778e6bac2818b7","afterSHA256":"9027342eb8430f11699811edb871aeb2e37b28e9f874a2aa48778e6bac2818b7","reason":"Same source body in authored semantic page"},{"oldId":"b-a9af19a3a0c72657660d2c76","targetId":"b-a9af19a3a0c72657660d2c76","page":"reading-opening","operation":"move","beforeSHA256":"15f2350b8eba61ed88c65b414951f6b90177b9205911dfdd2370f59c412d610f","afterSHA256":"15f2350b8eba61ed88c65b414951f6b90177b9205911dfdd2370f59c412d610f","reason":"Same source body in authored semantic page"},{"oldId":"b-141c72bcb822e0929c57eefb","targetId":"b-141c72bcb822e0929c57eefb","page":"reading-opening","operation":"move","beforeSHA256":"dc06c1e728a4b5740ba634b145bc9a1da6323031626734cb4555db5a744df806","afterSHA256":"dc06c1e728a4b5740ba634b145bc9a1da6323031626734cb4555db5a744df806","reason":"Same source body in authored semantic page"},{"oldId":"b-34731f753252d6fd2ba4b2e2","targetId":"b-34731f753252d6fd2ba4b2e2","page":"reading-opening","operation":"move","beforeSHA256":"4e0de6d032a3765c60907de32f28cd988b659299663e842d319bdb0426243d78","afterSHA256":"4e0de6d032a3765c60907de32f28cd988b659299663e842d319bdb0426243d78","reason":"Same source body in authored semantic page"},{"oldId":"b-214dde640ab0be1487330c13","targetId":"b-214dde640ab0be1487330c13","page":"reading-opening","operation":"move","beforeSHA256":"019ed1e27e0ff2c7c0f21e08efbc88125eda7605362a3738b0d9b2c523479e80","afterSHA256":"019ed1e27e0ff2c7c0f21e08efbc88125eda7605362a3738b0d9b2c523479e80","reason":"Same source body in authored semantic page"}],"blocks":[{"id":"b-8e4b889b6be5c6093d3d3283","type":"p","scope":"document","url":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.html#b-8e4b889b6be5c6093d3d3283","content":"[LAB 09 · 检测几何：框、匹配、NMS 与 AP](/course/notebooks/90d213b4e1e04c7e889ee6fc042b2358.html)","sha256":"9027342eb8430f11699811edb871aeb2e37b28e9f874a2aa48778e6bac2818b7","chapter":{"id":"course","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"unit":{"id":"notebook-blocks/e4240ba681bd49f3871529d076ed4b97","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"page":{"id":"reading-opening","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","number":1},"sectionTitle":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","currentQuestion":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","sourceSlides":[],"knowledgeBlock":null,"formulas":[],"previousBlock":null,"nextBlock":"b-a9af19a3a0c72657660d2c76","prerequisites":[],"relatedBlocks":[]},{"id":"b-a9af19a3a0c72657660d2c76","type":"h1","scope":"document","url":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.html#b-a9af19a3a0c72657660d2c76","content":"# 6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","sha256":"15f2350b8eba61ed88c65b414951f6b90177b9205911dfdd2370f59c412d610f","chapter":{"id":"course","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"unit":{"id":"notebook-blocks/e4240ba681bd49f3871529d076ed4b97","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"page":{"id":"reading-opening","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","number":1},"sectionTitle":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","currentQuestion":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","sourceSlides":[],"knowledgeBlock":null,"formulas":[],"previousBlock":"b-8e4b889b6be5c6093d3d3283","nextBlock":"b-141c72bcb822e0929c57eefb","prerequisites":[{"id":"b-8e4b889b6be5c6093d3d3283","url":"#b-8e4b889b6be5c6093d3d3283","role":"problem-context"}],"relatedBlocks":[]},{"id":"b-141c72bcb822e0929c57eefb","type":"p","scope":"document","url":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.html#b-141c72bcb822e0929c57eefb","content":"并入相关学习路径","sha256":"dc06c1e728a4b5740ba634b145bc9a1da6323031626734cb4555db5a744df806","chapter":{"id":"course","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"unit":{"id":"notebook-blocks/e4240ba681bd49f3871529d076ed4b97","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"page":{"id":"reading-opening","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","number":1},"sectionTitle":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","currentQuestion":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","sourceSlides":[],"knowledgeBlock":null,"formulas":[],"previousBlock":"b-a9af19a3a0c72657660d2c76","nextBlock":"b-34731f753252d6fd2ba4b2e2","prerequisites":[{"id":"b-a9af19a3a0c72657660d2c76","url":"#b-a9af19a3a0c72657660d2c76","role":"section-title"},{"id":"b-8e4b889b6be5c6093d3d3283","url":"#b-8e4b889b6be5c6093d3d3283","role":"problem-context"}],"relatedBlocks":[]},{"id":"b-34731f753252d6fd2ba4b2e2","type":"p","scope":"document","url":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.html#b-34731f753252d6fd2ba4b2e2","content":"固定框匹配、PR/AP、NMS误删相邻对象反例","sha256":"4e0de6d032a3765c60907de32f28cd988b659299663e842d319bdb0426243d78","chapter":{"id":"course","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"unit":{"id":"notebook-blocks/e4240ba681bd49f3871529d076ed4b97","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"page":{"id":"reading-opening","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","number":1},"sectionTitle":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","currentQuestion":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","sourceSlides":[],"knowledgeBlock":null,"formulas":[],"previousBlock":"b-141c72bcb822e0929c57eefb","nextBlock":"b-214dde640ab0be1487330c13","prerequisites":[{"id":"b-a9af19a3a0c72657660d2c76","url":"#b-a9af19a3a0c72657660d2c76","role":"section-title"},{"id":"b-8e4b889b6be5c6093d3d3283","url":"#b-8e4b889b6be5c6093d3d3283","role":"problem-context"}],"relatedBlocks":[]},{"id":"b-214dde640ab0be1487330c13","type":"pre","scope":"document","url":"https://codingai-lec03.pages.dev/course/notebook-blocks/e4240ba681bd49f3871529d076ed4b97.html#b-214dde640ab0be1487330c13","content":"```\nGT = np.array([[0, 0, 2, 2], [3, 0, 5, 2]], dtype=float)\nPRED = np.array([[0, 0, 2, 2], [.2, 0, 2.2, 2], [3.4, 0, 5.4, 2], [7, 7, 8, 8]], float)\nSCORES = np.array([.9, .8, .7, .4])\nCLASSES = np.zeros(4, dtype=int)\n\niou_matrix = box_iou(PRED, GT)\ntraining_assignment, forced_anchors = assign_training_anchors(PRED, GT, positive_iou=.5)\nbaseline = {f\"AP{round(t * 100)}\": evaluate_single_class(PRED, SCORES, GT, t)\n            for t in EVAL_THRESHOLDS}\nkept, suppressed = class_aware_nms(PRED, SCORES, CLASSES, NMS_THRESHOLD)\nafter_nms = {f\"AP{round(t * 100)}\": evaluate_single_class(PRED[kept], SCORES[kept], GT, t)\n             for t in EVAL_THRESHOLDS}\nfor result in after_nms.values():\n    for row in result[\"rows\"]:\n        row[\"prediction\"] = int(kept[row[\"prediction\"] - 1] + 1)\n\n# Two real, same-class objects overlap by IoU=0.6. Both predictions are exact.\nNEIGHBOR_GT = np.array([[0, 0, 2, 2], [.5, 0, 2.5, 2]], float)\nNEIGHBOR_SCORES = np.array([.95, .90])\nneighbor_kept, neighbor_suppressed = class_aware_nms(\n    NEIGHBOR_GT, NEIGHBOR_SCORES, [0, 0], NMS_THRESHOLD)\nneighbor_before = evaluate_single_class(NEIGHBOR_GT, NEIGHBOR_SCORES, NEIGHBOR_GT, .5)\nneighbor_after = evaluate_single_class(NEIGHBOR_GT[neighbor_kept],\n                                      NEIGHBOR_SCORES[neighbor_kept], NEIGHBOR_GT, .5)\nneighbor_kept_070, _ = class_aware_nms(NEIGHBOR_GT, NEIGHBOR_SCORES, [0, 0], .70)\n\n# A forced training positive can fall below the positive IoU threshold.\nLOW_OVERLAP_ANCHORS = np.array([[0, 0, .7, .7], [.8, 0, .9, .1], [3, 0, 3.6, .6]])\nlow_assignment, low_forced = assign_training_anchors(LOW_OVERLAP_ANCHORS, GT, .5)\n\n# Protocol counterexample: two exact copies of GT1, while GT2 overlaps it by 0.6.\n# The production evaluator above must NOT reassign a duplicate to second-best GT2.\nDUPLICATE_NEIGHBOR_PRED = np.repeat(NEIGHBOR_GT[:1], 2, axis=0)\nprotocol_standard = evaluate_single_class(DUPLICATE_NEIGHBOR_PRED, NEIGHBOR_SCORES,\n                                          NEIGHBOR_GT, .5)\n# The following trace deliberately implements the OTHER protocol for comparison.\nalternative_used = np.zeros(len(NEIGHBOR_GT), dtype=bool)\nalternative_tp = []\nalternative_ious = box_iou(DUPLICATE_NEIGHBOR_PRED, NEIGHBOR_GT)\nfor pred_i in np.argsort(-NEIGHBOR_SCORES, kind=\"stable\"):\n    eligible = np.flatnonzero((~alternative_used) & (alternative_ious[pred_i] >= .5))\n    if len(eligible):\n        gt_i = int(eligible[np.argmax(alternative_ious[pred_i, eligible])])\n        alternative_used[gt_i] = True\n    alternative_tp.append(int(len(eligible) > 0))\nalternative_recall = np.cumsum(alternative_tp) / len(NEIGHBOR_GT)\nalternative_precision = np.cumsum(alternative_tp) / np.arange(1, len(alternative_tp) + 1)\nalternative_ap = all_points_ap(alternative_recall, alternative_precision)[0]\n\n# Meaningful invariants and protocol boundaries, not a network benchmark.\nassert iou_matrix.shape == (4, 2)\nnp.testing.assert_allclose(iou_matrix, [[1, 0], [9 / 11, 0], [0, 2 / 3], [0, 0]])\nassert training_assignment.tolist() == [0, 0, 1, -1]\nassert [r[\"status\"] for r in baseline[\"AP50\"][\"rows\"]] == [\"TP\", \"FP\", \"TP\", \"FP\"]\nassert [r[\"status\"] for r in baseline[\"AP75\"][\"rows\"]] == [\"TP\", \"FP\", \"FP\", \"FP\"]\nnp.testing.assert_allclose([baseline[\"AP50\"][\"ap\"], baseline[\"AP75\"][\"ap\"]], [5 / 6, .5])\nassert kept.tolist() == [0, 2, 3]\nnp.testing.assert_allclose([after_nms[\"AP50\"][\"ap\"], after_nms[\"AP75\"][\"ap\"]], [1, .5])\nnp.testing.assert_allclose([neighbor_before[\"ap\"], neighbor_after[\"ap\"]], [1, .5])\nassert neighbor_kept_070.tolist() == [0, 1]\nassert low_assignment.tolist() == [0, -1, 1] and low_forced.tolist() == [True, False, True]\nassert class_aware_nms([[0, 0, 2, 2]] * 2, [.9, .8], [0, 1], .5)[0].tolist() == [0, 1]\nassert class_aware_nms([[0, 0, 2, 2]] * 2, [.9, .8], [0, 0], 1.0)[0].tolist() == [0, 1]\nassert box_iou([[0, 0, 0, 0]], [[0, 0, 0, 0]]).item() == 0\nassert evaluate_single_class([], [], GT, .5)[\"ap\"] == 0\nassert [r[\"status\"] for r in protocol_standard[\"rows\"]] == [\"TP\", \"FP\"]\nassert alternative_tp == [1, 1]\nnp.testing.assert_allclose([protocol_standard[\"ap\"], alternative_ap], [.5, 1.0])\n\nresults = {\n    \"data_identity\": \"deterministic teaching example; no model trained or inferred\",\n    \"coordinates\": \"continuous xyxy, no +1\",\n    \"ap_protocol\": \"all-points precision envelope; single image / one class; not COCO mAP\",\n    \"matching_protocol\": \"VOC-style: maximum-IoU same-image/class GT, then used check; no fallback; IoU >= threshold\",\n    \"nms_protocol\": {\"class_aware\": True, \"suppress_if\": \"IoU > threshold\", \"threshold\": NMS_THRESHOLD},\n    \"input_shapes\": {\"gt\": list(GT.shape), \"pred\": list(PRED.shape), \"scores\": list(SCORES.shape)},\n    \"gt\": GT.tolist(), \"pred\": PRED.tolist(), \"scores\": SCORES.tolist(),\n    \"iou_matrix_pred_by_gt\": iou_matrix.tolist(),\n    \"training_assignment_gt_1based\": [int(x + 1) if x >= 0 else None for x in training_assignment],\n    \"training_forced\": forced_anchors.tolist(),\n    \"baseline\": baseline, \"nms_kept_predictions\": (kept + 1).tolist(),\n    \"nms_suppressed\": suppressed, \"after_nms\": after_nms,\n    \"neighbor_case\": {\"gt\": NEIGHBOR_GT.tolist(), \"mutual_iou\": box_iou(NEIGHBOR_GT[:1], NEIGHBOR_GT[1:]).item(),\n                      \"before_nms\": neighbor_before, \"after_nms\": neighbor_after,\n                      \"suppressed\": neighbor_suppressed, \"kept_at_nms_070\": (neighbor_kept_070 + 1).tolist()},\n    \"low_iou_forced_training_case\": {\"anchors\": LOW_OVERLAP_ANCHORS.tolist(),\n                                     \"ious\": box_iou(LOW_OVERLAP_ANCHORS, GT).tolist(),\n                                     \"assignment_gt_1based\": [int(x + 1) if x >= 0 else None for x in low_assignment],\n                                     \"forced\": low_forced.tolist()},\n    \"matching_protocol_counterexample\": {\"gt\": NEIGHBOR_GT.tolist(),\n                                         \"pred\": DUPLICATE_NEIGHBOR_PRED.tolist(),\n                                         \"iou_matrix\": alternative_ious.tolist(),\n                                         \"best_gt_then_used\": protocol_standard,\n                                         \"best_unused_alternative\": {\"tp\": alternative_tp,\n                                             \"recall\": alternative_recall.tolist(),\n                                             \"precision\": alternative_precision.tolist(),\n                                             \"ap\": alternative_ap}},\n    \"checks\": \"all assertions passed\",\n}\n(OUT / \"results.json\").write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding=\"utf-8\")\nprint(\"IoU matrix (prediction x GT):\\n\", np.round(iou_matrix, 6))\nprint(\"Training anchor targets (GT1, GT2; None=background):\", results[\"training_assignment_gt_1based\"])\nfor label, experiment in [(\"Before NMS\", baseline), (\"After NMS\", after_nms)]:\n    for metric, result in experiment.items():\n        print(label, metric, \"=\", round(result[\"ap\"], 9),\n              \"sequence:\", [r[\"status\"] for r in result[\"rows\"]], \"recall:\", result[\"final_recall\"])\nprint(\"NMS kept original predictions:\", (kept + 1).tolist())\nprint(\"Neighbor objects AP50:\", neighbor_before[\"ap\"], \"->\", neighbor_after[\"ap\"],\n      \"; recall:\", neighbor_before[\"final_recall\"], \"->\", neighbor_after[\"final_recall\"])\nprint(\"Low-IoU forced anchors:\", results[\"low_iou_forced_training_case\"])\nprint(\"Protocol counterexample (two copies of GT1): maximum-IoU GT then used ->\",\n      [r[\"status\"] for r in protocol_standard[\"rows\"]], \"AP50\", protocol_standard[\"ap\"],\n      \"; best-unused alternative ->\", alternative_tp, \"AP50\", alternative_ap)\n```","sha256":"019ed1e27e0ff2c7c0f21e08efbc88125eda7605362a3738b0d9b2c523479e80","chapter":{"id":"course","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"unit":{"id":"notebook-blocks/e4240ba681bd49f3871529d076ed4b97","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码"},"page":{"id":"reading-opening","title":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","number":1},"sectionTitle":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","currentQuestion":"6 · 反例：相邻真实对象被 NMS 合并 · 实现代码","sourceSlides":[],"knowledgeBlock":null,"formulas":[],"previousBlock":"b-34731f753252d6fd2ba4b2e2","nextBlock":null,"prerequisites":[{"id":"b-a9af19a3a0c72657660d2c76","url":"#b-a9af19a3a0c72657660d2c76","role":"section-title"},{"id":"b-8e4b889b6be5c6093d3d3283","url":"#b-8e4b889b6be5c6093d3d3283","role":"problem-context"}],"relatedBlocks":[]}]}
