[深度學習] 基於切片輔助超推理庫SAHI最佳化小目標識別

落痕的寒假發表於2023-01-03

物件檢測是迄今為止計算機視覺中最重要的應用領域。然而,小物體的檢測和大影像的推理仍然是實際使用中的主要問題,這是因為小目標物體有效特徵少,覆蓋範圍少。小目標物體的定義通常有兩種方式。一種是絕對尺度定義,即以物體的畫素尺寸來判斷是否為小目標,如在COCO資料集中,尺寸小於32×32畫素的目標被判定為小目標。另外一種是相對尺度定義,即以物體在影像中的佔比面積比例來判斷是否為小目標,例如國際光學工程學會SPIE定義,若目標尺寸小於原圖的0.12%則可以判定成小目標。
SAHI: Slicing Aided Hyper Inference(切片輔助超推理)透過影像切片的方式來檢測小目標。SAHI檢測過程可以描述為:透過滑動視窗將影像切分成若干區域,各個區域分別進行預測,同時也對整張圖片進行推理。然後將各個區域的預測結果和整張圖片的預測結果合併,最後用NMS(非極大值抑制)進行過濾。用動圖表示該識別過程如下:

SAHI的官方倉庫地址為:sahi。關於SAHI的使用可以閱讀官方demo和官方文件:sahi-demosahi-docs。如果想進一步瞭解SAHI具體工作效能和原理,可以閱讀官方發表的論文:Slicing Aided Hyper Inference and Fine-Tuning for Small Object Detection
SAHI安裝指令如下:

pip install sahi

本文所有演算法展示效果和程式碼見:

github: Python-Study-Notes

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:
        # 設定起始切分座標
        x_min = x_max = 0
        y_max = y_min + slice_height
        # 列迴圈
        while x_max < image_width:
            x_max = x_min + slice_width
            # 如果影像不夠切分,框往左或往上移動
            if 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
    
  3. 儲存圖片結果和標註結果,幷包裝返回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)
<matplotlib.image.AxesImage at 0x210a7583250>

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<00:00, 334.21it/s]
100%|████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 11.80it/s]

切分子圖12張
獲得標註框18個

展示切分後的子圖和標註框

axarr_row = 3
axarr_col = math.ceil(len(coco_dict['images']) / axarr_row)
f, axarr = plt.subplots(axarr_row, axarr_col, figsize=(10, 7))
for index, img in enumerate(coco_dict['images']):
    img = Image.open(os.path.join(output_dir, img["file_name"]))
    for ann_ind in range(len(coco_dict["annotations"])):
        # 搜尋與當前影像匹配的邊界框
        if coco_dict["annotations"][ann_ind]["image_id"] == coco_dict["images"][index]["id"]:
            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[int(index / axarr_col), int(index % axarr_col)].imshow(img)

png

1.2 影像預測

1.2.1 介面介紹

SHAI提供了影像切片預測的封裝介面,具體的函式介面如下:

AutoDetectionModel類

SAHI基於AutoDetectionModel類的from_pretrained函式載入深度學習模型。目前支援YOLOv5 models, MMDetection models, Detectron2 models和HuggingFace object detection models等深度學習模型庫,如果想支援新的模型庫,可以參考sahi/models目錄下的模型檔案,新建模型檢測類。

模型預測

  • 基於get_prediction函式呼叫模型預測單張圖片,也就是直接呼叫AutoDetectionModel類提供的模型,直接推理單張圖片。

  • 基於get_sliced_prediction函式以切分圖片的方式進行預測。在get_sliced_prediction函式內部會先切分圖片,然後對每個子圖單獨進行模型推理;如果設定了對整張原圖進行推理,那麼也會整合原圖推理的結果以增加模型精度。最後對所有的預測結果進行nms整合,相近的兩個預測框也會進行合併。get_sliced_prediction函式介面如下:

