目标检测数据集图片及标签同步裁剪

目录

前言

具体方法

使用介绍

完整代码


前言

在目标检测任务中,模型的训练依赖于大量高质量的标注数据。然而,获取足够多的标注数据集往往代价高昂,并且某些情况下,数据集中的样本分布不均衡,这会导致模型的泛化能力不足。为此,数据增强成为提升模型性能的常用方法之一。

在数据增强的各种方法中,裁剪是一种高效且常用的技术。通过对图片进行随机或特定区域的裁剪,能够生成更多的训练样本,提高模型对不同尺度和位置目标的鲁棒性。此外,裁剪还可以有效解决原始图片尺寸过大的问题,避免对训练性能造成影响。大尺寸图片会增加模型的计算开销,导致训练速度变慢、内存消耗增大。而通过裁剪,图片尺寸得以缩小,训练效率随之提升。

然而,裁剪不仅仅是简单地对图片进行操作,目标检测任务中,每个目标的边界框(标签)必须与图片裁剪过程同步更新,否则裁剪后的标签将失去准确性,影响模型的训练效果。本篇文章将重点介绍如何在进行目标检测数据集图片裁剪时,同时对目标的标签进行精确调整,确保裁剪后的图片和标签保持一致,从而构建高质量的增强数据集,助力模型的准确性与泛化能力提升。

具体方法

本文所使用的裁剪方式为图片的宽度和高度各自从中间切开,将一个图片分为左上,右上,左下,右下四个部分,其中保留含有标签的那部分图片,其余未含有标签的部分则不保留。

python 复制代码
def crop_image(image, save_dir, name, suf, boxes):
	H, W, _ = image.shape
	# 左上区域
	if boxes[0] == 1:
		img_top_left = image[0:H // 2, 0:W // 2]
		save_path = os.path.join(save_dir, f"{name}_1{suf}")
		cv.imwrite(save_path, img_top_left)
	# 右上区域
	if boxes[1] == 1:
		img_top_right = image[0:H // 2, W // 2:W]
		save_path = os.path.join(save_dir, f"{name}_2{suf}")
		cv.imwrite(save_path, img_top_right)
	# 左下区域
	if boxes[2] == 1:
		img_bottom_left = image[H // 2:H, 0:W // 2]
		save_path = os.path.join(save_dir, f"{name}_3{suf}")
		cv.imwrite(save_path, img_bottom_left)
	# 右下区域
	if boxes[3] == 1:
		img_bottom_right = image[H // 2:H, W // 2:W]
		save_path = os.path.join(save_dir, f"{name}_4{suf}")
		cv.imwrite(save_path, img_bottom_right)

这部分为裁剪图片的函数定义,可以看到只保留含有标签的部分图像

python 复制代码
def split_box(box, shape):
	W, H = shape[1], shape[0]
	n, xmin, ymin, xmax, ymax = box
	regions = []
	# 左上区域
	if xmin < W / 2 and ymin < H / 2:
		xmin_new = max(0, xmin)
		ymin_new = max(0, ymin)
		xmax_new = min(W / 2, xmax)
		ymax_new = min(H / 2, ymax)
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'left_top'))
	# 右上区域
	if xmax > W / 2 and ymin < H / 2:
		xmin_new = max(W / 2, xmin) - W / 2
		ymin_new = max(0, ymin)
		xmax_new = min(W, xmax) - W / 2
		ymax_new = min(H / 2, ymax)
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'right_top'))
	# 左下区域
	if xmin < W / 2 and ymax > H / 2:
		xmin_new = max(0, xmin)
		ymin_new = max(H / 2, ymin) - H / 2
		xmax_new = min(W / 2, xmax)
		ymax_new = min(H, ymax) - H / 2
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'left_bottom'))
	# 右下区域
	if xmax > W / 2 and ymax > H / 2:
		xmin_new = max(W / 2, xmin) - W / 2
		ymin_new = max(H / 2, ymin) - H / 2
		xmax_new = min(W, xmax) - W / 2
		ymax_new = min(H, ymax) - H / 2
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'right_bottom'))
	normalized_regions = []
	for region in regions:
		box = region[1:5]  # 获取 (xmin, ymin, xmax, ymax)
		normalized_box = xyxy2xywhn((W/2, H/2), box)  # 归一化
		normalized_regions.append((region[0], *normalized_box, region[5]))  # 保留标签
	return normalized_regions

这部分函数为标签裁剪,这里我将每个标签都完整的分割出来,避免因为标签在图片中间交界处而导致的标签发生丢失现象,同时每部分标签加入一定的标志,全部运行完之后返回这部分标志,经过处理后传入图片裁剪中就可以知道哪部分图片需要保留,避免了图片重复写入。

使用介绍

完整代码如下,修改其中路径部分即可使用,需要注意本文仅支持标签文件为yolo格式的txt文件,如果对于转换格式不熟悉的可以去看我下面这篇文章,里面有相关的介绍及方法

