[深度学习] 基于切片辅助超推理库SAHI优化小目标识别

慈云数据 2024-03-12 技术支持 112 0

对象检测是迄今为止计算机视觉中最重要的应用领域。然而,小物体的检测和大图像的推理仍然是实际使用中的主要问题,这是因为小目标物体有效特征少,覆盖范围少。小目标物体的定义通常有两种方式。一种是绝对尺度定义,即以物体的像素尺寸来判断是否为小目标,如在COCO数据集中,尺寸小于32×32像素的目标被判定为小目标。另外一种是相对尺度定义,即以物体在图像中的占比面积比例来判断是否为小目标,例如国际光学工程学会SPIE定义,若目标尺寸小于原图的0.12%则可以判定成小目标。

SAHI: Slicing Aided Hyper Inference(切片辅助超推理)通过图像切片的方式来检测小目标。SAHI检测过程可以描述为:通过滑动窗口将图像切分成若干区域,各个区域分别进行预测,同时也对整张图片进行推理。然后将各个区域的预测结果和整张图片的预测结果合并,最后用NMS(非极大值抑制)进行过滤。用动图表示该识别过程如下:

SAHI的官方仓库地址为:sahi。关于SAHI的使用可以阅读官方demo和官方文档:sahi-demo和sahi-docs。如果想进一步了解SAHI具体工作性能和原理,可以阅读官方发表的论文:Slicing Aided Hyper Inference and Fine-Tuning for Small Object Detection。

SAHI安装指令如下:

pip install sahi

本文所有算法展示效果和代码见:

github: Python-Study-Notes

