Python批量生成旋转图片标签
本文最后更新于 892 天前,其中的信息可能已经有所发展或是发生改变。

最近在做目标检测,用yolov5。手上有一批数据集,包含图像和xml及txt格式的标签。现在将这些图片用处理工具做了一键旋转、水平翻转和垂直翻转。由于数据量还是蛮大的,像我这种懒人是不可能愿意重新标数据集的,肯定会想着去搞个一键生成的小玩意儿。毕竟俗话说得好,自己动手丰衣足食。说干就干,一段Python代码批量将文件夹下所有标签进行转换。

在xml和txt标签里,我选择了xml。两个原因,一个是xml的标签代表的含义更容易理解,另一个就是我手上有xml转txt的工具却没有txt转xml的。所以做了xml就相当于全有了,岂不美哉?

转换的思路挺简单的,首先观察一下xml里的重要信息。重点就两个地方,第一个是

<size>
	<width>720</width>
	<height>1280</height>
	<depth>3</depth>
</size>

这里定义了图片的长和宽,这两个数据必然是用的到的。

第二个就是标注的长方形了:

<bndbox>
	<xmin>0</xmin>
	<ymin>21</ymin>
	<xmax>287</xmax>
	<ymax>565</ymax>
</bndbox>

有了图片的长和宽,有了每个数据的位置。通过数学知识我们就很容易计算出新的标签位置。比如以下三种:

  • 180°旋转:就是x方向和y方向都要变化,并且满足长度减去最大值就是新得到的最小值,减去最小值就是新得到的最大值
  • 沿x轴翻转:沿x轴进行翻转就是x坐标不变,只有y坐标变化,变化规则与180°是相同的
  • 沿y轴翻转:沿y轴进行翻转与沿x轴翻转恰好相反,y坐标不变而x轴变化,变化规则也与180°相同

按照上面的思路,我们可以很简单的用代码来描述

new_xmax = width-int(xmin))
new_xmin = width-int(xmax))
new_ymax = height-int(ymin)
new_ymin = height-int(ymax)

核心思路都有了,接下来就简单了。我接下来的操作也是十分暴力,遍历所有xml文件,读入到字符串中,正则提取出所有的xmin、ymin、xmax、ymax,然后逐个遍历,将原文本用新计算出来的结果替换掉。大致思路如下:

xmins = re.findall(r"<xmin>(.+?)</xmin>", content)
xmaxs = re.findall(r"<xmax>(.+?)</xmax>", content)
ymins = re.findall(r"<ymin>(.+?)</ymin>", content)
ymaxs = re.findall(r"<ymax>(.+?)</ymax>", content)
for i, xmin in enumerate(xmins):
    content = content.replace("<xmax>"+xmaxs[i]+"</xmax>", "<xmax>"+str(width-int(xmin))+"</xmax>")
for i, xmax in enumerate(xmaxs):
    content = content.replace("<xmin>"+xmins[i]+"</xmin>", "<xmin>"+str(width-int(xmax))+"</xmin>")
for i, ymin in enumerate(ymins):
    content = content.replace("<ymax>"+ymaxs[i]+"</ymax>", "<ymax>"+str(height-int(ymin))+"</ymax>")
for i, ymax in enumerate(ymaxs):
    content = content.replace("<ymin>"+ymins[i]+"</ymin>", "<ymin>"+str(height-int(ymax))+"</ymin>")

写好的代码运行一遍,结果就有了。停停停,千万别用上面这段代码。为什么?因为如果是那样的话,我就不会写这么一篇了。我当时用这种简单粗暴的手段处理完以后,完全没有意识到事情的严重性,直接把结果扔到yolo里跑去了。结果yolo给了我一堆warings,警告我标签中读入了负数值,不合法。我这一下可是蒙了,负数值?只好回来检查。

根据yolo中给出警告的文件,我打开了相应的xml,一看傻眼了,好家伙,我居然有xmin比xmax还大的情况。这是什么回事?我首先仔细理了一遍思路,觉得我的计算逻辑没有问题,那问题就一定出在replace里了。我认真的debug,一步一步看结果,思考,最后发现了如下两个问题。

  • 当原文中恰好出现了多个xmin或xmax等的值恰好相同时,我的replace函数就会把所有的都替换掉
  • 被我replace后的结果,也有可能与后序要查找的值刚好相同,这时前面算出来的值就又被替换走了。

果真是replace惹的祸啊,为了解决这两个问题,我用了如下的解决措施:

  • 每次replace,只replace匹配到的第一个值
  • 每次replace的结果中需要加入一段破坏字符,保证后续不会被匹配到

这样就可以按照顺序正确的生成新标签了,核心代码描述如下:

for i, xmin in enumerate(xmins):
    content = content.replace("<xmax>"+xmaxs[i]+"</xmax>", "<xmax>"+str(width-int(xmin))+"calculating</xmax>", 1)
for i, xmax in enumerate(xmaxs):
    content = content.replace("<xmin>"+xmins[i]+"</xmin>", "<xmin>"+str(width-int(xmax))+"calculating</xmin>", 1)
for i, ymin in enumerate(ymins):
    content = content.replace("<ymax>"+ymaxs[i]+"</ymax>", "<ymax>"+str(height-int(ymin))+"calculating</ymax>", 1)