def get_sliced_prediction(
    image,
    detection_model=None,
    slice_height: int = None,
    slice_width: int = None,
    overlap_height_ratio: float = 0.2,
    overlap_width_ratio: float = 0.2,
    perform_standard_pred: bool = True, # 是否單獨對原圖進行識別
    postprocess_type: str = "GREEDYNMM", # 合併結果的方式,可選'NMM', 'GRREDYNMM', 'NMS'
    postprocess_match_metric: str = "IOS", # NMS匹配方式IOU或者IOS
    postprocess_match_threshold: float = 0.5, # 匹配置信度
    postprocess_class_agnostic: bool = False, # 在合併結果時,是否將不同類別的檢測框放在一起處理
    verbose: int = 1, 
    merge_buffer_length: int = None, # 低配裝置使用,以加快處理
    auto_slice_resolution: bool = True,
)
  • 基於predict函式進行批處理,predict函式進一步封裝了識別程式碼,如果想使用該函式,閱讀predict原始碼引數介面即可。

1.2.2 應用例項

直接預測圖片

from sahi import AutoDetectionModel
from sahi.predict import get_prediction

# 初始化檢測模型,缺少yolov5程式碼,pip install yolov5即可
detection_model = AutoDetectionModel.from_pretrained(
    model_type='yolov5', # 模型型別
    model_path='./yolov5n.pt', # 模型檔案路徑
    confidence_threshold=0.3, # 檢測閾值
    device="cpu",  # or 'cuda:0'
);
image = 'image/small-vehicles1.jpeg'

# 獲得模型直接預測結果
result = get_prediction(image, detection_model)

# result是SAHI的PredictionResult物件,可獲得推理時間,檢測影像,檢測影像尺寸,檢測結果
# 檢視標註框,可以用於儲存為其他格式
for pred in result.object_prediction_list:
    bbox = pred.bbox  # 標註框BoundingBox物件,可以獲得邊界框的座標、面積
    category = pred.category  # 類別Category物件,可獲得類別id和類別名
    score = pred.score.value  # 預測置信度

# 儲存檔案結果
export_dir = "result"
file_name = "res"
result.export_visuals(export_dir=export_dir, file_name=file_name)

# 展示結果
from PIL import Image
import os
image_path = os.path.join(export_dir,file_name+'.png')
img = Image.open(image_path).convert('RGB')
img

png

切片預測圖片

from sahi import AutoDetectionModel
from sahi.predict import get_sliced_prediction

# 初始化檢測模型
detection_model = AutoDetectionModel.from_pretrained(
    model_type='yolov5',
    model_path='yolov5n.pt',
    confidence_threshold=0.3,
    device="cpu",  # or 'cuda:0'
)
image = 'image/small-vehicles1.jpeg'


result = get_sliced_prediction(
    image,
    detection_model,
    slice_height = 256,
    slice_width = 256,
    overlap_height_ratio = 0.2,
    overlap_width_ratio = 0.2,
    perform_standard_pred = True,
)

# result是SAHI的PredictionResult物件,可獲得推理時間,檢測影像,檢測影像尺寸,檢測結果
# 檢視標註框,可以用於儲存為其他格式
for pred in result.object_prediction_list:
    bbox = pred.bbox  # 標註框BoundingBox物件,可以獲得邊界框的座標、面積
    category = pred.category  # 類別Category物件,可獲得類別id和類別名
    score = pred.score.value  # 預測置信度

# 儲存檔案結果
export_dir = "result"
file_name = "res"
result.export_visuals(export_dir=export_dir, file_name=file_name)
# 結果匯出為coco標註形式
coco_anno = result.to_coco_annotations()
# 結果匯出為coco預測形式
coco_pred = result.to_coco_predictions()

# 展示結果
from PIL import Image
import os
image_path = os.path.join(export_dir,file_name+'.png')
img = Image.open(image_path).convert('RGB')
img

Performing prediction on 15 number of slices.

png

相對單張圖片直接識別,透過切片的方式能夠識別到更多的小目標。由於使用的模型是yolov5n,可以看到一些識別結果不正確,比如同一輛車在不同子圖被分別識別為卡車或汽車,一種好的解決辦法是將postprocess_class_agnostic引數設定為True,將不同類別的檢測框放在一起進行合併,同時降低 postprocess_match_threshold以濾除結果。

