voc数据集_目标检测数据集的增强(旋转,镜像,亮度等)含针对VOC标注格式数据的源码...
对目标检测数据集的增强方案有很多,网上的资料写的零零散散,而且我好像没有找到一个系统完整的针对某套完整数据集的源码,这两天自己写了几个增强方案的源码,针对的是voc格式的数据集!
只要有自己标注好的数据,按VOC格式组织好文件,就能用上这套代码。比如目前只有几百张数据,那么这样增强后会大大的扩充自己的数据集。
源码地址github.com目录
0对图像/标签的旋转
1不损失图像信息的旋转
2对图像/标签的镜像
3对原图和标签的亮度/对比度变换
0对图像/标签的旋转
难点在于对标签的旋转,前面其实我有写过一篇文章介绍过旋转大致原理
lim0:目标检测增强数据——旋转目标和对应标签zhuanlan.zhihu.com前面的文章的代码只是针对一张图片的一个demo。本文会给出一个具体的处理VOC数据集格式的完整代码,涉及对.xml文件的基本操作,以及对文件,文件夹的基本操作,虽然都很基础,但是很多细节还是值得记录:
需要处理的数据文件树(VOC数据格式)
|---Annotations
----0.xml
......
----XX.xml
|---JPEGImages
----0.jpg
......
----XX..jpg
#定义旋转图像文件的函数
def getRotatedImg(Pi_angle,img_path,img_write_path):
img = cv2.imread(img_path)
.....
###详细代码见github
rotated_img = cv2.warpAffine(img, M, (cols, rows)) # 旋转后的图像保持大小不变
cv2.imwrite(img_write_path,rotated_img)
return a,b
#定义处理.xml文件的函数
def getRotatedAnno(Pi_angle,a,b,anno_path,anno_write_path):
tree = ET.parse(anno_path)
root = tree.getroot()
objects = root.findall("object")
for obj in objects:
.....
###详细代码见github
tree.write(anno_write_path) # 保存修改后的XML文件
def rotate(angle,img_dir,anno_dir,img_write_dir,anno_write_dir):
if not os.path.exists(img_write_dir):
os.makedirs(img_write_dir)
if not os.path.exists(anno_write_dir):
os.makedirs(anno_write_dir)
Pi_angle = -angle * math.pi / 180.0 # 弧度制,后面旋转坐标需要用到,注意负号!!!
img_names=os.listdir(img_dir)
for img_name in img_names:
.....
###详细代码见github
#
a,b=getRotatedImg(Pi_angle,img_path,img_write_path)
getRotatedAnno(Pi_angle,a,b,anno_path,anno_write_path)
angles=[-30,30,.....]
img_dir='several/JPEGImages'
anno_dir='several/Annotations'
img_write_dir='Rotated/rotated_JPEGImages'
anno_write_dir='Rotated/rotated_Annotations'
for angle in angles:
rotate(angle,img_dir,anno_dir,img_write_dir,anno_write_dir)
1不损失图像信息的旋转
由于图像的旋转会使得一部分信息损失(可以脑补),而且不是很容易的判断旋转后的图像是否还包含我们的完整目标。 因此可以尝试把原图像先安装长/短边的长度填充为一个正方形,这样可以确保填充后的图像在旋转过程中,原图中的目标信息不会丢失。 然后可以旋转作一个裁剪(根据目标的标签坐标,只要避开旋转后的标签坐标就能很好的裁剪出图像),当然也可以不裁剪,但是这样会降低模型 训练的效率
img=cv2.imread('14h.jpg')
rows, cols = img.shape[:2]
##填充图像为正方形,而且要能保证填充后的图像在0到360°旋转的时候,原图像的像素不会损失
re=cv2.copyMakeBorder(img,int(cols/2),int(cols/2),int(rows/2),int(rows/2),cv2.BORDER_CONSTANT)
def getRotatedImg(Pi_angle,img_path,img_write_path):
img = cv2.imread(img_path)
rows, cols = img.shape[:2]
a, b = cols / 2, rows / 2
M = cv2.getRotationMatrix2D((a, b), angle, 1)
rotated_img = cv2.warpAffine(img, M, (cols, rows)) # 旋转后的图像保持大小不变
cv2.imwrite(img_write_path,rotated_img)
for angle in range(0,180,30):
Pi_angle = -angle * math.pi / 180.0
img_path='re.jpg'
img_write_path=str(angle)+'.jpg'
getRotatedImg(Pi_angle, img_path, img_write_path)
#验证是否标签被正确的改变
#for origin image: xmin:606 ymin:489 xmax:855 ymax:1023
cv2.rectangle(re,(606+int(rows/2),489+int(cols/2)),(855+int(rows/2),1023+int(cols/2)),(0,255,0),4)
#
def crop():
pass
2对图像/标签的镜像
该部分主要是对图像进行水平,竖直,对角的镜像变换。 对图像的操作cv2.flip();对标签的变化其实就是利用原图的长宽来减去原像素,三种情况有所不同,但很好理解,只是有些细节需要注意,见代码! 注:对原图的对角镜像其实等价于对原图旋转180°。原代码全部贴出来太多了,这里只展示结构,详细代码请见作者github,文章开头已经给出
W,H代表图像的尺度
x_out=W-x_in 水平只是x变,y不变
y_out=W-y_in 竖直只是y变,x不变
对角X变,y变
注意细节就是变完后的:xmin,xmax,ymin,ymax是哪一个,自己要判断好,见代码!!!
def h_MirrorImg(img_path,img_write_path):
img = cv2.imread(img_path)
mirror_img = cv2.flip(img, 1) #水平镜像
cv2.imwrite(img_write_path,mirror_img)
def v_MirrorImg(img_path,img_write_path):
pass #竖直镜像
def a_MirrorImg(img_path,img_write_path):
pass #对角镜像
def h_MirrorAnno(anno_path,anno_write_path):
tree = ET.parse(anno_path)
root = tree.getroot()
size=root.find('size')
w=int(size.find('width').text)
objects = root.findall("object")
for obj in objects:
bbox = obj.find('bndbox')
x1 = float(bbox.find('xmin').text)
x2 = float(bbox.find('xmax').text)
x1=w-x1+1 #这就是镜像后的坐标和原坐标的对应关系,但为了避免为0或小于0最好加1
x2=w-x2+1
assert x1>0
assert x2>0
bbox.find('xmin').text=str(int(x2)) #细节:是x2而不是x1
bbox.find('xmax').text=str(int(x1))
tree.write(anno_write_path) # 保存修改后的XML文件
def v_MirrorAnno(anno_path,anno_write_path): #竖直镜像标签的处理
pass ###详细代码见github
tree.write(anno_write_path)
def a_MirrorAnno(anno_path,anno_write_path): #对角镜像标签的处理
pass ###详细代码见github
tree.write(anno_write_path)
def mirror(img_dir,anno_dir,img_write_dir,anno_write_dir):
for img_name in img_names:
###详细代码见github
3对原图和标签的亮度/对比度变换
该部分本还应该有对图像的颜色变换,下次更新!
对亮度和对比度的调节利用的是对像素的简单线性变换,就是一个一次函数
output_pixel=alpha*(input_pixel)+beta
def getColorImg(alpha,beta,img_path,img_write_path):
img = cv2.imread(img_path)
colored_img = np.uint8(np.clip((alpha * img + beta), 0, 255))
cv2.imwrite(img_write_path,colored_img)
def getColorAnno(anno_path,anno_write_path):
tree = ET.parse(anno_path)
tree.write(anno_write_path) # 标签不用变,因为变换后的图像和原图是一样大小
def color(alpha,beta,img_dir,anno_dir,img_write_dir,anno_write_dir):
####详细代码见github
for img_name in img_names:
###详细代码见github
getColorImg(alpha,beta,img_path,img_write_path)
getColorAnno(anno_path,anno_write_path)
alphas=[0.3,0.5,1.2,1.6]
beta=10
后面有时间的话还会继续补充给出一些其它方案的代码