{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":19989,"databundleVersionId":1160143,"sourceType":"competition"},{"sourceId":1219292,"sourceType":"datasetVersion","datasetId":648709},{"sourceId":1251699,"sourceType":"datasetVersion","datasetId":719180},{"sourceId":1285895,"sourceType":"datasetVersion","datasetId":742407},{"sourceId":7002739,"sourceType":"datasetVersion","datasetId":4025711}],"dockerImageVersionId":29956,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nfrom tqdm.auto import tqdm\nimport shutil as sh","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-04-13T10:29:18.785851Z","iopub.execute_input":"2024-04-13T10:29:18.786259Z","iopub.status.idle":"2024-04-13T10:29:18.794985Z","shell.execute_reply.started":"2024-04-13T10:29:18.786223Z","shell.execute_reply":"2024-04-13T10:29:18.793960Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"用滑动窗口的方式，先使用分类识别出需要检测的目标，再使用回归将窗口调整为和被检测的目标一样的大小\nhttps://yiyan.baidu.com/share/F9YWF5EXtB ","metadata":{}},{"cell_type":"markdown","source":"# 获取 yolov5 代码库","metadata":{}},{"cell_type":"code","source":"#!git clone https://github.com/ultralytics/yolov5\n#!mv yolov5/* ./\n\n!cp -r ../input/yolov5train/* .","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-04-13T10:29:18.797578Z","iopub.execute_input":"2024-04-13T10:29:18.798000Z","iopub.status.idle":"2024-04-13T10:29:20.098495Z","shell.execute_reply.started":"2024-04-13T10:29:18.797957Z","shell.execute_reply":"2024-04-13T10:29:20.097444Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install --no-deps '../input/weightedboxesfusion/' > /dev/null","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:29:20.100311Z","iopub.execute_input":"2024-04-13T10:29:20.100793Z","iopub.status.idle":"2024-04-13T10:29:45.545646Z","shell.execute_reply.started":"2024-04-13T10:29:20.100742Z","shell.execute_reply":"2024-04-13T10:29:45.544551Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv('../input/global-wheat-detection/train.csv')\nbboxs = np.stack(df['bbox'].apply(lambda x: np.fromstring(x[1:-1], sep=',')))\nfor i, column in enumerate(['x', 'y', 'w', 'h']):\n    df[column] = bboxs[:,i]\n    #enumerate(['x', 'y', 'w', 'h'])用于遍历列名列表，并将bbox数组中的对应列值分配给新的DataFrame列\ndf.drop(columns=['bbox'], inplace=True)\ndf['x_center'] = df['x'] + df['w']/2\ndf['y_center'] = df['y'] + df['h']/2\ndf['classes'] = 0\n#由于只检测一种物体（小麦），所以所有标签都设置为0\ndf = df[['image_id','x', 'y', 'w', 'h','x_center','y_center','classes']]\n#重新排序并选择DataFrame的某些列，得到一个新的DataFrame，只包含指定的列","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:29:45.548678Z","iopub.execute_input":"2024-04-13T10:29:45.548992Z","iopub.status.idle":"2024-04-13T10:29:46.714757Z","shell.execute_reply.started":"2024-04-13T10:29:45.548959Z","shell.execute_reply":"2024-04-13T10:29:46.713833Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:29:46.716479Z","iopub.execute_input":"2024-04-13T10:29:46.716797Z","iopub.status.idle":"2024-04-13T10:29:46.740642Z","shell.execute_reply.started":"2024-04-13T10:29:46.716764Z","shell.execute_reply":"2024-04-13T10:29:46.739532Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"index = list(set(df.image_id))","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:29:46.742167Z","iopub.execute_input":"2024-04-13T10:29:46.742601Z","iopub.status.idle":"2024-04-13T10:29:46.768490Z","shell.execute_reply.started":"2024-04-13T10:29:46.742548Z","shell.execute_reply":"2024-04-13T10:29:46.767490Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Convert train data to yolov5 format\nBased on [this notebook](https://www.kaggle.com/orkatz2/yolov5-train)","metadata":{}},{"cell_type":"code","source":"def convertTrainLabel():\n    source = 'train'\n    if True:\n        for fold in [0]:\n            val_index = index[len(index)*fold//5:len(index)*(fold+1)//5]\n            #len(index)*fold//5 计算出验证集的起始索引，len(index)*(fold+1)//5 计算出验证集的结束索引\n            #除以5是为了将数据集分成5份，进行5折交叉验证。每次交叉验证会将数据集分成5份，其中4份用作训练集，1份用作验证集。\n            for name,mini in tqdm(df.groupby('image_id')):\n            #使用df.groupby('image_id')对数据集进行分组，name表示分组的名称，mini表示分组的数据。\n            #遍历每个分组，如果name在val_index中，将path2save设为'val2017/'，否则设为'train2017/'。\n                if name in val_index:\n                    path2save = 'val2017/'\n                else:\n                    path2save = 'train2017/'\n                if not os.path.exists('convertor/fold{}/labels/'.format(fold)+path2save):\n                    os.makedirs('convertor/fold{}/labels/'.format(fold)+path2save)\n                 #检查是否存在文件夹'convertor/fold{}/labels/'.format(fold)+path2save，如果不存在，则创建该文件夹。\n            #根据索引，数据集被分为训练集和验证集。对于每个image_id，根据其是否在val_index中来确定它应该被放在哪个集合中\n                with open('convertor/fold{}/labels/'.format(fold)+path2save+name+\".txt\", 'w+') as f:\n                    row = mini[['classes','x_center','y_center','w','h']].astype(float).values\n                    row = row/1024\n                    row = row.astype(str)\n                    for j in range(len(row)):\n                        text = ' '.join(row[j])\n                        f.write(text)\n                        f.write(\"\\n\")\n                #对于每个image_id，其相关的标签数据（类别、中心坐标、宽度和高度）被提取出来，除以1024进行归一化，然后保存为文本文件。每个小麦的标签占一行，格式是class x_center y_center width height。\n                if not os.path.exists('convertor/fold{}/images/{}'.format(fold,path2save)):\n                    os.makedirs('convertor/fold{}/images/{}'.format(fold,path2save))\n                    # #检查是否存在文件夹'convertor/fold{}/labels/'.format(fold)+path2save，如果不存在，则创建该文件夹。\n                sh.copy(\"../input/global-wheat-detection/{}/{}.jpg\".format(source,name),'convertor/fold{}/images/{}/{}.jpg'.format(fold,path2save,name))\n                #最后，图像文件从原始位置复制到相应的训练和验证文件夹中。","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:29:46.770168Z","iopub.execute_input":"2024-04-13T10:29:46.770500Z","iopub.status.idle":"2024-04-13T10:29:46.789626Z","shell.execute_reply.started":"2024-04-13T10:29:46.770463Z","shell.execute_reply":"2024-04-13T10:29:46.788173Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from ensemble_boxes import weighted_boxes_fusion\n#用于合并重叠的边界框\ndef run_wbf(boxes, scores, image_size=1023, iou_thr=0.5, skip_box_thr=0.7, weights=None):\n    \n    labels = [np.zeros(score.shape[0]) for score in scores]\n    #初始化一个与 scores 列表长度相同的 labels 列表，其中每个元素都是一个全零的NumPy数组，其长度与对应 score 数组的长度相同。\n    boxes = [box/(image_size) for box in boxes]\n    #将 boxes 列表中的每个边界框坐标除以 image_size，以将其标准化到0到1的范围内\n    boxes, scores, labels = weighted_boxes_fusion(boxes, scores, labels, weights=None, iou_thr=iou_thr, skip_box_thr=skip_box_thr)\n    #调用 weighted_boxes_fusion 函数，传入标准化后的边界框、分数、标签和可选的权重。这个函数将执行加权边界框融合，并返回融合后的边界框、分数和标签。\n    boxes = boxes*(image_size)\n    #将融合后的边界框坐标乘以 image_size，以将其转换回原始像素空间。\n    return boxes, scores, labels\n    #返回融合后的边界框、分数和标签\n#分数通常指的是检测框的置信度分数，分数越高，表示模型越确信检测框内的内容符合某一类别的物体\n    \n#处理图像旋转时的边界框变换时非常有用，尤其是在使用测试时增强（TTA）技术时，需要确保边界框与旋转后的图像保持一致。\ndef TTAImage(image, index):\n    image1 = image.copy()\n    #创建了一个image的副本，命名为image1，这样在后续处理中不会修改原始的image\n    if index==0: \n        rotated_image = cv2.rotate(image1, cv2.ROTATE_90_CLOCKWISE)\n        return rotated_image\n    #如果index为0，它将image1顺时针旋转90度，并返回旋转后的图像。\n    elif index==1:\n        rotated_image2 = cv2.rotate(image1, cv2.ROTATE_90_CLOCKWISE)\n        rotated_image2 = cv2.rotate(rotated_image2, cv2.ROTATE_90_CLOCKWISE)\n        return rotated_image2\n    #如果index为1，它首先将image1顺时针旋转90度，然后再将结果顺时针旋转90度（总共旋转180度），并返回旋转后的图像。\n    elif index==2:\n        rotated_image3 = cv2.rotate(image1, cv2.ROTATE_90_CLOCKWISE)\n        rotated_image3 = cv2.rotate(rotated_image3, cv2.ROTATE_90_CLOCKWISE)\n        rotated_image3 = cv2.rotate(rotated_image3, cv2.ROTATE_90_CLOCKWISE)\n        return rotated_image3\n    #如果index为2，它首先将image1顺时针旋转90度，然后将结果再顺时针旋转90度，接着将再次旋转后的结果顺时针旋转90度（总共旋转270度），并返回旋转后的图像。\n    elif index == 3:\n        return image1\n    #如果index为3，它直接返回原始的image1，不进行任何旋转。\n    \n    \n    \n#确保了在旋转图像后，边界框也相应地进行了旋转，并且坐标仍然位于正确的位置。\ndef rotBoxes90(boxes, im_w, im_h):\n    ret_boxes =[]\n    #创建了一个空列表ret_boxes，用于存储旋转后的边界框坐标\n    for box in boxes:\n        x1, y1, x2, y2 = box\n        x1, y1, x2, y2 = x1-im_w//2, im_h//2 - y1, x2-im_w//2, im_h//2 - y2\n        #将边界框的坐标从图像坐标系转换为以图像中心为原点的坐标系。这样做是为了方便进行后续的旋转操作。这里通过减去图像宽度的一半和图像高度的一半来实现坐标变换。\n        x1, y1, x2, y2 = y1, -x1, y2, -x2\n        #对边界框的坐标进行90度的顺时针旋转\n        x1, y1, x2, y2 = int(x1+im_w//2), int(im_h//2 - y1), int(x2+im_w//2), int(im_h//2 - y2)\n        #将旋转后的坐标从以图像中心为原点的坐标系转换回图像坐标系。\n        x1a, y1a, x2a, y2a = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)\n        #为了确保边界框的坐标是有序的（即左上角坐标在左上角，右下角坐标在右下角），这里使用min和max函数重新计算(x1, y1)和(x2, y2)。\n        ret_boxes.append([x1a, y1a, x2a, y2a])\n        #将调整后的边界框坐标添加到ret_boxes列表中。\n    return np.array(ret_boxes)\n    #将ret_boxes列表转换为NumPy数组，并返回\n\n    \n#使用给定的深度学习模型对单个图像进行物体检测，并返回检测到的物体的边界框坐标和对应的置信度。\ndef detect1Image(im0, imgsz, model, device, conf_thres, iou_thres):\n    #im0：原始图像。imgsz：目标图像大小，模型输入所需的大小。model：预训练的深度学习模型。device：模型运行在哪个设备上，例如 'cuda' 或 'cpu'。conf_thres：置信度阈值，用于过滤低置信度的检测结果。iou_thres：IoU阈值，用于非极大值抑制（NMS）。\n    img = letterbox(im0, new_shape=imgsz)[0]\n    img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416\n    img = np.ascontiguousarray(img)\n    #使用`letterbox`函数调整图像大小到`imgsz`，同时保持图像的宽高比。将图像从BGR格式转换为RGB格式，并改变其维度顺序以匹配模型的输入格式。确保图像数据是连续的，这对于后续转换为PyTorch张量很重要。\n\n    img = torch.from_numpy(img).to(device)\n    img =  img.float()  # uint8 to fp16/32\n    img /= 255.0  \n    #将图像从NumPy数组转换为PyTorch张量。  将图像数据从uint8格式转换为float格式。将图像的像素值归一化到0-1范围。\n    if img.ndimension() == 3:\n        img = img.unsqueeze(0)\n    #如果图像是一个三维张量（HxWxC），则增加一个额外的维度使其成为一个四维张量（1xHxWxC），这是大多数深度学习模型所期望的输入格式。\n    # Inference\n    pred = model(img, augment=False)[0]\n    #使用预处理后的图像作为输入，对模型进行推理，得到检测结果。\n    # Apply NMS\n    pred = non_max_suppression(pred, conf_thres, iou_thres)\n    #使用NMS处理检测结果，去除重叠度较高的检测框，只保留置信度较高的框\n    boxes = []\n    scores = []\n    for i, det in enumerate(pred):  # detections per image\n        # save_path = 'draw/' + image_id + '.jpg'\n        if det is not None and len(det):\n            det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()\n            for *xyxy, conf, cls in det:\n                boxes.append([int(xyxy[0]), int(xyxy[1]), int(xyxy[2]), int(xyxy[3])])\n                scores.append(conf)\n    #遍历每个检测结果。  如果检测结果不为空，则将其坐标从模型输入大小缩放到原始图像大小。 提取每个检测框的坐标和置信度，并分别添加到`boxes`和`scores`列表中\n\n    return np.array(boxes), np.array(scores) \n   #将边界框和置信度转换为NumPy数组并返回。","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:29:46.791151Z","iopub.execute_input":"2024-04-13T10:29:46.791453Z","iopub.status.idle":"2024-04-13T10:29:46.828752Z","shell.execute_reply.started":"2024-04-13T10:29:46.791423Z","shell.execute_reply":"2024-04-13T10:29:46.827778Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Make pseudo labels for Yolov5","metadata":{}},{"cell_type":"code","source":"convertTrainLabel()","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:29:46.830294Z","iopub.execute_input":"2024-04-13T10:29:46.830695Z","iopub.status.idle":"2024-04-13T10:30:24.251830Z","shell.execute_reply.started":"2024-04-13T10:29:46.830661Z","shell.execute_reply":"2024-04-13T10:30:24.250978Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Retrain yolov5 with pseudo data","metadata":{}},{"cell_type":"markdown","source":"# Final prediction","metadata":{}},{"cell_type":"code","source":"from utils.datasets import *\nfrom utils.utils import *\ndef detect():\n    #配置和准备小麦检测任务的一些参数和变量\n    source = '../input/global-wheat-detection/test/'\n    #指定了测试图像所在的文件夹路径。\n    weights = '../input/yolov5/yolov5/bestv4.pt'\n    #指定了模型权重文件的默认路径\n    imgsz = 1024\n    #设置了输入图像的大小为 1024x1024\n    conf_thres = 0.5\n    iou_thres = 0.6\n    #conf_thres 是置信度阈值，用于过滤低置信度的检测结果。iou_thres` 是IoU（Intersection over Union）阈值，用于非极大值抑制（NMS）算法中。\n    is_TTA = True\n    #在检测时使用测试时增强技术，该技术通过对图像进行多次变换并平均结果来提高模型的性能。\n    imagenames =  os.listdir(source)\n    #使用 os.listdir 函数获取 source 文件夹下的所有文件名，并将它们存储在 imagenames 列表中\n    device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n    #代码检查是否有可用的CUDA设备（即GPU）。如果有，则设置 device 为 'cuda'，否则设置为 'cpu'。这决定了模型将在哪个设备上运行\n    \n    # 加载模型\n    model = torch.load(weights, map_location=device)['model'].float()  # load to FP32\n    #使用torch.load函数加载权重文件，map_location=device确保模型被加载到指定的设备（CPU或GPU）上。从加载的字典中取出'model'键对应的值，即模型本身将模型转换为浮点数（FP32）格式\n    #准备模型进行推理\n    model.to(device).eval()\n    #创建数据集:\n    dataset = LoadImages(source, img_size=imgsz)\n    \n    #初始化结果列表和可视化设置\n    results = []\n    #初始化一个空列表，用于存储检测结果。\n    fig, ax = plt.subplots(5, 2, figsize=(30, 70))\n    #创建一个5行2列的子图网格，并设置整个图的大小为宽30英寸、高70英寸。\n    count = 0\n    #初始化一个计数器，可能用于跟踪当前正在处理的图像数量或用于可视化过程中的索引\n    \n    \n    #通过循环遍历imagenames列表中存储的所有图像文件名\n    for name in imagenames:\n        #图像加载与检查:\n        image_id = name.split('.')[0]\n        #通过分割文件名，得到图像的image_id\n        im01 = cv2.imread('%s/%s.jpg'%(source,image_id))  # BGR\n        assert im01 is not None, 'Image Not Found '\n        # Padded resize\n        im_w, im_h = im01.shape[:2]\n        #图像预处理与TTA\n        #如果is_TTA为True，则对图像进行多次变换并分别进行模型推理，然后将结果合并\n        if is_TTA:\n            enboxes = []\n            enscores = []\n            for i in range(4):\n                im0 = TTAImage(im01, i)\n                boxes, scores = detect1Image(im0, imgsz, model, device, conf_thres, iou_thres)\n                for _ in range(3-i):\n                    boxes = rotBoxes90(boxes, im_w, im_h)\n                    \n                if 1: #i<3:\n                    enboxes.append(boxes)\n                    enscores.append(scores) \n            boxes, scores = detect1Image(im01, imgsz, model, device, conf_thres, iou_thres)\n            enboxes.append(boxes)\n            enscores.append(scores)\n\n            boxes, scores, labels = run_wbf(enboxes, enscores, image_size = im_w, iou_thr=0.6, skip_box_thr=0.5)\n            boxes = boxes.astype(np.int32).clip(min=0, max=im_w)\n        #如果is_TTA为False，则直接对原始图像进行模型推理\n        else:\n            boxes, scores = detect1Image(im01, imgsz, model, device, conf_thres, iou_thres)\n            \n            \n        #检测框处理\n        boxes[:, 2] = boxes[:, 2] - boxes[:, 0]\n        boxes[:, 3] = boxes[:, 3] - boxes[:, 1]\n        boxes = boxes[scores >= 0.05].astype(np.int32)\n        scores = scores[scores >=float(0.05)]\n        #调整检测框的坐标，过滤掉得分低于0.05的框，并将检测框坐标转换为整数类型\n        \n        #可视化\n        if count<10:\n            #sample = image.permute(1,2,0).cpu().numpy()\n            for box, score in zip(boxes,scores):\n                cv2.rectangle(im0,\n                              (box[0], box[1]),\n                              (box[2]+box[0], box[3]+box[1]),\n                              (220, 0, 0), 2)\n                cv2.putText(im0, '%.2f'%(score), (box[0], box[1]), cv2.FONT_HERSHEY_SIMPLEX ,  \n                   0.5, (255,255,255), 2, cv2.LINE_AA)\n            ax[count%5][count//5].imshow(im0)\n            count+=1\n        #如果count小于10（意味着只可视化前10个图像的检测结果），则在子图上绘制带有得分的检测框，并显示图像。\n        \n        #结果收集\n        result = {\n            'image_id': image_id,\n        }\n        results.append(result)\n        #为每个图像创建一个字典，包含image_id，并将其添加到results列表\n    return results\n    #返回结果","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:30:24.253132Z","iopub.execute_input":"2024-04-13T10:30:24.253425Z","iopub.status.idle":"2024-04-13T10:30:26.829884Z","shell.execute_reply.started":"2024-04-13T10:30:24.253394Z","shell.execute_reply":"2024-04-13T10:30:26.828783Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"results = detect()","metadata":{"execution":{"iopub.status.busy":"2024-04-13T10:30:26.831373Z","iopub.execute_input":"2024-04-13T10:30:26.831716Z","iopub.status.idle":"2024-04-13T10:30:45.075827Z","shell.execute_reply.started":"2024-04-13T10:30:26.831682Z","shell.execute_reply":"2024-04-13T10:30:45.074697Z"},"trusted":true},"execution_count":null,"outputs":[]}]}