文章目录一. OBB说明二. yolo obb数据标签定义三. yolo obb检测结果输出四. anylabeling中的多边形标签转换yolo obb标签方法(一) 通过x-anylabeling进行转换(二) 通过脚本文件进行转换一. OBB说明OBB 全称Oriented Bounding Box定向旋转边界框在传统水平框(x,y,w,h)基础上增加旋转角度 θ用 5 个参数精准贴合任意倾斜角度的目标避免水平框框入大量背景、密集目标 IoU 失真漏检的问题。方式 1OpenCV 定义最常用DOTA、YOLOv8/11 OBB 默认角度范围θ ∈ [-90°, 0°]规则以矩形最短底边为基准从 X 轴水平方向顺时针旋转到该底边的角度w、h 不强制区分长边短边可互换。方式 2长边定义法YOLO 系列主流规则固定w 为矩形长边、h 为短边θ 是长边与 X 轴正方向逆时针夹角彻底避免角度周期性歧义。二. yolo obb数据标签定义由于一个 OBB 及其 180° 旋转是相同的因此旋转定义为模 180°且该框没有方向性。在内部角度以弧度存储并归一化为 [-π/4, 3π/4) ([-45°, 135°))边框宽度 w 被视为较长的一边角度定义为从正 x 轴到 w 方向的顺时针角度。[0°, 90°) 形式是正则化的 DOTA 风格惯例在训练或推理过程中并不适用。YOLO OBB 格式通过其四个角点的坐标来指定边框这些坐标按照此结构在 0 到 1 之间进行归一化class_index x1 y1 x2 y2 x3 y3 x4 y4在内部YOLO 以 xywhr 格式处理损失和输出该格式表示 边界框 的中心点 (xy)、宽度、高度和旋转角度三. yolo obb检测结果输出旋转边框检测为每张图像返回一个 Results 对象。主要的预测字段是 result.obb其中包含每个检测到物体的旋转边框、类别 ID 和置信度分数。属性类型形状描述result.obbOBB(N)旋转边界框。result.obb.datatorch.float32(N,7/8)包含置信度/类别的原始旋转框。result.obb.xywhrtorch.float32(N,5)xywhr旋转框。result.obb.xyxyxyxytorch.float32(N,4,2)四个角点。result.obb.conftorch.float32(N,)置信度得分。四. anylabeling中的多边形标签转换yolo obb标签方法(一) 通过x-anylabeling进行转换将多边形标签转换为有向边界框将有向边界框转为yolo obb标签(二) 通过脚本文件进行转换importosimportjsonimportrandomimportshutilfrompathlibimportPathfromPILimportImagedefget_image_size(img_path):获取图片宽高withImage.open(img_path)asimg:returnimg.width,img.heightdefanylabeling_json_to_obb_txt(json_path,img_w,img_h,save_txt_path): AnyLabeling JSON 转 YOLO OBB 标签txt OBB格式class_id x1 y1 x2 y2 x3 y3 x4 y4 全部归一化0~1 withopen(json_path,r,encodingutf-8)asf:datajson.load(f)lines[]forshapeindata.get(shapes,[]):labelshape.get(label)iflabelnotinCLASS_MAP:print(f警告未定义类别{label}跳过该目标)continuecls_idCLASS_MAP[label]pointsshape.get(points)iflen(points)!4:print(f警告非4点旋转框跳过{json_path}中的该标注)continue# 4个点归一化norm_points[]forx,yinpoints:nxx/img_w nyy/img_h norm_points.append(f{nx:.6f}{ny:.6f})linef{cls_id}{ .join(norm_points)}lines.append(line)# 写入标签文件withopen(save_txt_path,w,encodingutf-8)asf:f.write(\n.join(lines))definit_folder():初始化输出文件夹结构img_dirPath(OUTPUT_DIR)/imageslabel_dirPath(OUTPUT_DIR)/labelsforsplitin[train,val,test]:(img_dir/split).mkdir(parentsTrue,exist_okTrue)(label_dir/split).mkdir(parentsTrue,exist_okTrue)defgenerate_yaml():生成YOLO数据集yaml配置文件yaml_pathPath(OUTPUT_DIR)/dataset.yamlclass_names[kfork,vinsorted(CLASS_MAP.items(),keylambdax:x[1])]yaml_contentfpath:{os.path.abspath(OUTPUT_DIR)}train: images/train val: images/val test: images/test names:{chr(10).join([f{i}:{name}fori,nameinenumerate(class_names)])}withopen(yaml_path,w,encodingutf-8)asf:f.write(yaml_content)print(f✅ 数据集配置文件已生成{yaml_path})defcollect_all_samples():递归遍历指定子文件夹收集(图片,json)配对样本samples[]root_pathPath(ROOT_DIR)# 如果指定了目标子文件夹只遍历这些文件夹ifTARGET_SUB_FOLDERS:forsub_nameinTARGET_SUB_FOLDERS:sub_dirroot_path/sub_nameifnotsub_dir.exists():print(f⚠️ 子文件夹{sub_name}不存在跳过)continue# 在当前子文件夹下递归查找所有jsonforjson_fileinsub_dir.rglob(*.json):img_pathNoneforsuffixinIMG_SUFFIX:temp_imgjson_file.with_suffix(suffix)iftemp_img.exists():img_pathtemp_imgbreakifimg_pathisNone:print(f⚠️ 未找到{json_file.name}对应的图片跳过)continuesamples.append((img_path,json_file))else:# 未指定子文件夹遍历根目录下所有子文件夹forjson_fileinroot_path.rglob(*.json):img_pathNoneforsuffixinIMG_SUFFIX:temp_imgjson_file.with_suffix(suffix)iftemp_img.exists():img_pathtemp_imgbreakifimg_pathisNone:print(f⚠️ 未找到{json_file.name}对应的图片跳过)continuesamples.append((img_path,json_file))print(f 总共收集到有效样本数量{len(samples)})returnsamplesdefsplit_dataset(samples):按8:1:1随机划分数据集random.shuffle(samples)totallen(samples)train_numint(total*TRAIN_RATIO)val_numint(total*VAL_RATIO)train_samplessamples[:train_num]val_samplessamples[train_num:train_numval_num]test_samplessamples[train_numval_num:]print(f 划分结果)print(f训练集{len(train_samples)}张)print(f验证集{len(val_samples)}张)print(f测试集{len(test_samples)}张)return{train:train_samples,val:val_samples,test:test_samples}defcopy_and_convert(split_name,sample_list):复制图片转换json到obb标签并保存到对应数据集文件夹img_out_basePath(OUTPUT_DIR)/images/split_name label_out_basePath(OUTPUT_DIR)/labels/split_nameforimg_path,json_pathinsample_list:img_w,img_hget_image_size(img_path)stemimg_path.stem# 复制图片dst_imgimg_out_base/img_path.name shutil.copy2(img_path,dst_img)# 转换并保存obb标签dst_labellabel_out_base/f{stem}.txtanylabeling_json_to_obb_txt(json_path,img_w,img_h,dst_label)defmain():random.seed(RANDOM_SEED)ifnotPath(ROOT_DIR).exists():print(f❌ 错误根文件夹不存在{ROOT_DIR})returninit_folder()all_samplescollect_all_samples()ifnotall_samples:print(❌ 未找到任何图片json配对样本程序退出)returnsplit_datasplit_dataset(all_samples)forsplit,samplesinsplit_data.items():copy_and_convert(split,samples)print(f✅{split}集处理完成)generate_yaml()print(\n 全部任务执行完成)print(f数据集输出目录{os.path.abspath(OUTPUT_DIR)})if__name____main__:# 所有配置参数放在此处if __name__下、main()调用前 ROOT_DIRrD:\\test\\OUTPUT_DIRrD:\\train\\TRAIN_RATIO0.8VAL_RATIO0.1TEST_RATIO0.1RANDOM_SEED42IMG_SUFFIX(.jpg,.jpeg,.png,.bmp)# 新增需要处理的子文件夹列表为空列表则处理全部子文件夹# 示例TARGET_SUB_FOLDERS [folder1, folder2, folder3]TARGET_SUB_FOLDERS[]# 类别映射标签名:类别ID从0开始CLASS_MAP{rect3_1:0,}# main()
yolov11 obb数据集准备说明
文章目录一. OBB说明二. yolo obb数据标签定义三. yolo obb检测结果输出四. anylabeling中的多边形标签转换yolo obb标签方法(一) 通过x-anylabeling进行转换(二) 通过脚本文件进行转换一. OBB说明OBB 全称Oriented Bounding Box定向旋转边界框在传统水平框(x,y,w,h)基础上增加旋转角度 θ用 5 个参数精准贴合任意倾斜角度的目标避免水平框框入大量背景、密集目标 IoU 失真漏检的问题。方式 1OpenCV 定义最常用DOTA、YOLOv8/11 OBB 默认角度范围θ ∈ [-90°, 0°]规则以矩形最短底边为基准从 X 轴水平方向顺时针旋转到该底边的角度w、h 不强制区分长边短边可互换。方式 2长边定义法YOLO 系列主流规则固定w 为矩形长边、h 为短边θ 是长边与 X 轴正方向逆时针夹角彻底避免角度周期性歧义。二. yolo obb数据标签定义由于一个 OBB 及其 180° 旋转是相同的因此旋转定义为模 180°且该框没有方向性。在内部角度以弧度存储并归一化为 [-π/4, 3π/4) ([-45°, 135°))边框宽度 w 被视为较长的一边角度定义为从正 x 轴到 w 方向的顺时针角度。[0°, 90°) 形式是正则化的 DOTA 风格惯例在训练或推理过程中并不适用。YOLO OBB 格式通过其四个角点的坐标来指定边框这些坐标按照此结构在 0 到 1 之间进行归一化class_index x1 y1 x2 y2 x3 y3 x4 y4在内部YOLO 以 xywhr 格式处理损失和输出该格式表示 边界框 的中心点 (xy)、宽度、高度和旋转角度三. yolo obb检测结果输出旋转边框检测为每张图像返回一个 Results 对象。主要的预测字段是 result.obb其中包含每个检测到物体的旋转边框、类别 ID 和置信度分数。属性类型形状描述result.obbOBB(N)旋转边界框。result.obb.datatorch.float32(N,7/8)包含置信度/类别的原始旋转框。result.obb.xywhrtorch.float32(N,5)xywhr旋转框。result.obb.xyxyxyxytorch.float32(N,4,2)四个角点。result.obb.conftorch.float32(N,)置信度得分。四. anylabeling中的多边形标签转换yolo obb标签方法(一) 通过x-anylabeling进行转换将多边形标签转换为有向边界框将有向边界框转为yolo obb标签(二) 通过脚本文件进行转换importosimportjsonimportrandomimportshutilfrompathlibimportPathfromPILimportImagedefget_image_size(img_path):获取图片宽高withImage.open(img_path)asimg:returnimg.width,img.heightdefanylabeling_json_to_obb_txt(json_path,img_w,img_h,save_txt_path): AnyLabeling JSON 转 YOLO OBB 标签txt OBB格式class_id x1 y1 x2 y2 x3 y3 x4 y4 全部归一化0~1 withopen(json_path,r,encodingutf-8)asf:datajson.load(f)lines[]forshapeindata.get(shapes,[]):labelshape.get(label)iflabelnotinCLASS_MAP:print(f警告未定义类别{label}跳过该目标)continuecls_idCLASS_MAP[label]pointsshape.get(points)iflen(points)!4:print(f警告非4点旋转框跳过{json_path}中的该标注)continue# 4个点归一化norm_points[]forx,yinpoints:nxx/img_w nyy/img_h norm_points.append(f{nx:.6f}{ny:.6f})linef{cls_id}{ .join(norm_points)}lines.append(line)# 写入标签文件withopen(save_txt_path,w,encodingutf-8)asf:f.write(\n.join(lines))definit_folder():初始化输出文件夹结构img_dirPath(OUTPUT_DIR)/imageslabel_dirPath(OUTPUT_DIR)/labelsforsplitin[train,val,test]:(img_dir/split).mkdir(parentsTrue,exist_okTrue)(label_dir/split).mkdir(parentsTrue,exist_okTrue)defgenerate_yaml():生成YOLO数据集yaml配置文件yaml_pathPath(OUTPUT_DIR)/dataset.yamlclass_names[kfork,vinsorted(CLASS_MAP.items(),keylambdax:x[1])]yaml_contentfpath:{os.path.abspath(OUTPUT_DIR)}train: images/train val: images/val test: images/test names:{chr(10).join([f{i}:{name}fori,nameinenumerate(class_names)])}withopen(yaml_path,w,encodingutf-8)asf:f.write(yaml_content)print(f✅ 数据集配置文件已生成{yaml_path})defcollect_all_samples():递归遍历指定子文件夹收集(图片,json)配对样本samples[]root_pathPath(ROOT_DIR)# 如果指定了目标子文件夹只遍历这些文件夹ifTARGET_SUB_FOLDERS:forsub_nameinTARGET_SUB_FOLDERS:sub_dirroot_path/sub_nameifnotsub_dir.exists():print(f⚠️ 子文件夹{sub_name}不存在跳过)continue# 在当前子文件夹下递归查找所有jsonforjson_fileinsub_dir.rglob(*.json):img_pathNoneforsuffixinIMG_SUFFIX:temp_imgjson_file.with_suffix(suffix)iftemp_img.exists():img_pathtemp_imgbreakifimg_pathisNone:print(f⚠️ 未找到{json_file.name}对应的图片跳过)continuesamples.append((img_path,json_file))else:# 未指定子文件夹遍历根目录下所有子文件夹forjson_fileinroot_path.rglob(*.json):img_pathNoneforsuffixinIMG_SUFFIX:temp_imgjson_file.with_suffix(suffix)iftemp_img.exists():img_pathtemp_imgbreakifimg_pathisNone:print(f⚠️ 未找到{json_file.name}对应的图片跳过)continuesamples.append((img_path,json_file))print(f 总共收集到有效样本数量{len(samples)})returnsamplesdefsplit_dataset(samples):按8:1:1随机划分数据集random.shuffle(samples)totallen(samples)train_numint(total*TRAIN_RATIO)val_numint(total*VAL_RATIO)train_samplessamples[:train_num]val_samplessamples[train_num:train_numval_num]test_samplessamples[train_numval_num:]print(f 划分结果)print(f训练集{len(train_samples)}张)print(f验证集{len(val_samples)}张)print(f测试集{len(test_samples)}张)return{train:train_samples,val:val_samples,test:test_samples}defcopy_and_convert(split_name,sample_list):复制图片转换json到obb标签并保存到对应数据集文件夹img_out_basePath(OUTPUT_DIR)/images/split_name label_out_basePath(OUTPUT_DIR)/labels/split_nameforimg_path,json_pathinsample_list:img_w,img_hget_image_size(img_path)stemimg_path.stem# 复制图片dst_imgimg_out_base/img_path.name shutil.copy2(img_path,dst_img)# 转换并保存obb标签dst_labellabel_out_base/f{stem}.txtanylabeling_json_to_obb_txt(json_path,img_w,img_h,dst_label)defmain():random.seed(RANDOM_SEED)ifnotPath(ROOT_DIR).exists():print(f❌ 错误根文件夹不存在{ROOT_DIR})returninit_folder()all_samplescollect_all_samples()ifnotall_samples:print(❌ 未找到任何图片json配对样本程序退出)returnsplit_datasplit_dataset(all_samples)forsplit,samplesinsplit_data.items():copy_and_convert(split,samples)print(f✅{split}集处理完成)generate_yaml()print(\n 全部任务执行完成)print(f数据集输出目录{os.path.abspath(OUTPUT_DIR)})if__name____main__:# 所有配置参数放在此处if __name__下、main()调用前 ROOT_DIRrD:\\test\\OUTPUT_DIRrD:\\train\\TRAIN_RATIO0.8VAL_RATIO0.1TEST_RATIO0.1RANDOM_SEED42IMG_SUFFIX(.jpg,.jpeg,.png,.bmp)# 新增需要处理的子文件夹列表为空列表则处理全部子文件夹# 示例TARGET_SUB_FOLDERS [folder1, folder2, folder3]TARGET_SUB_FOLDERS[]# 类别映射标签名:类别ID从0开始CLASS_MAP{rect3_1:0,}# main()