目标检测项目中面对高分辨率图像的滑动窗口技术(一)(代码开源,超简便API封装,直接调用进行切图及保存)

本文主要是介绍目标检测项目中面对高分辨率图像的滑动窗口技术(一)(代码开源,超简便API封装,直接调用进行切图及保存),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、技术背景

二、解决方法介绍:滑动窗口切图、随机中心点切图

三、程序代码

四、使用文档

一、技术背景

        在目标检测项目中,面对高分辨率、小目标的图片数据(如航拍图片数据),若对图片直接resize到模型合适的大小,会损失大量信息,模型无法学到信息,因此需对大分辨率图片进行处理,常见的技术方法有:滑动窗口、随机中心点切图

相关知识等详情可参考以下博客:

YOLOv5 小目标检测、无人机视角小目标检测_liguiyuan112的博客-CSDN博客_yolov5 小目标

二、解决方法介绍:滑动窗口切图、随机中心点切图

1、滑动窗口切图:设置一个指定大小的窗口,对高分辨率图像进行滑动切分,由于切分可能导致目标图像被分割,因此可设置重叠率overlap使相邻切分子图之间具有重叠部分,可以较好解决目标被分割的情况,但仍可能隐式存在切分子图目标框不完整的情况,设置指定iou值,当新目标框与原图上的目标框的iou值大于一定值,才进行保存其子图目标框信息,若小于则去除,即子图上标签中不存在该目标框对象。 

原图:

滑动切图效果:

2、随机中心点切图:针对一张图上的所有目标框,按照一定的分辨率以每个目标框的中心点为子图中心进行裁剪一次,但由于都以目标框中心为子图中心可能使得模型过拟合于中心点位置,这是不可取的,因此加上随机偏差进行裁剪。注:虽训练集使用随机中心点切图方式,但仍然对测试集进行滑动切图再预测。

随机中心点切图效果:如下具有几个目标框就有几个切分子图

 三、程序代码

        废话少说,直接上代码,已全部封装好,均为独立完成,创作不易,转发请标注本文地址,后文给出使用文档,安装好环境后(matplotlib、numpy、pandas、torch、PIL)直接调包进行切图及保存即可。

        两个封装类:slidingWindowCrop、randomCenterCrop

Crop.py