image = 'image/small-vehicles1.jpeg'


result = get_sliced_prediction(
    image,
    detection_model,
    slice_height = 256,
    slice_width = 256,
    overlap_height_ratio = 0.2,
    overlap_width_ratio = 0.2,
    perform_standard_pred = True,
    postprocess_match_threshold = 0.2,
    postprocess_class_agnostic = True,
)


# 儲存檔案結果
export_dir = "result"
file_name = "res"
result.export_visuals(export_dir=export_dir, file_name=file_name)

# 展示結果
from PIL import Image
import os
image_path = os.path.join(export_dir,file_name+'.png')
img = Image.open(image_path).convert('RGB')
img
Performing prediction on 15 number of slices.

png

1.3 SAHI工具函式

SAHI提供多個工具函式以處理COCO資料集,具體使用可以閱讀sahi-docs-coco

1.3.1 coco資料集製作與精度分析

以下程式碼建立了coco標註資料,並儲存到本地

from sahi.utils.file import save_json
from sahi.utils.coco import Coco, CocoCategory, CocoImage, CocoAnnotation,CocoPrediction


# 建立coco物件
coco = Coco()

# 新增類
coco.add_category(CocoCategory(id=0, name='human'))
coco.add_category(CocoCategory(id=1, name='vehicle'))

# 迴圈遍歷影像
for i in range(3):
    # 建立單個影像
    coco_image = CocoImage(
        file_name="image{}.jpg".format(i), height=1080, width=1920)

    # 新增影像對應的標註
    coco_image.add_annotation(
        CocoAnnotation(
            # [x_min, y_min, width, height]
            bbox=[0, 0, 200, 200],
            category_id=0,
            category_name='human'
        )
    )
    coco_image.add_annotation(
        CocoAnnotation(
            bbox=[200, 100, 300, 300],
            category_id=1,
            category_name='vehicle'
        )
    )
    
    # 新增影像預測資料
    coco_image.add_prediction(
      CocoPrediction(
        score=0.864434,
        bbox=[0, 0, 150, 150],
        category_id=0,
        category_name='human'
      )
    )
    coco_image.add_prediction(
      CocoPrediction(
        score=0.653424,
        bbox=[200, 100, 250, 200],
        category_id=1,
        category_name='vehicle'
      )
)
    # 將影像新增到coco物件
    coco.add_image(coco_image)

# 提取json標註資料,不會儲存影像預測結果
coco_json = coco.json

# 將json標註資料儲存為json本地檔案
save_json(coco_json, "coco_dataset.json")

# 提取預測結果json檔案,並儲存到本地
predictions_array = coco.prediction_array
save_json(predictions_array, "coco_predictions.json")

當我們獲得了預測資料,我們可以基於pycocotools工具分析預測資料的精度,pycocotools是目標檢測必備工具,官方倉庫地址為cocoapi,結果分析程式碼如下:

# 需要單獨安裝pycocotools
from pycocotools.cocoeval import COCOeval
from pycocotools.coco import COCO

coco_ground_truth = COCO(annotation_file="coco_dataset.json")
coco_predictions = coco_ground_truth.loadRes("coco_predictions.json")

coco_evaluator = COCOeval(coco_ground_truth, coco_predictions, "bbox")
# 進行匹配計算
coco_evaluator.evaluate()
# 進行結果的累加
coco_evaluator.accumulate()
# 輸出結果
coco_evaluator.summarize()
loading annotations into memory...
Done (t=0.00s)
creating index...
index created!
Loading and preparing results...
DONE (t=0.00s)
creating index...
index created!
Running per image evaluation...
Evaluate annotation type *bbox*
DONE (t=0.00s).
Accumulating evaluation results...
DONE (t=0.01s).
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.200
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.200
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.200
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.200
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.200
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.200

統計資料集標註資訊

from sahi.utils.coco import Coco

coco = Coco.from_coco_dict_or_path("coco_dataset.json")

# 獲得資料集狀態,指標說明看欄位名就能懂
stats = coco.stats
stats
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 1504.59it/s]





