【目标检测】大图包括标签切分,并转换成txt格式

news2025/2/23 10:38:39

前言

遥感图像比较大,通常需要切分成小块再进行训练,之前写过一篇关于大图裁切和拼接的文章【目标检测】图像裁剪/标签可视化/图像拼接处理脚本,不过当时的工作流是先将大图切分成小图,再在小图上进行标注,于是就不考虑标签变换的问题。

最近项目遇到的问题是,一批大图已经做好标注,需要将其裁切,同时标签也要进行同步裁切。本文讲解如何实现这一需求,同时将labelimg直出的xml格式标签转换成yolov5等模型需要的txt标签。

图片裁剪

图片裁剪还是沿用了一套之前博文提到的编码规则,即将图片裁成1280x1280的图像块,裁剪后通过文件名来标记图像块在原始图像中的位置。

import configparser
import shutil
import yaml
import os.path
from pathlib import Path
from PIL import Image
from tqdm import tqdm

rootdir = r"E:\Dataset\数据集\可见光数据\原始未裁剪\img"
savedir = r'E:\Dataset\数据集\可见光数据\裁剪后数据\img'  # 保存图片文件夹

dis = 1280
leap = 1280


def main():
    # 创建输出文件夹
    if Path(savedir).exists():
        shutil.rmtree(savedir)
    os.mkdir(savedir)

    num_dir = len(os.listdir(rootdir))  # 得到文件夹下数量
    num = 0

    for parent, dirnames, filenames in os.walk(rootdir):  # 遍历每一张图片
        filenames.sort()
        for filename in tqdm(filenames):
            currentPath = os.path.join(parent, filename)
            suffix = currentPath.split('.')[-1]
            if suffix == 'jpg' or suffix == 'png' or suffix == 'JPG' or suffix == 'PNG':
                img = Image.open(currentPath)
                width = img.size[0]
                height = img.size[1]
                i = j = 0
                for i in range(0, width, leap):
                    for j in range(0, height, leap):
                        box = (i, j, i + dis, j + dis)
                        image = img.crop(box)  # 图像裁剪
                        image.save(savedir + '/' + filename.split(suffix)[0][:-1] + "__" + str(i) + "__" + str(j) + ".jpg")


if __name__ == '__main__':
    main()

标签裁剪

标签读取

首先需要通过lxml库对xml格式的数据进行解析,主要提取两个信息,1是目标类别,2是目标bbox坐标。

通过递归形式,将xml转换成字典形式,然后就可以获取到需要的信息。

def parse_xml_to_dict(xml):
    """
    将xml文件解析成字典形式
    """
    if len(xml) == 0:  # 遍历到底层,直接返回tag对应的信息
        return {xml.tag: xml.text}
    result = {}
    for child in xml:
        child_result = parse_xml_to_dict(child)  # 递归遍历标签信息
        if child.tag != 'object':
            result[child.tag] = child_result[child.tag]
        else:
            if child.tag not in result:
                result[child.tag] = []
            result[child.tag].append(child_result[child.tag])
    return {xml.tag: result}

def main():
    xml_path = r"label.xml"
    with open(xml_path, encoding="utf-8") as fid:
        xml_str = fid.read()
    xml = etree.fromstring(xml_str)
    data = parse_xml_to_dict(xml)["annotation"]
   	for obj in data["object"]:
        # 获取每个object的box信息
        xmin = float(obj["bndbox"]["xmin"])
        xmax = float(obj["bndbox"]["xmax"])
        ymin = float(obj["bndbox"]["ymin"])
        ymax = float(obj["bndbox"]["ymax"])
        class_name = obj["name"]

标签位置重置

由于图像裁剪成小的图像块,标签也要转换成图像块对应的bbox。不过,对于裁剪的图像,存在的一个问题是,如果标签被切分成两半,该如何进行处理。

下面是我的处理思路,通过对图像块的位置编码,可以分成四种情况。

第一种情况,标签四个角全在图像块中,此时不用做过多处理。
(下图仅为示意,实际尺寸比例未精确,黑色为bbox,红色为切割线)

在这里插入图片描述

第二种情况,标签被左右裁开。此时,将左右两部分都当作一个label分给相应的图像块。

在这里插入图片描述

第三种情况,标签被上下裁开。此时,将上下两部分都当作一个label分给相应的图像块。

在这里插入图片描述

第四种情况,标签被四块裁开,此时,每一块都过于细小,对于小目标而言,这种情况比较少见,因此舍弃该标签。

在这里插入图片描述

对应代码:

xmin_index = int(xmin / leap)
xmax_index = int(xmax / leap)
ymin_index = int(ymin / leap)
ymax_index = int(ymax / leap)

xmin = xmin % leap
xmax = xmax % leap
ymin = ymin % leap
ymax = ymax % leap

