以下代碼可以將Labelme標注的旋轉框Xml格式文件轉換為Yolo標注格式的txt文件,以便用Yolo OBB訓練自己的數據集:
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Linq;
using System.Globalization;
namespace XmlToDotaConverter
{
class Program
{
static readonly List<string> clsList = new List<string> { "Hole1", "Hole2" };
? ? ? ? static void Main(string[] args)
{
string roxmlPath = @"C:\BAN\orgin-xml";
string dotaxmlPath = @"C:\BAN\new-xml";
string outPath = @"C:\BAN\out-txt";
? ? ? ? ? ? // 創建輸出目錄
Directory.CreateDirectory(dotaxmlPath);
Directory.CreateDirectory(outPath);
? ? ? ? ? ? // 第一步:轉換XML格式
var xmlFiles = Directory.GetFiles(roxmlPath, "*.xml");
foreach (var xmlFile in xmlFiles)
{
string outputXml = Path.Combine(dotaxmlPath, Path.GetFileName(xmlFile));
EditXml(xmlFile, outputXml);
}
? ? ? ? ? ? // 第二步:轉換為TXT格式
ToTxt(dotaxmlPath, outPath);
}
? ? ? ? static void EditXml(string xmlFile, string dotaxmlFile)
{
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
? ? ? ? ? ? XmlNodeList objectNodes = doc.SelectNodes("//object");
foreach (XmlNode objNode in objectNodes)
{
XmlNode bndbox = objNode.SelectSingleNode("bndbox");
XmlNode robndbox = objNode.SelectSingleNode("robndbox");
? ? ? ? ? ? ? ? // 處理普通矩形框
if (robndbox == null && bndbox != null)
{
ConvertRectangleBox(bndbox);
}
// 處理旋轉矩形框
else if (robndbox != null)
{
ConvertRotatedBox(objNode, robndbox);
}
}
? ? ? ? ? ? // 保存修改后的XML
doc.Save(dotaxmlFile);
}
? ? ? ? static void ConvertRectangleBox(XmlNode bndbox)
{
double xmin = Math.Max(GetDoubleValue(bndbox, "xmin"), 0);
double ymin = Math.Max(GetDoubleValue(bndbox, "ymin"), 0);
double xmax = Math.Max(GetDoubleValue(bndbox, "xmax"), 0);
double ymax = Math.Max(GetDoubleValue(bndbox, "ymax"), 0);
? ? ? ? ? ? // 移除舊節點
RemoveChildNodes(bndbox);
? ? ? ? ? ? // 添加四個角點坐標
AddPoint(bndbox, "x0", xmin.ToString());
AddPoint(bndbox, "y0", ymax.ToString());
AddPoint(bndbox, "x1", xmax.ToString());
AddPoint(bndbox, "y1", ymax.ToString());
AddPoint(bndbox, "x2", xmax.ToString());
AddPoint(bndbox, "y2", ymin.ToString());
AddPoint(bndbox, "x3", xmin.ToString());
AddPoint(bndbox, "y3", ymin.ToString());
}
? ? ? ? static void ConvertRotatedBox(XmlNode objNode, XmlNode robndbox)
{
// 將robndbox重命名為bndbox
XmlNode bndbox = objNode.OwnerDocument.CreateElement("bndbox");
objNode.ReplaceChild(bndbox, robndbox);
? ? ? ? ? ? double cx = GetDoubleValue(robndbox, "cx");
double cy = GetDoubleValue(robndbox, "cy");
double w = GetDoubleValue(robndbox, "w");
double h = GetDoubleValue(robndbox, "h");
double angle = GetDoubleValue(robndbox, "angle");
? ? ? ? ? ? // 計算旋轉后的四個角點
var p0 = RotatePoint(cx, cy, cx - w / 2, cy - h / 2, -angle);
var p1 = RotatePoint(cx, cy, cx + w / 2, cy - h / 2, -angle);
var p2 = RotatePoint(cx, cy, cx + w / 2, cy + h / 2, -angle);
var p3 = RotatePoint(cx, cy, cx - w / 2, cy + h / 2, -angle);
? ? ? ? ? ? // 添加四個角點坐標
AddPoint(bndbox, "x0", p0.X.ToString());
AddPoint(bndbox, "y0", p0.Y.ToString());
AddPoint(bndbox, "x1", p1.X.ToString());
AddPoint(bndbox, "y1", p1.Y.ToString());
AddPoint(bndbox, "x2", p2.X.ToString());
AddPoint(bndbox, "y2", p2.Y.ToString());
AddPoint(bndbox, "x3", p3.X.ToString());
AddPoint(bndbox, "y3", p3.Y.ToString());
}
? ? ? ? static (int X, int Y) RotatePoint(double cx, double cy, double xp, double yp, double theta)
{
double xoff = xp - cx;
double yoff = yp - cy;
double cosTheta = Math.Cos(theta);
double sinTheta = Math.Sin(theta);
double pResx = cosTheta * xoff + sinTheta * yoff;
double pResy = -sinTheta * xoff + cosTheta * yoff;
return ((int)(cx + pResx), (int)(cy + pResy));
}
? ? ? ? static void ToTxt(string xmlPath, string outPath)
{
foreach (string xmlFile in Directory.GetFiles(xmlPath, "*.xml"))
{
string fileName = Path.GetFileNameWithoutExtension(xmlFile);
string txtPath = Path.Combine(outPath, fileName + ".txt");
? ? ? ? ? ? ? ? XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
? ? ? ? ? ? ? ? using StreamWriter writer = new StreamWriter(txtPath);
foreach (XmlNode objNode in doc.SelectNodes("//object"))
{
string cls = objNode.SelectSingleNode("name").InnerText;
XmlNode bndbox = objNode.SelectSingleNode("bndbox");
? ? ? ? ? ? ? ? ? ? int[] points = new int[8];
points[0] = Math.Max((int)GetDoubleValue(bndbox, "x0"), 0);
points[1] = Math.Max((int)GetDoubleValue(bndbox, "y0"), 0);
points[2] = Math.Max((int)GetDoubleValue(bndbox, "x1"), 0);
points[3] = Math.Max((int)GetDoubleValue(bndbox, "y1"), 0);
points[4] = Math.Max((int)GetDoubleValue(bndbox, "x2"), 0);
points[5] = Math.Max((int)GetDoubleValue(bndbox, "y2"), 0);
points[6] = Math.Max((int)GetDoubleValue(bndbox, "x3"), 0);
points[7] = Math.Max((int)GetDoubleValue(bndbox, "y3"), 0);
? ? ? ? ? ? ? ? ? ? int clsIndex = clsList.IndexOf(cls);
if (clsIndex == -1) continue;
? ? ? ? ? ? ? ? ? ? writer.WriteLine($"{points[0]} {points[1]} {points[2]} {points[3]} " +
$"{points[4]} {points[5]} {points[6]} {points[7]} " +
$"{cls} {clsIndex}");
}
}
}
? ? ? ? #region Helper Methods
static double GetDoubleValue(XmlNode parent, string nodeName)
{
return double.Parse(parent.SelectSingleNode(nodeName).InnerText,
CultureInfo.InvariantCulture);
}
? ? ? ? static void RemoveChildNodes(XmlNode node)
{
while (node.HasChildNodes)
{
node.RemoveChild(node.FirstChild);
}
}
? ? ? ? static void AddPoint(XmlNode parent, string name, string value)
{
XmlElement elem = parent.OwnerDocument.CreateElement(name);
elem.InnerText = value;
parent.AppendChild(elem);
}
#endregion
}
}