{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":19989,"databundleVersionId":1160143},{"sourceType":"datasetVersion","sourceId":13986085,"datasetId":8914930,"databundleVersionId":14761363,"isSourceIdPinned":false},{"sourceType":"datasetVersion","sourceId":1132110,"datasetId":637207,"databundleVersionId":1162637,"isSourceIdPinned":false}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# 🟦实践任务：可视化测试集中所有图片的模型预测结果\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nimport torch\nimport cv2\nimport re\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom torchvision.models.detection import fasterrcnn_resnet50_fpn\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"=\"*60)\nprint(\"小麦目标检测 - 测试集可视化实践任务\")\nprint(\"=\"*60)\n\n# ============================================\n# 1. 设置中文字体\n# ============================================\nprint(\"\\n1. 设置中文字体...\")\n\nchinese_font_path = '/kaggle/input/chinese-font'\nif os.path.exists(chinese_font_path):\n    # 查找字体文件\n    font_files = [f for f in os.listdir(chinese_font_path) \n                 if f.lower().endswith(('.ttf', '.otf', '.ttc'))]\n    \n    if font_files:\n        font_file = os.path.join(chinese_font_path, font_files[0])\n        print(f\"  使用字体文件: {font_files[0]}\")\n        \n        try:\n            from matplotlib.font_manager import FontProperties, fontManager\n            fontManager.addfont(font_file)\n            prop = FontProperties(fname=font_file)\n            font_name = prop.get_name()\n            plt.rcParams['font.sans-serif'] = [font_name]\n            plt.rcParams['axes.unicode_minus'] = False\n            print(f\"  ✓ 中文字体设置成功: {font_name}\")\n        except Exception as e:\n            print(f\"  字体设置失败: {e}\")\n            print(\"  使用默认字体\")\n    else:\n        print(\"  未找到字体文件，使用默认字体\")\nelse:\n    print(f\"  警告: 中文字体路径不存在: {chinese_font_path}\")\n    print(\"  使用默认字体\")\n\n# ============================================\n# 2. 检查数据路径\n# ============================================\nprint(\"\\n2. 检查数据路径...\")\n\ndata_paths = {\n    'wheat_data': '/kaggle/input/global-wheat-detection',\n    'chinese_font': '/kaggle/input/chinese-font',\n    'fasterrcnn': '/kaggle/input/fasterrcnn'\n}\n\nfor name, path in data_paths.items():\n    if os.path.exists(path):\n        print(f\"  ✓ {name}: {path}\")\n        if name != 'chinese_font':\n            # 显示内容\n            items = os.listdir(path)[:5]  # 显示前5个\n            print(f\"    内容示例: {', '.join(items)}\")\n            if len(os.listdir(path)) > 5:\n                print(f\"    还有 {len(os.listdir(path))-5} 个文件/文件夹\")\n    else:\n        print(f\"  ✗ {name}: 路径不存在\")\n\n# ============================================\n# 3. 加载数据\n# ============================================\nprint(\"\\n3. 加载数据...\")\n\n# 加载训练数据\ntrain_csv_path = '/kaggle/input/global-wheat-detection/train.csv'\nsubmit_csv_path = '/kaggle/input/global-wheat-detection/sample_submission.csv'\n\nif os.path.exists(train_csv_path):\n    train_df = pd.read_csv(train_csv_path)\n    print(f\"  ✓ 训练数据加载成功: {train_df.shape}\")\nelse:\n    print(f\"  ✗ 训练数据不存在: {train_csv_path}\")\n    train_df = pd.DataFrame()\n\nif os.path.exists(submit_csv_path):\n    submit_df = pd.read_csv(submit_csv_path)\n    print(f\"  ✓ 提交数据加载成功: {submit_df.shape}\")\nelse:\n    print(f\"  ✗ 提交数据不存在: {submit_csv_path}\")\n    submit_df = pd.DataFrame()\n\n# 设置路径\ntrain_dir = '/kaggle/input/global-wheat-detection/train'\ntest_dir = '/kaggle/input/global-wheat-detection/test'\n\nif os.path.exists(train_dir):\n    train_images = [f for f in os.listdir(train_dir) if f.endswith('.jpg')]\n    print(f\"  ✓ 训练图像: {len(train_images)} 张\")\nelse:\n    print(f\"  ✗ 训练图像目录不存在: {train_dir}\")\n\nif os.path.exists(test_dir):\n    test_images = [f for f in os.listdir(test_dir) if f.endswith('.jpg')]\n    print(f\"  ✓ 测试图像: {len(test_images)} 张\")\nelse:\n    print(f\"  ✗ 测试图像目录不存在: {test_dir}\")\n\n# ============================================\n# 4. 数据预处理（与之前代码保持一致）\n# ============================================\nprint(\"\\n4. 数据预处理...\")\n\nif not train_df.empty:\n    # 删除不需要的列\n    train_df = train_df.drop(columns=['width', 'height', 'source'], errors='ignore')\n    \n    # 解析bbox字段\n    train_df['x'] = -1\n    train_df['y'] = -1\n    train_df['w'] = -1\n    train_df['h'] = -1\n\n    def expand_bbox(x):\n        r = np.array(re.findall(\"([0-9]+[.]?[0-9]*)\", x))\n        if len(r) == 0:\n            r = [-1, -1, -1, -1]\n        return r\n\n    train_df[['x', 'y', 'w', 'h']] = np.stack(train_df['bbox'].apply(lambda x: expand_bbox(x))) \n    train_df['x'] = train_df['x'].astype(np.float16)                                          \n    train_df['y'] = train_df['y'].astype(np.float16)\n    train_df['w'] = train_df['w'].astype(np.float16)\n    train_df['h'] = train_df['h'].astype(np.float16)\n    \n    print(f\"  ✓ 数据预处理完成\")\n    print(f\"    唯一图像ID数量: {train_df['image_id'].nunique()}\")\n    print(f\"    最大标注框数: {train_df['image_id'].value_counts().max()}\")\n    print(f\"    最小标注框数: {train_df['image_id'].value_counts().min()}\")\n\n# ============================================\n# 5. 定义数据集类（与之前代码保持一致）\n# ============================================\nprint(\"\\n5. 定义数据集类...\")\n\nclass WheatDataset(Dataset):\n    def __init__(self, dataframe, image_dir, transforms=None, train=True):\n        super().__init__()\n        self.image_ids = dataframe['image_id'].unique()\n        self.df = dataframe\n        self.image_dir = image_dir\n        self.transforms = transforms\n        self.train = train\n\n    def __len__(self) -> int:\n        return self.image_ids.shape[0]\n\n    def __getitem__(self, index: int):\n        image_id = self.image_ids[index]\n        image_path = os.path.join(self.image_dir, f\"{image_id}.jpg\")\n        \n        # 读取图像\n        image = cv2.imread(image_path, cv2.IMREAD_COLOR)\n        if image is None:\n            print(f\"警告: 无法读取图像 {image_path}\")\n            image = np.zeros((1024, 1024, 3), dtype=np.float32)\n        else:\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n            image /= 255.0\n        \n        if self.transforms is not None:  \n            image = self.transforms(image)\n        \n        if self.train == False:  \n            return image, image_id\n        \n        # 训练模式：获取标注信息\n        records = self.df[self.df['image_id'] == image_id]   \n        boxes = records[['x', 'y', 'w', 'h']].values\n        boxes[:, 2] = boxes[:, 0] + boxes[:, 2]  # x + w\n        boxes[:, 3] = boxes[:, 1] + boxes[:, 3]  # y + h\n        boxes = torch.as_tensor(boxes, dtype=torch.float32)\n        \n        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])\n        area = torch.as_tensor(area, dtype=torch.float16)\n\n        labels = torch.ones((records.shape[0],), dtype=torch.int64)\n        iscrowd = torch.zeros((records.shape[0],), dtype=torch.int32)\n        \n        target = {\n            'boxes': boxes,\n            'labels': labels,\n            'image_id': torch.tensor([index]),\n            'area': area,\n            'iscrowd': iscrowd\n        }\n\n        return image, target, image_id\n\n# ============================================\n# 6. 辅助函数\n# ============================================\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\nclass Averager:\n    def __init__(self):\n        self.current_total = 0.0\n        self.iterations = 0.0\n\n    def send(self, value):\n        self.current_total += value\n        self.iterations += 1\n\n    @property\n    def value(self):\n        if self.iterations == 0:\n            return 0\n        else:\n            return 1.0 * self.current_total / self.iterations\n\n    def reset(self):\n        self.current_total = 0.0\n        self.iterations = 0.0\n\n# ============================================\n# 7. 检查模型权重文件\n# ============================================\nprint(\"\\n6. 检查模型权重文件...\")\n\nfasterrcnn_path = '/kaggle/input/fasterrcnn'\nWEIGHTS_FILE = None\n\nif os.path.exists(fasterrcnn_path):\n    model_files = [f for f in os.listdir(fasterrcnn_path) \n                  if f.lower().endswith(('.pth', '.pt', '.pth.tar'))]\n    \n    if model_files:\n        WEIGHTS_FILE = os.path.join(fasterrcnn_path, model_files[0])\n        print(f\"  ✓ 找到模型权重文件: {model_files[0]}\")\n        \n        # 检查文件大小\n        file_size = os.path.getsize(WEIGHTS_FILE) / (1024*1024)  # MB\n        print(f\"    文件大小: {file_size:.2f} MB\")\n    else:\n        print(f\"  ✗ 未找到模型权重文件\")\nelse:\n    print(f\"  ✗ 模型路径不存在: {fasterrcnn_path}\")\n\n# ============================================\n# 8. 创建测试数据集\n# ============================================\nprint(\"\\n7. 创建测试数据集...\")\n\nif os.path.exists(test_dir) and not submit_df.empty:\n    trans = transforms.Compose([transforms.ToTensor()])\n    \n    test_dataset = WheatDataset(submit_df, test_dir, trans, False)\n    print(f\"  ✓ 测试数据集创建成功\")\n    print(f\"    测试图像数量: {len(test_dataset)}\")\n    \n    # 创建数据加载器\n    test_data_loader = DataLoader(\n        test_dataset,\n        batch_size=4,  # 较小的批次大小，便于内存管理\n        shuffle=False,\n        num_workers=2,\n        collate_fn=collate_fn\n    )\n    \n    print(f\"  ✓ 测试数据加载器创建成功\")\n    print(f\"    批次大小: 4\")\n    print(f\"    总批次数: {len(test_data_loader)}\")\n    \n    # 测试数据集创建成功标志\n    dataset_created = True\nelse:\n    print(f\"  ✗ 无法创建测试数据集\")\n    dataset_created = False\n\n# ============================================\n# 9. 加载模型\n# ============================================\nprint(\"\\n8. 加载模型...\")\n\nif WEIGHTS_FILE and dataset_created:\n    try:\n        # 设备设置\n        device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n        print(f\"  ✓ 使用设备: {device}\")\n        \n        # 创建模型\n        model = fasterrcnn_resnet50_fpn(weights=None, weights_backbone=None)\n        num_classes = 2  # 背景 + 小麦\n        in_features = model.roi_heads.box_predictor.cls_score.in_features\n        model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)\n        \n        # 加载权重\n        print(f\"  ✓ 加载模型权重...\")\n        state_dict = torch.load(WEIGHTS_FILE, map_location=device)\n        model.load_state_dict(state_dict)\n        model.to(device)\n        model.eval()\n        print(f\"  ✓ 模型加载成功\")\n        \n        model_loaded = True\n        \n    except Exception as e:\n        print(f\"  ✗ 模型加载失败: {e}\")\n        model_loaded = False\nelse:\n    print(f\"  ✗ 无法加载模型\")\n    model_loaded = False\n\n# ============================================\n# 10. 主实践任务：可视化所有测试图像预测结果\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"开始实践任务：可视化所有测试图像预测结果\")\nprint(\"=\"*60)\n\nif model_loaded and dataset_created:\n    # 创建输出目录\n    output_dir = '/kaggle/working/test_predictions_visualization'\n    os.makedirs(output_dir, exist_ok=True)\n    \n    # 检测阈值\n    detection_threshold = 0.45\n    print(f\"检测阈值: {detection_threshold}\")\n    print(f\"输出目录: {output_dir}\")\n    \n    # 存储统计信息\n    all_predictions = []\n    total_images = 0\n    total_boxes = 0\n    \n    print(f\"\\n开始处理测试集...\")\n    \n    # 遍历测试数据加载器\n    for batch_idx, (images, image_ids) in enumerate(test_data_loader):\n        print(f\"\\r处理批次: {batch_idx+1}/{len(test_data_loader)}\", end=\"\", flush=True)\n        \n        # 将图像移到设备\n        images = [img.to(device) for img in images]\n        \n        # 进行推理\n        with torch.no_grad():\n            outputs = model(images)\n        \n        # 处理每个图像的预测结果\n        for i, (image, output, image_id) in enumerate(zip(images, outputs, image_ids)):\n            total_images += 1\n            \n            # 提取预测结果\n            boxes = output['boxes'].data.cpu().numpy()\n            scores = output['scores'].data.cpu().numpy()\n            \n            # 根据阈值过滤预测框\n            keep_indices = scores >= detection_threshold\n            filtered_boxes = boxes[keep_indices]\n            filtered_scores = scores[keep_indices]\n            \n            num_detections = len(filtered_boxes)\n            total_boxes += num_detections\n            \n            # 存储预测信息\n            all_predictions.append({\n                'image_id': image_id,\n                'num_detections': num_detections,\n                'scores': filtered_scores.copy(),\n                'boxes': filtered_boxes.copy()\n            })\n            \n            # 准备可视化\n            image_np = image.permute(1, 2, 0).cpu().numpy()\n            \n            # 创建可视化图像\n            fig, ax = plt.subplots(1, 1, figsize=(12, 8))\n            ax.imshow(image_np)\n            \n            # 绘制预测框\n            colors = ['red', 'blue', 'green', 'yellow', 'cyan', 'magenta']\n            for j, box in enumerate(filtered_boxes):\n                x1, y1, x2, y2 = map(int, box)\n                score = filtered_scores[j]\n                color = colors[j % len(colors)]\n                \n                # 创建矩形框\n                rect = patches.Rectangle(\n                    (x1, y1), \n                    x2 - x1, \n                    y2 - y1, \n                    linewidth=2, \n                    edgecolor=color, \n                    facecolor='none'\n                )\n                ax.add_patch(rect)\n                \n                # 添加置信度标签\n                ax.text(\n                    x1, y1 - 8, \n                    f'{score:.3f}', \n                    color=color, \n                    fontsize=8, \n                    fontweight='bold',\n                    bbox=dict(facecolor='white', alpha=0.7, edgecolor='none', pad=1)\n                )\n            \n            # 设置标题\n            title = f'测试图像: {image_id}\\n检测到 {num_detections} 个小麦穗'\n            if num_detections > 0:\n                title += f'\\n置信度范围: {filtered_scores.min():.3f} - {filtered_scores.max():.3f}'\n            \n            ax.set_title(title, fontsize=12, fontweight='bold')\n            ax.set_axis_off()\n            \n            # 保存可视化结果\n            output_path = os.path.join(output_dir, f'{image_id}_prediction.png')\n            plt.tight_layout()\n            plt.savefig(output_path, dpi=100, bbox_inches='tight')\n            plt.close(fig)\n    \n    print(f\"\\n\\n✓ 处理完成！\")\n    print(f\"  总处理图像数: {total_images}\")\n    print(f\"  总检测框数: {total_boxes}\")\n    print(f\"  平均每张图像检测数: {total_boxes/total_images if total_images > 0 else 0:.2f}\")\n    \n    # ============================================\n    # 11. 生成统计报告\n    # ============================================\n    print(\"\\n\" + \"=\"*60)\n    print(\"生成统计报告\")\n    print(\"=\"*60)\n    \n    if all_predictions:\n        # 提取统计信息\n        num_detections_list = [p['num_detections'] for p in all_predictions]\n        all_scores = np.concatenate([p['scores'] for p in all_predictions if len(p['scores']) > 0])\n        \n        # 基础统计\n        print(\"基础统计:\")\n        print(f\"  总测试图片数: {len(all_predictions)}\")\n        print(f\"  总检测框数: {sum(num_detections_list)}\")\n        print(f\"  检测到目标的图片数: {sum(n > 0 for n in num_detections_list)}\")\n        print(f\"  未检测到目标的图片数: {sum(n == 0 for n in num_detections_list)}\")\n        print(f\"  检测率: {sum(n > 0 for n in num_detections_list)/len(num_detections_list)*100:.1f}%\")\n        print(f\"  平均每张图片检测数: {np.mean(num_detections_list):.2f}\")\n        print(f\"  中位数检测数: {np.median(num_detections_list):.1f}\")\n        \n        if len(all_scores) > 0:\n            print(f\"\\n置信度统计:\")\n            print(f\"  平均置信度: {all_scores.mean():.3f}\")\n            print(f\"  中位数置信度: {np.median(all_scores):.3f}\")\n            print(f\"  最高置信度: {all_scores.max():.3f}\")\n            print(f\"  最低置信度: {all_scores.min():.3f}\")\n        \n        # 检测数量分布\n        print(f\"\\n检测数量分布 (阈值={detection_threshold}):\")\n        distribution_ranges = [(0, 0), (1, 1), (2, 3), (4, 5), (6, 10), (11, 20), (21, 50), (51, 1000)]\n        \n        for low, high in distribution_ranges:\n            if low == high == 0:\n                count = sum(1 for n in num_detections_list if n == 0)\n                percentage = count/len(num_detections_list)*100\n                print(f\"  {low}个: {count:3d} 张 ({percentage:5.1f}%)\")\n            elif low == high:\n                count = sum(1 for n in num_detections_list if n == low)\n                if count > 0:\n                    percentage = count/len(num_detections_list)*100\n                    print(f\"  {low}个: {count:3d} 张 ({percentage:5.1f}%)\")\n            else:\n                count = sum(1 for n in num_detections_list if low <= n <= high)\n                if count > 0:\n                    percentage = count/len(num_detections_list)*100\n                    print(f\"  {low}-{high}个: {count:3d} 张 ({percentage:5.1f}%)\")\n        \n        # 保存详细统计到CSV\n        stats_data = []\n        for pred in all_predictions:\n            stats_data.append({\n                'image_id': pred['image_id'],\n                'num_detections': pred['num_detections'],\n                'max_score': pred['scores'].max() if len(pred['scores']) > 0 else 0,\n                'mean_score': pred['scores'].mean() if len(pred['scores']) > 0 else 0,\n                'min_score': pred['scores'].min() if len(pred['scores']) > 0 else 0\n            })\n        \n        stats_df = pd.DataFrame(stats_data)\n        stats_csv_path = os.path.join(output_dir, 'detection_statistics.csv')\n        stats_df.to_csv(stats_csv_path, index=False)\n        print(f\"\\n✓ 详细统计已保存: {stats_csv_path}\")\n        \n        # 生成提交文件\n        submission_data = []\n        for pred in all_predictions:\n            image_id = pred['image_id']\n            boxes = pred['boxes']\n            scores = pred['scores']\n            \n            # 格式化为Kaggle提交格式\n            pred_strings = []\n            for box, score in zip(boxes, scores):\n                x1, y1, x2, y2 = box\n                w = x2 - x1\n                h = y2 - y1\n                pred_strings.append(f\"{score:.4f} {x1:.1f} {y1:.1f} {w:.1f} {h:.1f}\")\n            \n            prediction_string = \" \".join(pred_strings)\n            submission_data.append({\n                'image_id': image_id,\n                'PredictionString': prediction_string\n            })\n        \n        submission_df = pd.DataFrame(submission_data)\n        submission_path = os.path.join(output_dir, 'submission.csv')\n        submission_df.to_csv(submission_path, index=False)\n        print(f\"✓ 提交文件已生成: {submission_path}\")\n        \n        # ============================================\n        # 12. 可视化统计图表\n        # ============================================\n        print(\"\\n生成统计图表...\")\n        \n        fig, axes = plt.subplots(2, 2, figsize=(15, 12))\n        \n        # 子图1：检测数量分布直方图\n        axes[0, 0].hist(num_detections_list, bins=min(30, max(num_detections_list)+1),\n                       edgecolor='black', alpha=0.7, color='skyblue')\n        axes[0, 0].set_xlabel('每张图片的检测数量', fontsize=12)\n        axes[0, 0].set_ylabel('图片数量', fontsize=12)\n        axes[0, 0].set_title('检测数量分布', fontsize=14, fontweight='bold')\n        axes[0, 0].grid(True, alpha=0.3)\n        \n        # 子图2：置信度分布\n        if len(all_scores) > 0:\n            axes[0, 1].hist(all_scores, bins=30, edgecolor='black',\n                           alpha=0.7, color='lightcoral')\n            axes[0, 1].set_xlabel('置信度分数', fontsize=12)\n            axes[0, 1].set_ylabel('检测框数量', fontsize=12)\n            axes[0, 1].set_title('置信度分数分布', fontsize=14, fontweight='bold')\n            axes[0, 1].axvline(x=detection_threshold, color='red',\n                             linestyle='--', label=f'阈值={detection_threshold}')\n            axes[0, 1].legend()\n            axes[0, 1].grid(True, alpha=0.3)\n        \n        # 子图3：检测数量饼图\n        labels_pie = ['0个', '1-3个', '4-10个', '11-20个', '21+个']\n        sizes_pie = [\n            sum(1 for n in num_detections_list if n == 0),\n            sum(1 for n in num_detections_list if 1 <= n <= 3),\n            sum(1 for n in num_detections_list if 4 <= n <= 10),\n            sum(1 for n in num_detections_list if 11 <= n <= 20),\n            sum(1 for n in num_detections_list if n >= 21)\n        ]\n        \n        non_zero_indices = [i for i, s in enumerate(sizes_pie) if s > 0]\n        if non_zero_indices:\n            non_zero_labels = [labels_pie[i] for i in non_zero_indices]\n            non_zero_sizes = [sizes_pie[i] for i in non_zero_indices]\n            colors_pie = plt.cm.Set3(np.linspace(0, 1, len(non_zero_sizes)))\n            \n            axes[1, 0].pie(non_zero_sizes, labels=non_zero_labels, colors=colors_pie,\n                          autopct='%1.1f%%', startangle=90)\n            axes[1, 0].set_title('检测数量比例分布', fontsize=14, fontweight='bold')\n        \n        # 子图4：检测数量箱线图\n        box_data = [num_detections_list]\n        axes[1, 1].boxplot(box_data, vert=True, patch_artist=True,\n                          boxprops=dict(facecolor='lightblue', color='blue'),\n                          medianprops=dict(color='red', linewidth=2))\n        axes[1, 1].set_ylabel('检测数量', fontsize=12)\n        axes[1, 1].set_title('检测数量箱线图', fontsize=14, fontweight='bold')\n        axes[1, 1].set_xticks([1])\n        axes[1, 1].set_xticklabels(['所有测试图片'])\n        axes[1, 1].grid(True, alpha=0.3, axis='y')\n        \n        # 添加统计信息到箱线图\n        stats_text = f\"总数: {len(num_detections_list)}张\\n均值: {np.mean(num_detections_list):.2f}\\n中位数: {np.median(num_detections_list):.1f}\"\n        axes[1, 1].text(0.02, 0.98, stats_text, transform=axes[1, 1].transAxes,\n                       fontsize=10, verticalalignment='top',\n                       bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))\n        \n        plt.suptitle(f'测试集预测综合分析 (阈值={detection_threshold})',\n                    fontsize=16, fontweight='bold', y=1.02)\n        plt.tight_layout()\n        \n        # 保存统计图表\n        stats_chart_path = os.path.join(output_dir, 'statistics_summary.png')\n        plt.savefig(stats_chart_path, dpi=150, bbox_inches='tight')\n        plt.show()\n        \n        print(f\"✓ 统计图表已保存: {stats_chart_path}\")\n        \n        # ============================================\n        # 13. 显示示例结果\n        # ============================================\n        print(\"\\n\" + \"=\"*60)\n        print(\"示例结果展示\")\n        print(\"=\"*60)\n        \n        # 获取一些有代表性的图片\n        output_files = [f for f in os.listdir(output_dir) \n                       if f.endswith('_prediction.png')]\n        \n        if output_files:\n            # 选择前6张图片显示\n            display_files = output_files[:6]\n            \n            fig, axes = plt.subplots(2, 3, figsize=(18, 12))\n            axes = axes.flatten()\n            \n            for idx, img_file in enumerate(display_files):\n                if idx < len(axes):\n                    img_path = os.path.join(output_dir, img_file)\n                    img = plt.imread(img_path)\n                    \n                    axes[idx].imshow(img)\n                    \n                    # 从文件名中提取图像ID\n                    img_id = img_file.replace('_prediction.png', '')\n                    \n                    # 查找对应的统计信息\n                    img_stats = stats_df[stats_df['image_id'] == img_id]\n                    if not img_stats.empty:\n                        title = f'{img_id}\\n检测数: {int(img_stats.iloc[0][\"num_detections\"])}'\n                        if img_stats.iloc[0]['max_score'] > 0:\n                            title += f'\\n最高分: {img_stats.iloc[0][\"max_score\"]:.3f}'\n                    else:\n                        title = img_id\n                    \n                    axes[idx].set_title(title, fontsize=10)\n                    axes[idx].set_axis_off()\n            \n            # 隐藏多余的子图\n            for idx in range(len(display_files), 6):\n                axes[idx].axis('off')\n            \n            plt.suptitle('测试集预测可视化示例', fontsize=16, fontweight='bold')\n            plt.tight_layout()\n            plt.show()\n            \n            print(f\"显示 {len(display_files)} 个示例结果\")\n        else:\n            print(\"未找到可视化结果文件\")\n    \n    else:\n        print(\"没有生成预测结果\")\n    \n    # ============================================\n    # 14. 生成最终报告\n    # ============================================\n    print(\"\\n\" + \"=\"*60)\n    print(\"任务完成报告\")\n    print(\"=\"*60)\n    \n    # 检查输出目录内容\n    print(\"输出目录内容:\")\n    output_items = os.listdir(output_dir)\n    png_files = [f for f in output_items if f.endswith('.png')]\n    csv_files = [f for f in output_items if f.endswith('.csv')]\n    \n    print(f\"  可视化图片: {len(png_files)} 张\")\n    print(f\"  数据文件: {len(csv_files)} 个\")\n    \n    if png_files:\n        print(f\"  示例图片: {png_files[0]}\")\n    \n    print(\"\\n文件路径:\")\n    print(f\"  可视化结果目录: {output_dir}\")\n    print(f\"  详细统计文件: {os.path.join(output_dir, 'detection_statistics.csv')}\")\n    print(f\"  提交文件: {os.path.join(output_dir, 'submission.csv')}\")\n    print(f\"  统计图表: {os.path.join(output_dir, 'statistics_summary.png')}\")\n    \n    print(\"\\n\" + \"=\"*60)\n    print(\"实践任务完成!\")\n    print(\"=\"*60)\n    print(f\"✓ 已处理 {total_images} 张测试图片\")\n    print(f\"✓ 生成 {len(png_files)} 张可视化图片\")\n    print(f\"✓ 生成详细统计报告\")\n    print(f\"✓ 生成提交文件\")\n    print(f\"✓ 所有文件保存在: {output_dir}\")\n    \nelse:\n    print(\"✗ 无法执行实践任务:\")\n    if not model_loaded:\n        print(\"  - 模型未成功加载\")\n    if not dataset_created:\n        print(\"  - 测试数据集未成功创建\")\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"全部任务执行完毕!\")\nprint(\"=\"*60)\n\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-05T03:10:52.602031Z","iopub.execute_input":"2025-12-05T03:10:52.602391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# 实践任务：可视化测试集中所有图片的模型预测结果\n# ============================================\n\nprint(\"=\"*70)\nprint(\"🟦 实践任务：可视化测试集中所有图片的模型预测结果\")\nprint(\"=\"*70)\n\n# 设备设置\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\nprint(f\"使用设备: {device}\")\n\n# 确保模型在设备上并设置为评估模式\nmodel.to(device)\nmodel.eval()\nprint(\"模型已设置为评估模式\")\n\n# 创建测试数据集\ntest_dataset = WheatDataset(submit_df, test_dir, transforms.ToTensor(), False)\nprint(f\"测试数据集大小: {len(test_dataset)} 张图片\")\n\n# 创建测试数据加载器\ntest_data_loader = DataLoader(\n    test_dataset,\n    batch_size=4,  # 根据GPU内存调整批次大小\n    shuffle=False,\n    num_workers=2,\n    collate_fn=collate_fn\n)\nprint(f\"数据加载器创建成功，批次大小: 4，总批次数: {len(test_data_loader)}\")\n\n# 创建输出目录\noutput_dir = '/kaggle/working/test_predictions_all'\nos.makedirs(output_dir, exist_ok=True)\nprint(f\"输出目录: {output_dir}\")\n\n# 设置检测阈值\ndetection_threshold = 0.45\nprint(f\"检测阈值: {detection_threshold}\")\n\n# 存储所有预测结果的统计信息\nall_predictions_stats = []\ntotal_images_processed = 0\ntotal_boxes_detected = 0\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"开始批量推理和可视化\")\nprint(\"=\"*70)\n\n# 开始计时\nimport time\nstart_time = time.time()\n\n# 遍历测试集中的所有图片进行推理\nfor batch_idx, (images, image_ids) in enumerate(test_data_loader):\n    batch_start_time = time.time()\n    \n    # 显示处理进度\n    progress = (batch_idx + 1) / len(test_data_loader) * 100\n    print(f\"\\r处理进度: {batch_idx+1}/{len(test_data_loader)} 批次 [{progress:.1f}%]\", end=\"\", flush=True)\n    \n    # 将图像移到设备\n    images = [img.to(device) for img in images]\n    \n    # 进行推理（无梯度计算）\n    with torch.no_grad():\n        outputs = model(images)\n    \n    # 处理每个图像的预测结果\n    for i, (image, output, image_id) in enumerate(zip(images, outputs, image_ids)):\n        total_images_processed += 1\n        \n        # 任务功能1: 提取预测 boxes 与 scores\n        boxes = output['boxes'].data.cpu().numpy()\n        scores = output['scores'].data.cpu().numpy()\n        \n        # 任务功能2: 根据阈值过滤低置信度框\n        keep_indices = scores >= detection_threshold\n        filtered_boxes = boxes[keep_indices]\n        filtered_scores = scores[keep_indices]\n        \n        num_detections = len(filtered_boxes)\n        total_boxes_detected += num_detections\n        \n        # 存储统计信息\n        all_predictions_stats.append({\n            'image_id': image_id,\n            'num_detections': num_detections,\n            'max_score': filtered_scores.max() if num_detections > 0 else 0,\n            'mean_score': filtered_scores.mean() if num_detections > 0 else 0,\n            'min_score': filtered_scores.min() if num_detections > 0 else 0,\n            'boxes': filtered_boxes,\n            'scores': filtered_scores\n        })\n        \n        # 任务功能3: 在图像上绘制预测框\n        # 准备图像用于可视化\n        image_np = image.permute(1, 2, 0).cpu().numpy()\n        \n        # 创建可视化图像\n        fig, ax = plt.subplots(1, 1, figsize=(12, 8))\n        \n        # 显示原始图像\n        ax.imshow(image_np)\n        \n        # 定义颜色方案\n        colors = ['red', 'blue', 'green', 'yellow', 'cyan', 'magenta', 'orange', 'purple']\n        \n        # 绘制每个预测框\n        for j, (box, score) in enumerate(zip(filtered_boxes, filtered_scores)):\n            x1, y1, x2, y2 = map(int, box)\n            \n            # 选择颜色（循环使用）\n            color = colors[j % len(colors)]\n            \n            # 绘制矩形框\n            rect = patches.Rectangle(\n                (x1, y1), \n                x2 - x1, \n                y2 - y1, \n                linewidth=2, \n                edgecolor=color, \n                facecolor='none',\n                alpha=0.8\n            )\n            ax.add_patch(rect)\n            \n            # 添加置信度标签\n            label_text = f'{score:.3f}'\n            ax.text(\n                x1, \n                y1 - 10 if y1 > 20 else y1 + 15,  # 避免标签超出图像边界\n                label_text,\n                color=color,\n                fontsize=9,\n                fontweight='bold',\n                bbox=dict(\n                    facecolor='white',\n                    alpha=0.7,\n                    edgecolor=color,\n                    boxstyle='round,pad=0.3'\n                )\n            )\n        \n        # 设置图像标题和信息\n        title = f'测试图像: {image_id}\\n'\n        title += f'检测到 {num_detections} 个小麦穗'\n        \n        if num_detections > 0:\n            title += f' | 置信度: {filtered_scores.min():.3f}-{filtered_scores.max():.3f}'\n        \n        ax.set_title(title, fontsize=12, fontweight='bold', pad=20)\n        ax.set_axis_off()\n        \n        # 在图像右下角添加阈值信息\n        ax.text(\n            0.98, 0.02, \n            f'阈值: {detection_threshold}', \n            transform=ax.transAxes,\n            fontsize=9,\n            color='black',\n            bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'),\n            horizontalalignment='right',\n            verticalalignment='bottom'\n        )\n        \n        # 保存可视化结果\n        output_path = os.path.join(output_dir, f'{image_id}_prediction.png')\n        plt.tight_layout()\n        plt.savefig(output_path, dpi=100, bbox_inches='tight')\n        plt.close(fig)\n    \n    batch_time = time.time() - batch_start_time\n    if batch_idx % 5 == 0:  # 每5个批次显示一次详细信息\n        print(f\"\\n批次 {batch_idx+1} 处理完成，耗时: {batch_time:.2f}秒\")\n\n# 计算总耗时\ntotal_time = time.time() - start_time\n\nprint(f\"\\n\\n\" + \"=\"*70)\nprint(\"批量推理和可视化完成！\")\nprint(\"=\"*70)\n\nprint(f\"处理统计:\")\nprint(f\"  总处理图像数: {total_images_processed}\")\nprint(f\"  总检测框数: {total_boxes_detected}\")\nprint(f\"  平均每张图像检测数: {total_boxes_detected/total_images_processed if total_images_processed > 0 else 0:.2f}\")\nprint(f\"  总耗时: {total_time:.2f}秒\")\nprint(f\"  平均每张图像耗时: {total_time/total_images_processed if total_images_processed > 0 else 0:.2f}秒\")\n\n# ============================================\n# 生成详细统计报告\n# ============================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"生成详细统计报告\")\nprint(\"=\"*70)\n\nif all_predictions_stats:\n    # 提取统计数据\n    detection_counts = [stats['num_detections'] for stats in all_predictions_stats]\n    max_scores = [stats['max_score'] for stats in all_predictions_stats]\n    \n    # 合并所有置信度分数\n    all_scores = []\n    for stats in all_predictions_stats:\n        if stats['num_detections'] > 0:\n            all_scores.extend(stats['scores'])\n    all_scores = np.array(all_scores)\n    \n    print(\"检测数量统计:\")\n    print(f\"  总图像数: {len(all_predictions_stats)}\")\n    print(f\"  检测到目标的图像数: {sum(1 for count in detection_counts if count > 0)}\")\n    print(f\"  未检测到目标的图像数: {sum(1 for count in detection_counts if count == 0)}\")\n    print(f\"  检测率: {sum(1 for count in detection_counts if count > 0)/len(detection_counts)*100:.1f}%\")\n    print(f\"  平均检测数: {np.mean(detection_counts):.2f}\")\n    print(f\"  中位数检测数: {np.median(detection_counts):.1f}\")\n    print(f\"  最大检测数: {max(detection_counts)}\")\n    print(f\"  最小检测数: {min(detection_counts)}\")\n    \n    if len(all_scores) > 0:\n        print(\"\\n置信度统计:\")\n        print(f\"  平均置信度: {all_scores.mean():.3f}\")\n        print(f\"  中位数置信度: {np.median(all_scores):.3f}\")\n        print(f\"  最高置信度: {all_scores.max():.3f}\")\n        print(f\"  最低置信度: {all_scores.min():.3f}\")\n        print(f\"  标准差: {all_scores.std():.3f}\")\n        \n        # 置信度分布\n        print(\"\\n置信度分布:\")\n        score_ranges = [(0.9, 1.0), (0.8, 0.9), (0.7, 0.8), (0.6, 0.7), \n                       (0.5, 0.6), (0.45, 0.5), (0.0, 0.45)]\n        for low, high in score_ranges:\n            count = np.sum((all_scores >= low) & (all_scores <= high))\n            if count > 0:\n                percentage = count / len(all_scores) * 100\n                print(f\"  {low:.1f}-{high:.1f}: {count:4d} 个 ({percentage:5.1f}%)\")\n    \n    print(f\"\\n检测数量分布 (阈值={detection_threshold}):\")\n    count_ranges = [(0, 0), (1, 1), (2, 3), (4, 5), (6, 10), \n                   (11, 20), (21, 50), (51, 100), (101, 1000)]\n    \n    for low, high in count_ranges:\n        if low == high == 0:\n            count = sum(1 for n in detection_counts if n == 0)\n            percentage = count / len(detection_counts) * 100\n            print(f\"  {low}个: {count:3d} 张 ({percentage:5.1f}%)\")\n        elif low == high:\n            count = sum(1 for n in detection_counts if n == low)\n            if count > 0:\n                percentage = count / len(detection_counts) * 100\n                print(f\"  {low}个: {count:3d} 张 ({percentage:5.1f}%)\")\n        else:\n            count = sum(1 for n in detection_counts if low <= n <= high)\n            if count > 0:\n                percentage = count / len(detection_counts) * 100\n                print(f\"  {low}-{high}个: {count:3d} 张 ({percentage:5.1f}%)\")\n    \n    # 保存详细统计到CSV\n    stats_data = []\n    for stats in all_predictions_stats:\n        stats_data.append({\n            'image_id': stats['image_id'],\n            'num_detections': stats['num_detections'],\n            'max_score': stats['max_score'],\n            'mean_score': stats['mean_score'],\n            'min_score': stats['min_score'],\n            'detection_status': '有检测' if stats['num_detections'] > 0 else '无检测'\n        })\n    \n    stats_df = pd.DataFrame(stats_data)\n    stats_csv_path = os.path.join(output_dir, 'detailed_statistics.csv')\n    stats_df.to_csv(stats_csv_path, index=False)\n    print(f\"\\n✓ 详细统计已保存: {stats_csv_path}\")\n    \n    # 生成提交文件\n    submission_data = []\n    for stats in all_predictions_stats:\n        image_id = stats['image_id']\n        boxes = stats['boxes']\n        scores = stats['scores']\n        \n        # 格式化为Kaggle提交格式: \"score x y w h\"\n        pred_strings = []\n        for box, score in zip(boxes, scores):\n            x1, y1, x2, y2 = box\n            w = x2 - x1\n            h = y2 - y1\n            pred_strings.append(f\"{score:.4f} {x1:.1f} {y1:.1f} {w:.1f} {h:.1f}\")\n        \n        prediction_string = \" \".join(pred_strings)\n        submission_data.append({\n            'image_id': image_id,\n            'PredictionString': prediction_string\n        })\n    \n    submission_df = pd.DataFrame(submission_data)\n    submission_path = os.path.join(output_dir, 'submission.csv')\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"✓ 提交文件已生成: {submission_path}\")\n    \n    # ============================================\n    # 生成可视化统计图表\n    # ============================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"生成可视化统计图表\")\n    print(\"=\"*70)\n    \n    fig, axes = plt.subplots(2, 3, figsize=(18, 12))\n    \n    # 图表1: 检测数量分布直方图\n    axes[0, 0].hist(detection_counts, \n                   bins=min(20, max(detection_counts) + 1),\n                   edgecolor='black', \n                   alpha=0.7, \n                   color='skyblue',\n                   rwidth=0.8)\n    axes[0, 0].set_xlabel('每张图片的检测数量', fontsize=12)\n    axes[0, 0].set_ylabel('图片数量', fontsize=12)\n    axes[0, 0].set_title('检测数量分布', fontsize=14, fontweight='bold')\n    axes[0, 0].grid(True, alpha=0.3, linestyle='--')\n    axes[0, 0].axvline(x=np.mean(detection_counts), color='red', \n                      linestyle='--', linewidth=2, label=f'均值={np.mean(detection_counts):.1f}')\n    axes[0, 0].legend()\n    \n    # 图表2: 置信度分布直方图\n    if len(all_scores) > 0:\n        axes[0, 1].hist(all_scores, bins=30, \n                       edgecolor='black', alpha=0.7, color='lightcoral')\n        axes[0, 1].set_xlabel('置信度分数', fontsize=12)\n        axes[0, 1].set_ylabel('检测框数量', fontsize=12)\n        axes[0, 1].set_title('置信度分数分布', fontsize=14, fontweight='bold')\n        axes[0, 1].axvline(x=detection_threshold, color='red', \n                          linestyle='--', linewidth=2, label=f'阈值={detection_threshold}')\n        axes[0, 1].axvline(x=np.mean(all_scores), color='blue', \n                          linestyle='--', linewidth=2, label=f'均值={np.mean(all_scores):.3f}')\n        axes[0, 1].legend()\n        axes[0, 1].grid(True, alpha=0.3, linestyle='--')\n    else:\n        axes[0, 1].text(0.5, 0.5, '未检测到任何目标', \n                       ha='center', va='center', fontsize=14)\n        axes[0, 1].axis('off')\n    \n    # 图表3: 检测状态饼图\n    detection_status = ['有检测', '无检测']\n    status_counts = [\n        sum(1 for count in detection_counts if count > 0),\n        sum(1 for count in detection_counts if count == 0)\n    ]\n    colors_pie = ['lightgreen', 'lightcoral']\n    \n    wedges, texts, autotexts = axes[0, 2].pie(\n        status_counts, \n        labels=detection_status, \n        colors=colors_pie,\n        autopct='%1.1f%%',\n        startangle=90,\n        explode=(0.05, 0),\n        shadow=True\n    )\n    for autotext in autotexts:\n        autotext.set_color('black')\n        autotext.set_fontsize(10)\n        autotext.set_fontweight('bold')\n    axes[0, 2].set_title('检测状态分布', fontsize=14, fontweight='bold')\n    \n    # 图表4: 检测数量箱线图\n    box_data = [detection_counts]\n    bp = axes[1, 0].boxplot(\n        box_data, \n        vert=True, \n        patch_artist=True,\n        boxprops=dict(facecolor='lightblue', color='blue', linewidth=2),\n        medianprops=dict(color='red', linewidth=2),\n        whiskerprops=dict(color='black', linewidth=1.5),\n        capprops=dict(color='black', linewidth=1.5),\n        flierprops=dict(marker='o', color='red', alpha=0.5)\n    )\n    axes[1, 0].set_ylabel('检测数量', fontsize=12)\n    axes[1, 0].set_title('检测数量箱线图', fontsize=14, fontweight='bold')\n    axes[1, 0].set_xticks([1])\n    axes[1, 0].set_xticklabels(['所有测试图片'])\n    axes[1, 0].grid(True, alpha=0.3, axis='y', linestyle='--')\n    \n    # 在箱线图上添加统计信息\n    stats_text = f\"\"\"统计摘要:\n总数: {len(detection_counts)} 张\n均值: {np.mean(detection_counts):.2f}\n中位数: {np.median(detection_counts):.1f}\n标准差: {np.std(detection_counts):.2f}\n最小值: {min(detection_counts)}\n最大值: {max(detection_counts)}\nQ1: {np.percentile(detection_counts, 25):.1f}\nQ3: {np.percentile(detection_counts, 75):.1f}\"\"\"\n    \n    axes[1, 0].text(0.02, 0.98, stats_text, transform=axes[1, 0].transAxes,\n                   fontsize=9, verticalalignment='top',\n                   bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))\n    \n    # 图表5: 检测数量与最大置信度关系\n    if len(all_scores) > 0:\n        scatter = axes[1, 1].scatter(\n            detection_counts, \n            max_scores,\n            c=detection_counts,\n            cmap='viridis',\n            alpha=0.6,\n            edgecolors='black',\n            linewidth=0.5,\n            s=50\n        )\n        axes[1, 1].set_xlabel('检测数量', fontsize=12)\n        axes[1, 1].set_ylabel('最大置信度', fontsize=12)\n        axes[1, 1].set_title('检测数量 vs 最大置信度', fontsize=14, fontweight='bold')\n        axes[1, 1].grid(True, alpha=0.3, linestyle='--')\n        \n        # 添加颜色条\n        cbar = plt.colorbar(scatter, ax=axes[1, 1])\n        cbar.set_label('检测数量', fontsize=10)\n    else:\n        axes[1, 1].text(0.5, 0.5, '无置信度数据', \n                       ha='center', va='center', fontsize=14)\n        axes[1, 1].axis('off')\n    \n    # 图表6: 检测数量累积分布\n    sorted_counts = np.sort(detection_counts)\n    cumulative = np.arange(1, len(sorted_counts) + 1) / len(sorted_counts)\n    axes[1, 2].plot(sorted_counts, cumulative, 'b-', linewidth=2)\n    axes[1, 2].fill_between(sorted_counts, cumulative, alpha=0.3, color='blue')\n    axes[1, 2].set_xlabel('检测数量', fontsize=12)\n    axes[1, 2].set_ylabel('累积比例', fontsize=12)\n    axes[1, 2].set_title('检测数量累积分布', fontsize=14, fontweight='bold')\n    axes[1, 2].grid(True, alpha=0.3, linestyle='--')\n    \n    # 添加参考线\n    median_idx = np.where(sorted_counts >= np.median(detection_counts))[0][0]\n    axes[1, 2].axvline(x=np.median(detection_counts), color='red', \n                      linestyle='--', linewidth=1.5, alpha=0.7)\n    axes[1, 2].axhline(y=cumulative[median_idx], color='red', \n                      linestyle='--', linewidth=1.5, alpha=0.7)\n    \n    plt.suptitle(f'测试集预测结果综合分析报告 (阈值={detection_threshold})', \n                fontsize=16, fontweight='bold', y=1.02)\n    plt.tight_layout()\n    \n    # 保存统计图表\n    stats_chart_path = os.path.join(output_dir, 'comprehensive_analysis.png')\n    plt.savefig(stats_chart_path, dpi=150, bbox_inches='tight')\n    plt.show()\n    \n    print(f\"✓ 综合分析图表已保存: {stats_chart_path}\")\n    \n    # ============================================\n    # 显示示例可视化结果\n    # ============================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"示例可视化结果展示\")\n    print(\"=\"*70)\n    \n    # 从输出目录获取一些示例图片\n    output_files = sorted([f for f in os.listdir(output_dir) \n                          if f.endswith('_prediction.png')])\n    \n    if output_files:\n        # 选择不同类型的结果展示\n        sample_files = []\n        \n        # 1. 检测数量最多的\n        max_det_idx = np.argmax(detection_counts)\n        max_det_image_id = all_predictions_stats[max_det_idx]['image_id']\n        sample_files.append(f\"{max_det_image_id}_prediction.png\")\n        \n        # 2. 检测数量最少的非零\n        non_zero_indices = [i for i, count in enumerate(detection_counts) if count > 0]\n        if non_zero_indices:\n            min_det_idx = non_zero_indices[np.argmin([detection_counts[i] for i in non_zero_indices])]\n            min_det_image_id = all_predictions_stats[min_det_idx]['image_id']\n            sample_files.append(f\"{min_det_image_id}_prediction.png\")\n        \n        # 3. 最高置信度的\n        if len([s for s in max_scores if s > 0]) > 0:\n            max_conf_idx = np.argmax(max_scores)\n            max_conf_image_id = all_predictions_stats[max_conf_idx]['image_id']\n            sample_files.append(f\"{max_conf_image_id}_prediction.png\")\n        \n        # 4-6. 随机选择\n        import random\n        random.seed(42)\n        other_files = random.sample(output_files, min(3, len(output_files)-len(sample_files)))\n        sample_files.extend(other_files[:3])\n        \n        # 只显示存在的文件\n        sample_files = [f for f in sample_files if f in output_files]\n        \n        if sample_files:\n            fig, axes = plt.subplots(2, 3, figsize=(18, 12))\n            axes = axes.flatten()\n            \n            for idx, img_file in enumerate(sample_files[:6]):\n                img_path = os.path.join(output_dir, img_file)\n                img = plt.imread(img_path)\n                \n                axes[idx].imshow(img)\n                \n                # 获取对应的图像ID\n                img_id = img_file.replace('_prediction.png', '')\n                \n                # 查找对应的统计信息\n                img_stats = None\n                for stats in all_predictions_stats:\n                    if stats['image_id'] == img_id:\n                        img_stats = stats\n                        break\n                \n                # 设置标题\n                if img_stats:\n                    title = f'{img_id}\\n'\n                    title += f'检测数: {img_stats[\"num_detections\"]}'\n                    if img_stats['num_detections'] > 0:\n                        title += f'\\n置信度: {img_stats[\"min_score\"]:.3f}-{img_stats[\"max_score\"]:.3f}'\n                else:\n                    title = img_id\n                \n                axes[idx].set_title(title, fontsize=11, fontweight='bold')\n                axes[idx].set_axis_off()\n            \n            # 隐藏多余的子图\n            for idx in range(len(sample_files), 6):\n                axes[idx].axis('off')\n            \n            plt.suptitle('测试集预测可视化示例 (不同类型的结果)', \n                        fontsize=16, fontweight='bold', y=1.02)\n            plt.tight_layout()\n            plt.show()\n            \n            print(f\"显示 {len(sample_files)} 个示例结果:\")\n            for i, img_file in enumerate(sample_files):\n                img_id = img_file.replace('_prediction.png', '')\n                for stats in all_predictions_stats:\n                    if stats['image_id'] == img_id:\n                        print(f\"  {i+1}. {img_id}: {stats['num_detections']}个检测\")\n                        break\n        else:\n            print(\"未找到示例文件\")\n    else:\n        print(\"未找到可视化结果文件\")\n    \n    # ============================================\n    # 生成最终任务报告\n    # ============================================\n    print(\"\\n\" + \"=\"*70)\n    print(\"实践任务完成报告\")\n    print(\"=\"*70)\n    \n    report_content = f\"\"\"\n    实践任务：可视化测试集中所有图片的模型预测结果\n    {'='*60}\n    \n    任务完成情况:\n    {'-'*60}\n    ✓ 对测试集中的所有图像依次进行推理（inference）\n      - 总处理图像数: {total_images_processed}\n      - 平均每张图像耗时: {total_time/total_images_processed if total_images_processed > 0 else 0:.2f}秒\n    \n    ✓ 提取每张图像的预测 boxes 与 scores\n      - 总检测框数: {total_boxes_detected}\n      - 平均每张图像检测数: {total_boxes_detected/total_images_processed if total_images_processed > 0 else 0:.2f}\n    \n    ✓ 根据阈值过滤低置信度框\n      - 检测阈值: {detection_threshold}\n      - 过滤后有效检测框: {len(all_scores) if 'all_scores' in locals() else 0}\n    \n    ✓ 在图像上绘制预测框并显示结果\n      - 生成可视化图片: {len(output_files) if 'output_files' in locals() else 0}张\n      - 检测率: {sum(1 for count in detection_counts if count > 0)/len(detection_counts)*100 if detection_counts else 0:.1f}%\n    \n    输出文件:\n    {'-'*60}\n    1. 可视化图片目录: {output_dir}\n       - 每张测试图像的可视化结果\n       - 文件名格式: [image_id]_prediction.png\n    \n    2. 统计文件:\n       - 详细统计: {os.path.join(output_dir, 'detailed_statistics.csv')}\n       - 提交文件: {os.path.join(output_dir, 'submission.csv')}\n    \n    3. 分析图表:\n       - 综合分析: {os.path.join(output_dir, 'comprehensive_analysis.png')}\n    \n    模型性能分析:\n    {'-'*60}\n    - 平均检测数量: {np.mean(detection_counts) if detection_counts else 0:.2f}\n    - 检测数量范围: {min(detection_counts) if detection_counts else 0} - {max(detection_counts) if detection_counts else 0}\n    - 置信度范围: {all_scores.min() if len(all_scores) > 0 else 0:.3f} - {all_scores.max() if len(all_scores) > 0 else 0:.3f}\n    - 检测成功率: {sum(1 for count in detection_counts if count > 0)/len(detection_counts)*100 if detection_counts else 0:.1f}%\n    \n    建议与改进:\n    {'-'*60}\n    1. 如果检测率过低，可考虑降低阈值 (如 0.3-0.35)\n    2. 如果假阳性过多，可提高阈值 (如 0.5-0.6)\n    3. 检查未检测到目标的图像，分析原因\n    4. 可根据置信度分布调整后处理策略\n    5. 考虑对不同置信度区间采用不同颜色标注\n    \n    任务总结:\n    {'-'*60}\n    本次实践任务成功实现了对测试集中所有图像的批量推理和可视化，\n    通过统计分析了解了模型在测试集上的表现，为模型优化提供了数据支持。\n    \"\"\"\n    \n    # 保存报告\n    report_path = os.path.join(output_dir, 'task_report.txt')\n    with open(report_path, 'w', encoding='utf-8') as f:\n        f.write(report_content)\n    \n    print(report_content)\n    print(f\"\\n✓ 详细报告已保存: {report_path}\")\n    \nelse:\n    print(\"没有生成预测结果，无法生成统计报告\")\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"✅ 实践任务完成！\")\nprint(\"=\"*70)\nprint(\"通过本实践任务，你已经成功：\")\nprint(\"1. 掌握了如何对测试集进行批量推理\")\nprint(\"2. 学会了如何过滤和可视化预测结果\")\nprint(\"3. 能够生成详细的统计报告和分析图表\")\nprint(\"4. 了解了模型在测试集上的整体表现\")\nprint(\"=\"*70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T03:17:29.330221Z","iopub.execute_input":"2025-12-05T03:17:29.330533Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# 实践任务：可视化测试集中所有图片的模型预测结果\n# ============================================\n\nprint(\"=\"*70)\nprint(\"🟦 实践任务：可视化测试集中所有图片的模型预测结果\")\nprint(\"=\"*70)\n\n# 设备设置\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\nprint(f\"使用设备: {device}\")\n\n# 确保模型在设备上并设置为评估模式\nmodel.to(device)\nmodel.eval()\nprint(\"模型已设置为评估模式\")\n\n# 创建测试数据集\ntest_dataset = WheatDataset(submit_df, test_dir, transforms.ToTensor(), False)\nprint(f\"测试数据集大小: {len(test_dataset)} 张图片\")\n\n# 创建测试数据加载器\ntest_data_loader = DataLoader(\n    test_dataset,\n    batch_size=4,  # 根据GPU内存调整批次大小\n    shuffle=False,\n    num_workers=2,\n    collate_fn=collate_fn\n)\nprint(f\"数据加载器创建成功，批次大小: 4，总批次数: {len(test_data_loader)}\")\n\n# 创建输出目录\noutput_dir = '/kaggle/working/test_predictions_all'\nos.makedirs(output_dir, exist_ok=True)\nprint(f\"输出目录: {output_dir}\")\n\n# 设置检测阈值\ndetection_threshold = 0.45\nprint(f\"检测阈值: {detection_threshold}\")\n\n# 存储所有预测结果的统计信息\nall_predictions_stats = []\ntotal_images_processed = 0\ntotal_boxes_detected = 0\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"开始批量推理和可视化\")\nprint(\"=\"*70)\n\n# 开始计时\nimport time\nstart_time = time.time()\n\n# 首先，让我们看看测试图像\nprint(\"\\n📸 测试图像示例预览:\")\n# 显示前4张测试图像的原始图像\nfig, axes = plt.subplots(2, 2, figsize=(12, 10))\nfor i in range(min(4, len(test_dataset))):\n    image, image_id = test_dataset[i]\n    image_np = image.permute(1, 2, 0).cpu().numpy()\n    \n    row = i // 2\n    col = i % 2\n    axes[row, col].imshow(image_np)\n    axes[row, col].set_title(f'测试图像: {image_id}', fontsize=10)\n    axes[row, col].axis('off')\nplt.suptitle('测试集原始图像示例', fontsize=14, fontweight='bold')\nplt.tight_layout()\nplt.show()\n\n# 遍历测试集中的所有图片进行推理和可视化\nfor batch_idx, (images, image_ids) in enumerate(test_data_loader):\n    batch_start_time = time.time()\n    \n    # 显示处理进度\n    progress = (batch_idx + 1) / len(test_data_loader) * 100\n    print(f\"\\r处理进度: {batch_idx+1}/{len(test_data_loader)} 批次 [{progress:.1f}%]\", end=\"\", flush=True)\n    \n    # 将图像移到设备\n    images = [img.to(device) for img in images]\n    \n    # 进行推理（无梯度计算）\n    with torch.no_grad():\n        outputs = model(images)\n    \n    # 处理每个图像的预测结果\n    for i, (image, output, image_id) in enumerate(zip(images, outputs, image_ids)):\n        total_images_processed += 1\n        \n        # 任务功能1: 提取预测 boxes 与 scores\n        boxes = output['boxes'].data.cpu().numpy()\n        scores = output['scores'].data.cpu().numpy()\n        \n        # 任务功能2: 根据阈值过滤低置信度框\n        keep_indices = scores >= detection_threshold\n        filtered_boxes = boxes[keep_indices]\n        filtered_scores = scores[keep_indices]\n        \n        num_detections = len(filtered_boxes)\n        total_boxes_detected += num_detections\n        \n        # 存储统计信息\n        all_predictions_stats.append({\n            'image_id': image_id,\n            'num_detections': num_detections,\n            'max_score': filtered_scores.max() if num_detections > 0 else 0,\n            'mean_score': filtered_scores.mean() if num_detections > 0 else 0,\n            'min_score': filtered_scores.min() if num_detections > 0 else 0,\n            'boxes': filtered_boxes,\n            'scores': filtered_scores\n        })\n        \n        # 任务功能3: 在图像上绘制预测框并显示结果\n        # 准备图像用于可视化\n        image_np = image.permute(1, 2, 0).cpu().numpy()\n        \n        # 创建可视化图像\n        fig, ax = plt.subplots(1, 1, figsize=(10, 8))\n        \n        # 显示原始图像\n        ax.imshow(image_np)\n        \n        # 定义颜色方案 - 根据置信度使用不同颜色\n        def get_color_by_score(score):\n            if score >= 0.8:\n                return 'red'  # 高置信度\n            elif score >= 0.6:\n                return 'orange'  # 中高置信度\n            elif score >= 0.45:\n                return 'yellow'  # 阈值附近\n            else:\n                return 'green'  # 低置信度\n        \n        # 绘制每个预测框\n        for j, (box, score) in enumerate(zip(filtered_boxes, filtered_scores)):\n            x1, y1, x2, y2 = map(int, box)\n            \n            # 根据置信度选择颜色\n            color = get_color_by_score(score)\n            \n            # 绘制矩形框\n            rect = patches.Rectangle(\n                (x1, y1), \n                x2 - x1, \n                y2 - y1, \n                linewidth=2, \n                edgecolor=color, \n                facecolor='none',\n                alpha=0.8\n            )\n            ax.add_patch(rect)\n            \n            # 添加置信度标签\n            label_text = f'{score:.2f}'\n            ax.text(\n                x1, \n                y1 - 8 if y1 > 20 else y1 + 15,  # 避免标签超出图像边界\n                label_text,\n                color=color,\n                fontsize=8,\n                fontweight='bold',\n                bbox=dict(\n                    facecolor='white',\n                    alpha=0.7,\n                    edgecolor=color,\n                    boxstyle='round,pad=0.3'\n                )\n            )\n        \n        # 设置图像标题和信息\n        title = f'测试图像: {image_id}\\n'\n        title += f'检测到 {num_detections} 个小麦穗'\n        \n        if num_detections > 0:\n            title += f'\\n置信度范围: {filtered_scores.min():.3f} - {filtered_scores.max():.3f}'\n        \n        ax.set_title(title, fontsize=11, fontweight='bold', pad=15)\n        ax.set_axis_off()\n        \n        # 在图像右下角添加阈值和颜色说明\n        info_text = f'阈值: {detection_threshold}\\n'\n        info_text += '颜色: 🔴≥0.8 🟠≥0.6 🟡≥0.45'\n        \n        ax.text(\n            0.98, 0.02, \n            info_text, \n            transform=ax.transAxes,\n            fontsize=8,\n            color='black',\n            bbox=dict(facecolor='white', alpha=0.8, edgecolor='gray', pad=5),\n            horizontalalignment='right',\n            verticalalignment='bottom'\n        )\n        \n        # 保存可视化结果\n        output_path = os.path.join(output_dir, f'{image_id}_prediction.png')\n        plt.tight_layout()\n        plt.savefig(output_path, dpi=100, bbox_inches='tight')\n        plt.close(fig)\n        \n        # 如果是前4张图像，显示它们\n        if total_images_processed <= 4:\n            print(f\"\\n📊 显示第 {total_images_processed} 张图像的预测结果:\")\n            \n            # 重新打开保存的图像显示\n            fig2, ax2 = plt.subplots(1, 1, figsize=(10, 8))\n            saved_img = plt.imread(output_path)\n            ax2.imshow(saved_img)\n            ax2.set_title(f'预测结果预览: {image_id}', fontsize=12, fontweight='bold')\n            ax2.axis('off')\n            plt.tight_layout()\n            plt.show()\n            \n            # 显示统计信息\n            print(f\"  图像ID: {image_id}\")\n            print(f\"  检测数量: {num_detections}\")\n            if num_detections > 0:\n                print(f\"  置信度范围: {filtered_scores.min():.3f} - {filtered_scores.max():.3f}\")\n                print(f\"  平均置信度: {filtered_scores.mean():.3f}\")\n    \n    batch_time = time.time() - batch_start_time\n\n# 计算总耗时\ntotal_time = time.time() - start_time\n\nprint(f\"\\n\\n\" + \"=\"*70)\nprint(\"批量推理和可视化完成！\")\nprint(\"=\"*70)\n\nprint(f\"📈 处理统计:\")\nprint(f\"  总处理图像数: {total_images_processed}\")\nprint(f\"  总检测框数: {total_boxes_detected}\")\nprint(f\"  平均每张图像检测数: {total_boxes_detected/total_images_processed if total_images_processed > 0 else 0:.2f}\")\nprint(f\"  总耗时: {total_time:.2f}秒\")\nprint(f\"  平均每张图像耗时: {total_time/total_images_processed if total_images_processed > 0 else 0:.2f}秒\")\n\n# ============================================\n# 实时显示更多可视化结果\n# ============================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"📸 更多可视化结果展示\")\nprint(\"=\"*70)\n\n# 从输出目录获取一些不同类型的图片展示\noutput_files = sorted([f for f in os.listdir(output_dir) \n                      if f.endswith('_prediction.png')])\n\nif output_files:\n    # 选择6张不同类型的图像展示\n    print(\"选择6张不同类型的预测结果进行展示:\")\n    \n    # 根据检测数量排序\n    sorted_stats = sorted(all_predictions_stats, key=lambda x: x['num_detections'])\n    \n    # 选择展示的图片\n    display_indices = []\n    \n    # 1. 无检测的图片（如果有）\n    no_detection = [i for i, stats in enumerate(all_predictions_stats) if stats['num_detections'] == 0]\n    if no_detection:\n        display_indices.append(no_detection[0])\n    \n    # 2. 少量检测的图片\n    few_detection = [i for i, stats in enumerate(all_predictions_stats) if 1 <= stats['num_detections'] <= 3]\n    if few_detection:\n        display_indices.append(few_detection[len(few_detection)//2])\n    \n    # 3. 中等数量检测的图片\n    medium_detection = [i for i, stats in enumerate(all_predictions_stats) if 4 <= stats['num_detections'] <= 10]\n    if medium_detection:\n        display_indices.append(medium_detection[len(medium_detection)//2])\n    \n    # 4. 大量检测的图片\n    many_detection = [i for i, stats in enumerate(all_predictions_stats) if stats['num_detections'] > 10]\n    if many_detection:\n        display_indices.append(many_detection[len(many_detection)//2])\n    \n    # 5. 最高置信度的图片\n    if any(stats['max_score'] > 0 for stats in all_predictions_stats):\n        max_conf_idx = max(range(len(all_predictions_stats)), \n                          key=lambda i: all_predictions_stats[i]['max_score'] if all_predictions_stats[i]['max_score'] > 0 else -1)\n        display_indices.append(max_conf_idx)\n    \n    # 6. 随机选择一张\n    import random\n    random.seed(42)\n    remaining_indices = [i for i in range(len(all_predictions_stats)) if i not in display_indices]\n    if remaining_indices:\n        display_indices.append(random.choice(remaining_indices))\n    \n    # 确保不重复且不超过6个\n    display_indices = list(set(display_indices))[:6]\n    \n    # 显示选中的图片\n    fig, axes = plt.subplots(2, 3, figsize=(18, 12))\n    axes = axes.flatten()\n    \n    for idx, stat_idx in enumerate(display_indices):\n        stats = all_predictions_stats[stat_idx]\n        img_file = f\"{stats['image_id']}_prediction.png\"\n        img_path = os.path.join(output_dir, img_file)\n        \n        if os.path.exists(img_path):\n            img = plt.imread(img_path)\n            axes[idx].imshow(img)\n            \n            # 设置标题\n            title = f\"{stats['image_id']}\\n\"\n            title += f\"检测数: {stats['num_detections']}\"\n            if stats['num_detections'] > 0:\n                title += f\"\\n置信度: {stats['min_score']:.2f}-{stats['max_score']:.2f}\"\n            \n            axes[idx].set_title(title, fontsize=10, fontweight='bold')\n            axes[idx].axis('off')\n            \n            # 打印信息\n            print(f\"  {idx+1}. {stats['image_id']}: {stats['num_detections']}个检测\", end=\"\")\n            if stats['num_detections'] > 0:\n                print(f\" (置信度: {stats['min_score']:.2f}-{stats['max_score']:.2f})\")\n            else:\n                print()\n        else:\n            axes[idx].text(0.5, 0.5, '图像未找到', \n                          ha='center', va='center', fontsize=12)\n            axes[idx].axis('off')\n    \n    # 隐藏多余的子图\n    for idx in range(len(display_indices), 6):\n        axes[idx].axis('off')\n    \n    plt.suptitle('测试集预测结果多样本展示', fontsize=16, fontweight='bold', y=1.02)\n    plt.tight_layout()\n    plt.show()\n\n# ============================================\n# 生成统计图表并显示\n# ============================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"📊 生成统计图表\")\nprint(\"=\"*70)\n\nif all_predictions_stats:\n    # 提取统计数据\n    detection_counts = [stats['num_detections'] for stats in all_predictions_stats]\n    all_scores = []\n    for stats in all_predictions_stats:\n        if stats['num_detections'] > 0:\n            all_scores.extend(stats['scores'])\n    \n    # 创建统计图表\n    fig, axes = plt.subplots(2, 2, figsize=(14, 12))\n    \n    # 图表1: 检测数量分布\n    axes[0, 0].hist(detection_counts, \n                   bins=min(20, max(detection_counts) + 1),\n                   edgecolor='black', \n                   alpha=0.7, \n                   color='skyblue',\n                   rwidth=0.8)\n    axes[0, 0].set_xlabel('每张图片的检测数量', fontsize=12)\n    axes[0, 0].set_ylabel('图片数量', fontsize=12)\n    axes[0, 0].set_title('检测数量分布', fontsize=14, fontweight='bold')\n    axes[0, 0].grid(True, alpha=0.3, linestyle='--')\n    axes[0, 0].axvline(x=np.mean(detection_counts), color='red', \n                      linestyle='--', linewidth=2, label=f'均值={np.mean(detection_counts):.1f}')\n    axes[0, 0].legend()\n    \n    # 图表2: 检测状态饼图\n    detection_status = ['有检测', '无检测']\n    status_counts = [\n        sum(1 for count in detection_counts if count > 0),\n        sum(1 for count in detection_counts if count == 0)\n    ]\n    colors_pie = ['lightgreen', 'lightcoral']\n    \n    wedges, texts, autotexts = axes[0, 1].pie(\n        status_counts, \n        labels=detection_status, \n        colors=colors_pie,\n        autopct='%1.1f%%',\n        startangle=90,\n        explode=(0.05, 0)\n    )\n    for autotext in autotexts:\n        autotext.set_color('black')\n        autotext.set_fontsize(10)\n        autotext.set_fontweight('bold')\n    axes[0, 1].set_title('检测状态分布', fontsize=14, fontweight='bold')\n    \n    # 图表3: 置信度分布（如果有检测结果）\n    if all_scores:\n        axes[1, 0].hist(all_scores, bins=30, \n                       edgecolor='black', alpha=0.7, color='lightcoral')\n        axes[1, 0].set_xlabel('置信度分数', fontsize=12)\n        axes[1, 0].set_ylabel('检测框数量', fontsize=12)\n        axes[1, 0].set_title('置信度分数分布', fontsize=14, fontweight='bold')\n        axes[1, 0].axvline(x=detection_threshold, color='red', \n                          linestyle='--', linewidth=2, label=f'阈值={detection_threshold}')\n        axes[1, 0].grid(True, alpha=0.3, linestyle='--')\n        axes[1, 0].legend()\n    else:\n        axes[1, 0].text(0.5, 0.5, '未检测到任何目标', \n                       ha='center', va='center', fontsize=14)\n        axes[1, 0].axis('off')\n    \n    # 图表4: 检测数量箱线图\n    box_data = [detection_counts]\n    axes[1, 1].boxplot(\n        box_data, \n        vert=True, \n        patch_artist=True,\n        boxprops=dict(facecolor='lightblue', color='blue', linewidth=2),\n        medianprops=dict(color='red', linewidth=2),\n        whiskerprops=dict(color='black', linewidth=1.5)\n    )\n    axes[1, 1].set_ylabel('检测数量', fontsize=12)\n    axes[1, 1].set_title('检测数量箱线图', fontsize=14, fontweight='bold')\n    axes[1, 1].set_xticks([1])\n    axes[1, 1].set_xticklabels(['所有测试图片'])\n    axes[1, 1].grid(True, alpha=0.3, axis='y', linestyle='--')\n    \n    # 添加统计信息\n    stats_text = f\"\"\"统计摘要:\n总数: {len(detection_counts)}张\n均值: {np.mean(detection_counts):.2f}\n中位数: {np.median(detection_counts):.1f}\n最小值: {min(detection_counts)}\n最大值: {max(detection_counts)}\"\"\"\n    \n    axes[1, 1].text(0.02, 0.98, stats_text, transform=axes[1, 1].transAxes,\n                   fontsize=9, verticalalignment='top',\n                   bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))\n    \n    plt.suptitle(f'测试集预测结果统计分析 (阈值={detection_threshold})', \n                fontsize=16, fontweight='bold', y=1.02)\n    plt.tight_layout()\n    \n    # 保存统计图表\n    stats_chart_path = os.path.join(output_dir, 'statistics_summary.png')\n    plt.savefig(stats_chart_path, dpi=150, bbox_inches='tight')\n    plt.show()\n    \n    print(f\"📈 统计图表已生成并显示\")\n\n# ============================================\n# 显示最后处理的一批图像\n# ============================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"🎯 最后一批处理结果展示\")\nprint(\"=\"*70)\n\n# 获取最后一批处理的图像\nlast_batch_images = []\nlast_batch_ids = []\n\n# 重新加载最后一批数据用于展示\ntest_data_loader_last = DataLoader(\n    test_dataset,\n    batch_size=4,\n    shuffle=False,\n    num_workers=0,\n    collate_fn=collate_fn\n)\n\n# 获取最后一批\nfor images, image_ids in test_data_loader_last:\n    last_batch_images = images\n    last_batch_ids = image_ids\n    # 只取最后一批\n\nif last_batch_images:\n    print(f\"展示最后一批处理的图像 ({len(last_batch_images)}张):\")\n    \n    # 对最后一批图像进行推理\n    last_batch_images = [img.to(device) for img in last_batch_images]\n    with torch.no_grad():\n        last_outputs = model(last_batch_images)\n    \n    # 显示最后一批的预测结果\n    fig, axes = plt.subplots(2, 2, figsize=(14, 12))\n    axes = axes.flatten()\n    \n    for i, (image, output, image_id) in enumerate(zip(last_batch_images, last_outputs, last_batch_ids)):\n        if i < 4:  # 最多显示4张\n            # 提取预测结果\n            boxes = output['boxes'].data.cpu().numpy()\n            scores = output['scores'].data.cpu().numpy()\n            \n            # 过滤\n            keep_indices = scores >= detection_threshold\n            filtered_boxes = boxes[keep_indices]\n            filtered_scores = scores[keep_indices]\n            \n            # 准备图像\n            image_np = image.permute(1, 2, 0).cpu().numpy()\n            \n            # 显示\n            axes[i].imshow(image_np)\n            \n            # 绘制框\n            for box, score in zip(filtered_boxes, filtered_scores):\n                x1, y1, x2, y2 = map(int, box)\n                color = 'red' if score >= 0.8 else ('orange' if score >= 0.6 else 'yellow')\n                \n                rect = patches.Rectangle(\n                    (x1, y1), x2 - x1, y2 - y1,\n                    linewidth=2, edgecolor=color, facecolor='none', alpha=0.7\n                )\n                axes[i].add_patch(rect)\n                \n                axes[i].text(x1, y1-5, f'{score:.2f}', \n                           color=color, fontsize=8, fontweight='bold',\n                           bbox=dict(facecolor='white', alpha=0.7))\n            \n            axes[i].set_title(f'{image_id} - {len(filtered_boxes)}个检测', fontsize=10)\n            axes[i].axis('off')\n            \n            print(f\"  {i+1}. {image_id}: {len(filtered_boxes)}个检测\")\n    \n    # 隐藏多余的子图\n    for i in range(len(last_batch_images), 4):\n        axes[i].axis('off')\n    \n    plt.suptitle('最后一批图像预测结果', fontsize=16, fontweight='bold')\n    plt.tight_layout()\n    plt.show()\n\n# ============================================\n# 生成最终报告\n# ============================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"📋 实践任务完成报告\")\nprint(\"=\"*70)\n\nprint(f\"\"\"\n✅ 实践任务完成情况总结:\n{'━'*60}\n\n📸 图片可视化任务完成:\n  1. 已处理所有 {total_images_processed} 张测试图像\n  2. 生成了 {len(output_files) if 'output_files' in locals() else 0} 张可视化图片\n  3. 每张图片都包含了:\n     - 原始测试图像\n     - 预测边界框（不同颜色表示不同置信度）\n     - 置信度标签\n     - 检测统计信息\n\n🔍 可视化特点:\n  • 红色框: 高置信度 (≥0.8)\n  • 橙色框: 中高置信度 (≥0.6)\n  • 黄色框: 阈值附近 (≥{detection_threshold})\n  • 每个框上方显示置信度分数\n\n📊 统计结果:\n  • 总检测框数: {total_boxes_detected}\n  • 平均每张图像检测数: {total_boxes_detected/total_images_processed if total_images_processed > 0 else 0:.2f}\n  • 检测率: {sum(1 for stats in all_predictions_stats if stats['num_detections'] > 0)/len(all_predictions_stats)*100 if all_predictions_stats else 0:.1f}%\n  • 处理耗时: {total_time:.2f}秒\n\n📁 输出文件:\n  1. 可视化图片目录: {output_dir}\n     - 格式: [image_id]_prediction.png\n  2. 统计图表: {stats_chart_path if 'stats_chart_path' in locals() else '未生成'}\n  3. 详细统计文件: {os.path.join(output_dir, 'detailed_statistics.csv') if os.path.exists(output_dir) else '未生成'}\n\n🎯 任务目标达成:\n  ✓ 对测试集中的所有图像依次进行推理\n  ✓ 提取每张图像的预测boxes与scores  \n  ✓ 根据阈值过滤低置信度框\n  ✓ 在图像上绘制预测框并显示结果\n\n📈 模型表现分析:\n  - 模型在测试集上的检测效果已通过可视化完整展示\n  - 可以通过生成的图片直观评估检测质量\n  - 统计图表提供了量化分析数据\n\n💡 后续建议:\n  1. 检查无检测的图像，分析原因\n  2. 调整阈值优化检测结果\n  3. 根据可视化结果改进模型\n\"\"\")\n\n# 保存最终的可视化结果网格\nprint(\"\\n📸 生成最终的可视化结果网格...\")\n\n# 从输出目录中选择9张代表性图片\nrepresentative_files = []\nif 'output_files' in locals() and output_files:\n    # 选择9张不同检测数量的图片\n    import random\n    random.seed(42)\n    \n    # 按检测数量分组\n    groups = {'无检测': [], '少量': [], '中等': [], '大量': []}\n    for stats in all_predictions_stats:\n        if stats['num_detections'] == 0:\n            groups['无检测'].append(stats['image_id'])\n        elif 1 <= stats['num_detections'] <= 3:\n            groups['少量'].append(stats['image_id'])\n        elif 4 <= stats['num_detections'] <= 10:\n            groups['中等'].append(stats['image_id'])\n        else:\n            groups['大量'].append(stats['image_id'])\n    \n    # 从每组中选择图片\n    for group_name, image_ids in groups.items():\n        if image_ids:\n            selected_id = random.choice(image_ids)\n            representative_files.append(f\"{selected_id}_prediction.png\")\n    \n    # 如果不足9张，随机补充\n    while len(representative_files) < 9 and len(output_files) > len(representative_files):\n        additional_file = random.choice([f for f in output_files if f not in representative_files])\n        representative_files.append(additional_file)\n    \n    representative_files = representative_files[:9]\n    \n    # 显示最终的可视化网格\n    fig, axes = plt.subplots(3, 3, figsize=(15, 15))\n    axes = axes.flatten()\n    \n    for idx, img_file in enumerate(representative_files):\n        if idx < 9:\n            img_path = os.path.join(output_dir, img_file)\n            if os.path.exists(img_path):\n                img = plt.imread(img_path)\n                axes[idx].imshow(img)\n                \n                # 设置标题（简化）\n                img_id = img_file.replace('_prediction.png', '')\n                axes[idx].set_title(img_id[:10] + '...', fontsize=9)\n                axes[idx].axis('off')\n            else:\n                axes[idx].text(0.5, 0.5, '图像未找到', \n                              ha='center', va='center', fontsize=10)\n                axes[idx].axis('off')\n    \n    plt.suptitle('测试集预测结果可视化网格', fontsize=16, fontweight='bold', y=1.02)\n    plt.tight_layout()\n    plt.show()\n    \n    print(f\"✅ 已生成包含 {len(representative_files)} 张图片的可视化网格\")\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"🎉 实践任务完成！所有测试图像的预测结果已可视化完成！\")\nprint(\"=\"*70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T03:23:22.366816Z","iopub.execute_input":"2025-12-05T03:23:22.367163Z"}},"outputs":[],"execution_count":null}]}