import osos.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"import randomimport numpy as np
import pandas as pd
import matplotlib.pyplot as pltplt.rcParams['font.family'] = 'SimHei'  # 正常显示中文
plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号from PIL import Imagefrom torch.utils.data.dataset import Datasetfrom torchvision.transforms import transformsdef exist_objs(list_1, list_2, new_box_iou_limit=0.35):'''list_1:当前slice的图像list_2:原图中的所有目标return:原图中位于当前slicze中的目标集合'''return_objs = []# 当该原图无目标框时返回空列表if len(list_2) == 0:return return_objs# 判断新框与旧框的iou是否满足限制条件,若满足则将新框保留作为子图的目标框def judge_iou_limit():new_box_area = (xmax_new - xmin_new) * (ymax_new - ymin_new)if new_box_area / (new_box_area + box_area) >= new_box_iou_limit:return_objs.append([category, xmin_new, ymin_new, xmax_new, ymax_new])s_xmin, s_ymin, s_xmax, s_ymax = list_1[0], list_1[1], list_1[2], list_1[3]for object_box in list_2:category, xmin, ymin, xmax, ymax = object_box[0], object_box[1], object_box[2], object_box[3], object_box[4]box_area = (xmax - xmin) * (ymax - ymin)# 1if s_xmin <= xmin < s_xmax and s_ymin <= ymin < s_ymax:  # 目标点的左上角在切图区域中if s_xmin < xmax <= s_xmax and s_ymin < ymax <= s_ymax:  # 目标点的右下角在切图区域中xmin_new = xmin - s_xminymin_new = ymin - s_yminxmax_new = xmin_new + (xmax - xmin)ymax_new = ymin_new + (ymax - ymin)judge_iou_limit()if s_xmin <= xmin < s_xmax and ymin < s_ymin:  # 目标点的左上角在切图区域上方# 2if s_xmin < xmax <= s_xmax and s_ymin < ymax <= s_ymax:  # 目标点的右下角在切图区域中xmin_new = xmin - s_xminymin_new = 0xmax_new = xmax - s_xminymax_new = ymax - s_yminjudge_iou_limit()# 3if xmax > s_xmax and s_ymin < ymax <= s_ymax:  # 目标点的右下角在切图区域右方xmin_new = xmin - s_xminymin_new = 0xmax_new = s_xmax - s_xminymax_new = ymax - s_yminjudge_iou_limit()if s_ymin < ymin <= s_ymax and xmin < s_xmin:  # 目标点的左上角在切图区域左方# 4if s_xmin < xmax <= s_xmax and s_ymin < ymax <= s_ymax:  # 目标点的右下角在切图区域中xmin_new = 0ymin_new = ymin - s_yminxmax_new = xmax - s_xminymax_new = ymax - s_yminjudge_iou_limit()# 5if s_xmin < xmax < s_xmax and ymax >= s_ymax:  # 目标点的右下角在切图区域下方xmin_new = 0ymin_new = ymin - s_yminxmax_new = xmax - s_xminymax_new = s_ymax - s_yminjudge_iou_limit()# 6if s_xmin >= xmin and ymin <= s_ymin:  # 目标点的左上角在切图区域左上方if s_xmin < xmax <= s_xmax and s_ymin < ymax <= s_ymax:  # 目标点的右下角在切图区域中xmin_new = 0ymin_new = 0xmax_new = xmax - s_xminymax_new = ymax - s_yminjudge_iou_limit()# 7if s_xmin <= xmin < s_xmax and s_ymin <= ymin < s_ymax:  # 目标点的左上角在切图区域中if ymax >= s_ymax and xmax >= s_xmax:  # 目标点的右下角在切图区域右下方xmin_new = xmin - s_xminymin_new = ymin - s_yminxmax_new = s_xmax - s_xminymax_new = s_ymax - s_yminjudge_iou_limit()# 8if s_xmin < xmax < s_xmax and ymax >= s_ymax:  # 目标点的右下角在切图区域下方xmin_new = xmin - s_xminymin_new = ymin - s_yminxmax_new = xmax - s_xminymax_new = s_ymax - s_yminjudge_iou_limit()# 9if xmax > s_xmax and s_ymin < ymax <= s_ymax:  # 目标点的右下角在切图区域右方xmin_new = xmin - s_xminymin_new = ymin - s_yminxmax_new = s_xmax - s_xminymax_new = ymax - s_yminjudge_iou_limit()return return_objs# 通过子图宽高以及重叠率来计算得到行/列切分位置列表
def computeSlicePosition(WidthOrHeight, sliceWidthOrHeight, overlap):# 计算步长dx_or_dy = int(sliceWidthOrHeight * (1 - overlap))sp = np.array(range(0, WidthOrHeight, dx_or_dy))# 获取最终切点位置:当切点位置加上子图宽/高大于原图宽高时end_index = list(sp + sliceWidthOrHeight >= WidthOrHeight).index(True) + 1return sp[:end_index].tolist()def slice_imag(image, sliceWidth=2200, sliceHeight=1900, image_name=None, object_list=[],overlap=0.5, new_box_iou_limit=0.4, figsize=(10, 8), imshow=False, label_names=None):"""object_list:原图labelsoverlap:分割子图间的重叠部分(长、宽)new_box_area_limit:子图上的目标框面积限制(小于原图目标框面积指定比例则去除该框)"""#     print('name:',image_name,'width:',image.shape[2],'height:',image.shape[1])n_imgs = 0  # 表示第几张子图slice_images = []  # 存储切分后的子图exiset_obj_lists = []  # 存储每个子图的目标框# 存储每行/每列的切分位置rangeHeight = computeSlicePosition(image.shape[1], sliceHeight, overlap)rangeWidth = computeSlicePosition(image.shape[2], sliceWidth, overlap)#     print(rangeHeight,rangeWidth)if imshow:nrow = len(rangeHeight)ncol = len(rangeWidth)print('行列数为:', nrow, ncol)fig, axes = plt.subplots(nrow, ncol, figsize=figsize)axes = axes.flatten()for y0 in rangeHeight:for x0 in rangeWidth:n_imgs += 1if y0 + sliceHeight >= image.shape[1]:y = image.shape[1] - sliceHeightelse:y = y0if x0 + sliceWidth >= image.shape[2]:x = image.shape[2] - sliceWidthelse:x = x0slice_xmax = x + sliceWidthslice_ymax = y + sliceHeightsub_image = image[:, y:slice_ymax, x:slice_xmax]slice_images.append(sub_image)# 得到每个分割子图上的目标位置信息exiset_obj_list = exist_objs([x, y, slice_xmax, slice_ymax], object_list,new_box_iou_limit)#             print(exiset_obj_list)exiset_obj_lists.append(exiset_obj_list)if imshow:# 展示分割后的子图axes[n_imgs - 1].imshow(sub_image.permute((1, 2, 0)).numpy())axes[n_imgs - 1].axes.get_xaxis().set_visible(False)axes[n_imgs - 1].axes.get_yaxis().set_visible(False)# 在新的子图上展示目标框for category, *position in exiset_obj_list:axes[n_imgs - 1].add_patch(bbox_to_rect(position, color='red'))if label_names:axes[n_imgs - 1].text(position[0], position[1], label_names[category], color='blue')if imshow:fig.show()# 返回切割后的子图,以及子图的目标框return slice_images, exiset_obj_lists# 将(左上X,左上Y,右下X,右下Y)格式转换成matplotlib格式:
# ((左上X,左上Y),宽,高)
def bbox_to_rect(bbox, color):return plt.Rectangle(xy=(bbox[0], bbox[1]), width=bbox[2] - bbox[0], height=bbox[3] - bbox[1], fill=False, edgecolor=color,linewidth=2, )# 保存类别以及坐标信息
def save_txt(path, position_list, mode='a+'):with open(path, 'a+') as f:for i in range(len(position_list)):f.write(str(position_list[i]))if i == len(position_list) - 1:f.write('\n')else:f.write(' ')# 数据集类
class MyDataset(Dataset):def __init__(self, images_path, transform=None):self.transform = transforms.Compose([transforms.ToTensor()  # 这里仅以最基本的为例]) if not transform else transformself.image_path = images_path if os.path.isdir(images_path) else os.path.abspath(os.path.dirname(images_path))self.image_names = os.listdir(self.image_path) if os.path.isdir(images_path) else [images_path.split('/')[-1]]def __len__(self):return len(self.image_names)def __getitem__(self, index):image_name = self.image_names[index]image = Image.open(os.path.join(self.image_path, image_name)).convert('RGB')  # 读取到的是RGB, C, H, W#         print(image_name)image = self.transform(image)return imagedef get_name(self, index):image_name = self.image_names[index]return image_name# 将x1y1x2y2格式转换为yolo格式
def toYolo(box, imageWidth, imageHeight):center_x = (box[1] + box[3]) / 2 / imageWidthcenter_y = (box[2] + box[4]) / 2 / imageHeightwidth = (box[3] - box[1]) / imageWidthheight = (box[4] - box[2]) / imageHeightreturn box[0], center_x, center_y, width, height# 切图主类
class Crop():def __init__(self, ):self.dataSet = Noneself.labelPath = ''self.label_names = Nonedef inputImage(self, imagePath):self.dataSet = MyDataset(imagePath)def inputLabel(self, labelPath, label_names=None, coordinates='x1y1x2y2'):self.labelPath = labelPathself.label_names = label_namesif coordinates not in ['yolo', 'x1y1x2y2']:raise Exception('coordinates参数需指定yolo或x1y1x2y2之一')self.inputlabel_coordinates = coordinatesdef getLabel(self, index, ):if self.labelPath == '':print('未定义标签地址,若需使用标签请使用inputLabel方法传入标签地址')return []else:txtPath = os.path.join(self.labelPath, self.dataSet.get_name(index).split('.')[0] + '.txt')try:object_list = (pd.read_table(txtPath, header=None, sep=' ')).valuesif self.inputlabel_coordinates == 'yolo':Height, Width = self.dataSet[index].shape[1:3]# 转换为x1y1x2y2object_list[:, 1], object_list[:, 2], object_list[:, 3], object_list[:, 4] = \((object_list[:, 1] - object_list[:, 3] / 2) * Width).astype(int), \((object_list[:, 2] - object_list[:, 4] / 2) * Height).astype(int), \((object_list[:, 1] + object_list[:, 3] / 2) * Width).astype(int), \((object_list[:, 2] + object_list[:, 4] / 2) * Height).astype(int)except:# 若读取报错(表示文件为空),则指定列表为空。object_list = []return object_list# 展示图片def showImage(self, index, figsize=(10, 8)):plt.figure(figsize=figsize)plt.title(self.dataSet.get_name(index))if self.labelPath != '':labels = self.getLabel(index)fig = plt.imshow(self.dataSet[index].permute((1, 2, 0)))for cls, *box in labels:fig.axes.add_patch(bbox_to_rect(box, color='red'))# 注释虫子名称plt.text(box[0], box[1], self.label_names[cls] if self.label_names else None, color='blue')else:plt.imshow(self.dataSet[index].permute((1, 2, 0)))plt.show()class slidingWindowCrop(Crop):def __init__(self, windowSize=None, rowcol=None):if not ((windowSize or rowcol) and not (windowSize and rowcol)):raise Exception('windowSize and rowcol must Only one can be defined')self.windowSize = windowSize  # (Width, Height)self.rowcol = rowcol  # (row, col)self.labelPath = ''self.dataSet = Noneself.label_names = Nonedef showSliceImage(self, index, overlap, new_box_iou_limit=0.35, figsize=(10, 8)):object_list = self.getLabel(index)  # 获取该原图上的目标框数据集合Width, Height = self.dataSet[index].shape[:0:-1]  # 获取原图片的宽高if self.rowcol:# 通过行列数计算得到滑动窗口长宽windowSize = self.ranksGetWindowSize(self.rowcol, (Width, Height), overlap)else:windowSize = self.windowSizeprint(f'{self.dataSet.get_name(index)}子图宽高为:', windowSize[0], windowSize[1])slice_imag(self.dataSet[index], sliceWidth=windowSize[0], sliceHeight=windowSize[1], object_list=object_list,overlap=overlap, new_box_iou_limit=new_box_iou_limit, imshow=True, figsize=figsize,label_names=self.label_names)# 当为'rowcol'定义时,通过行列数以及overlap来确定窗口的大小@staticmethoddef ranksGetWindowSize(nrow_ncol, Width_Height, overlap):nrow, ncol = nrow_ncolWidth, Height = Width_Height# 由于通过行列数以及overla计算得到的子图长宽存在小数,取整时会导致行列数变化,通过更新高宽来保持行列数# 且若刚好为整数又会因切图时的程序会导致少一行/一列,因此需减少高/宽来保持行列数不变def contral_rowcol(RowOrCol, WidthOrHeight, sliceWidthOrHeight, overlap):# 通过以下公式若大于col+1或row+1,则说明会导致多一行/一列,则通过增加长/宽来避免while len(computeSlicePosition(WidthOrHeight, sliceWidthOrHeight, overlap)) > RowOrCol:sliceWidthOrHeight += 1# 通过以下公式若小于col或row,则说明会导致少一行/一列,则通过减小长/宽来避免while len(computeSlicePosition(WidthOrHeight, sliceWidthOrHeight, overlap)) < RowOrCol:sliceWidthOrHeight -= 1#             print(len(computeSlicePosition(WidthOrHeight,sliceWidthOrHeight,overlap)))return sliceWidthOrHeight# 通过公式计算满足指定overlap以及指定行列数时的长宽值sliceWidth = np.int(Width / (ncol * (1 - overlap) + overlap))sliceHeight = np.int(Height / (nrow * (1 - overlap) + overlap))# 更新长宽值以保证切分时的行列数不变# 当计算得到的值等于原图宽/高时说明指定行/列为1,因此不需要重新更新(若更新则程序会使导致多一行/列)if Width != sliceWidth:sliceWidth = contral_rowcol(ncol, Width, sliceWidth, overlap)if Height != sliceHeight:sliceHeight = contral_rowcol(nrow, Height, sliceHeight, overlap)return int(sliceWidth), int(sliceHeight)def __repeatMethod__(self, index, overlap=0.5, new_box_iou_limit=0.35):if overlap >= 1 or overlap < 0:raise Exception("overlap must >=0 and <1")image_name = self.dataSet.get_name(index)  # 指定图片的名字object_list = self.getLabel(index)  # 指定图片的目标框Width, Height = self.dataSet[index].shape[:0:-1]  # 获取原图片的宽高if self.rowcol:# 通过行列数计算得到滑动窗口长宽windowSize = self.ranksGetWindowSize(self.rowcol, (Width, Height), overlap)else:windowSize = self.windowSizeprint(f'{self.dataSet.get_name(index)}子图宽高为:', windowSize[0], windowSize[1])sliceWidth, sliceHeight = windowSize# 获取切分子图以及子图目标框slice_images, exiset_obj_lists = slice_imag(self.dataSet[index], sliceWidth=sliceWidth, sliceHeight=sliceHeight,object_list=object_list, overlap=overlap,new_box_iou_limit=new_box_iou_limit, )ncol = len(computeSlicePosition(Width, sliceWidth, overlap))  # 一行有几个子图nrow = len(computeSlicePosition(Height, sliceHeight, overlap))  # 有几行print('行列数为:', nrow, ncol)return image_name, slice_images, windowSize, exiset_obj_lists, nrow, ncoldef saveSubImage(self, index, imgs_save_path, overlap=0.5, resize=None, new_box_iou_limit=0.35):"""通过索引保存子图"""# 如果不存在文件夾则创建if not os.path.exists(imgs_save_path):os.makedirs(imgs_save_path)image_name, slice_images, windowSize, exiset_obj_lists, nrow, ncol = self.__repeatMethod__(index, overlap,new_box_iou_limit)# 图片resize尺寸,若为none则尺寸不变resize = (windowSize[0], windowSize[1]) if not resize else resizen_save_imgs = 0for num, sub_image, in enumerate(slice_images):n_save_imgs += 1# 子图位置编号sub_row = (num) // ncolsub_col = (num) % ncolpath = os.path.join(imgs_save_path, image_name.split('.')[0] + f'_{sub_row}' + f'_{sub_col}.jpg')print('save:', path)# 保存图片到指定路径并将图片resize为(640,640)transforms.ToPILImage()(sub_image).resize(resize).save(path)return n_save_imgsdef saveSubImageAndTxt(self, index, imgs_save_path, labels_save_path, overlap=0.5,resize=None, new_box_iou_limit=0.35, coordinates='yolo'):if coordinates in ['yolo', 'x1y1x2y2']:passelse:raise Exception('coordinates参数需指定yolo或x1y1x2y2之一')# 如果不存在文件夾则创建if not os.path.exists(imgs_save_path):os.makedirs(imgs_save_path)if not os.path.exists(labels_save_path):os.makedirs(labels_save_path)image_name, slice_images, windowSize, exiset_obj_lists, nrow, ncol = self.__repeatMethod__(index, overlap,new_box_iou_limit)# 图片resize尺寸,若为none则尺寸不变resize = (windowSize[0], windowSize[1]) if not resize else resizen_save_imgs = 0for num, (sub_image, exiset_obj_list) in enumerate(zip(slice_images, exiset_obj_lists)):# 子图位置编号sub_row = (num) // ncolsub_col = (num) % ncolif exiset_obj_list:n_save_imgs += 1path_image = os.path.join(imgs_save_path, image_name.split('.')[0] + f'_{sub_row}' + f'_{sub_col}.jpg')# 保存图片到指定路径并将图片resize为transforms.ToPILImage()(sub_image).resize(resize).save(path_image)# 保存子图相对应labels的txt文件到指定路径path_label = os.path.join(labels_save_path,image_name.split('.')[0] + f'_{sub_row}' + f'_{sub_col}.txt')print('save:', path_image, '  ', path_label)# 如果已存在该子图名称文件,可能会重复写入,因此移除来重新写入if os.path.exists(path_label):os.remove(path_label)for box in exiset_obj_list:save_txt(path_label, toYolo(box, windowSize[0], windowSize[1]) if coordinates == 'yolo' else box)return n_save_imgsdef saveSubTxt(self, index, labels_save_path, overlap=0.5, new_box_iou_limit=0.35, coordinates='yolo'):if coordinates in ['yolo', 'x1y1x2y2']:passelse:raise Exception('coordinates参数需指定yolo或x1y1x2y2之一')if not os.path.exists(labels_save_path):os.makedirs(labels_save_path)image_name, slice_images, windowSize, exiset_obj_lists, nrow, ncol = self.__repeatMethod__(index, overlap,new_box_iou_limit)n_save_txts = 0for num, (sub_image, exiset_obj_list) in enumerate(zip(slice_images, exiset_obj_lists)):# 子图位置编号sub_row = (num) // ncolsub_col = (num) % ncolif exiset_obj_list:n_save_txts += 1# 保存子图相对应labels的txt文件到指定路径path_label = os.path.join(labels_save_path,image_name.split('.')[0] + f'_{sub_row}' + f'_{sub_col}.txt')print('save:', path_label)# 如果已存在该子图名称文件,可能会重复写入,因此移除来重新写入if os.path.exists(path_label):os.remove(path_label)for box in exiset_obj_list:save_txt(path_label, toYolo(box, windowSize[0], windowSize[1]) if coordinates == 'yolo' else box)return n_save_txtsdef randomCropPosition(labels, Width, Height, subWidth, subHeight):label, xmin, ymin, xmax, ymax = labelswidth_range_min = -(subWidth - (xmax - xmin)) if xmin - (subWidth - (xmax - xmin)) > 0 else -xminwidth_range_max = 0 if xmin + subWidth < Width else width_range_min + (Width - xmax)height_range_min = -(subHeight - (ymax - ymin)) if ymin - (subHeight - (ymax - ymin)) > 0 else -yminheight_range_max = 0 if ymin + subHeight < Height else height_range_min + (Height - ymax)try:subwidth_deviation = random.randint(int(width_range_min), int(width_range_max))subheight_deviation = random.randint(int(height_range_min), int(height_range_max))except:# 出现异常:子图宽高小于目标框宽高return Nonesub_xmin, sub_ymin = xmin + subwidth_deviation, ymin + subheight_deviationsub_xmax, sub_ymax = sub_xmin + subWidth, sub_ymin + subHeightreturn sub_xmin, sub_ymin, sub_xmax, sub_ymaxdef randomCrop(image, image_name, labels=[], subWidth=1000, subHeight=1000, new_box_iou_limit=0.3,imshow=True, label_names=None, figsize=(10, 8)):if len(labels) == 0:print('无目标框,不进行切分')return [], []if imshow:nrow = int(np.sqrt(len(labels)))ncol = int(np.ceil(len(labels) / nrow))_, axes = plt.subplots(nrow, ncol, figsize=figsize)axes = axes.flatten()images, exiset_obj_lists = [], []for num, label in enumerate(labels):try:sub_xmin, sub_ymin, sub_xmax, sub_ymax = map(int, randomCropPosition(label, image.shape[2], image.shape[1],subWidth, subHeight))except TypeError:print(f'{image_name}:the subimage\'s Width/Height must > box\'s Width/Height')return [], []exiset_obj_list = exist_objs([sub_xmin, sub_ymin, sub_xmax, sub_ymax], labels,new_box_iou_limit)sub_image = image[:, sub_ymin:sub_ymax, sub_xmin:sub_xmax]#         print(exiset_obj_list)images.append(sub_image)exiset_obj_lists.append(exiset_obj_list)if imshow:# 展示分割后的子图axes[num].imshow(sub_image.permute((1, 2, 0)).numpy())#             axes[num].axes.get_xaxis().set_visible(False)#             axes[num].axes.get_yaxis().set_visible(False)# 在新的子图上展示目标框for category, *position in exiset_obj_list:axes[num].add_patch(bbox_to_rect(position, color='red'))if label_names:axes[num].text(position[0], position[1], label_names[category], color='blue')plt.show()return images, exiset_obj_listsclass randomCenterCrop(Crop):def __init__(self, windowSize):self.windowSize = windowSize  # (Width, Height)self.dataSet = Noneself.labelPath = ''def showCopImage(self, index, new_box_iou_limit=0.35,figsize=(10, 8), ):image = self.dataSet[index]image_name = self.dataSet.get_name(index)labels = self.getLabel(index)images, exiset_obj_lists = randomCrop(image, image_name, labels, self.windowSize[0], self.windowSize[1],new_box_iou_limit=new_box_iou_limit,label_names=self.label_names, figsize=figsize, imshow=True)def saveSubImageAndTxt(self, index, imgs_save_path, labels_save_path, coordinates='yolo',resize=None, new_box_iou_limit=0.35, ):if coordinates in ['yolo', 'x1y1x2y2']:passelse:raise Exception('coordinates参数需指定yolo或x1y1x2y2之一')# 如果不存在文件夾则创建if not os.path.exists(imgs_save_path):os.makedirs(imgs_save_path)if not os.path.exists(labels_save_path):os.makedirs(labels_save_path)image = self.dataSet[index]image_name = self.dataSet.get_name(index)labels = self.getLabel(index)images, exiset_obj_lists = randomCrop(image, image_name, labels, self.windowSize[0], self.windowSize[1],new_box_iou_limit=new_box_iou_limit, imshow=False)image_name = self.dataSet.get_name(index)# 图片resize尺寸,若为none则尺寸不变resize = (self.windowSize[0], self.windowSize[1]) if not resize else resizen_save_imgs = 0for num, (sub_image, exiset_obj_list) in enumerate(zip(images, exiset_obj_lists)):if exiset_obj_list:n_save_imgs += 1# 在多次切图时,保存的子图名称序号依次增加num_ = n_save_imgswhile True:path_image = os.path.join(imgs_save_path, image_name.split('.')[0] + f'_{num_ - 1}.jpg')if not os.path.exists(path_image):breakelse:num_ += 1# 保存图片到指定路径并将图片resize为transforms.ToPILImage()(sub_image).resize(resize).save(path_image)# 保存子图相对应labels的txt文件到指定路径path_label = os.path.join(labels_save_path, image_name.split('.')[0] + f'_{num_ - 1}.txt')print('save:', path_image, '  ', path_label)# 如果已存在该子图名称文件,可能会重复写入,因此移除来重新写入if os.path.exists(path_label):os.remove(path_label)for box in exiset_obj_list:save_txt(path_label,toYolo(box, self.windowSize[0], self.windowSize[1]) if coordinates == 'yolo' else box)return n_save_imgs

程序保存为py文件 

四、使用文档

一)滑动窗口切图:slidingWindowCrop

1、切图对象属性介绍:

windowSize:滑动窗口大小

rowcol:指定行列数,windowSize与rowcol只能定义一个 

labelPath:标签路径

dataSet:图片数据集对象,为torch中的dataset对象,可通过索引获取图片数组数据

label_names:标签所代表的的类别名称,为字典类型。

2、传入图片数据:self.inputImage

 作者图片文件夹展示: 

3、传入标签数据:self.inputLabel; 可以不传入,直接进行切图,如用于测试集

作者txt文件展示:为左上x,y右下x,y坐标格式,也可指定yolo格式输入

4、展示原图:self.showImage

5、展示切图效果:self.showSliceImage

6、保存切图结果:封装了三个方法,分别为仅保存子图、仅保存txt与均保存。

1)self.saveSubImage:以原图文件名加上行列索引号命名子图,函数返回保存的子图数量。

2)self.saveSubTxt:保存切分子图的标签数据,函数返回保存文件数,也为具有目标框的子图数。

3)、self.saveSubImageAndTxt:同时保存切分子图以及标签

7、对数据集所有图片进行切图并保存:

二)随机中心点切图:randomCenterCrop

 该类对象属性方法与滑动窗口基本一致,但保存的方法只有saveSubImageAndTxt(单独保存子图或标签没有意义) 

 

保存图片:以原图为文件名加上序号从0开始命名子图,当保存的文件夹中已存在子图名,则序号自动加1 

 

对数据集所有子图切图并保存:不存在目标框的原图不进行处理

 作者会在下篇博客分享对子图预测的结果进行拼接到原图上的程序开源以及使用文档,造轮子不易,喜欢点个赞加关注,谢谢了。

这篇关于目标检测项目中面对高分辨率图像的滑动窗口技术(一)(代码开源,超简便API封装,直接调用进行切图及保存)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/549747

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n