# 第一种情况,两个点在相同的图像块中
if xmin_index == xmax_index and ymin_index == ymax_index:
    info = xml2txt(xmin, xmax, ymin, ymax, class_name, img_width, img_height)
    file_name = img_name + "__" + str(xmin_index * leap) + "__" + str(ymin_index * leap) + ".txt"
    write_txt(info, file_name)
# 第二种情况,目标横跨左右两幅图
elif xmin_index + 1 == xmax_index and ymin_index == ymax_index:
    # 保存左半目标
    info = xml2txt(xmin, leap, ymin, ymax, class_name, img_width, img_height)
    file_name = img_name + "__" + str(xmin_index * leap) + "__" + str(ymax_index * leap) + ".txt"
    write_txt(info, file_name)
    # 保存右半目标
    info = xml2txt(0, xmax, ymin, ymax, class_name, img_width, img_height)
    file_name = img_name + "__" + str(xmax_index * leap) + "__" + str(ymax_index * leap) + ".txt"
    write_txt(info, file_name)
# 第三种情况,目标纵跨上下两幅图
elif xmin_index == xmax_index and ymin_index + 1 == ymax_index:
    # 保存上半目标
    info = xml2txt(xmin, xmax, ymin, leap, class_name, img_width, img_height)
    file_name = img_name + "__" + str(xmin_index * leap) + "__" + str(ymin_index * leap) + ".txt"
    write_txt(info, file_name)
    # 保存下半目标
    info = xml2txt(xmin, xmax, 0, ymax, class_name, img_width, img_height)
    file_name = img_name + "__" + str(xmin_index * leap) + "__" + str(ymax_index * leap) + ".txt"
    write_txt(info, file_name)

标签转换成txt格式

xml格式是 xmin,ymin,xmax,ymax,对应左上角和左下角矩形框的全局像素点坐标。
txt格式是 class, xcenter, ycenter, w, h, 对应中心点和bbox的宽和高,不过该坐标是相对坐标,这里转换时需要除以小图的宽高。

相关代码:

def xml2txt(xmin, xmax, ymin, ymax, class_name, img_width, img_height):
    # 类别索引
    class_index = class_dict.index(class_name)

    # 将box信息转换到yolo格式
    xcenter = xmin + (xmax - xmin) / 2
    ycenter = ymin + (ymax - ymin) / 2
    w = xmax - xmin
    h = ymax - ymin

    # 绝对坐标转相对坐标,保存6位小数
    xcenter = round(xcenter / img_width, 6)
    ycenter = round(ycenter / img_height, 6)
    w = round(w / img_width, 6)
    h = round(h / img_height, 6)

    info = [str(i) for i in [class_index, xcenter, ycenter, w, h]]
    return info

完整代码

最后附上批量处理的完整代码:

import os
from tqdm import tqdm
from lxml import etree

xml_file_path = "E:/Dataset/数据集/可见光数据/原始未裁剪/labels"
output_txt_path = "E:/Dataset/数据集/可见光数据/裁剪后数据/labels"

class_dict = ['class1', 'class2']
leap = 1280


def parse_xml_to_dict(xml):
    """
    将xml文件解析成字典形式
    """
    if len(xml) == 0:  # 遍历到底层,直接返回tag对应的信息
        return {xml.tag: xml.text}

    result = {}
    for child in xml:
        child_result = parse_xml_to_dict(child)  # 递归遍历标签信息
        if child.tag != 'object':
            result[child.tag] = child_result[child.tag]
        else:
            if child.tag not in result:
                result[child.tag] = []
            result[child.tag].append(child_result[child.tag])
    return {xml.tag: result}


def xml2txt(xmin, xmax, ymin, ymax, class_name, img_width, img_height):
    # 类别索引
    class_index = class_dict.index(class_name)

    # 将box信息转换到yolo格式
    xcenter = xmin + (xmax - xmin) / 2
    ycenter = ymin + (ymax - ymin) / 2
    w = xmax - xmin
    h = ymax - ymin

    # 绝对坐标转相对坐标,保存6位小数
    xcenter = round(xcenter / img_width, 6)
    ycenter = round(ycenter / img_height, 6)
    w = round(w / img_width, 6)
    h = round(h / img_height, 6)

    info = [str(i) for i in [class_index, xcenter, ycenter, w, h]]
    return info


def write_txt(info, file_name):
    with open(file_name, encoding="utf-8", mode="a") as f:
        # 若文件不为空,添加换行
        if os.path.getsize(file_name):
            f.write("\n" + " ".join(info))
        else:
            f.write(" ".join(info))