VOC格式转YOLO格式,xml文件转txt文件简单通用代码_voc转yolo-CSDN博客文章浏览阅读509次,点赞8次,收藏4次。很多人在进行目标检测训练时习惯将得到的数据标注为XML文件的VOC格式,或者在网上获取的数据集被标注为XML文件,但是不同的标注工具进行的标注会产生不同的标注xml文件,这里我写了一种通用的针对含有最基本图片和标注坐标信息的xml进行转换,在这里简单介绍并分享出来xml文件中最基本需要含有的信息为size,object下的name和bndbox,具体示例如下图(如果xml文件中没有size也就是图片的宽和高则需要单独对每个图片进行读取,感兴趣可以私聊,这里不展开介绍)_voc转yolohttps://blog.csdn.net/qq_67105081/article/details/140000193?spm=1001.2014.3001.5501转换过之后想要再次转换回voc格式的xml文件可以去查看我的下面这篇博客

yolov8等目标检测数据集YOLO格式转VOC格式,txt文件转xml文件,格式转换_yolo格式标签转voc格式-CSDN博客文章浏览阅读255次,点赞3次,收藏8次。YOLO格式的标注文件是归一化之后的数据,每一行有5个数值,从左到右分别是类别,中心坐标x,中心坐标y,宽度w,高度h,这些数据经过计算后即可得到xmin,ymin,xmax,ymax的值,将这些信息相应的填入xml文件中即可。还有一个关键部分是xml文件的构建,如下代码构建,变量确认后替换。_yolo格式标签转voc格式https://blog.csdn.net/qq_67105081/article/details/142915326?spm=1001.2014.3001.5501

完整代码

python 复制代码
# 作者:CSDN-笑脸惹桃花 https://blog.csdn.net/qq_67105081?type=blog
# github:peng-xiaobai https://github.com/peng-xiaobai/Data-Augmentor
import os
import numpy as np
import cv2 as cv

def GetFileList(dir):
	l = []
	files = os.listdir(dir)
	for file in files:
		if file.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tif')):
			l.append(os.path.join(dir, file))
	return l

def xywhn2xyxy(x, w, h):
	y = np.copy(x) if isinstance(x, np.ndarray) else x.copy() if hasattr(x, 'copy') else x
	y[0] = w * (x[0] - 0.5 * x[2])
	y[1] = h * (x[1] - 0.5 * x[3])
	y[2] = w * (x[0] + 0.5 * x[2])
	y[3] = h * (x[1] + 0.5 * x[3])
	return y

def xyxy2xywhn(size, box):
	x_center = (box[0] + box[2]) / 2.0
	y_center = (box[1] + box[3]) / 2.0
	w = box[2] - box[0]
	h = box[3] - box[1]
	x = x_center / size[0]
	y = y_center / size[1]
	w = w / size[0]
	h = h / size[1]
	return x, y, w, h

def crop_image(image, save_dir, name, suf, boxes):
	H, W, _ = image.shape
	# 左上区域
	if boxes[0] == 1:
		img_top_left = image[0:H // 2, 0:W // 2]
		save_path = os.path.join(save_dir, f"{name}_1{suf}")
		cv.imwrite(save_path, img_top_left)
	# 右上区域
	if boxes[1] == 1:
		img_top_right = image[0:H // 2, W // 2:W]
		save_path = os.path.join(save_dir, f"{name}_2{suf}")
		cv.imwrite(save_path, img_top_right)
	# 左下区域
	if boxes[2] == 1:
		img_bottom_left = image[H // 2:H, 0:W // 2]
		save_path = os.path.join(save_dir, f"{name}_3{suf}")
		cv.imwrite(save_path, img_bottom_left)
	# 右下区域
	if boxes[3] == 1:
		img_bottom_right = image[H // 2:H, W // 2:W]
		save_path = os.path.join(save_dir, f"{name}_4{suf}")
		cv.imwrite(save_path, img_bottom_right)

def split_box(box, shape):
	W, H = shape[1], shape[0]
	n, xmin, ymin, xmax, ymax = box
	regions = []
	# 左上区域
	if xmin < W / 2 and ymin < H / 2:
		xmin_new = max(0, xmin)
		ymin_new = max(0, ymin)
		xmax_new = min(W / 2, xmax)
		ymax_new = min(H / 2, ymax)
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'left_top'))
	# 右上区域
	if xmax > W / 2 and ymin < H / 2:
		xmin_new = max(W / 2, xmin) - W / 2
		ymin_new = max(0, ymin)
		xmax_new = min(W, xmax) - W / 2
		ymax_new = min(H / 2, ymax)
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'right_top'))
	# 左下区域
	if xmin < W / 2 and ymax > H / 2:
		xmin_new = max(0, xmin)
		ymin_new = max(H / 2, ymin) - H / 2
		xmax_new = min(W / 2, xmax)
		ymax_new = min(H, ymax) - H / 2
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'left_bottom'))
	# 右下区域
	if xmax > W / 2 and ymax > H / 2:
		xmin_new = max(W / 2, xmin) - W / 2
		ymin_new = max(H / 2, ymin) - H / 2
		xmax_new = min(W, xmax) - W / 2
		ymax_new = min(H, ymax) - H / 2
		regions.append((n, xmin_new, ymin_new, xmax_new, ymax_new, 'right_bottom'))
	normalized_regions = []
	for region in regions:
		box = region[1:5]  # 获取 (xmin, ymin, xmax, ymax)
		normalized_box = xyxy2xywhn((W/2, H/2), box)  # 归一化
		normalized_regions.append((region[0], *normalized_box, region[5]))  # 保留标签
	return normalized_regions

