yolov5已经普及常用,最常规的步骤就是获取数据(图片)、标注图片(标注xml文件)、对标注的数据增强(椒盐模糊、高斯模糊、翻转等) 、训练数据。
在这里,假如客户新增一个目标,需要客户自己标注数据、增强数据和训练数据不合理,我们可以希望实现这样一个场景:建立一个界面,打开界面后:1.客户对新的产品从不同的角度拍七八张照片;2.客户在界面上对七八张图片的目标用长方形画框;3.界面系统自动对这几张标注好的图片进行数据扩增;4.将扩增后的数据自动移入训练的文件夹;5.训练模型。
其实,希望客户做以上的1、2、5三步就行,3和4由界面系统自动完成,这样就能很简单的完成新模型的标注和训练。下面分别是标注和数据扩增的代码。
1.图片标注
代码如下:
import cv2
import numpy as np
#1. 在图片中标注矩形框
def draw_circle(event, x, y, flags, param):
global ix, iy, drawing, px, py
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix, iy = x, y
elif event == cv2.EVENT_MOUSEMOVE:
if drawing == True:
# cv2.rectangle(img, (ix, iy), (px, py), (0, 0, 0), thickness=2) # 将刚刚拖拽的矩形涂黑
# cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), 0)
px, py = x, y
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), 0)
preds.append([classes, [iy, ix, y, x]])
# px, py = -1, -1
# 2. 将矩形框的坐标和名称写入xml文件
# 2.写入xml文件
def img_xml(img_path, xml_path, img_name, pred):
if len(pred) != 0:
# 1.读取图片(xml需要写入图片的长宽高)
img = cv2.imread(img_path)
# 2.写入xml文件
# (1)写入文件头部
files_path = img_path.split("\\")[-2]
print("..:", files_path)
xml_file = open((xml_path + img_name + '.xml'), 'w')
xml_file.write('<annotation>\n')
xml_file.write(' <folder>' + files_path + '</folder>\n')
xml_file.write(' <filename>' + img_name + '.jpg' + '</filename>\n')
xml_file.write(' <path>' + img_path + '</path>\n')
xml_file.write(' <source>\n')
xml_file.write(' <database>Unknown</database>\n')
xml_file.write(' </source>\n')
# (2)写入图片的长宽高信息
xml_file.write(' <size>\n')
xml_file.write(' <width>' + str(img.shape[1]) + '</width>\n')
xml_file.write(' <height>' + str(img.shape[0]) + '</height>\n')
xml_file.write(' <depth>' + str(img.shape[2]) + '</depth>\n')
xml_file.write(' </size>\n')
xml_file.write(' <segmented>0</segmented>\n')
# 3.写入字符串信息:[["water",[15,20,30,40]],["red",[12,13,14,15]]]
for item in pred:
xml_file.write(' <object>\n')
xml_file.write(' <name>' + str(item[0]) + '</name>\n')
xml_file.write(' <pose>Unspecified</pose>\n')
xml_file.write(' <truncated>0</truncated>\n')
xml_file.write(' <difficult>0</difficult>\n')
xml_file.write(' <bndbox>\n')
# 写入字符串信息
# [top, left, bottom, right]
xml_file.write(' <xmin>' + str(item[1][1]) + '</xmin>\n')
xml_file.write(' <ymin>' + str(item[1][0]) + '</ymin>\n')
xml_file.write(' <xmax>' + str(item[1][3]) + '</xmax>\n')
xml_file.write(' <ymax>' + str(item[1][2]) + '</ymax>\n')
xml_file.write(' </bndbox>\n')
xml_file.write(' </object>\n')
xml_file.write('</annotation>\n')
if __name__ == "__main__":
# 1.读取图片
img_path = "D:\AI\yq\datas\imgs\\4.jpg"
img = cv2.imread(img_path)
xml_path = "D:\AI\yq\datas\labels\\"
img_name = "4"
# pred = [["water", [15, 20, 30, 40]], ["red", [12, 13, 14, 15]]] #[top,left,bottom,right]
classes = "luoding"
preds = []
#2. 鼠标给图片画矩形,并将矩形的左上角和右下角的坐标记录下来
drawing = False # 鼠标按下为真
mode = True # 如果为真,画矩形,按m切换为曲线
ix, iy = -1, -1
px, py = -1, -1
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)
while (1):
cv2.imshow('image', img)
k = cv2.waitKey(1) & 0xFF
if k == ord('q'):
print(preds)
img_xml(img_path, xml_path, img_name, preds)
break
elif k == 27:
break
cv2.destroyAllWindows()
以上是标注图片,并生成xml文件的代码,结果如下:
在以上代码中,有2个函数,函数draw_circle(event, x, y, flags, param)是在图片中用鼠标画矩形框,并将矩形框的左上角和右下角的坐标记录下来(preds用来记录每个目标的坐标和目标名称),preds的打印结果如下:
这个数据加上图片的路径和名称就可以写出图片目标的xml文件了。
函数img_xml(img_path, xml_path, img_name, pred)是将用鼠标标注的图片信息写入xml文件,函数中的参数为
img_path:标注图片的路径
xml_path:保存xml文件的路径
img_name:保存图片的名称(不带.后缀,如1.jpg的图片名称为1)
pred:图片中目标的信息,是函数draw_circle(event, x, y, flags, param)的返回值
输出结果如下:
2.数据扩增
常见的数据扩增的方式有:原数据成倍增加、椒盐模糊、高斯模糊、水平翻转、垂直翻转、按照某个角度翻转、平移、缩放、裁剪、亮度、直方图均衡化、调整白平衡、mixup、cutmix、cutout等。这些数据增强的方式,有的只需要处理图片,而图片中的xml文件中的坐标值不变,有的则会改变目标在图片中的具体位置,这时需要修改xml文件中的坐标值。下面分别介绍不同的扩增:
2.1 图片的水平翻转
图片水平翻转后,其目标的坐标会发生变换。其xml文件需要修改扩增后的图片路径、图片名称、图片中目标的名称、图片中目标的坐标,其他信息可以根据自己的需要对该代码稍微修改即可。代码如下:
import cv2
import os
import random
import xml.etree.ElementTree as ET
from shutil import copyfile
# 1. 读取xml文件
def xml_read(xml_path):
# 2. xml 文件的读取
tree = ET.parse(xml_path)
root = tree.getroot()
ss = []
modifys = []
width = 0
for element in root.iter():
if element.tag == "width":
width = int(element.text)
# print("width:", width)
if element.tag == "name":
name = element.text
ss.append(name)
if element.tag == "xmin":
xmin = int(element.text)
# print("xmin:", xmin)
ss.append(xmin)
if element.tag == "ymin":
ymin = int(element.text)
# print("ymin:", ymin)
ss.append(ymin)
if element.tag == "xmax":
xmax = int(element.text)
# print("xmax:", xmax)
ss.append(xmax)
if element.tag == "ymax":
ymax = int(element.text)
# print("ymax:", ymax)
ss.append(ymax)
if len(ss) == 5:
x_min = ss[1]
y_min = ss[2]
x_max = ss[3]
y_max = ss[4]
modifys.append([ss[0], [y_min, x_min, y_max, x_max]])
ss = []
return modifys
# 2.写入xml文件
def img_xml(img_path, xml_path, img_name, pred):
if len(pred) != 0:
# 1.读取图片(xml需要写入图片的长宽高)
img = cv2.imread(img_path)
# 2.写入xml文件
# (1)写入文件头部
folder = img_path.split("\\")[-2]
filename = img_path.split("\\")[-1]
print(filename)
xml_file = open((xml_path + img_name + '.xml'), 'w')
xml_file.write('<annotation>\n')
xml_file.write(' <folder>' + folder + '</folder>\n')
xml_file.write(' <filename>' + filename + '</filename>\n')
xml_file.write(' <path>' + img_path + '</path>\n')
xml_file.write(' <source>\n')
xml_file.write(' <database>Unknown</database>\n')
xml_file.write(' </source>\n')
# (2)写入图片的长宽高信息
xml_file.write(' <size>\n')
xml_file.write(' <width>' + str(img.shape[1]) + '</width>\n')
xml_file.write(' <height>' + str(img.shape[0]) + '</height>\n')
xml_file.write(' <depth>' + str(img.shape[2]) + '</depth>\n')
xml_file.write(' </size>\n')
xml_file.write(' <segmented>0</segmented>\n')
# 3.写入字符串信息:[["water",[15,20,30,40]],["red",[12,13,14,15]]]
for item in pred:
xml_file.write(' <object>\n')
xml_file.write(' <name>' + str(item[0]) + '</name>\n')
xml_file.write(' <pose>Unspecified</pose>\n')
xml_file.write(' <truncated>0</truncated>\n')
xml_file.write(' <difficult>0</difficult>\n')
xml_file.write(' <bndbox>\n')
# 写入字符串信息
# [top, left, bottom, right]
xml_file.write(' <xmin>' + str(item[1][1]) + '</xmin>\n')
xml_file.write(' <ymin>' + str(item[1][0]) + '</ymin>\n')
xml_file.write(' <xmax>' + str(item[1][3]) + '</xmax>\n')
xml_file.write(' <ymax>' + str(item[1][2]) + '</ymax>\n')
xml_file.write(' </bndbox>\n')
xml_file.write(' </object>\n')
xml_file.write('</annotation>\n')
# 3.3 图片的水平翻转
# (1. img_path:图片具体路径,如: "D:\AI\yq\datas\strange\imgs\\1.jpg"
# (2. xml_path:图片对应的xml文件的具体路径,如:"D:\AI\yq\datas\strange\labels\\1.xml"
# (3. img_saves:需要保存的增强的图片路径,如:"D:\AI\yq\datas\strange\imgs_strange\\"
# (4. xml_saves:需要保存的增强图片的xml文件的路径,如:xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# (5. img_name:增强的图片名称,如 5 (5.jpg)
def img_horizen_rotation(img_path, xml_path, img_saves, xml_saves, img_name):# img, xmls,img3_path, xmls_path
print("............... 图片翻转 ..................")
# 1. 图片翻转
img = cv2.imread(img_path)
imgs = cv2.flip(img, 1)
# 2.将翻转后的图片保存在增强图片的文件夹
img_save_path = img_saves + img_name + ".jpg"
cv2.imwrite(img_save_path, imgs)
# 3. xml 文件的读取
pred = xml_read(xml_path)
# 4. 图片翻转后,需要修改图片中目标的坐标,所以修改pred中的坐标
preds = []
width = imgs.shape[1]
for items in pred:
x_min = width - items[1][3]
y_min = items[1][0]
x_max = width - items[1][1]
y_max = items[1][2]
preds.append([items[0], [y_min, x_min, y_max, x_max]])
# 5. 将翻转的图片信息写入xml文件
img_xml(img_save_path, xml_saves, img_name, preds)
# 6. 画图
for item in preds:
a = (item[1][1], item[1][0])
b = (item[1][3], item[1][2])
cv2.rectangle(imgs, a, b, (0, 255, 0), 2)
cv2.imshow("s2", imgs)
cv2.waitKey(0)
if __name__ == "__main__":
# 要处理的图片和对应的xml文件路径
img_path = "D:\AI\yq\datas\strange\imgs\\1.jpg"
xml_path = "D:\AI\yq\datas\strange\labels\\1.xml"
# 保存处理后的图片和xml文件路径
img_saves = "D:\AI\yq\datas\strange\imgs_strange\\"
xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# 水平翻转后的图片路径
img3_path = "D:\AI\yq\datas\strange\imgs_strange\\5.jpg"
img_name = "5"
# 3. 处理图片,增加xml文件
# xml文件需要修改的信息: (1. <folder>imgs</folder> imgs: 图片所在的文件夹名称
# (2. <filename>1.jpg</filename> 1.jpg:图片名称
# (3. <path>D:\AI\yq\datas\imgs\1.jpg</path> D:\AI\yq\datas\imgs\1.jpg:图片路径
# (4. ['luoding', [241, 1095, 505, 1307]] 'luoding':目标的名称
# (5. [241, 1095, 505, 1307]]:目标在图片中的坐标
# 3.3 图片水平旋转
img_horizen_rotation(img_path, xml_path, img_saves, xml_saves, img_name)
以上代码中进行了原图片中目标坐标的转换,并写入新的xml文件。结果如下:
后面一张是前面一张翻转后的图片,画的框是前面图片标注的框翻转后画在后面图片的目标上,可以看到转换后的坐标框是和目标物体吻合的。增强后的xml文件如下:
2.2 椒盐算法
代码如下:
import cv2
import os
import random
import xml.etree.ElementTree as ET
from shutil import copyfile
# 1. 读取xml文件
def xml_read(xml_path):
# 2. xml 文件的读取
tree = ET.parse(xml_path)
root = tree.getroot()
ss = []
modifys = []
width = 0
for element in root.iter():
if element.tag == "width":
width = int(element.text)
# print("width:", width)
if element.tag == "name":
name = element.text
ss.append(name)
if element.tag == "xmin":
xmin = int(element.text)
# print("xmin:", xmin)
ss.append(xmin)
if element.tag == "ymin":
ymin = int(element.text)
# print("ymin:", ymin)
ss.append(ymin)
if element.tag == "xmax":
xmax = int(element.text)
# print("xmax:", xmax)
ss.append(xmax)
if element.tag == "ymax":
ymax = int(element.text)
# print("ymax:", ymax)
ss.append(ymax)
if len(ss) == 5:
x_min = ss[1]
y_min = ss[2]
x_max = ss[3]
y_max = ss[4]
modifys.append([ss[0], [y_min, x_min, y_max, x_max]])
ss = []
return modifys
# 2.写入xml文件
def img_xml(img_path, xml_path, img_name, pred):
if len(pred) != 0:
# 1.读取图片(xml需要写入图片的长宽高)
img = cv2.imread(img_path)
# 2.写入xml文件
# (1)写入文件头部
folder = img_path.split("\\")[-2]
filename = img_path.split("\\")[-1]
print(filename)
xml_file = open((xml_path + img_name + '.xml'), 'w')
xml_file.write('<annotation>\n')
xml_file.write(' <folder>' + folder + '</folder>\n')
xml_file.write(' <filename>' + filename + '</filename>\n')
xml_file.write(' <path>' + img_path + '</path>\n')
xml_file.write(' <source>\n')
xml_file.write(' <database>Unknown</database>\n')
xml_file.write(' </source>\n')
# (2)写入图片的长宽高信息
xml_file.write(' <size>\n')
xml_file.write(' <width>' + str(img.shape[1]) + '</width>\n')
xml_file.write(' <height>' + str(img.shape[0]) + '</height>\n')
xml_file.write(' <depth>' + str(img.shape[2]) + '</depth>\n')
xml_file.write(' </size>\n')
xml_file.write(' <segmented>0</segmented>\n')
# 3.写入字符串信息:[["water",[15,20,30,40]],["red",[12,13,14,15]]]
for item in pred:
xml_file.write(' <object>\n')
xml_file.write(' <name>' + str(item[0]) + '</name>\n')
xml_file.write(' <pose>Unspecified</pose>\n')
xml_file.write(' <truncated>0</truncated>\n')
xml_file.write(' <difficult>0</difficult>\n')
xml_file.write(' <bndbox>\n')
# 写入字符串信息
# [top, left, bottom, right]
xml_file.write(' <xmin>' + str(item[1][1]) + '</xmin>\n')
xml_file.write(' <ymin>' + str(item[1][0]) + '</ymin>\n')
xml_file.write(' <xmax>' + str(item[1][3]) + '</xmax>\n')
xml_file.write(' <ymax>' + str(item[1][2]) + '</ymax>\n')
xml_file.write(' </bndbox>\n')
xml_file.write(' </object>\n')
xml_file.write('</annotation>\n')
# 3.2.椒盐算法增强
# (1. img_path:图片具体路径,如: "D:\AI\yq\datas\strange\imgs\\1.jpg"
# (2. xml_path:图片对应的xml文件的具体路径,如:"D:\AI\yq\datas\strange\labels\\1.xml"
# (3. img_saves:需要保存的增强的图片路径,如:"D:\AI\yq\datas\strange\imgs_strange\\"
# (4. xml_saves:需要保存的增强图片的xml文件的路径,如:xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# (5. img_name:增强的图片名称,如 5 (5.jpg)
# (6. percentage:椒盐算法加噪点的比率
def pepper_and_salt(img_path, xml_path, img_saves, xml_saves, img_name,percentage):
# 1. 椒盐算法
img = cv2.imread(img_path)
num=int(percentage*img.shape[0]*img.shape[1])# 椒盐噪声点数量
random.randint(0, img.shape[0])
img2=img.copy()
for i in range(num):
X=random.randint(0,img2.shape[0]-1)#从0到图像长度之间的一个随机整数,因为是闭区间所以-1
Y=random.randint(0,img2.shape[1]-1)
if random.randint(0,1) ==0: #黑白色概率55开
img2[X,Y] = (255,255,255) # 白色
else:
img2[X,Y] = (0, 0, 0) # 黑色
# 2. 保存图片,将椒盐处理后的图片保存到增强图片的文件夹
img_save_path = img_saves + img_name + ".jpg"
cv2.imwrite(img_save_path, img2)
# 3. xml 文件的读取
pred = xml_read(xml_path)
# 4. 将椒盐的图片信息写入xml文件,由于成倍增加图片不影响图片中的目标信息,故只需要修改xml文件的前3项信息即可
img_xml(img_save_path, xml_saves, img_name, pred)
# 3. 画图
for item in pred:
a = (item[1][1], item[1][0])
b = (item[1][3], item[1][2])
cv2.rectangle(img, a, b, (0, 255, 0), 2)
cv2.rectangle(img2, a, b, (0, 255, 0), 2)
cv2.imshow("s1", img)
cv2.imshow("s2", img2)
cv2.waitKey(0)
if __name__ == "__main__":
# 要处理的图片和对应的xml文件路径
img_path = "D:\AI\yq\datas\strange\imgs\\1.jpg"
xml_path = "D:\AI\yq\datas\strange\labels\\1.xml"
# 保存处理后的图片和xml文件路径
img_saves = "D:\AI\yq\datas\strange\imgs_strange\\"
xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# 水平翻转后的图片路径
img3_path = "D:\AI\yq\datas\strange\imgs_strange\\5.jpg"
img_name = "5"
# 3.2 椒盐算法
pepper_and_salt(img_path, xml_path, img_saves, xml_saves, img_name, percentage = 0.08)
这里只写了算法的函数,由于椒盐算法没有改变目标物在图片中的位置,所以不需要修改xml文件或者只需要修改新的图片路径即可。输出效果如下:
前面的图片是原图,后面的图片是对原图做了椒盐变换,并将标注的坐标画成了框,显然是吻合的。写入的xml文件如下:
2.3.图片数量增加1倍
代码如下:
import cv2
import os
import random
import xml.etree.ElementTree as ET
from shutil import copyfile
# 1. 读取xml文件
def xml_read(xml_path):
# 2. xml 文件的读取
tree = ET.parse(xml_path)
root = tree.getroot()
ss = []
modifys = []
width = 0
for element in root.iter():
if element.tag == "width":
width = int(element.text)
# print("width:", width)
if element.tag == "name":
name = element.text
ss.append(name)
if element.tag == "xmin":
xmin = int(element.text)
# print("xmin:", xmin)
ss.append(xmin)
if element.tag == "ymin":
ymin = int(element.text)
# print("ymin:", ymin)
ss.append(ymin)
if element.tag == "xmax":
xmax = int(element.text)
# print("xmax:", xmax)
ss.append(xmax)
if element.tag == "ymax":
ymax = int(element.text)
# print("ymax:", ymax)
ss.append(ymax)
if len(ss) == 5:
x_min = ss[1]
y_min = ss[2]
x_max = ss[3]
y_max = ss[4]
modifys.append([ss[0], [y_min, x_min, y_max, x_max]])
ss = []
return modifys
# 2.写入xml文件
def img_xml(img_path, xml_path, img_name, pred):
if len(pred) != 0:
# 1.读取图片(xml需要写入图片的长宽高)
img = cv2.imread(img_path)
# 2.写入xml文件
# (1)写入文件头部
folder = img_path.split("\\")[-2]
filename = img_path.split("\\")[-1]
print(filename)
xml_file = open((xml_path + img_name + '.xml'), 'w')
xml_file.write('<annotation>\n')
xml_file.write(' <folder>' + folder + '</folder>\n')
xml_file.write(' <filename>' + filename + '</filename>\n')
xml_file.write(' <path>' + img_path + '</path>\n')
xml_file.write(' <source>\n')
xml_file.write(' <database>Unknown</database>\n')
xml_file.write(' </source>\n')
# (2)写入图片的长宽高信息
xml_file.write(' <size>\n')
xml_file.write(' <width>' + str(img.shape[1]) + '</width>\n')
xml_file.write(' <height>' + str(img.shape[0]) + '</height>\n')
xml_file.write(' <depth>' + str(img.shape[2]) + '</depth>\n')
xml_file.write(' </size>\n')
xml_file.write(' <segmented>0</segmented>\n')
# 3.写入字符串信息:[["water",[15,20,30,40]],["red",[12,13,14,15]]]
for item in pred:
xml_file.write(' <object>\n')
xml_file.write(' <name>' + str(item[0]) + '</name>\n')
xml_file.write(' <pose>Unspecified</pose>\n')
xml_file.write(' <truncated>0</truncated>\n')
xml_file.write(' <difficult>0</difficult>\n')
xml_file.write(' <bndbox>\n')
# 写入字符串信息
# [top, left, bottom, right]
xml_file.write(' <xmin>' + str(item[1][1]) + '</xmin>\n')
xml_file.write(' <ymin>' + str(item[1][0]) + '</ymin>\n')
xml_file.write(' <xmax>' + str(item[1][3]) + '</xmax>\n')
xml_file.write(' <ymax>' + str(item[1][2]) + '</ymax>\n')
xml_file.write(' </bndbox>\n')
xml_file.write(' </object>\n')
xml_file.write('</annotation>\n')
# 3.1 原始图片成倍增加
# (1. img_path:图片具体路径,如: "D:\AI\yq\datas\strange\imgs\\1.jpg"
# (2. xml_path:图片对应的xml文件的具体路径,如:"D:\AI\yq\datas\strange\labels\\1.xml"
# (3. img_saves:需要保存的增强的图片路径,如:"D:\AI\yq\datas\strange\imgs_strange\\"
# (4. xml_saves:需要保存的增强图片的xml文件的路径,如:xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# (5. img_name: 增强的图片名称,如 5 (5.jpg)
def img_double(img_path, xml_path, img_saves, xml_saves, img_name):
print(" ......................... 图片倍增 ............................\n")
# 1.读取图片
img = cv2.imread(img_path)
# 2.读取xml文件,写成pred
pred = xml_read(xml_path)
print(pred)
# 3. 将图片写入要保存的图片路径
#img_save_path = os.path.join(img_saves, img_name + ".jpg")
img_save_path = img_saves + img_name + ".jpg"
cv2.imwrite(img_save_path, img)
# 4. 将增强的图片信息写入xml文件,由于成倍增加图片不影响图片中的目标信息,故只需要修改xml文件的前3项信息即可
img_xml(img_save_path, xml_saves, img_name, pred)
# 3. 画图
for item in pred:
a = (item[1][1], item[1][0])
b = (item[1][3], item[1][2])
cv2.rectangle(img, a, b, (0, 255, 0), 2)
cv2.imshow("s1", img)
cv2.imshow("s2", img)
cv2.waitKey(0)
if __name__ == "__main__":
# 要处理的图片和对应的xml文件路径
img_path = "D:\AI\yq\datas\strange\imgs\\1.jpg"
xml_path = "D:\AI\yq\datas\strange\labels\\1.xml"
# 保存处理后的图片和xml文件路径
img_saves = "D:\AI\yq\datas\strange\imgs_strange\\"
xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# 水平翻转后的图片路径
img3_path = "D:\AI\yq\datas\strange\imgs_strange\\5.jpg"
img_name = "5"
# 3. 处理图片,增加xml文件
# xml文件需要修改的信息: (1. <folder>imgs</folder> imgs: 图片所在的文件夹名称
# (2. <filename>1.jpg</filename> 1.jpg:图片名称
# (3. <path>D:\AI\yq\datas\imgs\1.jpg</path> D:\AI\yq\datas\imgs\1.jpg:图片路径
# (4. ['luoding', [241, 1095, 505, 1307]] 'luoding':目标的名称
# (5. [241, 1095, 505, 1307]]:目标在图片中的坐标
# 3.1 图片倍增
img_double(img_path, xml_path, img_saves, xml_saves,img_name)
以上代码中,对每个函数已经做了具体的解释:
1. xml_read(xml_path):输入:标注的原函数的xml文件路径
处理:读取原图片对应的xml文件的目标的名称和坐标
返回结果: [['luoding', [241, 399, 505, 611]], ['luoding', [412, 678, 679, 1155]], ['luoding', [200, 1056, 450, 1156]], ['luoding', [765, 1334, 906, 1439]], ['luoding', [771, 641, 949, 845]]] ,表示图片中有5个'luoding',后面是对应了每个目标的坐标
2. img_xml(img_path, xml_path, img_name, pred):
输入:(1. img_path:保存的增加的图片的路径
(2. xml_path:保存的增加的图片对应的xml文件的路径
(3. img_name:保存的增加的图片的名称,如 5 (5.jpg)
(4. pred:函数xml_read(xml_path)的返回值
处理:根据函数的输入信息,将增加的图片的信息写入新的xml文件
3. img_double(img_path, xml_path, img_saves, xml_saves,img_name)
该函数的输入在代码中写的很详细了,这里不用具体的介绍。
增加数量的图片的自动标注结果如下:
2.4 图片旋转任意角度
代码如下:
import cv2
import os
import numpy as np
import random
import xml.etree.ElementTree as ET
from shutil import copyfile
# 1. 读取xml文件
def xml_read(xml_path):
# 2. xml 文件的读取
tree = ET.parse(xml_path)
root = tree.getroot()
ss = []
modifys = []
width = 0
for element in root.iter():
if element.tag == "width":
width = int(element.text)
# print("width:", width)
if element.tag == "name":
name = element.text
ss.append(name)
if element.tag == "xmin":
xmin = int(element.text)
# print("xmin:", xmin)
ss.append(xmin)
if element.tag == "ymin":
ymin = int(element.text)
# print("ymin:", ymin)
ss.append(ymin)
if element.tag == "xmax":
xmax = int(element.text)
# print("xmax:", xmax)
ss.append(xmax)
if element.tag == "ymax":
ymax = int(element.text)
# print("ymax:", ymax)
ss.append(ymax)
if len(ss) == 5:
x_min = ss[1]
y_min = ss[2]
x_max = ss[3]
y_max = ss[4]
modifys.append([ss[0], [y_min, x_min, y_max, x_max]])
ss = []
return modifys
# 2.写入xml文件
def img_xml(img_path, xml_path, img_name, pred):
if len(pred) != 0:
# 1.读取图片(xml需要写入图片的长宽高)
img = cv2.imread(img_path)
# 2.写入xml文件
# (1)写入文件头部
folder = img_path.split("\\")[-2]
filename = img_path.split("\\")[-1]
print(filename)
xml_file = open((xml_path + img_name + '.xml'), 'w')
xml_file.write('<annotation>\n')
xml_file.write(' <folder>' + folder + '</folder>\n')
xml_file.write(' <filename>' + filename + '</filename>\n')
xml_file.write(' <path>' + img_path + '</path>\n')
xml_file.write(' <source>\n')
xml_file.write(' <database>Unknown</database>\n')
xml_file.write(' </source>\n')
# (2)写入图片的长宽高信息
xml_file.write(' <size>\n')
xml_file.write(' <width>' + str(img.shape[1]) + '</width>\n')
xml_file.write(' <height>' + str(img.shape[0]) + '</height>\n')
xml_file.write(' <depth>' + str(img.shape[2]) + '</depth>\n')
xml_file.write(' </size>\n')
xml_file.write(' <segmented>0</segmented>\n')
# 3.写入字符串信息:[["water",[15,20,30,40]],["red",[12,13,14,15]]]
for item in pred:
xml_file.write(' <object>\n')
xml_file.write(' <name>' + str(item[0]) + '</name>\n')
xml_file.write(' <pose>Unspecified</pose>\n')
xml_file.write(' <truncated>0</truncated>\n')
xml_file.write(' <difficult>0</difficult>\n')
xml_file.write(' <bndbox>\n')
# 写入字符串信息
# [top, left, bottom, right]
xml_file.write(' <xmin>' + str(item[1][1]) + '</xmin>\n')
xml_file.write(' <ymin>' + str(item[1][0]) + '</ymin>\n')
xml_file.write(' <xmax>' + str(item[1][3]) + '</xmax>\n')
xml_file.write(' <ymax>' + str(item[1][2]) + '</ymax>\n')
xml_file.write(' </bndbox>\n')
xml_file.write(' </object>\n')
xml_file.write('</annotation>\n')
# 按照任意角度旋转图片
def rotate_img(img, angle):
'''
img --image
angle --rotation angle
return--rotated img
'''
h, w = img.shape[:2]
rotate_center = (w/2, h/2)
#获取旋转矩阵
# 参数1为旋转中心点;
# 参数2为旋转角度,正值-逆时针旋转;负值-顺时针旋转
# 参数3为各向同性的比例因子,1.0原图,2.0变成原来的2倍,0.5变成原来的0.5倍
M = cv2.getRotationMatrix2D(rotate_center, angle, 1.0)
#计算图像新边界
new_w = int(h * np.abs(M[0, 1]) + w * np.abs(M[0, 0]))
new_h = int(h * np.abs(M[0, 0]) + w * np.abs(M[0, 1]))
#调整旋转矩阵以考虑平移
M[0, 2] += (new_w - w) / 2
M[1, 2] += (new_h - h) / 2
rotated_img = cv2.warpAffine(img, M, (new_w, new_h))
return rotated_img
# 3.4 图片的任意角度旋转
# (1. img_path:图片具体路径,如: "D:\AI\yq\datas\strange\imgs\\1.jpg"
# (2. xml_path:图片对应的xml文件的具体路径,如:"D:\AI\yq\datas\strange\labels\\1.xml"
# (3. img_saves:需要保存的增强的图片路径,如:"D:\AI\yq\datas\strange\imgs_strange\\"
# (4. xml_saves:需要保存的增强图片的xml文件的路径,如:xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# (5. img_name:增强的图片名称,如 5 (5.jpg)
# (6. angle:图片的旋转角度(角度制)
def img_rotation(img_path, xml_path, img_saves, xml_saves, img_name,angle):
print("............... 图片翻转 ..................")
# 1. 图片翻转
img = cv2.imread(img_path)
imgs = rotate_img(img, angle) # 旋转后的图片
# print(imgs.shape,img.shape)
# cv2.imshow("s1", imgs)
# cv2.waitKey(0)
# 2.将翻转后的图片保存在增强图片的文件夹
img_save_path = img_saves + img_name + ".jpg"
cv2.imwrite(img_save_path, imgs)
# 3. xml 文件的读取
pred = xml_read(xml_path)
# 4. 图片翻转后,需要修改图片中目标的坐标,所以修改pred中的坐标,以图片的中心点旋转
preds = []
# 图片中心坐标为 (x_center,y_center)
x_center = img.shape[1] / 2
y_center = img.shape[0] / 2
# 旋转后的图片的中心位置
x_centers = imgs.shape[1] / 2
y_centers = imgs.shape[0] / 2
# 旋转后的图片的坐标平移
x_var = x_centers - x_center
y_var = y_centers - y_center
# 将旋转角度转换为弧度制
angles = angle * 3.1415926 / 180
# 根据坐标旋转处理每个目标的坐标
for items in pred:
# 将目标坐标平移到图片的中心位置
x_min = items[1][1] - x_center
y_min = items[1][0] - y_center
x_max = items[1][3] - x_center
y_max = items[1][2] - y_center
# 坐标变换,根据图片的中心位置旋转,目标坐标也以该中心位置为坐标原点进行旋转,从而获得新的坐标
x_0 = x_min * np.cos(angles) + y_min * np.sin(angles) + x_center + x_var
y_0 = y_min * np.cos(angles) - x_min * np.sin(angles) + y_center + y_var
x_1 = x_min * np.cos(angles) + y_max * np.sin(angles) + x_center + x_var
y_1 = y_max * np.cos(angles) - x_min * np.sin(angles) + y_center + y_var
x_2 = x_max * np.cos(angles) + y_min * np.sin(angles) + x_center + x_var
y_2 = y_min * np.cos(angles) - x_max * np.sin(angles) + y_center + y_var
x_3 = x_max * np.cos(angles) + y_max * np.sin(angles) + x_center + x_var
y_3 = y_max * np.cos(angles) - x_max * np.sin(angles) + y_center + y_var
xmin = np.asarray([x_0, x_1, x_2, x_3]).min()
xmax = np.asarray([ x_0, x_1, x_2, x_3]).max()
ymin = np.asarray([y_0, y_1, y_2, y_3]).min()
ymax = np.asarray([y_0, y_1, y_2, y_3]).max()
preds.append([items[0],[int(ymin), int(xmin), int(ymax), int(xmax)]])
# 5. 将翻转的图片信息写入xml文件
img_xml(img_save_path, xml_saves, img_name, preds)
# 6. 画图
for item in preds:
a = (item[1][1], item[1][0])
b = (item[1][3], item[1][2])
cv2.rectangle(imgs, a, b, (0, 255, 0), 2)
cv2.imshow("s2", imgs)
cv2.waitKey(0)
if __name__ == "__main__":
# 要处理的图片和对应的xml文件路径
img_path = "D:\AI\yq\datas\strange\imgs\\1.jpg"
xml_path = "D:\AI\yq\datas\strange\labels\\1.xml"
# 保存处理后的图片和xml文件路径
img_saves = "D:\AI\yq\datas\strange\imgs_strange\\"
xml_saves = "D:\AI\yq\datas\strange\labels_strange\\"
# 水平翻转后的图片路径
img3_path = "D:\AI\yq\datas\strange\imgs_strange\\5.jpg"
img_name = "5"
# 3.4 图片以某个角度旋转,如上下旋转(90度)
img_rotation(img_path, xml_path, img_saves, xml_saves, img_name,angle=60)
下面是旋转后对坐标变换,然后在旋转后的图片上画的图片
比如,60度时的xml文件如下:
通过以上的结果可以看出,标定每问题。