数据增强相关代码
This commit is contained in:
319
copypaste整张特征图.py
Normal file
319
copypaste整张特征图.py
Normal file
@ -0,0 +1,319 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from tqdm import tqdm
|
||||
|
||||
# --- 1. 核心配置 ---
|
||||
# 特征素材来源目录(包含完整图片和对应标签)
|
||||
# SOURCE_FEATURE_IMAGE_DIR = r"D:\DataPreHandler\yuanshi_data\images\train\images"
|
||||
# SOURCE_FEATURE_LABEL_DIR = r"D:\DataPreHandler\yuanshi_data\images\train\labels"
|
||||
# SOURCE_FEATURE_IMAGE_DIR = r"D:\DataPreHandler\yuanshi_data\images\val\images"
|
||||
# SOURCE_FEATURE_LABEL_DIR = r"D:\DataPreHandler\yuanshi_data\images\val\labels"
|
||||
SOURCE_FEATURE_IMAGE_DIR = r"D:\DataPreHandler\yuanshi_data\images\test\images"
|
||||
SOURCE_FEATURE_LABEL_DIR = r"D:\DataPreHandler\yuanshi_data\images\test\labels"
|
||||
|
||||
# 底图目录
|
||||
# BASE_IMAGE_DIR = r"D:\DataPreHandler\images\dituchoqu\train"
|
||||
BASE_IMAGE_DIR = r"D:\DataPreHandler\images\dituchoqu_huashen\train"
|
||||
# BASE_IMAGE_DIR = r"D:\DataPreHandler\images\test"
|
||||
|
||||
# 输出目录
|
||||
OUTPUT_IMAGE_DIR = r"D:\DataPreHandler\data\train3\images"
|
||||
OUTPUT_LABEL_DIR = r"D:\DataPreHandler\data\train3\labels"
|
||||
# OUTPUT_IMAGE_DIR = r"D:\DataPreHandler\data\val3\images"
|
||||
# OUTPUT_LABEL_DIR = r"D:\DataPreHandler\data\val3\labels"
|
||||
# OUTPUT_IMAGE_DIR = r"D:\DataPreHandler\data\test\da\images"
|
||||
# OUTPUT_LABEL_DIR = r"D:\DataPreHandler\data\test\da\labels"
|
||||
|
||||
# 底图数量
|
||||
SELECT_BASE_IMAGE_COUNT = 0
|
||||
|
||||
|
||||
AUGMENTATION_FACTOR = 1
|
||||
TARGET_CLASSES = [0, 1, 2, 3, 4, 5, 6]
|
||||
PASTE_COUNT_RANGE = (2, 3)
|
||||
MIN_SCALED_SIZE = 50
|
||||
MAX_OVERLAP_IOU = 0.0 # 仅限制“跨特征图”无重叠
|
||||
MAX_RETRY_COUNT = 300
|
||||
SCALE_FACTOR_RANGE = (0.3, 0.9)
|
||||
|
||||
|
||||
# --- 2. 工具函数 ---(核心修改:删除内部重叠检测)
|
||||
def calculate_iou(box1, box2):
|
||||
"""计算两个边界框的交并比(像素坐标)——保持不变"""
|
||||
box1 = [int(round(x)) for x in box1]
|
||||
box2 = [int(round(x)) for x in box2]
|
||||
|
||||
inter_x1 = max(box1[0], box2[0])
|
||||
inter_y1 = max(box1[1], box2[1])
|
||||
inter_x2 = min(box1[2], box2[2])
|
||||
inter_y2 = min(box1[3], box2[3])
|
||||
|
||||
inter_width = max(0, inter_x2 - inter_x1)
|
||||
inter_height = max(0, inter_y2 - inter_y1)
|
||||
inter_area = inter_width * inter_height
|
||||
|
||||
box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
|
||||
box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
|
||||
union_area = box1_area + box2_area - inter_area
|
||||
|
||||
return inter_area / union_area if union_area > 0 else 0.0
|
||||
|
||||
|
||||
def collect_feature_images(feature_image_dir, feature_label_dir, target_classes):
|
||||
"""收集特征图片及其标签——核心修改:删除内部重叠过滤"""
|
||||
print(f"从 {feature_image_dir} 收集特征图片及标签,目标类别 {target_classes}...")
|
||||
feature_list = [] # 元素格式:(图片路径, 标签列表, 原始宽, 原始高)
|
||||
|
||||
feature_image_files = [f for f in os.listdir(feature_image_dir)
|
||||
if f.lower().endswith(('.jpg', '.png', '.jpeg'))]
|
||||
if not feature_image_files:
|
||||
raise FileNotFoundError(f"未找到特征图片!")
|
||||
|
||||
for filename in tqdm(feature_image_files, desc="收集特征图片"):
|
||||
img_path = os.path.join(feature_image_dir, filename)
|
||||
label_file = os.path.splitext(filename)[0] + ".txt"
|
||||
label_path = os.path.join(feature_label_dir, label_file)
|
||||
|
||||
if not os.path.exists(label_path):
|
||||
continue
|
||||
|
||||
img = cv2.imread(img_path)
|
||||
if img is None:
|
||||
tqdm.write(f"警告:无法读取图片 {filename},已跳过")
|
||||
continue
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
|
||||
# 仅过滤缩小后可能过小的特征图(保留内部重叠的图)
|
||||
min_required_orig_size = MIN_SCALED_SIZE / max(SCALE_FACTOR_RANGE)
|
||||
if orig_w < min_required_orig_size or orig_h < min_required_orig_size:
|
||||
tqdm.write(f"警告:特征图片 {filename} 原始尺寸过小,已跳过")
|
||||
continue
|
||||
|
||||
# 读取并过滤目标类别(不再检查内部重叠)
|
||||
labels = []
|
||||
with open(label_path, 'r') as f:
|
||||
for line in f.readlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split()
|
||||
cls_id = int(float(parts[0]))
|
||||
if cls_id not in target_classes:
|
||||
continue
|
||||
xc, yc, w, h = [float(p) for p in parts[1:]]
|
||||
if 0 <= xc <= 1 and 0 <= yc <= 1 and 0 < w <= 1 and 0 < h <= 1:
|
||||
labels.append((cls_id, xc, yc, w, h))
|
||||
|
||||
# 只要有有效标签就保留(无论内部是否重叠)
|
||||
if labels:
|
||||
feature_list.append((img_path, labels, orig_w, orig_h))
|
||||
|
||||
if not feature_list:
|
||||
raise ValueError(f"未收集到有效的特征图片及标签!")
|
||||
|
||||
print(f"特征图片收集完成:共 {len(feature_list)} 张有效特征图片(允许内部目标重叠)")
|
||||
return feature_list
|
||||
|
||||
|
||||
def paste_feature_images_to_base(base_image, feature_list):
|
||||
"""将特征图粘贴到底图——保留“跨特征图无重叠”逻辑(核心)"""
|
||||
base_h, base_w = base_image.shape[:2]
|
||||
pasted_labels = [] # 最终输出的标签
|
||||
pasted_target_boxes = [] # 已粘贴的目标框(像素坐标)
|
||||
pasted_feature_regions = [] # 已粘贴的特征图整体区域(防跨图重叠)
|
||||
pasted_feature_count = 0 # 成功粘贴的特征图数量
|
||||
skipped_small = 0 # 因尺寸过小跳过的次数
|
||||
skipped_region_overlap = 0 # 因特征图整体区域重叠跳过的次数
|
||||
skipped_target_overlap = 0 # 因跨图目标重叠跳过的次数
|
||||
retry_count = 0 # 重试次数
|
||||
|
||||
target_paste_count = random.randint(*PASTE_COUNT_RANGE)
|
||||
print(f" 计划粘贴 {target_paste_count} 张特征图...")
|
||||
|
||||
while pasted_feature_count < target_paste_count and retry_count < MAX_RETRY_COUNT:
|
||||
retry_count += 1
|
||||
|
||||
# 1. 随机选择一张特征图(允许内部重叠)
|
||||
feature_img_path, feature_labels, orig_w, orig_h = random.choice(feature_list)
|
||||
|
||||
# 2. 随机缩放特征图,确保尺寸符合要求
|
||||
scale_factor = random.uniform(*SCALE_FACTOR_RANGE)
|
||||
scaled_w = int(round(orig_w * scale_factor))
|
||||
scaled_h = int(round(orig_h * scale_factor))
|
||||
|
||||
if scaled_w < MIN_SCALED_SIZE or scaled_h < MIN_SCALED_SIZE:
|
||||
skipped_small += 1
|
||||
continue
|
||||
if scaled_w >= base_w or scaled_h >= base_h:
|
||||
skipped_small += 1
|
||||
continue
|
||||
|
||||
# 3. 随机生成粘贴位置(确保特征图完全在底图内)
|
||||
paste_x1 = random.randint(0, base_w - scaled_w)
|
||||
paste_y1 = random.randint(0, base_h - scaled_h)
|
||||
paste_x2 = paste_x1 + scaled_w
|
||||
paste_y2 = paste_y1 + scaled_h
|
||||
current_feature_region = (paste_x1, paste_y1, paste_x2, paste_y2)
|
||||
|
||||
# 4. 关键:检测当前特征图整体区域与已粘贴区域是否重叠(防跨图重叠)
|
||||
region_overlap = False
|
||||
for existing_region in pasted_feature_regions:
|
||||
if calculate_iou(current_feature_region, existing_region) > MAX_OVERLAP_IOU:
|
||||
region_overlap = True
|
||||
skipped_region_overlap += 1
|
||||
break
|
||||
if region_overlap:
|
||||
continue
|
||||
|
||||
# 5. 计算当前特征图目标在底图上的像素坐标(保留原始内部重叠)
|
||||
temp_target_boxes = []
|
||||
valid = True
|
||||
for (cls_id, xc, yc, w, h) in feature_labels:
|
||||
# 特征图内目标坐标 → 缩放后 → 底图坐标(内部重叠会保留)
|
||||
orig_x1 = (xc - w/2) * orig_w
|
||||
orig_y1 = (yc - h/2) * orig_h
|
||||
orig_x2 = (xc + w/2) * orig_w
|
||||
orig_y2 = (yc + h/2) * orig_h
|
||||
|
||||
scaled_x1 = orig_x1 * scale_factor
|
||||
scaled_y1 = orig_y1 * scale_factor
|
||||
scaled_x2 = orig_x2 * scale_factor
|
||||
scaled_y2 = orig_y2 * scale_factor
|
||||
|
||||
base_x1 = paste_x1 + scaled_x1
|
||||
base_y1 = paste_y1 + scaled_y1
|
||||
base_x2 = paste_x1 + scaled_x2
|
||||
base_y2 = paste_y1 + scaled_y2
|
||||
|
||||
# 确保目标框完全在底图内(不考虑内部重叠)
|
||||
if base_x1 < 0 or base_y1 < 0 or base_x2 > base_w or base_y2 > base_h:
|
||||
valid = False
|
||||
break
|
||||
temp_target_boxes.append((base_x1, base_y1, base_x2, base_y2))
|
||||
|
||||
if not valid:
|
||||
continue
|
||||
|
||||
# 6. 关键:检测当前特征图目标与已粘贴目标是否重叠(防跨图目标重叠)
|
||||
target_overlap = False
|
||||
for temp_box in temp_target_boxes:
|
||||
for existing_box in pasted_target_boxes:
|
||||
if calculate_iou(temp_box, existing_box) > MAX_OVERLAP_IOU:
|
||||
target_overlap = True
|
||||
skipped_target_overlap += 1
|
||||
break
|
||||
if target_overlap:
|
||||
break
|
||||
if target_overlap:
|
||||
continue
|
||||
|
||||
# 7. 粘贴特征图并记录信息(保留内部重叠)
|
||||
feature_img = cv2.imread(feature_img_path)
|
||||
if feature_img is None:
|
||||
continue
|
||||
scaled_feature_img = cv2.resize(feature_img, (scaled_w, scaled_h), interpolation=cv2.INTER_LINEAR)
|
||||
base_image[paste_y1:paste_y2, paste_x1:paste_x2] = scaled_feature_img
|
||||
|
||||
# 记录标签、目标框、特征图整体区域
|
||||
for (cls_id, xc, yc, w, h), temp_box in zip(feature_labels, temp_target_boxes):
|
||||
base_xc = (temp_box[0] + temp_box[2]) / 2 / base_w
|
||||
base_yc = (temp_box[1] + temp_box[3]) / 2 / base_h
|
||||
base_w_box = (temp_box[2] - temp_box[0]) / base_w
|
||||
base_h_box = (temp_box[3] - temp_box[1]) / base_h
|
||||
|
||||
pasted_labels.append((cls_id, base_xc, base_yc, base_w_box, base_h_box))
|
||||
pasted_target_boxes.append(temp_box)
|
||||
pasted_feature_regions.append(current_feature_region)
|
||||
pasted_feature_count += 1
|
||||
print(f" 已成功粘贴 {pasted_feature_count}/{target_paste_count} 张特征图")
|
||||
|
||||
# 输出统计(无内部重叠相关提示)
|
||||
print(
|
||||
f" 粘贴统计:成功{pasted_feature_count}张 → "
|
||||
f"跳小尺寸{skipped_small} → 跳区域重叠{skipped_region_overlap} → "
|
||||
f"跳目标重叠{skipped_target_overlap} → 总目标数{len(pasted_labels)}"
|
||||
)
|
||||
return base_image, pasted_labels
|
||||
|
||||
|
||||
# --- 3. 主函数 ---(无修改)
|
||||
def main():
|
||||
os.makedirs(OUTPUT_IMAGE_DIR, exist_ok=True)
|
||||
os.makedirs(OUTPUT_LABEL_DIR, exist_ok=True)
|
||||
|
||||
try:
|
||||
feature_list = collect_feature_images(
|
||||
SOURCE_FEATURE_IMAGE_DIR, SOURCE_FEATURE_LABEL_DIR, TARGET_CLASSES
|
||||
)
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
print(f"错误:{e}")
|
||||
return
|
||||
|
||||
# 筛选指定数量的底图
|
||||
base_files = [f for f in os.listdir(BASE_IMAGE_DIR)
|
||||
if f.lower().endswith(('.jpg', '.png', '.jpeg'))]
|
||||
total_base_count = len(base_files)
|
||||
if total_base_count == 0:
|
||||
print(f"错误:底图目录 {BASE_IMAGE_DIR} 无有效图片!")
|
||||
return
|
||||
|
||||
if SELECT_BASE_IMAGE_COUNT < 0:
|
||||
print(f"错误:底图数量不能为负数(当前设置:{SELECT_BASE_IMAGE_COUNT})!")
|
||||
return
|
||||
elif SELECT_BASE_IMAGE_COUNT == 0:
|
||||
selected_base_files = base_files
|
||||
print(f"\n找到 {total_base_count} 张底图,使用全部底图...\n")
|
||||
else:
|
||||
if SELECT_BASE_IMAGE_COUNT > total_base_count:
|
||||
selected_base_files = base_files
|
||||
print(f"\n警告:设置底图数({SELECT_BASE_IMAGE_COUNT})超过可用数({total_base_count}),使用全部底图...\n")
|
||||
else:
|
||||
selected_base_files = random.sample(base_files, SELECT_BASE_IMAGE_COUNT)
|
||||
print(f"\n找到 {total_base_count} 张底图,随机选择 {SELECT_BASE_IMAGE_COUNT} 张...\n")
|
||||
|
||||
# 处理底图
|
||||
for base_filename in tqdm(selected_base_files, desc="处理底图"):
|
||||
base_name, ext = os.path.splitext(base_filename)
|
||||
base_path = os.path.join(BASE_IMAGE_DIR, base_filename)
|
||||
base_img = cv2.imread(base_path)
|
||||
if base_img is None:
|
||||
tqdm.write(f"\n警告:无法读取底图 {base_filename},已跳过")
|
||||
continue
|
||||
|
||||
for aug_idx in range(AUGMENTATION_FACTOR):
|
||||
print(f"\n底图 {base_filename}(增强序号 {aug_idx}):", end="")
|
||||
base_copy = base_img.copy()
|
||||
pasted_img, labels = paste_feature_images_to_base(base_copy, feature_list)
|
||||
|
||||
if not labels:
|
||||
tqdm.write(f"\n警告:未粘贴任何目标,已跳过该结果")
|
||||
continue
|
||||
|
||||
# 保存图片和标签
|
||||
output_img_name = f"{base_name}_feat_paste_{aug_idx}.jpg"
|
||||
output_img_path = os.path.join(OUTPUT_IMAGE_DIR, output_img_name)
|
||||
cv2.imwrite(output_img_path, pasted_img)
|
||||
|
||||
output_label_name = f"{base_name}_feat_paste_{aug_idx}.txt"
|
||||
output_label_path = os.path.join(OUTPUT_LABEL_DIR, output_label_name)
|
||||
with open(output_label_path, 'w') as f:
|
||||
for (cls_id, x, y, w, h) in labels:
|
||||
f.write(f"{cls_id} {x:.6f} {y:.6f} {w:.6f} {h:.6f}\n")
|
||||
|
||||
print(f" → 已保存(目标数:{len(labels)})")
|
||||
|
||||
# 统计结果
|
||||
output_img_count = len([f for f in os.listdir(OUTPUT_IMAGE_DIR) if f.endswith(('.jpg', '.png'))])
|
||||
output_label_count = len([f for f in os.listdir(OUTPUT_LABEL_DIR) if f.endswith('.txt')])
|
||||
print(f"\n✅ 全部处理完成!")
|
||||
print(f" - 实际处理底图数量:{len(selected_base_files)} 张")
|
||||
print(f" - 生成图片:{output_img_count} 张")
|
||||
print(f" - 生成标签:{output_label_count} 个")
|
||||
print(f" - 输出路径:\n 图片 → {OUTPUT_IMAGE_DIR}\n 标签 → {OUTPUT_LABEL_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user