def save_boxes(label_path, boxes):
	# 先获取标注文件的基本路径和扩展名
	base_label_path, ext = os.path.splitext(label_path)

	for box in boxes:
		# 获取区域标识(左上、右上、左下、右下)
		region = box[5]
		if region == 'left_top':
			suffix = '_1'
		elif region == 'right_top':
			suffix = '_2'
		elif region == 'left_bottom':
			suffix = '_3'
		elif region == 'right_bottom':
			suffix = '_4'
		else:
			continue  # 如果区域标识不在预期范围内,跳过
		specific_label_path = f"{base_label_path}{suffix}{ext}"
		with open(specific_label_path, 'a') as f:
			f.write(f"{int(box[0])} " + " ".join(map(str, box[1:5])) + '\n')


fileDir = r"E:\peanut_data\j"  # 原图片路径
label_path = r"E:\peanut_data\txt"  # 原label的路径
list1 = GetFileList(fileDir)

image_save_path_head = r"E:\peanut_data\j1"  # 分割后有标注图片储存路径
label_save_path_head = r"E:\peanut_data\txt1"  # 标签储存路径


if not os.path.exists(image_save_path_head):
	os.makedirs(image_save_path_head)
if not os.path.exists(label_save_path_head):
	os.makedirs(label_save_path_head)

for i in list1:
	l = [0, 0, 0, 0]
	img = cv.imread(i)
	shape = img.shape
	seq = 1
	name, suf = os.path.splitext(os.path.basename(i))
	labelname = os.path.join(label_path, name) + '.txt'  # 找到对应图片的label
	pos = []
	with open(labelname, 'r') as f1:
		#print(labelname)
		while True:
			lines = f1.readline()
			if lines == '\n':
				lines = None
			if not lines:
				break
			p_tmp = [float(i) for i in lines.split()]
			pos.append(p_tmp)
		pos = np.array(pos)
		for k in pos:
			k[1:] = xywhn2xyxy(k[1:], shape[1], shape[0])
			regions = split_box(k, shape)
			labelname_new = os.path.join(label_save_path_head, name) + '.txt'
			save_boxes(labelname_new, regions)
			for region in regions:
				region_name = region[-1]
				if region_name == 'left_top':
					l[0] = 1
					print("The region is 'left_top'.")
				elif region_name == 'right_top':
					l[1] = 1
					print("The region is 'right_top'.")
				elif region_name == 'left_bottom':
					l[2] = 1
					print("The region is 'left_bottom'.")
				elif region_name == 'right_bottom':
					l[3] = 1
					print("The region is 'right_bottom'.")
		f1.close()
	crop_image(img, image_save_path_head, name, suf, l)
相关推荐
迅易科技26 分钟前
借助腾讯云质检平台的新范式,做工业制造企业质检的“AI慧眼”
人工智能·视觉检测·制造
古希腊掌管学习的神1 小时前
[机器学习]XGBoost(3)——确定树的结构
人工智能·机器学习
ZHOU_WUYI2 小时前
4.metagpt中的软件公司智能体 (ProjectManager 角色)
人工智能·metagpt
靴子学长3 小时前
基于字节大模型的论文翻译(含免费源码)
人工智能·深度学习·nlp
AI_NEW_COME3 小时前
知识库管理系统可扩展性深度测评
人工智能
海棠AI实验室4 小时前
AI的进阶之路:从机器学习到深度学习的演变(一)
人工智能·深度学习·机器学习
hunteritself4 小时前
AI Weekly『12月16-22日』:OpenAI公布o3,谷歌发布首个推理模型,GitHub Copilot免费版上线!
人工智能·gpt·chatgpt·github·openai·copilot
IT古董5 小时前
【机器学习】机器学习的基本分类-强化学习-策略梯度(Policy Gradient,PG)
人工智能·机器学习·分类
centurysee5 小时前
【最佳实践】Anthropic:Agentic系统实践案例
人工智能
mahuifa5 小时前
混合开发环境---使用编程AI辅助开发Qt
人工智能·vscode·qt·qtcreator·编程ai