def main():
    for xml_file in os.listdir(xml_file_path):
        with open(os.path.join(xml_file_path, xml_file), encoding="utf-8") as fid:
            xml_str = fid.read()
        xml = etree.fromstring(xml_str)
        data = parse_xml_to_dict(xml)["annotation"]

        # img_height = int(data["size"]["height"])
        # img_width = int(data["size"]["width"])
        img_height = leap
        img_width = leap

        img_name = xml_file[:-4]

        for obj in data["object"]:
            # 获取每个object的box信息
            xmin = float(obj["bndbox"]["xmin"])
            xmax = float(obj["bndbox"]["xmax"])
            ymin = float(obj["bndbox"]["ymin"])
            ymax = float(obj["bndbox"]["ymax"])
            class_name = obj["name"]

            xmin_index = int(xmin / leap)
            xmax_index = int(xmax / leap)
            ymin_index = int(ymin / leap)
            ymax_index = int(ymax / leap)

            xmin = xmin % leap
            xmax = xmax % leap
            ymin = ymin % leap
            ymax = ymax % leap

            # 第一种情况,两个点在相同的图像块中
            if xmin_index == xmax_index and ymin_index == ymax_index:
                info = xml2txt(xmin, xmax, ymin, ymax, class_name, img_width, img_height)
                file_name = output_txt_path + "/" + img_name + "__" + str(xmin_index * leap) + "__" + str(
                    ymin_index * leap) + ".txt"
                write_txt(info, file_name)
            # 第二种情况,目标横跨左右两幅图
            elif xmin_index + 1 == xmax_index and ymin_index == ymax_index:
                # 保存左半目标
                info = xml2txt(xmin, leap, ymin, ymax, class_name, img_width, img_height)
                file_name = output_txt_path + "/" + img_name + "__" + str(xmin_index * leap) + "__" + str(
                    ymax_index * leap) + ".txt"
                write_txt(info, file_name)
                # 保存右半目标
                info = xml2txt(0, xmax, ymin, ymax, class_name, img_width, img_height)
                file_name = output_txt_path + "/" + img_name + "__" + str(xmax_index * leap) + "__" + str(
                    ymax_index * leap) + ".txt"
                write_txt(info, file_name)
            # 第三种情况,目标纵跨上下两幅图
            elif xmin_index == xmax_index and ymin_index + 1 == ymax_index:
                # 保存上半目标
                info = xml2txt(xmin, xmax, ymin, leap, class_name, img_width, img_height)
                file_name = output_txt_path + "/" + img_name + "__" + str(xmin_index * leap) + "__" + str(
                    ymin_index * leap) + ".txt"
                write_txt(info, file_name)
                # 保存下半目标
                info = xml2txt(xmin, xmax, 0, ymax, class_name, img_width, img_height)
                file_name = output_txt_path + "/" + img_name + "__" + str(xmin_index * leap) + "__" + str(
                    ymax_index * leap) + ".txt"
                write_txt(info, file_name)


if __name__ == "__main__":
    main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1071715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[NewStarCTF 2023 公开赛道] week1 Crypto

brainfuck 题目描述&#xff1a; [>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<-]>>>>>>>.>----.<-----.>-----.>-----.<<<-.>>..…

深入了解归并排序:原理、性能分析与 Java 实现

归并排序&#xff08;Merge Sort&#xff09;是一种高效且稳定的排序算法&#xff0c;其优雅的分治策略使它成为排序领域的一颗明珠。它的核心思想是将一个未排序的数组分割成两个子数组&#xff0c;然后递归地对子数组进行排序&#xff0c;最后将这些排好序的子数组合并起来。…

在JavaScript中,什么是IIFE(Immediately Invoked Function Expression)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

现代化战机之路:美国空军U-2侦察机基于Jenkins和k8s的CI/CD架构演进

▲ 点击上方"DevOps和k8s全栈技术"关注公众 华为北京研究所Q27大楼 随着技术的不断进步&#xff0c;军事领域也在积极采纳现代化工具来提高战备水平和效率。美国空军的U-2侦察机项目是一个鲜明的例子&#xff0c;它成功地借助Jenkins和Kubernetes&#xff08;k8s&…

Oracle修改数据之后提交事务如何回滚?

在 MySQL 和 Oracle 数据库中&#xff0c;事务提交后都无法回滚。 在 MySQL 中&#xff0c;恢复机制是通过回滚日志&#xff08;undo log&#xff09;实现的&#xff0c;所有事务进行的修改都会先记录到这个回滚日志中&#xff0c;然后在对数据库中的对应行进行写入。当事务已经…

IDE环境要注意统一编码,否则出现中文乱码找不到头绪