{'num_images': 3,
 'num_annotations': 6,
 'num_categories': 2,
 'num_negative_images': 0,
 'num_images_per_category': {'human': 3, 'vehicle': 3},
 'num_annotations_per_category': {'human': 3, 'vehicle': 3},
 'min_num_annotations_in_image': 2,
 'max_num_annotations_in_image': 2,
 'avg_num_annotations_in_image': 2.0,
 'min_annotation_area': 40000,
 'max_annotation_area': 90000,
 'avg_annotation_area': 65000.0,
 'min_annotation_area_per_category': {'human': 40000, 'vehicle': 90000},
 'max_annotation_area_per_category': {'human': 40000, 'vehicle': 90000}}

預測結果過濾

from sahi.utils.file import save_json
from sahi.utils.coco import remove_invalid_coco_results

# 去除預測結果中的無效邊界框,如邊界框座標為負的結果
coco_results = remove_invalid_coco_results("coco_predictions.json")

save_json(coco_results, "fixed_coco_result.json")

# 根據資料集實際標註資訊,進一步去除邊界框座標超過影像長寬的結果
coco_results = remove_invalid_coco_results("coco_predictions.json", "coco_dataset.json")

1.3.2 coco資料集處理

切分資料集

from sahi.utils.coco import Coco

# 指定coco檔案
coco_path = "coco_dataset.json"

# 初始coco物件
coco = Coco.from_coco_dict_or_path(coco_path)

# 拆分資料集為訓練集和驗證集,訓練集影像佔比0.85
result = coco.split_coco_as_train_val(
  train_split_rate=0.85
)

# 儲存訓練集和驗證集
save_json(result["train_coco"].json, "train_split.json")
save_json(result["val_coco"].json, "val_split.json")
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 3005.95it/s]

修改標註類別

from sahi.utils.coco import Coco
from sahi.utils.file import save_json


coco = Coco.from_coco_dict_or_path("coco_dataset.json")
print("標註類別:{}".format(coco.category_mapping))

# 修改資料集類別
# 將標註中human類的索引改為3,將原先vehicle類的標註刪除
# 新加big_vehicle類和car類
desired_name2id = {
  "big_vehicle": 1,
  "car": 2,
  "human": 3
}
# 更新標註類別
coco.update_categories(desired_name2id)

print("修改後標註類別:{}".format(coco.category_mapping))

# 儲存結果
save_json(coco.json, "updated_coco.json")
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 1002.78it/s]

標註類別:{0: 'human', 1: 'vehicle'}
修改後標註類別:{1: 'big_vehicle', 2: 'car', 3: 'human'}

按照標註框面積過濾資料集

from sahi.utils.coco import Coco
from sahi.utils.file import save_json

# 開啟標註資料
coco = Coco.from_coco_dict_or_path("coco_dataset.json")

# 過濾包含標註框面積小於min的影像
area_filtered_coco = coco.get_area_filtered_coco(min=50000)
# 過濾標註框面積不在[min,max]的影像
area_filtered_coco = coco.get_area_filtered_coco(min=50, max=80000)
# 篩選同時符合多個類別面積要求的影像
intervals_per_category = {
  "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")
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 1503.69it/s]

過濾無標註的圖片

from sahi.utils.coco import Coco
from sahi.utils.file import save_json
# 去除無標註框的圖片
coco = Coco.from_coco_dict_or_path("coco_dataset.json", ignore_negative_samples=True)
# 匯出資料
# save_json(coco.json, "coco_ignore_negative.json")
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 3007.39it/s]

裁剪標註框

from sahi.utils.coco import Coco
from sahi.utils.file import save_json


coco_path = "coco_dataset.json"

# 將溢位邊界框剪裁為影像寬度和高度
coco = Coco.from_coco_dict_or_path(coco_path, clip_bboxes_to_img_dims=True)

# 對已有coco物件,將溢位邊界框剪裁為影像寬度和高度
coco = coco.get_coco_with_clipped_bboxes()

save_json(coco.json, "coco.json")
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 1007.04it/s]