文章目录

  • 1 SAHI使用
    • 1.1 图像切片
      • 1.1.1 单张图像切片
      • 1.1.2 COCO数据集切片
      • 1.2 图像预测
        • 1.2.1 接口介绍
        • 1.2.2 应用实例
        • 1.3 SAHI工具函数
          • 1.3.1 coco数据集制作与精度分析
          • 1.3.2 coco数据集处理
          • 1.3.3 coco数据集转换
          • 1.4 总结
          • 2 参考

            1 SAHI使用

            import sahi
            # 打印sahi版本
            print(sahi.__version__)
            
            0.11.6
            

            1.1 图像切片

            SAHI提供了封装好的函数接口,以切分输入图像和其标注数据。切分后的子图及其标注数据可以用于识别,或者保存为本地数据以供模型训练。

            1.1.1 单张图像切片

            SAHI提供slice_image函数以切分单张图片及其标注文件(仅支持coco标注文件),slice_image函数接口介绍如下:

            # 返回SAHI的图像分片结果类SliceImageResult
            def slice_image(
                image: Union[str, Image.Image], # 单张图像地址或单个pillow image对象,必填参数
                coco_annotation_list: Optional[CocoAnnotation] = None, # coco标注文件
                output_file_name: Optional[str] = None, # 输出文件名前缀
                output_dir: Optional[str] = None, # 输出文件地址
                slice_height: int = None, # 子图切分高度
                slice_width: int = None, # 子图切分宽度
                overlap_height_ratio: float = None, # 子图高度间的重叠率
                overlap_width_ratio: float = None, # 子图宽度间的重叠率
                auto_slice_resolution: bool = True, # 如果没有设置slice_height和slice_width,则自动确定slice_height、slice_width、overlap_height_ratio、overlap_width_ratio
                min_area_ratio: float = 0.1, # 子图中标注框小于原始标注框占比,则放弃该标注框
                out_ext: Optional[str] = None, # 图像后缀格式
                verbose: bool = False, # 是否打印详细信息
            ) 
            

            slice_image函数源代码位于sahi/slicing.py中,这段代码可以单步调试看看怎么运行的,主要逻辑如下:

            1. 获得pillow image图像对象

            2. 调用get_slice_bboxes函数切分图像

              • 获得切分参数
                if slice_height and slice_width:
                    # 计算重叠像素
                    y_overlap = int(overlap_height_ratio * slice_height)
                    x_overlap = int(overlap_width_ratio * slice_width)
                elif auto_slice_resolution:
                    x_overlap, y_overlap, slice_width, slice_height = get_auto_slice_params(height=image_height, width=image_width)
                
                • 循环切分图像
                  # 行循环
                  while y_max  image_height or x_max > image_width:
                              xmax = min(image_width, x_max)
                              ymax = min(image_height, y_max)
                              xmin = max(0, xmax - slice_width)
                              ymin = max(0, ymax - slice_height)
                              slice_bboxes.append([xmin, ymin, xmax, ymax])
                          else:
                              slice_bboxes.append([x_min, y_min, x_max, y_max])
                          # 下一次切分从本次切分图像x_max-x_overlap开始
                          x_min = x_max - x_overlap
                      y_min = y_max - y_overlap
                  
                • 保存图片结果和标注结果,并包装返回SliceImageResult对象

            以下代码演示了对单张图片进行切片,并将切分后的子图保存到本地。

            展示原图

            # 展示输入图片
            from PIL import Image
            # 图像地址:https://github.com/obss/sahi/tree/main/demo/demo_data
            image_path = "image/small-vehicles1.jpeg"
            img = Image.open(image_path).convert('RGB')
            img
            

            png

            切分图片

            from sahi.slicing import slice_image
            # 输出文件名前缀
            output_file_name = "slice"
            # 输出文件夹
            output_dir = "result"
            # 切分图像
            slice_image_result = slice_image(
                image=image_path,
                output_file_name=output_file_name,
                output_dir=output_dir,
                slice_height=256,
                slice_width=256,
                overlap_height_ratio=0.2,
                overlap_width_ratio=0.2,
                verbose=False,
            )
            print("原图宽{},高{}".format(slice_image_result.original_image_width, slice_image_result.original_image_height))
            # 切分后的子图以形式:图像前缀_所在原图顶点坐标来保存文件
            print("切分子图{}张".format(len(slice_image_result.filenames)))
            
            原图宽1068,高580
            切分子图15张
            

            展示切分后的子图

            import matplotlib.pyplot as plt
            from PIL import Image
            import math
            import os
            axarr_row = 3
            axarr_col = math.ceil(len(slice_image_result.filenames)/axarr_row)
            f, axarr = plt.subplots(axarr_row, axarr_col, figsize=(14,7))
            for index, file in enumerate(slice_image_result.filenames):
                img = Image.open(os.path.join(slice_image_result.image_dir,file))
                axarr[int(index/axarr_col), int(index%axarr_col)].imshow(img)
            

            png

            1.1.2 COCO数据集切片

            SAHI提供slice_coco函数以切分coco数据集(仅支持coco数据集)。slice_coco函数接口介绍如下:

            # 返回切片后的coco标注字典文件,coco文件保存地址
            def slice_coco(
                coco_annotation_file_path: str, # coco标注文件
                image_dir: str, # coco图像集地址
                output_coco_annotation_file_name: str, # 输出coco标注集文件名,不需要加文件类型后缀
                output_dir: Optional[str] = None, # 输出文件地址
                ignore_negative_samples: bool = False, # 是否忽略没有标注框的子图
                slice_height: int = 512, # 切分子图高度
                slice_width: int = 512, # 切分子图宽度
                overlap_height_ratio: float = 0.2, # 子图高度之间的重叠率
                overlap_width_ratio: float = 0.2, # 子图宽度之间的重叠率
                min_area_ratio: float = 0.1, # 如果没有设置slice_height和slice_width,则自动确定slice_height、slice_width、overlap_height_ratio、overlap_width_ratio
                out_ext: Optional[str] = None,  # 保存图像的扩展
                verbose: bool = False, # 是否打印详细信息
            )
            

            slice_coco函数源代码位于sahi/slicing.py中,这段代码可以单步调试看看怎么做的,主要逻辑如下:

            1. 读取coco文件和图片信息
            2. 循环读取coco数据集的图片,每张图片调用get_slice_bboxes函数切分图像
            3. 创建coco dict结果并保存文件

            以下代码演示了对coco数据集进行切片,并将切分后的子图和标注文件保存到本地。coco数据集可以包含若干张图片,但是以下代码示例中只包含一张图片,方便演示。

            展示数据集

            # 展示图像
            from PIL import Image, ImageDraw
            from sahi.utils.file import load_json
            import matplotlib.pyplot as plt
            import os
            # coco图像集地址
            image_path = "image"
            # coco标注文件
            coco_annotation_file_path="image/terrain2_coco.json"
            # 加载数据集
            coco_dict = load_json(coco_annotation_file_path)
            f, axarr = plt.subplots(1, 1, figsize=(8, 8))
            # 读取图像
            img_ind = 0
            img = Image.open(os.path.join(image_path,coco_dict["images"][img_ind]["file_name"])).convert('RGBA')
            # 绘制标注框
            for ann_ind in range(len(coco_dict["annotations"])):
                xywh = coco_dict["annotations"][ann_ind]["bbox"]
                xyxy = [xywh[0], xywh[1], xywh[0] + xywh[2], xywh[1] + xywh[3]]
                ImageDraw.Draw(img, 'RGBA').rectangle(xyxy, width=5)
            axarr.imshow(img)
            
             
            

            png

            切分数据集

            from sahi.slicing import slice_coco
            # 保存的coco数据集标注文件名
            output_coco_annotation_file_name="sliced"
            # 输出文件夹
            output_dir = "result"
            # 切分数据集
            coco_dict, coco_path = slice_coco(
                coco_annotation_file_path=coco_annotation_file_path,
                image_dir=image_path,
                output_coco_annotation_file_name=output_coco_annotation_file_name,
                ignore_negative_samples=False,
                output_dir=output_dir,
                slice_height=320,
                slice_width=320,
                overlap_height_ratio=0.2,
                overlap_width_ratio=0.2,
                min_area_ratio=0.2,
                verbose=False
            )
            print("切分子图{}张".format(len(coco_dict['images'])))
            print("获得标注框{}个".format(len(coco_dict['annotations'])))
            
            indexing coco dataset annotations...
            Loading coco annotations: 100%|█████████████████████████████████████████████████████████| 1/1 [00:00
              "big_vehicle": 1,
              "car": 2,
              "human": 3
            }
            # 更新标注类别
            coco.update_categories(desired_name2id)
            print("修改后标注类别:{}".format(coco.category_mapping))
            # 保存结果
            save_json(coco.json, "updated_coco.json")
            
              "human": {"min": 20, "max": 30000},
              "vehicle": {"min": 50, "max": 90000},
            }
            area_filtered_coco = coco.get_area_filtered_coco(intervals_per_category=intervals_per_category)
            # 导出数据
            save_json(area_filtered_coco.json, "area_filtered_coco.json")
            
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon