目录
其实这部分内容并不多,主要就交代一下需要注意的地方
1.一定一定要备份
在拍摄图像数据集或者打完标签之类的时候,特别是进行大规模批量处理前,一定进行备份,这部分不需要代码,只需要简单的复制粘贴就可以,所以不过多陈述
![](https://i-blog.csdnimg.cn/blog_migrate/7a9232fcc6967384064561984d9fbff3.png)
2.图片拍摄
数据集收集,不在于数量,而在于质量。这其实是一个比较复杂的,需要进行权衡的工作,我建议交给比较机灵的人去干。
比如,假如拍了1000张水瓶,但是这些水瓶的角度和姿态都大差不差,那这就是废品,这会导致在训练之后对水瓶的识别会变得十分苛刻。
又比如,假如拍了1000张水瓶,但是这些水瓶的角度全都不一样,这其实也是废品,这会导致在训练之后对水瓶的识别变得十分松懈或者更加苛刻。
所以需要在复杂和单一中进行权衡,对更容易出现的角度和姿态进行多拍,对不容易出现的角度少拍但是不能不拍。
这边建议先把垃圾盛放台和摄像头的大小间距之类的先决定好后直接开始搭建拍摄台,1:1复刻垃圾桶正式出炉的拍摄状态,这样会很利于后续的识别,也可以直接随心一点扔,数量多了就能看出哪种姿态更加常见,但是这对拍摄台的特征要求比较高。
![](https://i-blog.csdnimg.cn/blog_migrate/7e08ba1cfa24a33846f94c85b2585b53.png)
3.图片处理
这部分我觉得没啥用,但是其实还是有点用的,比如最基础的旋转。
在旋转目标识别中有着非常大的作用。
或者比如曝光度或者对比度之类的,这使垃圾桶在面对不同光照下时的识别变得容易得多。
其他的比如改噪点啊,或者改色调啊,肯定还是有用,但是我感觉作用并不是那么明显了,我觉得可能会影响整体数据,也就不多阐述了,在本次数据集处理我是肯定不会做这些的,假如后人想试一试,那也可以搞一搞对这进行补充。
这是cv搞出来的实时图像处理,还是蛮酷的,可以用来装逼,代码用的是别人的,想要的话可以私信或者评论找我要
4.图片裁剪
图片裁剪一般都是进行大规模批量裁剪,所以像我最开始所说,一定要记得备份。
具体什么时候裁剪我就不多说了,肯定是会遇到的。
这里就放一下我用的批量裁剪代码吧
from PIL import Image
import os
import os.path
# 定义文件所在文件夹
image_dir = "文件夹路径"
for parent, dir_name, file_names in os.walk(image_dir): # 遍历每一张图片
for filename in file_names:
pic_name = os.path.join(parent, filename)
image = Image.open(pic_name)
_width, _height = image.size
print(_width, _height)
# 定义裁剪范围(left, upper, right, lower)
box = image.crop((440,0,1520,1080))
filename = 'cut_' + filename
box.save("文件夹路径" % filename)
print('Done!')
需要你改的地方就三部分
第一部分image_dir,改你图片输入的文件夹
第二部分box,里面有四个数,分别是左上右下,你可以理解为两个一维坐标,分别是以左为零点和上为零点,根据你给出的四个数画出四条直线然后剪切
最后box.save就是保存路径。
注:一定注意切的尺寸,虽然可能切不到要进行识别的物体,但是可能在后续打标签之类的可能会碰见问题
5.格式转化
假如你用的是rolabelimg,那输出的应该是xml文件,然后要对xml文件进行转化,使其保留核心内容然后变成txt文件,当然根据你的需求不同,你转化后的文件格式也不一样,比如yolo形式或者dota形式等等。
这里我就放一下我的代码,可以进行dota和yolo的双重转化。
方案一
# 文件名称 :roxml_to_dota.py
# 功能描述 :把rolabelimg标注的xml文件转换成dota能识别的xml文件,
# 再转换成dota格式的txt文件
# 把旋转框 cx,cy,w,h,angle,转换成四点坐标x1,y1,x2,y2,x3,y3,x4,y4
import os
import xml.etree.ElementTree as ET
import math
def edit_xml(xml_file, dotaxml_file):
"""
修改xml文件
:param xml_file:xml文件的路径
:return:
"""
tree = ET.parse(xml_file)
objs = tree.findall('object')
for ix, obj in enumerate(objs):
x0 = ET.Element("x0") # 创建节点
y0 = ET.Element("y0")
x1 = ET.Element("x1")
y1 = ET.Element("y1")
x2 = ET.Element("x2")
y2 = ET.Element("y2")
x3 = ET.Element("x3")
y3 = ET.Element("y3")
# obj_type = obj.find('bndbox')
# type = obj_type.text
# print(xml_file)
if (obj.find('robndbox') == None):
obj_bnd = obj.find('bndbox')
obj_xmin = obj_bnd.find('xmin')
obj_ymin = obj_bnd.find('ymin')
obj_xmax = obj_bnd.find('xmax')
obj_ymax = obj_bnd.find('ymax')
xmin = float(obj_xmin.text)
ymin = float(obj_ymin.text)
xmax = float(obj_xmax.text)
ymax = float(obj_ymax.text)
obj_bnd.remove(obj_xmin) # 删除节点
obj_bnd.remove(obj_ymin)
obj_bnd.remove(obj_xmax)
obj_bnd.remove(obj_ymax)
x0.text = str(xmin)
y0.text = str(ymax)
x1.text = str(xmax)
y1.text = str(ymax)
x2.text = str(xmax)
y2.text = str(ymin)
x3.text = str(xmin)
y3.text = str(ymin)
else:
obj_bnd = obj.find('robndbox')
obj_bnd.tag = 'bndbox' # 修改节点名
obj_cx = obj_bnd.find('cx')
obj_cy = obj_bnd.find('cy')
obj_w = obj_bnd.find('w')
obj_h = obj_bnd.find('h')
obj_angle = obj_bnd.find('angle')
cx = float(obj_cx.text)
cy = float(obj_cy.text)
w = float(obj_w.text)
h = float(obj_h.text)
angle = float(obj_angle.text)
obj_bnd.remove(obj_cx) # 删除节点
obj_bnd.remove(obj_cy)
obj_bnd.remove(obj_w)
obj_bnd.remove(obj_h)
obj_bnd.remove(obj_angle)
x0.text, y0.text = rotatePoint(cx, cy, cx - w / 2, cy - h / 2, -angle)
x1.text, y1.text = rotatePoint(cx, cy, cx + w / 2, cy - h / 2, -angle)
x2.text, y2.text = rotatePoint(cx, cy, cx + w / 2, cy + h / 2, -angle)
x3.text, y3.text = rotatePoint(cx, cy, cx - w / 2, cy + h / 2, -angle)
# obj.remove(obj_type) # 删除节点
obj_bnd.append(x0) # 新增节点
obj_bnd.append(y0)
obj_bnd.append(x1)
obj_bnd.append(y1)
obj_bnd.append(x2)
obj_bnd.append(y2)
obj_bnd.append(x3)
obj_bnd.append(y3)
tree.write(dotaxml_file, method='xml', encoding='utf-8') # 更新xml文件
# 转换成四点坐标
def rotatePoint(xc, yc, xp, yp, theta):
xoff = xp - xc;
yoff = yp - yc;
cosTheta = math.cos(theta)
sinTheta = math.sin(theta)
pResx = cosTheta * xoff + sinTheta * yoff
pResy = - sinTheta * xoff + cosTheta * yoff
return str(int(xc + pResx)), str(int(yc + pResy))
def totxt(xml_path, out_path):
# 想要生成的txt文件保存的路径,这里可以自己修改
files = os.listdir(xml_path)
for file in files:
tree = ET.parse(xml_path + os.sep + file)
root = tree.getroot()
name = file.strip('.xml')
output = out_path + name + '.txt'
file = open(output, 'w')
objs = tree.findall('object')
for obj in objs:
cls = obj.find('name').text
box = obj.find('bndbox')
x0 = int(float(box.find('x0').text))
y0 = int(float(box.find('y0').text))
x1 = int(float(box.find('x1').text))
y1 = int(float(box.find('y1').text))
x2 = int(float(box.find('x2').text))
y2 = int(float(box.find('y2').text))
x3 = int(float(box.find('x3').text))
y3 = int(float(box.find('y3').text))
file.write("{} {} {} {} {} {} {} {} {} 0\n".format(x0, y0, x1, y1, x2, y2, x3, y3, cls))
file.close()
print(output)
if __name__ == '__main__':
# -----**** 第一步:把xml文件统一转换成旋转框的xml文件 ****-----
roxml_path = "你的路径" # 目录下保存的是需要转换的xml文件
dotaxml_path = "你的路径"
out_path = "你的路径"
filelist = os.listdir(roxml_path)
for file in filelist:
edit_xml(os.path.join(roxml_path, file), os.path.join(dotaxml_path, file))
# -----**** 第二步:把旋转框xml文件转换成txt格式 ****-----
totxt(dotaxml_path, out_path)
常规需要改的仍然只有三部分roxml_path,dotaxml_path和out_path,分别是输入文件夹和输出dota文件夹和输出yolo文件夹。无需多做解释
假如项目不同使用的系统不同那对文件的内容可能要求也不同,那就需要对这段代码内部进行修改,那就需要你自己去研究和改了(其实也不难)
方案二
这一套是正在使用的数据集格式转化方案,具体方案要根据你的需求而定
# *_* coding : UTF-8 *_*
# 文件名称 :roxml_to_dota.py
# 功能描述 :把rolabelimg标注的xml文件转换成dota能识别的xml文件
# 就是把旋转框 cx,cy,w,h,angle,转换成四点坐标x1,y1,x2,y2,x3,y3,x4,y4
import os
import xml.etree.ElementTree as ET
import math
import cv2
import numpy as np
def edit_xml(xml_file):
if ".xml" not in xml_file:
return
tree = ET.parse(xml_file)
objs = tree.findall('object')
txt = xml_file.replace(".xml", ".txt")
jpg = xml_file.replace(".xml", ".png") # 图片是jpg格式
src = cv2.imread(jpg, 1)
with open(txt, 'w') as wf:
wf.write("imagesource:GoogleEarth\n")
wf.write("gsd:0.115726939386\n")
for ix, obj in enumerate(objs):
x0text = ""
y0text = ""
x1text = ""
y1text = ""
x2text = ""
y2text = ""
x3text = ""
y3text = ""
difficulttext = ""
className = ""
obj_type = obj.find('type')
type = obj_type.text
obj_name = obj.find('name')
className = obj_name.text
obj_difficult = obj.find('difficult')
difficulttext = obj_difficult.text
if type == 'bndbox': # 若用rolabelimg标注的是水平框
obj_bnd = obj.find('bndbox')
obj_xmin = obj_bnd.find('xmin')
obj_ymin = obj_bnd.find('ymin')
obj_xmax = obj_bnd.find('xmax')
obj_ymax = obj_bnd.find('ymax')
xmin = float(obj_xmin.text)
ymin = float(obj_ymin.text)
xmax = float(obj_xmax.text)
ymax = float(obj_ymax.text)
x0text = str(xmin)
y0text = str(ymin)
x1text = str(xmax)
y1text = str(ymin)
x2text = str(xmin)
y2text = str(ymax)
x3text = str(xmax)
y3text = str(ymax)
points = np.array([[int(float(x0text)), int(float(y0text))], [int(float(x1text)), int(float(y1text))],
[int(float(x2text)), int(float(y2text))],
[int(float(x3text)), int(float(y3text))]], np.int32)
cv2.polylines(src, [points], True, (255, 0, 0)) # 画任意多边
elif type == 'robndbox': # 若用rolabelimg标注的是旋转框
obj_bnd = obj.find('robndbox')
obj_bnd.tag = 'bndbox' # 修改节点名
obj_cx = obj_bnd.find('cx')
obj_cy = obj_bnd.find('cy')
obj_w = obj_bnd.find('w')
obj_h = obj_bnd.find('h')
obj_angle = obj_bnd.find('angle')
cx = float(obj_cx.text)
cy = float(obj_cy.text)
w = float(obj_w.text)
h = float(obj_h.text)
angle = float(obj_angle.text)
x0text, y0text = rotatePoint(cx, cy, cx - w / 2, cy - h / 2, -angle)
x1text, y1text = rotatePoint(cx, cy, cx + w / 2, cy - h / 2, -angle)
x2text, y2text = rotatePoint(cx, cy, cx + w / 2, cy + h / 2, -angle)
x3text, y3text = rotatePoint(cx, cy, cx - w / 2, cy + h / 2, -angle)
points = np.array([[int(float(x0text)), int(float(y0text))], [int(float(x1text)), int(float(y1text))],
[int(float(x2text)), int(float(y2text))],
[int(float(x3text)), int(float(y3text))]], np.int32)
cv2.polylines(src, [points], True, (255, 0, 0)) # 画任意多边形
# print(x0text,y0text,x1text,y1text,x2text,y2text,x3text,y3text,className,difficulttext)
wf.write(
"{} {} {} {} {} {} {} {} {} {}\n".format(x0text, y0text, x1text, y1text, x2text, y2text, x3text, y3text,
className, difficulttext))
# 转换成四点坐标
def rotatePoint(xc, yc, xp, yp, theta):
xoff = xp - xc;
yoff = yp - yc;
cosTheta = math.cos(theta)
sinTheta = math.sin(theta)
pResx = cosTheta * xoff + sinTheta * yoff
pResy = - sinTheta * xoff + cosTheta * yoff
return str(int(xc + pResx)), str(int(yc + pResy))
if __name__ == '__main__':
dir = "你的路径" # 此处的xml1130文件夹中必须既要放jpg图片集又要放图片对应的xml文件不然会报打不开文件的错误!!
filelist = os.listdir(dir)
for file in filelist:
edit_xml(os.path.join(dir, file))
需要改的只有dir一个文件夹路径。这套方案直接把图片和xml文件放在一起了,随后的txt文件也会直接生成在这个文件夹中,所以你需要后续的分类。
6.图片分类
# 功能描述:从指定文件夹中根据文件名后缀提取相应文件以txt文件为例,并存储到新文件夹中
import os
import shutil
def filterfile(file_dir, save_dir, suffix):
if os.path.exists(save_dir):
shutil.rmtree(save_dir) # 如果已经存在该文件夹,移除
if not os.path.exists(save_dir):
os.makedirs(save_dir) # 如果不存在该文件夹,则创建,用于储存后续提取出来的文件
filelist = [] # 存储要copy的文件全名
for dirpath, dirnames, filenames in os.walk(file_dir): # 根据路径执行树状的遍历,分别遍历根目录,根目录下的文件夹,文件夹下的文件
for file in filenames: # 遍历文件夹中的文件
file_type = file.split('.')[-1] # 对文件名根据.进行分隔,实现文件名,后缀名的分离
if (file_type in suffix): # 下面根据后缀名是否在列表中,提取文件
file_fullname = os.path.join(dirpath, file) # 文件全名
filelist.append(file_fullname) # 将符合要求的文件存放在列表中
for file in filelist:
shutil.copy(file, save_dir) # 将列表中的文件复制到新的文件夹
if __name__ == "__main__":
filterfile("mix", "label_txt", ['txt'])
file_dir指读的文件目录;save_dir为保存文件的目录
suffix用于存放打算提取的文件的后缀名,可以根据自己的需求进行修改
7.打标签
有的方案需要你进行打标签,比如建一个txt文件其中放置某一图片的分类,可能txt命名还要求和图片的名称一样,这时候手打就十分麻烦,可以用这套代码进行实现
import os
root_path="你的路径"
save_path="你的路径"#对应的label文件夹下也要建好相应的空子文件夹
names=os.listdir(root_path) #得到images文件夹下的夹的名称
for name in names:
path=os.path.join(root_path,name)
img_names=os.listdir(path) #得到子文件夹下的图片的名称
for img_name in img_names:
save_name = img_name.split(".jpg")[0]+'.txt' #得到相应的lable名称
txt_path=os.path.join(save_path,name) #得到label的子文件夹的路径
with open(os.path.join(txt_path,save_name), "w") as f: #结合子文件夹路径和相应子文件夹下图片的名称生成相应的子文件夹txt文件
f.write(name) #将label写入对应txt文件夹
print(f.name)
root_path:输入文件夹
save_path:输出文件夹
save_name:括号里面的是要去掉的后缀名,后面是加上的后缀名,就能变成新的同名不同格文件
8.重命名
肯定也会伴随重命名,但是一个个改也很麻烦,就直接用python批量实现
import os
root_dir="你的路径" #原文件夹路径
save_path="你的路径" #新建文件夹路径
img_path=os.listdir(root_dir)
a=0
for i in img_path:
a+=1
i= os.path.join(os.path.abspath(root_dir), i)
new_name=os.path.join(os.path.abspath(save_path), str(a) + '.txt') #此处可以修改图片名称
os.rename(i,new_name) #特别注意:rename会删除原图
root_dir为输入文件夹
save_dir为输出文件夹
假如两个一样那就直接是覆盖了
new_name就是改的名字,这里改的是按数字顺序命名txt
9.随机挑选数据集
有的代码运行要求train和val分开,所以这个时候就需要随机挑选数据集了
import os
import random
import shutil
# 源文件夹路径
src_path = "你的路径"
# 目标文件夹路径
dst_path = "你的路径"
# 获取源文件夹下的所有文件名
file_list = os.listdir(src_path)
# 计算挑选文件个数
num_files = len(file_list)
num_selected = int(num_files*0.3)
# 随机挑选文件名
selected_files = random.sample(file_list, num_selected)
# 遍历选中的文件,将其剪切粘贴至目标文件夹
for file_name in selected_files:
src_file = os.path.join(src_path, file_name)
dst_file = os.path.join(dst_path, file_name)
shutil.move(src_file, dst_file)
src_path输入文件夹
dst_path输出文件夹
num_selected就是用来改选多少的,通过改比例进行数量的更改
只写了这些不代表只有这些,后面再用到还会进行补充,有问题就评论私信,小问题