合併coco資料集

# from sahi.utils.coco import Coco
# from sahi.utils.file import save_json

# coco_1 = Coco.from_coco_dict_or_path("coco1.json", image_dir="images_1/")
# coco_2 = Coco.from_coco_dict_or_path("coco2.json", image_dir="images_2/")

# # 合併資料集
# coco_1.merge(coco_2)

# # 儲存
# save_json(coco_1.json, "merged_coco.json")

下采樣資料集

from sahi.utils.coco import Coco
from sahi.utils.file import save_json
coco_path = "coco_dataset.json"

coco = Coco.from_coco_dict_or_path(coco_path)

# 用1/10的影像建立Coco物件
# subsample_ratio表示每10張影像取1張影像
subsampled_coco = coco.get_subsampled_coco(subsample_ratio=10)

# 僅對包含標註框為category_id的影像進行下采樣,category_i=-1時表示負樣本
subsampled_coco = coco.get_subsampled_coco(subsample_ratio=10, category_id=0)

# 儲存資料集
save_json(subsampled_coco.json, "subsampled_coco.json")
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 1512.19it/s]

上取樣資料集

from sahi.utils.coco import Coco
from sahi.utils.file import save_json
coco_path = "coco_dataset.json"

coco = Coco.from_coco_dict_or_path(coco_path)

# 每個樣本重複10次
upsampled_coco = coco.get_upsampled_coco(upsample_ratio=10)


# 僅對包含標註框為category_id的影像進行取樣,category_i=-1時表示負樣本
subsampled_coco = coco.get_upsampled_coco(upsample_ratio=10, category_id=0)


# 匯出資料集
save_json(upsampled_coco.json, "upsampled_coco.json")
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 1503.51it/s]

1.3.3 coco資料集轉換

匯出為yolov5格式並分割資料集

from sahi.utils.coco import Coco

# 注意image_dir路徑
coco = Coco.from_coco_dict_or_path("coco_dataset.json", image_dir="images/")

# 匯出為yolov5資料集格式,train_split_rate設定訓練集資料比例
# coco.export_as_yolov5(
#   output_dir="output/",
#   train_split_rate=0.85
# )
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 3/3 [00:00<00:00, 1002.22it/s]

將訓練集和驗證集匯出為yolov5格式

from sahi.utils.coco import Coco, export_coco_as_yolov5

# 注意image_dir路徑
train_coco = Coco.from_coco_dict_or_path("train_split.json", image_dir="images/")
val_coco = Coco.from_coco_dict_or_path("val_split.json", image_dir="images/")

# 匯出資料集
# data_yml_path = export_coco_as_yolov5(
#   output_dir="output",
#   train_coco=train_coco,
#   val_coco=val_coco
# )
indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 2/2 [00:00<00:00, 1002.34it/s]


indexing coco dataset annotations...


Loading coco annotations: 100%|████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1003.42it/s]

1.4 總結

目標檢測過程中,透過對高解析度小目標影像進行滑動視窗切片,能夠有效提高大解析度小目標影像的識別精度。但是滑動切片識別有需要注意的地方:

  • 需要影像資料集是否符合通用的高分辨小目標影像標準,如果對普通資料集進行切片識別容易拆分已有目標物體,這樣做浪費推理時間也會導致最終檢測結果精度不高。
  • 滑動切片對識別模型的精度有一定的要求,一般來說模型越大精度越高,但是切片識別所花費的推理時間也越長。所以需要平衡模型精度和模型推理時間,而且也要確定滑動切片的尺度。
  • 滑動切片識別在識別目標類別較少的任務中,識別精度更高,因為後處理能過濾很多重複識別檢測框。

如果想了解其他的小目標識別方案,可以看看paddle家的paddledetection-smalldet。paddle提供了基於原圖和基於切圖的小目標識別方案,也提供了統計資料集尺寸分佈的程式碼(該統計程式碼對某些特定的資料集效果不好,具體原因看看程式碼)。推薦看看PaddleDetection的小目標識別方案,做的很不錯。

2 參考

相關文章