最近遇到在IDEA开发项目时&#xff0c;保存中文为乱码的现象&#xff0c;如图&#xff1a; 看了项目配置文件的编码都是UTF-8&#xff0c;在别的开发机上运行都正常&#xff0c;就是这台机器上有问题。 同事一时也找不到方法&#xff0c;因为没遇到同样的事情。 一直怀疑是编…

Spring源码解析——IOC之循环依赖处理

什么是循环依赖 循环依赖其实就是循环引用&#xff0c;也就是两个或则两个以上的bean互相持有对方&#xff0c;最终形成闭环。比如A依赖于B&#xff0c;B依赖于C&#xff0c;C又依赖于A。如下图所示&#xff1a; 注意&#xff0c;这里不是函数的循环调用&#xff0c;是对象的相…

tcpdump(三)命令行参数讲解(二)

一 tcpdump实战详解 骏马金龙tcpdump详解 强调&#xff1a; 注意区分选项参数和过滤条件 本文继上篇 网卡没有开启混杂模式 tcpdump默认开启混杂模式 --no-promiscuous-mode --> 可以指定在非混杂模式抓包 ① -vv 控制详细内容的输出 ② -s -s 长度: 可以只…

基于Java的社区生鲜在线电商平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

【排序算法】选择排序

文章目录 一&#xff1a;基本介绍1.1 概念1.2 算法思想1.3 思路分析图1.4 思路分析1.5 总结1.5.1 选择排序一共有数组大小-1轮排序1.5.2 每一轮排序&#xff0c;又是一个循环&#xff0c;循环的规则如下&#xff08;在代码中实现&#xff09;&#xff1a; 二&#xff1a;代码实…

大数据——Spark Streaming

是什么 Spark Streaming是一个可扩展、高吞吐、具有容错性的流式计算框架。 之前我们接触的spark-core和spark-sql都是离线批处理任务&#xff0c;每天定时处理数据&#xff0c;对于数据的实时性要求不高&#xff0c;一般都是T1的。但在企业任务中存在很多的实时性的任务需求&…

C#,数值计算——数据建模Fitexy的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Fitexy { private double a { get; set; } private double b { get; set; } private double siga { get; set; } private double sigb { get; set; } …

快速搭建Springboot项目(一)

目录 第一章、Spring Boot框架介绍1.1&#xff09;Springboot是什么&#xff0c;有什么好处1.2&#xff09;spring boot的两大策略与四大核心 第二章、快速搭建spring boot 项目2.1&#xff09;idea快速创建spring boot项目2.2&#xff09;pom文件内容的含义2.3&#xff09;起步…

195、SpringBoot--配置RabbitMQ消息Broker的SSL 和 管理控制台的HTTPS

开启Rabbitmq的一些命令&#xff1a; 小黑窗输入&#xff1a; rabbitmq-plugins enable rabbitmq_management 启动控制台插件&#xff0c;就是启动登录rabbitmq控制台的页面 rabbitmq_management 代表了RabbitMQ的管理界面。 rabbitmq-server 启动rabbitMQ服务器 上面这个&…

springboot中的静态资源规则~

静态资源处理&#xff1a; 默认的静态资源路径为 calsspath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/如果我们将静态资源放置上述四种路径处&#xff0c;那么可以通过项目根路径/静态资源名称的方式访问到&#xff0c;否则会访问不…

Oracle-ASM实例communication error问题处理

问题背景&#xff1a; Oracle数据库日志出现大量的WARNING: ASM communication error: op 0 state 0x0 (15055)错误 问题分析: 首先检查ASM实例的状态,尝试通过sqlplus / as sysasm连接asm实例&#xff0c;出现Connected to an idle instance连接asm实例失败 检查ASM实例的后台…

mysql面试题27:数据库中间件了解过吗?什么是sharding jdbc、mycat,并且讲讲怎么使用?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:数据库中间件了解过吗,比如sharding jdbc、mycat? 我知道的数据库中间件有以下这些: MySQL Proxy:MySQL Proxy是一个开源的数据库中间件,它位…

SSM170基于SSM的疫情物质管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

怎么禁止windows server2003系统中的用户进行本地登陆

随着科技的发展&#xff0c;电脑已经成为人们日常生活中必不可少的工具&#xff0c;电脑系统也在逐步更新&#xff0c;这就导致了许多人对于陌生的系统都不知道应该怎么办&#xff1f;当我们在使用windows server2003时&#xff0c;如何设置用户禁止本地登陆呢&#xff1f;接下…

【Linux初阶】多线程1 | 页表的索引作用 线程基础

本文要点 再次理解页表&#xff0c;了解页表是如何利用虚拟地址进行索引&#xff0c;实现数据读取和传输的了解线程概念&#xff0c;线程的优缺点&#xff0c;线程异常的后果了解线程和进程的差异了解线程库及其基本调用接口&#xff08;进程创建、终止、等待、控制&#xff0…