{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b7d9a65f",
   "metadata": {
    "origin_pos": 0
   },
   "source": [
    "# 目标检测数据集\n",
    ":label:`sec_object-detection-dataset`\n",
    "\n",
    "目标检测领域没有像MNIST和Fashion-MNIST那样的小数据集。\n",
    "为了快速测试目标检测模型，[**我们收集并标记了一个小型数据集**]。\n",
    "首先，我们拍摄了一组香蕉的照片，并生成了1000张不同角度和大小的香蕉图像。\n",
    "然后，我们在一些背景图片的随机位置上放一张香蕉的图像。\n",
    "最后，我们在图片上为这些香蕉标记了边界框。\n",
    "\n",
    "## [**下载数据集**]\n",
    "\n",
    "包含所有图像和CSV标签文件的香蕉检测数据集可以直接从互联网下载。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ac709a94",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-18T07:05:07.388030Z",
     "iopub.status.busy": "2023-08-18T07:05:07.387330Z",
     "iopub.status.idle": "2023-08-18T07:05:09.391756Z",
     "shell.execute_reply": "2023-08-18T07:05:09.390466Z"
    },
    "origin_pos": 2,
    "tab": [
     "pytorch"
    ]
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import os\n",
    "import pandas as pd\n",
    "import torch\n",
    "import torchvision\n",
    "from d2l import torch as d2l"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f606f485",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-18T07:05:09.398812Z",
     "iopub.status.busy": "2023-08-18T07:05:09.398133Z",
     "iopub.status.idle": "2023-08-18T07:05:09.406040Z",
     "shell.execute_reply": "2023-08-18T07:05:09.403044Z"
    },
    "origin_pos": 4,
    "tab": [
     "pytorch"
    ]
   },
   "outputs": [],
   "source": [
    "#@save\n",
    "d2l.DATA_HUB['banana-detection'] = (\n",
    "    d2l.DATA_URL + 'banana-detection.zip',\n",
    "    '5de26c8fce5ccdea9f91267273464dc968d20d72')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11e5ef46",
   "metadata": {
    "origin_pos": 5
   },
   "source": [
    "## 读取数据集\n",
    "\n",
    "通过`read_data_bananas`函数，我们[**读取香蕉检测数据集**]。\n",
    "该数据集包括一个的CSV文件，内含目标类别标签和位于左上角和右下角的真实边界框坐标。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "126b46ce",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-18T07:05:09.411930Z",
     "iopub.status.busy": "2023-08-18T07:05:09.411428Z",
     "iopub.status.idle": "2023-08-18T07:05:09.424725Z",
     "shell.execute_reply": "2023-08-18T07:05:09.423491Z"
    },
    "origin_pos": 7,
    "tab": [
     "pytorch"
    ]
   },
   "outputs": [],
   "source": [
    "#@save\n",
    "def read_data_bananas(is_train=True):\n",
    "    \"\"\"读取香蕉检测数据集中的图像和标签\"\"\"\n",
    "    data_dir = d2l.download_extract('banana-detection')\n",
    "    csv_fname = os.path.join(data_dir, 'bananas_train' if is_train\n",
    "                             else 'bananas_val', 'label.csv')\n",
    "    csv_data = pd.read_csv(csv_fname)\n",
    "    csv_data = csv_data.set_index('img_name')\n",
    "    images, targets = [], []\n",
    "    for img_name, target in csv_data.iterrows():\n",
    "        images.append(torchvision.io.read_image(\n",
    "            os.path.join(data_dir, 'bananas_train' if is_train else\n",
    "                         'bananas_val', 'images', f'{img_name}')))\n",
    "        # 这里的target包含（类别，左上角x，左上角y，右下角x，右下角y），\n",
    "        # 其中所有图像都具有相同的香蕉类（索引为0）\n",
    "        targets.append(list(target))\n",
    "    return images, torch.tensor(targets).unsqueeze(1) / 256"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21ae1840",
   "metadata": {
    "origin_pos": 9
   },
   "source": [
    "通过使用`read_data_bananas`函数读取图像和标签，以下`BananasDataset`类别将允许我们[**创建一个自定义`Dataset`实例**]来加载香蕉检测数据集。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c19364e8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-18T07:05:09.430946Z",
     "iopub.status.busy": "2023-08-18T07:05:09.430415Z",
     "iopub.status.idle": "2023-08-18T07:05:09.440758Z",
     "shell.execute_reply": "2023-08-18T07:05:09.437898Z"
    },
    "origin_pos": 11,
    "tab": [
     "pytorch"
    ]
   },
   "outputs": [],
   "source": [
    "#@save\n",
    "class BananasDataset(torch.utils.data.Dataset):\n",
    "    \"\"\"一个用于加载香蕉检测数据集的自定义数据集\"\"\"\n",
    "    def __init__(self, is_train):\n",
    "        self.features, self.labels = read_data_bananas(is_train)\n",
    "        print('read ' + str(len(self.features)) + (f' training examples' if\n",
    "              is_train else f' validation examples'))\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        return (self.features[idx].float(), self.labels[idx])\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.features)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8e6b437",
   "metadata": {
    "origin_pos": 13
   },
   "source": [
    "最后，我们定义`load_data_bananas`函数，来[**为训练集和测试集返回两个数据加载器实例**]。对于测试集，无须按随机顺序读取它。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "bcb3bd0f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-18T07:05:09.446644Z",
     "iopub.status.busy": "2023-08-18T07:05:09.446229Z",
     "iopub.status.idle": "2023-08-18T07:05:09.453297Z",
     "shell.execute_reply": "2023-08-18T07:05:09.451963Z"
    },
    "origin_pos": 15,
    "tab": [
     "pytorch"
    ]
   },
   "outputs": [],
   "source": [
    "#@save\n",
    "def load_data_bananas(batch_size):\n",
    "    \"\"\"加载香蕉检测数据集\"\"\"\n",
    "    train_iter = torch.utils.data.DataLoader(BananasDataset(is_train=True),\n",
    "                                             batch_size, shuffle=True)\n",
    "    val_iter = torch.utils.data.DataLoader(BananasDataset(is_train=False),\n",
    "                                           batch_size)\n",
    "    return train_iter, val_iter"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "227137fa",
   "metadata": {
    "origin_pos": 17
   },
   "source": [
    "让我们[**读取一个小批量，并打印其中的图像和标签的形状**]。\n",
    "图像的小批量的形状为（批量大小、通道数、高度、宽度），看起来很眼熟：它与我们之前图像分类任务中的相同。\n",
    "标签的小批量的形状为（批量大小，$m$，5），其中$m$是数据集的任何图像中边界框可能出现的最大数量。\n",
    "\n",
    "小批量计算虽然高效，但它要求每张图像含有相同数量的边界框，以便放在同一个批量中。\n",
    "通常来说，图像可能拥有不同数量个边界框；因此，在达到$m$之前，边界框少于$m$的图像将被非法边界框填充。\n",
    "这样，每个边界框的标签将被长度为5的数组表示。\n",
    "数组中的第一个元素是边界框中对象的类别，其中-1表示用于填充的非法边界框。\n",
    "数组的其余四个元素是边界框左上角和右下角的（$x$，$y$）坐标值（值域在0～1之间）。\n",
    "对于香蕉数据集而言，由于每张图像上只有一个边界框，因此$m=1$。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "4a40eaf3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-18T07:05:09.460535Z",
     "iopub.status.busy": "2023-08-18T07:05:09.459922Z",
     "iopub.status.idle": "2023-08-18T07:05:26.422407Z",
     "shell.execute_reply": "2023-08-18T07:05:26.421565Z"
    },
    "origin_pos": 18,
    "tab": [
     "pytorch"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading ../data/banana-detection.zip from http://d2l-data.s3-accelerate.amazonaws.com/banana-detection.zip...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "read 1000 training examples\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "read 100 validation examples\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(torch.Size([32, 3, 256, 256]), torch.Size([32, 1, 5]))"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "batch_size, edge_size = 32, 256\n",
    "train_iter, _ = load_data_bananas(batch_size)\n",
    "batch = next(iter(train_iter))\n",
    "batch[0].shape, batch[1].shape"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b064ea18",
   "metadata": {
    "origin_pos": 19
   },
   "source": [
    "## [**演示**]\n",
    "\n",
    "让我们展示10幅带有真实边界框的图像。\n",
    "我们可以看到在所有这些图像中香蕉的旋转角度、大小和位置都有所不同。\n",
    "当然，这只是一个简单的人工数据集，实践中真实世界的数据集通常要复杂得多。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "48ff9349",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2023-08-18T07:05:26.427628Z",
     "iopub.status.busy": "2023-08-18T07:05:26.427090Z",
     "iopub.status.idle": "2023-08-18T07:05:27.010623Z",
     "shell.execute_reply": "2023-08-18T07:05:27.009799Z"
    },
    "origin_pos": 21,
    "tab": [
     "pytorch"
    ]
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 298192 characters]\n",
      "text/plain": [
       "<Figure size 720x288 with 10 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "imgs = (batch[0][0:10].permute(0, 2, 3, 1)) / 255\n",
    "axes = d2l.show_images(imgs, 2, 5, scale=2)\n",
    "for ax, label in zip(axes, batch[1][0:10]):\n",
    "    d2l.show_bboxes(ax, [label[0][1:5] * edge_size], colors=['w'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87d070c1",
   "metadata": {
    "origin_pos": 23
   },
   "source": [
    "## 小结\n",
    "\n",
    "* 我们收集的香蕉检测数据集可用于演示目标检测模型。\n",
    "* 用于目标检测的数据加载与图像分类的数据加载类似。但是，在目标检测中，标签还包含真实边界框的信息，它不出现在图像分类中。\n",
    "\n",
    "## 练习\n",
    "\n",
    "1. 在香蕉检测数据集中演示其他带有真实边界框的图像。它们在边界框和目标方面有什么不同？\n",
    "1. 假设我们想要将数据增强（例如随机裁剪）应用于目标检测。它与图像分类中的有什么不同？提示：如果裁剪的图像只包含物体的一小部分会怎样？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d9a1555",
   "metadata": {
    "origin_pos": 25,
    "tab": [
     "pytorch"
    ]
   },
   "source": [
    "[Discussions](https://discuss.d2l.ai/t/3202)\n"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  },
  "required_libs": []
 },
 "nbformat": 4,
 "nbformat_minor": 5
}