for i, ymax in enumerate(ymaxs):
    content = content.replace("<ymin>"+ymins[i]+"</ymin>", "<ymin>"+str(height-int(ymax))+"calculating</ymin>", 1)

这件事告诉了我任何时候都不能轻敌啊,干任何事情都要细心、小心、耐心。思维要缜密,按照自己的想法不加验证的走,起飞了,往往也就要跌了。

下面贴出完整代码,需要自取。

rotate180.py

import os, re


def convert(inpath, outpath):
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    for file in os.listdir(inpath):
        if file.endswith('.xml'):
            new_file_name = file.replace('.xml', '-whirl.xml')
            with open(inpath+'/'+file) as f:
                content = f.read()
                file_name = ''.join(re.findall(r"<filename>(.*?)</filename>", content)[0].split('.')[:-1])
                width = int(re.findall(r"<width>(.+?)</width>", content)[0])
                height = int(re.findall(r"<height>(.+?)</height>", content)[0])
                xmins = re.findall(r"<xmin>(.+?)</xmin>", content)
                xmaxs = re.findall(r"<xmax>(.+?)</xmax>", content)
                ymins = re.findall(r"<ymin>(.+?)</ymin>", content)
                ymaxs = re.findall(r"<ymax>(.+?)</ymax>", content)
                for i, xmin in enumerate(xmins):
                    content = content.replace("<xmax>"+xmaxs[i]+"</xmax>", "<xmax>"+str(width-int(xmin))+"calculating</xmax>", 1)
                for i, xmax in enumerate(xmaxs):
                    content = content.replace("<xmin>"+xmins[i]+"</xmin>", "<xmin>"+str(width-int(xmax))+"calculating</xmin>", 1)
                for i, ymin in enumerate(ymins):
                    content = content.replace("<ymax>"+ymaxs[i]+"</ymax>", "<ymax>"+str(height-int(ymin))+"calculating</ymax>", 1)
                for i, ymax in enumerate(ymaxs):
                    content = content.replace("<ymin>"+ymins[i]+"</ymin>", "<ymin>"+str(height-int(ymax))+"calculating</ymin>", 1)
                content = content.replace('calculating', '')
                content = content.replace(file_name, file_name+'-whirl')
            with open(outpath+'/'+new_file_name, 'w') as f:
                f.write(content)
            print(f"converted {file} -> {new_file_name}")



if __name__ == '__main__':
    convert(r"your_xml_label_path", r"output_xml_path")

xfliper.py

import os, re


def convert(inpath, outpath):
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    for file in os.listdir(inpath):
        if file.endswith('.xml'):
            new_file_name = file.replace('.xml', '-xflip.xml')
            with open(inpath+'/'+file) as f:
                content = f.read()
                file_name = ''.join(re.findall(r"<filename>(.*?)</filename>", content)[0].split('.')[:-1])
                height = int(re.findall(r"<height>(.+?)</height>", content)[0])
                ymins = re.findall(r"<ymin>(.+?)</ymin>", content)
                ymaxs = re.findall(r"<ymax>(.+?)</ymax>", content)
                for i, ymin in enumerate(ymins):
                    content = content.replace("<ymax>"+ymaxs[i]+"</ymax>", "<ymax>"+str(height-int(ymin))+"calculating</ymax>")
                for i, ymax in enumerate(ymaxs):
                    content = content.replace("<ymin>"+ymins[i]+"</ymin>", "<ymin>"+str(height-int(ymax))+"calculating</ymin>")
                content = content.replace('calculating', '')
                content = content.replace(file_name, file_name+'-xflip')
            with open(outpath+'/'+new_file_name, 'w') as f:
                f.write(content)
            print(f"converted {file} -> {new_file_name}")



if __name__ == '__main__':
    convert(r"your_xml_label_path", r"output_xml_path")

yfliper.py

import os, re


def convert(inpath, outpath):
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    for file in os.listdir(inpath):
        if file.endswith('.xml'):
            new_file_name = file.replace('.xml', '-yflip.xml')
            with open(inpath+'/'+file) as f:
                content = f.read()
                file_name = ''.join(re.findall(r"<filename>(.*?)</filename>", content)[0].split('.')[:-1])
                width = int(re.findall(r"<width>(.+?)</width>", content)[0])
                xmins = re.findall(r"<xmin>(.+?)</xmin>", content)
                xmaxs = re.findall(r"<xmax>(.+?)</xmax>", content)
                for i, xmin in enumerate(xmins):
                    content = content.replace("<xmax>"+xmaxs[i]+"</xmax>", "<xmax>"+str(width-int(xmin))+"calculating</xmax>")
                for i, xmax in enumerate(xmaxs):
                    content = content.replace("<xmin>"+xmins[i]+"</xmin>", "<xmin>"+str(width-int(xmax))+"calculating</xmin>")
                content = content.replace('calculating', '')
                content = content.replace(file_name, file_name+'-yflip')
            with open(outpath+'/'+new_file_name, 'w') as f:
                f.write(content)
            print(f"converted {file} -> {new_file_name}")



if __name__ == '__main__':
    convert(r"your_xml_label_path", r"output_xml_path")

-=||=-收藏
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