当前位置:   article > 正文

YOLOV8目标检测数据增强以及标签格式转换(详细)_yolov8数据增强

yolov8数据增强

#目标检测,#深度学习,#数据增强,#标签格式转换

1.数据增强

        1.1使用ROBOFLOW进行数据增强

                首先使用ROBOFLOW进行数据增强有一定的限制,而且增强的效果也比较一般,因为这个软件是外国的,所以国内的网很难打开,建议挂梯子使用。

                打开roboflow的网址,注册一个账号,或者使用GitHub的账号都可以登录成功,登录成功后点击创建项目

                

                

                然后就会看见下一个页面,直接点击进去就行,不用修改。

                

以下是不放一起的演示图:

                

        点击save and continue,等待上传数据集到项目中,下面的图片分配看个人喜好来,我的是80:15:5

        图片的尺寸按照自己的需求来修改

      

点击添加增强方式,里面有多种选择,建议选择三四种就行了,多了也不好,最后就可以点击生成了,没有充钱只可以增强一倍和两倍多点哦~

        

点击导出数据集:

然后就完成ROBOFLOW数据增强啦(注意增强次数有限)。

        1.2使用代码数据增强

直接放代码:

        

  1. # -*- coding=utf-8 -*-
  2. # 裁剪(需改变bbox)
  3. # 2. 平移(需改变bbox)
  4. # 3. 改变亮度
  5. # 4. 加噪声
  6. # 5. 旋转角度(需要改变bbox)
  7. # 6. 镜像(需要改变bbox)
  8. # 7. cutout
  9. # 注意:
  10. # random.seed(),相同的seed,产生的随机数是一样
  11. import time
  12. import random
  13. import copy
  14. import cv2
  15. import os
  16. import math
  17. import numpy as np
  18. from skimage.util import random_noise
  19. from lxml import etree, objectify
  20. import xml.etree.ElementTree as ET
  21. import argparse
  22. # 显示图片
  23. def show_pic(img, bboxes=None):
  24. '''
  25. 输入:
  26. img:图像array
  27. bboxes:图像的所有boudning box list, 格式为[[x_min, y_min, x_max, y_max]....]
  28. names:每个box对应的名称
  29. '''
  30. for i in range(len(bboxes)):
  31. bbox = bboxes[i]
  32. x_min = bbox[0]
  33. y_min = bbox[1]
  34. x_max = bbox[2]
  35. y_max = bbox[3]
  36. cv2.rectangle(img, (int(x_min), int(y_min)), (int(x_max), int(y_max)), (0, 255, 0), 3)
  37. cv2.namedWindow('pic', 0) # 1表示原图
  38. cv2.moveWindow('pic', 0, 0)
  39. cv2.resizeWindow('pic', 1200, 800) # 可视化的图片大小
  40. cv2.imshow('pic', img)
  41. cv2.waitKey(0)
  42. cv2.destroyAllWindows()
  43. # 图像均为cv2读取
  44. class DataAugmentForObjectDetection():
  45. def __init__(self, rotation_rate=0.5, max_rotation_angle=5,
  46. crop_rate=0.5, shift_rate=0.5, change_light_rate=0.5,
  47. add_noise_rate=0.5, flip_rate=0.5,
  48. cutout_rate=0.5, cut_out_length=50, cut_out_holes=1, cut_out_threshold=0.5,
  49. is_addNoise=True, is_changeLight=True, is_cutout=False, is_rotate_img_bbox=True,
  50. is_crop_img_bboxes=True, is_shift_pic_bboxes=True, is_filp_pic_bboxes=True):
  51. # 配置各个操作的属性
  52. self.rotation_rate = rotation_rate
  53. self.max_rotation_angle = max_rotation_angle
  54. self.crop_rate = crop_rate
  55. self.shift_rate = shift_rate
  56. self.change_light_rate = change_light_rate
  57. self.add_noise_rate = add_noise_rate
  58. self.flip_rate = flip_rate
  59. self.cutout_rate = cutout_rate
  60. self.cut_out_length = cut_out_length
  61. self.cut_out_holes = cut_out_holes
  62. self.cut_out_threshold = cut_out_threshold
  63. # 是否使用某种增强方式
  64. self.is_addNoise = is_addNoise
  65. self.is_changeLight = is_changeLight
  66. self.is_cutout = is_cutout
  67. self.is_rotate_img_bbox = is_rotate_img_bbox
  68. self.is_crop_img_bboxes = is_crop_img_bboxes
  69. self.is_shift_pic_bboxes = is_shift_pic_bboxes
  70. self.is_filp_pic_bboxes = is_filp_pic_bboxes
  71. # 加噪声
  72. def _addNoise(self, img):
  73. '''
  74. 输入:
  75. img:图像array
  76. 输出:
  77. 加噪声后的图像array,由于输出的像素是在[0,1]之间,所以得乘以255
  78. '''
  79. # return cv2.GaussianBlur(img, (11, 11), 0)
  80. return random_noise(img, mode='gaussian', seed=int(time.time()), clip=True) * 255
  81. # 调整亮度
  82. def _changeLight(self, img):
  83. alpha = random.uniform(0.35, 1)
  84. blank = np.zeros(img.shape, img.dtype)
  85. return cv2.addWeighted(img, alpha, blank, 1 - alpha, 0)
  86. # cutout
  87. def _cutout(self, img, bboxes, length=100, n_holes=1, threshold=0.5):
  88. '''
  89. 原版本:https://github.com/uoguelph-mlrg/Cutout/blob/master/util/cutout.py
  90. Randomly mask out one or more patches from an image.
  91. Args:
  92. img : a 3D numpy array,(h,w,c)
  93. bboxes : 框的坐标
  94. n_holes (int): Number of patches to cut out of each image.
  95. length (int): The length (in pixels) of each square patch.
  96. '''
  97. def cal_iou(boxA, boxB):
  98. '''
  99. boxA, boxB为两个框,返回iou
  100. boxB为bouding box
  101. '''
  102. # determine the (x, y)-coordinates of the intersection rectangle
  103. xA = max(boxA[0], boxB[0])
  104. yA = max(boxA[1], boxB[1])
  105. xB = min(boxA[2], boxB[2])
  106. yB = min(boxA[3], boxB[3])
  107. if xB <= xA or yB <= yA:
  108. return 0.0
  109. # compute the area of intersection rectangle
  110. interArea = (xB - xA + 1) * (yB - yA + 1)
  111. # compute the area of both the prediction and ground-truth
  112. # rectangles
  113. boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
  114. boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
  115. iou = interArea / float(boxBArea)
  116. return iou
  117. # 得到h和w
  118. if img.ndim == 3:
  119. h, w, c = img.shape
  120. else:
  121. _, h, w, c = img.shape
  122. mask = np.ones((h, w, c), np.float32)
  123. for n in range(n_holes):
  124. chongdie = True # 看切割的区域是否与box重叠太多
  125. while chongdie:
  126. y = np.random.randint(h)
  127. x = np.random.randint(w)
  128. y1 = np.clip(y - length // 2, 0,
  129. h) # numpy.clip(a, a_min, a_max, out=None), clip这个函数将将数组中的元素限制在a_min, a_max之间,大于a_max的就使得它等于 a_max,小于a_min,的就使得它等于a_min
  130. y2 = np.clip(y + length // 2, 0, h)
  131. x1 = np.clip(x - length // 2, 0, w)
  132. x2 = np.clip(x + length // 2, 0, w)
  133. chongdie = False
  134. for box in bboxes:
  135. if cal_iou([x1, y1, x2, y2], box) > threshold:
  136. chongdie = True
  137. break
  138. mask[y1: y2, x1: x2, :] = 0.
  139. img = img * mask
  140. return img
  141. # 旋转
  142. def _rotate_img_bbox(self, img, bboxes, angle=5, scale=1.):
  143. '''
  144. 参考:https://blog.csdn.net/u014540717/article/details/53301195crop_rate
  145. 输入:
  146. img:图像array,(h,w,c)
  147. bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
  148. angle:旋转角度
  149. scale:默认1
  150. 输出:
  151. rot_img:旋转后的图像array
  152. rot_bboxes:旋转后的boundingbox坐标list
  153. '''
  154. # ---------------------- 旋转图像 ----------------------
  155. w = img.shape[1]
  156. h = img.shape[0]
  157. # 角度变弧度
  158. rangle = np.deg2rad(angle) # angle in radians
  159. # now calculate new image width and height
  160. nw = (abs(np.sin(rangle) * h) + abs(np.cos(rangle) * w)) * scale
  161. nh = (abs(np.cos(rangle) * h) + abs(np.sin(rangle) * w)) * scale
  162. # ask OpenCV for the rotation matrix
  163. rot_mat = cv2.getRotationMatrix2D((nw * 0.5, nh * 0.5), angle, scale)
  164. # calculate the move from the old center to the new center combined
  165. # with the rotation
  166. rot_move = np.dot(rot_mat, np.array([(nw - w) * 0.5, (nh - h) * 0.5, 0]))
  167. # the move only affects the translation, so update the translation
  168. rot_mat[0, 2] += rot_move[0]
  169. rot_mat[1, 2] += rot_move[1]
  170. # 仿射变换
  171. rot_img = cv2.warpAffine(img, rot_mat, (int(math.ceil(nw)), int(math.ceil(nh))), flags=cv2.INTER_LANCZOS4)
  172. # ---------------------- 矫正bbox坐标 ----------------------
  173. # rot_mat是最终的旋转矩阵
  174. # 获取原始bbox的四个中点,然后将这四个点转换到旋转后的坐标系下
  175. rot_bboxes = list()
  176. for bbox in bboxes:
  177. xmin = bbox[0]
  178. ymin = bbox[1]
  179. xmax = bbox[2]
  180. ymax = bbox[3]
  181. point1 = np.dot(rot_mat, np.array([(xmin + xmax) / 2, ymin, 1]))
  182. point2 = np.dot(rot_mat, np.array([xmax, (ymin + ymax) / 2, 1]))
  183. point3 = np.dot(rot_mat, np.array([(xmin + xmax) / 2, ymax, 1]))
  184. point4 = np.dot(rot_mat, np.array([xmin, (ymin + ymax) / 2, 1]))
  185. # 合并np.array
  186. concat = np.vstack((point1, point2, point3, point4))
  187. # 改变array类型
  188. concat = concat.astype(np.int32)
  189. # 得到旋转后的坐标
  190. rx, ry, rw, rh = cv2.boundingRect(concat)
  191. rx_min = rx
  192. ry_min = ry
  193. rx_max = rx + rw
  194. ry_max = ry + rh
  195. # 加入list中
  196. rot_bboxes.append([rx_min, ry_min, rx_max, ry_max])
  197. return rot_img, rot_bboxes
  198. # 裁剪
  199. def _crop_img_bboxes(self, img, bboxes):
  200. '''
  201. 裁剪后的图片要包含所有的框
  202. 输入:
  203. img:图像array
  204. bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
  205. 输出:
  206. crop_img:裁剪后的图像array
  207. crop_bboxes:裁剪后的bounding box的坐标list
  208. '''
  209. # ---------------------- 裁剪图像 ----------------------
  210. w = img.shape[1]
  211. h = img.shape[0]
  212. x_min = w # 裁剪后的包含所有目标框的最小的框
  213. x_max = 0
  214. y_min = h
  215. y_max = 0
  216. for bbox in bboxes:
  217. x_min = min(x_min, bbox[0])
  218. y_min = min(y_min, bbox[1])
  219. x_max = max(x_max, bbox[2])
  220. y_max = max(y_max, bbox[3])
  221. d_to_left = x_min # 包含所有目标框的最小框到左边的距离
  222. d_to_right = w - x_max # 包含所有目标框的最小框到右边的距离
  223. d_to_top = y_min # 包含所有目标框的最小框到顶端的距离
  224. d_to_bottom = h - y_max # 包含所有目标框的最小框到底部的距离
  225. # 随机扩展这个最小框
  226. crop_x_min = int(x_min - random.uniform(0, d_to_left))
  227. crop_y_min = int(y_min - random.uniform(0, d_to_top))
  228. crop_x_max = int(x_max + random.uniform(0, d_to_right))
  229. crop_y_max = int(y_max + random.uniform(0, d_to_bottom))
  230. # 随机扩展这个最小框 , 防止别裁的太小
  231. # crop_x_min = int(x_min - random.uniform(d_to_left//2, d_to_left))
  232. # crop_y_min = int(y_min - random.uniform(d_to_top//2, d_to_top))
  233. # crop_x_max = int(x_max + random.uniform(d_to_right//2, d_to_right))
  234. # crop_y_max = int(y_max + random.uniform(d_to_bottom//2, d_to_bottom))
  235. # 确保不要越界
  236. crop_x_min = max(0, crop_x_min)
  237. crop_y_min = max(0, crop_y_min)
  238. crop_x_max = min(w, crop_x_max)
  239. crop_y_max = min(h, crop_y_max)
  240. crop_img = img[crop_y_min:crop_y_max, crop_x_min:crop_x_max]
  241. # ---------------------- 裁剪boundingbox ----------------------
  242. # 裁剪后的boundingbox坐标计算
  243. crop_bboxes = list()
  244. for bbox in bboxes:
  245. crop_bboxes.append([bbox[0] - crop_x_min, bbox[1] - crop_y_min, bbox[2] - crop_x_min, bbox[3] - crop_y_min])
  246. return crop_img, crop_bboxes
  247. # 平移
  248. def _shift_pic_bboxes(self, img, bboxes):
  249. '''
  250. 参考:https://blog.csdn.net/sty945/article/details/79387054
  251. 平移后的图片要包含所有的框
  252. 输入:
  253. img:图像array
  254. bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
  255. 输出:
  256. shift_img:平移后的图像array
  257. shift_bboxes:平移后的bounding box的坐标list
  258. '''
  259. # ---------------------- 平移图像 ----------------------
  260. w = img.shape[1]
  261. h = img.shape[0]
  262. x_min = w # 裁剪后的包含所有目标框的最小的框
  263. x_max = 0
  264. y_min = h
  265. y_max = 0
  266. for bbox in bboxes:
  267. x_min = min(x_min, bbox[0])
  268. y_min = min(y_min, bbox[1])
  269. x_max = max(x_max, bbox[2])
  270. y_max = max(y_max, bbox[3])
  271. d_to_left = x_min # 包含所有目标框的最大左移动距离
  272. d_to_right = w - x_max # 包含所有目标框的最大右移动距离
  273. d_to_top = y_min # 包含所有目标框的最大上移动距离
  274. d_to_bottom = h - y_max # 包含所有目标框的最大下移动距离
  275. x = random.uniform(-(d_to_left - 1) / 3, (d_to_right - 1) / 3)
  276. y = random.uniform(-(d_to_top - 1) / 3, (d_to_bottom - 1) / 3)
  277. M = np.float32([[1, 0, x], [0, 1, y]]) # x为向左或右移动的像素值,正为向右负为向左; y为向上或者向下移动的像素值,正为向下负为向上
  278. shift_img = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
  279. # ---------------------- 平移boundingbox ----------------------
  280. shift_bboxes = list()
  281. for bbox in bboxes:
  282. shift_bboxes.append([bbox[0] + x, bbox[1] + y, bbox[2] + x, bbox[3] + y])
  283. return shift_img, shift_bboxes
  284. # 镜像
  285. def _filp_pic_bboxes(self, img, bboxes):
  286. '''
  287. 参考:https://blog.csdn.net/jningwei/article/details/78753607
  288. 平移后的图片要包含所有的框
  289. 输入:
  290. img:图像array
  291. bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
  292. 输出:
  293. flip_img:平移后的图像array
  294. flip_bboxes:平移后的bounding box的坐标list
  295. '''
  296. # ---------------------- 翻转图像 ----------------------
  297. flip_img = copy.deepcopy(img)
  298. h, w, _ = img.shape
  299. sed = random.random()
  300. if 0 < sed < 0.33: # 0.33的概率水平翻转,0.33的概率垂直翻转,0.33是对角反转
  301. flip_img = cv2.flip(flip_img, 0) # _flip_x
  302. inver = 0
  303. elif 0.33 < sed < 0.66:
  304. flip_img = cv2.flip(flip_img, 1) # _flip_y
  305. inver = 1
  306. else:
  307. flip_img = cv2.flip(flip_img, -1) # flip_x_y
  308. inver = -1
  309. # ---------------------- 调整boundingbox ----------------------
  310. flip_bboxes = list()
  311. for box in bboxes:
  312. x_min = box[0]
  313. y_min = box[1]
  314. x_max = box[2]
  315. y_max = box[3]
  316. if inver == 0:
  317. #0:垂直翻转
  318. flip_bboxes.append([x_min, h - y_max, x_max, h - y_min])
  319. elif inver == 1:
  320. # 1:水平翻转
  321. flip_bboxes.append([w - x_max, y_min, w - x_min, y_max])
  322. elif inver == -1:
  323. # -1:水平垂直翻转
  324. flip_bboxes.append([w - x_max, h - y_max, w - x_min, h - y_min])
  325. return flip_img, flip_bboxes
  326. # 图像增强方法
  327. def dataAugment(self, img, bboxes):
  328. '''
  329. 图像增强
  330. 输入:
  331. img:图像array
  332. bboxes:该图像的所有框坐标
  333. 输出:
  334. img:增强后的图像
  335. bboxes:增强后图片对应的box
  336. '''
  337. change_num = 0 # 改变的次数
  338. # print('------')
  339. while change_num < 1: # 默认至少有一种数据增强生效
  340. if self.is_rotate_img_bbox:
  341. if random.random() > self.rotation_rate: # 旋转
  342. change_num += 1
  343. angle = random.uniform(-self.max_rotation_angle, self.max_rotation_angle)
  344. scale = random.uniform(0.7, 0.8)
  345. img, bboxes = self._rotate_img_bbox(img, bboxes, angle, scale)
  346. if self.is_shift_pic_bboxes:
  347. if random.random() < self.shift_rate: # 平移
  348. change_num += 1
  349. img, bboxes = self._shift_pic_bboxes(img, bboxes)
  350. if self.is_changeLight:
  351. if random.random() > self.change_light_rate: # 改变亮度
  352. change_num += 1
  353. img = self._changeLight(img)
  354. if self.is_addNoise:
  355. if random.random() < self.add_noise_rate: # 加噪声
  356. change_num += 1
  357. img = self._addNoise(img)
  358. if self.is_cutout:
  359. if random.random() < self.cutout_rate: # cutout
  360. change_num += 1
  361. img = self._cutout(img, bboxes, length=self.cut_out_length, n_holes=self.cut_out_holes,
  362. threshold=self.cut_out_threshold)
  363. if self.is_filp_pic_bboxes:
  364. if random.random() < self.flip_rate: # 翻转
  365. change_num += 1
  366. img, bboxes = self._filp_pic_bboxes(img, bboxes)
  367. return img, bboxes
  368. # xml解析工具
  369. class ToolHelper():
  370. # 从xml文件中提取bounding box信息, 格式为[[x_min, y_min, x_max, y_max, name]]
  371. def parse_xml(self, path):
  372. '''
  373. 输入:
  374. xml_path: xml的文件路径
  375. 输出:
  376. 从xml文件中提取bounding box信息, 格式为[[x_min, y_min, x_max, y_max, name]]
  377. '''
  378. tree = ET.parse(path)
  379. root = tree.getroot()
  380. objs = root.findall('object')
  381. coords = list()
  382. for ix, obj in enumerate(objs):
  383. name = obj.find('name').text
  384. box = obj.find('bndbox')
  385. x_min = int(box[0].text)
  386. y_min = int(box[1].text)
  387. x_max = int(box[2].text)
  388. y_max = int(box[3].text)
  389. coords.append([x_min, y_min, x_max, y_max, name])
  390. return coords
  391. # 保存图片结果
  392. def save_img(self, file_name, save_folder, img):
  393. cv2.imwrite(os.path.join(save_folder, file_name), img)
  394. # 保持xml结果
  395. def save_xml(self, file_name, save_folder, img_info, height, width, channel, bboxs_info):
  396. '''
  397. :param file_name:文件名
  398. :param save_folder:#保存的xml文件的结果
  399. :param height:图片的信息
  400. :param width:图片的宽度
  401. :param channel:通道
  402. :return:
  403. '''
  404. folder_name, img_name = img_info # 得到图片的信息
  405. E = objectify.ElementMaker(annotate=False)
  406. anno_tree = E.annotation(
  407. E.folder(folder_name),
  408. E.filename(img_name),
  409. E.path(os.path.join(folder_name, img_name)),
  410. E.source(
  411. E.database('Unknown'),
  412. ),
  413. E.size(
  414. E.width(width),
  415. E.height(height),
  416. E.depth(channel)
  417. ),
  418. E.segmented(0),
  419. )
  420. labels, bboxs = bboxs_info # 得到边框和标签信息
  421. for label, box in zip(labels, bboxs):
  422. anno_tree.append(
  423. E.object(
  424. E.name(label),
  425. E.pose('Unspecified'),
  426. E.truncated('0'),
  427. E.difficult('0'),
  428. E.bndbox(
  429. E.xmin(box[0]),
  430. E.ymin(box[1]),
  431. E.xmax(box[2]),
  432. E.ymax(box[3])
  433. )
  434. ))
  435. etree.ElementTree(anno_tree).write(os.path.join(save_folder, file_name), pretty_print=True)
  436. if __name__ == '__main__':
  437. need_aug_num = 6 # 每张图片需要增强的次数
  438. is_endwidth_dot = True # 文件是否以.jpg或者png结尾
  439. dataAug = DataAugmentForObjectDetection() # 数据增强工具类
  440. toolhelper = ToolHelper() # 工具
  441. # 获取相关参数
  442. parser = argparse.ArgumentParser()
  443. parser.add_argument('--source_img_path', type=str, default='D:/download/FloW_IMG/training/images')
  444. parser.add_argument('--source_xml_path', type=str, default='D:/download/FloW_IMG/training/annotations')
  445. parser.add_argument('--save_img_path', type=str, default='D:/datasets/Images')
  446. parser.add_argument('--save_xml_path', type=str, default='D:/datasets/Annotations')
  447. args = parser.parse_args()
  448. source_img_path = args.source_img_path # 图片原始位置
  449. source_xml_path = args.source_xml_path # xml的原始位置
  450. save_img_path = args.save_img_path # 图片增强结果保存文件
  451. save_xml_path = args.save_xml_path # xml增强结果保存文件
  452. # 如果保存文件夹不存在就创建
  453. if not os.path.exists(save_img_path):
  454. os.mkdir(save_img_path)
  455. if not os.path.exists(save_xml_path):
  456. os.mkdir(save_xml_path)
  457. for parent, _, files in os.walk(source_img_path):
  458. files.sort()
  459. for file in files:
  460. cnt = 0
  461. pic_path = os.path.join(parent, file)
  462. xml_path = os.path.join(source_xml_path, file[:-4] + '.xml')
  463. values = toolhelper.parse_xml(xml_path) # 解析得到box信息,格式为[[x_min,y_min,x_max,y_max,name]]
  464. coords = [v[:4] for v in values] # 得到框
  465. labels = [v[-1] for v in values] # 对象的标签
  466. # 如果图片是有后缀的
  467. if is_endwidth_dot:
  468. # 找到文件的最后名字
  469. dot_index = file.rfind('.')
  470. _file_prefix = file[:dot_index] # 文件名的前缀
  471. _file_suffix = file[dot_index:] # 文件名的后缀
  472. img = cv2.imread(pic_path)
  473. # show_pic(img, coords) # 显示原图
  474. while cnt < need_aug_num: # 继续增强
  475. auged_img, auged_bboxes = dataAug.dataAugment(img, coords)
  476. auged_bboxes_int = np.array(auged_bboxes).astype(np.int32)
  477. height, width, channel = auged_img.shape # 得到图片的属性
  478. img_name = '{}_{}{}'.format(_file_prefix, cnt + 1, _file_suffix) # 图片保存的信息
  479. toolhelper.save_img(img_name, save_img_path,
  480. auged_img) # 保存增强图片
  481. toolhelper.save_xml('{}_{}.xml'.format(_file_prefix, cnt + 1),
  482. save_xml_path, (save_img_path, img_name), height, width, channel,
  483. (labels, auged_bboxes_int)) # 保存xml文件
  484. # show_pic(auged_img, auged_bboxes) # 强化后的图
  485. print(img_name)
  486. cnt += 1 # 继续增强下一张

        此代码只需要修改505-508行的地址,前两个是原来的图片和标签地址,后两个是生成后可以存放的标签和图片地址,495行代码是修改生成的倍数,看个人需求来修改~

2.VOC格式转换为YOLO格式

        2.1代码方式转换:

代码如下所示:

  1. import xml.etree.ElementTree as ET
  2. import sys
  3. import os.path
  4. import cv2
  5. class XmlParse:
  6. def __init__(self, file_path):
  7. # 初始化成员变量:self.tree 和 self.root 分别用于存储XML文件解析后的ElementTree对象和根节点;self.xml_file_path 存储传入的XML文件路径。
  8. self.tree = None
  9. self.root = None
  10. self.xml_file_path = file_path
  11. # 使用 try...except...finally 结构处理可能出现的异常情况。
  12. def ReadXml(self): # 该方法用于读取XML文件并解析为ElementTree对象。
  13. try:
  14. self.tree = ET.parse(self.xml_file_path) # 使用 xml.etree.ElementTree.parse() 方法解析XML文件,将结果赋值给 self.tree
  15. self.root = self.tree.getroot() # 获取XML文件的根节点并赋值给 self.root。
  16. except Exception as e: # 在 except Exception as e 块内,捕获并打印解析失败的错误信息,并通过 sys.exit() 终止程序执行。
  17. print("parse xml faild!")
  18. sys.exit()
  19. else:
  20. pass
  21. finally: # finally 块会在不论是否出现异常的情况下都会被执行,这里返回解析好的 self.tree。
  22. return self.tree
  23. def WriteXml(self, destfile):
  24. dses_xml_file = os.path.abspath(destfile)
  25. self.tree.write(dses_xml_file, encoding="utf-8", xml_declaration=True)
  26. def xml2txt(xml, labels, name_list, img_path):
  27. for i, j in zip(os.listdir(xml), os.listdir(img_path)):
  28. p = os.path.join(xml + '/' + i) # xml路径
  29. xml_file = os.path.abspath(p) # 绝对路径
  30. parse = XmlParse(xml_file)
  31. tree = parse.ReadXml() # xml树
  32. root = tree.getroot() # 根节点
  33. W = float(root.find('size').find('width').text)
  34. H = float(root.find('size').find('height').text)
  35. fil_name = root.find('filename').text[:-4]
  36. if not os.path.exists(labels): # 如果路径不存在则创建
  37. os.mkdir(labels)
  38. out = open(labels + './' + fil_name + '.txt', 'w+')
  39. for obj in root.iter('object'):
  40. x_min = float(obj.find('bndbox').find('xmin').text)
  41. x_max = float(obj.find('bndbox').find('xmax').text)
  42. y_min = float(obj.find('bndbox').find('ymin').text)
  43. y_max = float(obj.find('bndbox').find('ymax').text)
  44. print(f'------------------------{i}-----------------------')
  45. print('W:', W, 'H:', H)
  46. # 计算公式
  47. xcenter = x_min + (x_max - x_min) / 2
  48. ycenter = y_min + (y_max - y_min) / 2
  49. w = x_max - x_min
  50. h = y_max - y_min
  51. # 目标框的中心点 宽高
  52. print('center_X: ', xcenter)
  53. print('center_Y: ', ycenter)
  54. print('target box_w: ', w)
  55. print('target box_h: ', h)
  56. # 归一化
  57. xcenter = round(xcenter / W, 6)
  58. ycenter = round(ycenter / H, 6)
  59. w = round(w / W, 6)
  60. h = round(h / H, 6)
  61. print('>>>>>>>>>>')
  62. print(xcenter)
  63. print(ycenter)
  64. print(w)
  65. print(h)
  66. class_dict = dict(zip(name_list, range(0, len(name_list))))
  67. class_name = obj.find('name').text
  68. if class_name not in name_list:
  69. pass
  70. else:
  71. class_id = class_dict[class_name]
  72. print('类别: ', class_id)
  73. print("创建成功: {}".format(fil_name + '.txt'))
  74. print('----------------------------------------------------')
  75. out.write(str(class_id) + " " + str(xcenter) + " " + str(ycenter) + " " + str(w) + " " + str(h) + "\n")
  76. # show_img
  77. m = os.path.join(img_path + '/' + j)
  78. block = cv2.imread(m)
  79. cv2.rectangle(block, pt1=(int((xcenter - w / 2) * W), int((ycenter - h / 2) * H)),
  80. pt2=(int((xcenter + w / 2) * W), int((ycenter + h / 2) * H)),
  81. color=(0, 255, 0), thickness=2)
  82. cv2.imshow('block', block)
  83. cv2.waitKey(300)
  84. def folder_Path():
  85. img_path = 'D:/datasets/Images'
  86. xml_path = 'D:/datasets/Annotations' # xml路径
  87. labels = 'D:/datasets/labels' # 转txt路径
  88. name_list = ['bottle'] # 类别名
  89. xml2txt(xml_path, labels, name_list, img_path)
  90. if __name__ == '__main__':
  91. folder_Path()

2.2 ROBOFLOW方式转换格式

        在上面ROBOFLOW数据增强的基础上,不选择数据增强的方式,然后直接创建新的数据集,就会在原有的基础上进行,然后再导出相应的格式,格式转换就完成了,不过也是次数有限,希望大家珍惜每一次免费的机会哦~

以上就是今天分享的全部内容,如果有用,期待大家的关注哦~我会持续更新遇到的问题以及解决方案。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/823843
推荐阅读
相关标签
  

闽ICP备14008679号