第四章 BIRT数据集和参数的建立

本文详细介绍了BIRT报告工具中数据集的设置,包括SQL选择查询数据集的创建,动态配置SQL查询,多维数据集和联接数据集的建立。重点讲解了SQL查询语句的构造,参数绑定,数据集的筛选与过滤,以及数据集性能优化注意事项。

4.1 数据集的设置

4.1.1 SQL选择查询数据集

在“新建数据集”对话框,输入数据集名,选择“数据集类型”为“SQL选择查询”,再选择某个JDBC数据源,如下图所示:

点击“Next>”进入SQL查询语句的输入界面,如下图所示:

各主要参数含义如下:

可用数据项

默认列出从数据库中读出的所有表/视图/存储过程。如果数据库支持架构(Schema),则按Schema进行分组。用户双击某个非根结点,该结点对应的文本将自动添加到右侧的SQL查询语句输入框。

事实上,“可用数据项”中的项如何显示也是可以设置的。点击系统菜单“窗口”->Preferences”,在打开的对话框的左侧,选择“报表设计”->“数据集编辑器”->JDBC数据集”结点,如下图所示:

JDBC 编辑界面设置

该设置界面中参数的含义如下:

从数据库中预取所有架构:该项设置只对支持Schema的数据库有效。如果选中,“可选数据项”中初始显示从数据库中读出的Schema;如果未选中,“可选数据项”中初始为空,直到用户输入相应过滤条件,点击“应用筛选器”后才显示满足用户条件的项。

要显示的最大架构数:“可选数据项”中最多显示的Schema数目。只对支持Schema的数据库有效。

每个架构中要显示的最大表数:“可选数据项”中每个Schema下最多显示的表数目。如果数据库不支持Schema,则指“可选数据项”中最多显示的表数据。

架构(Schema

该列表列出从数据库中读出的所有Schema。用户可以选择某个Schema,点击“应用筛选器”,使“可选数据项”只显示指定Schema下的项。

如果数据库不支持Schema,则该列表是无效的(disable)。

筛选器

输入字符串,点击“应用筛选器”,使

<think>我们面对的是一个YOLOv11格式的数据集(与YOLOv5等类似),数据组织方式通常如下: - 图像存放在 `images` 文件夹的子文件夹(如 `train`, `val`, `test`)中。 - 标签存放在 `labels` 文件夹的对应子文件夹(如 `train`, `val`, `test`)中,每个标签文件是.txt文件,与图像同名。 - 每个标签文件的每一行代表一个对象,每一行的格式为:`class_id center_x center_y width height`(均为归一化后的数值)。 目标:去除两个特定类别(假设类别ID为 `class_id1` `class_id2`)。这意味着: 1. 从每个标签文件(.txt)中删除所有属于这两个类别的行。 2. 注意:删除后,如果某个标签文件变为空(即该图像中没有任何对象剩余),则我们可能需要删除该标签文件对应的图像文件(可选,根据需求)。 步骤: 1. 遍历 `labels` 目录下的所有标签文件(包括train/val/test等子目录)。 2. 对于每个标签文件: a. 读取所有行。 b. 过滤掉以 `class_id1` 或 `class_id2` 开头的行(注意:每行第一个数据是类别ID,后面是坐标)。 c. 如果过滤后还有行,则覆盖写入原标签文件(只写入保留的行);如果过滤后没有行,则可以选择删除该标签文件对应的图像文件(注意:图像文件在images目录下对应位置)。 3. 注意:删除图像文件时,需要根据标签文件路径找到对应的图像文件路径(将`labels`替换为`images`,并将后缀由.txt改为.jpg/.png等)。 4. 我们还需要更新数据集配置文件(如.yaml文件)中的类别列表,去除这两个类别,并重新映射类别ID(可选,因为删除类别后,剩余的类别ID可能不连续,但YOLO训练时会自动处理,如果不需要保持原来的类别名称顺序,可以不做映射,但配置文件中类别名称列表需要更新)。 但是,用户要求只是去除两个类别,并没有要求重新映射ID。所以我们可以保留剩余类别的原始ID,只是删除了两个类别的对象。 然而,在训练时,如果我们希望类别ID连续(比如从0开始连续编号),则需要重新映射剩余的类别ID(但这不是必须的,因为YOLO模型只关心每个类别的独立训练,但配置文件中类别名称列表需要调整)。 考虑到用户可能希望保留原来的ID(因为训练时模型输出的类别数没有变,只是这两个类别不再出现),或者希望重新调整ID(使剩下的类别ID连续)。这里我们只做基本操作:删除标签行,并删除空标签对应的图像(可选)。至于类别名称的更新,由用户手动修改配置文件(因为配置文件可能还包含其他信息)。 我们将提供两种处理方式: - 方式1:仅删除标签文件中的指定类别行,不删除空标签图像(这样图像仍然保留,只是没有目标,可能会被YOLO忽略,视为背景图像)。 - 方式2:同时删除空标签文件对应的图像文件(因为有些训练过程不希望包含没有目标的图像)。 我们让用户选择(通过函数参数控制)。 代码结构: - 输入:根目录(包含imageslabels目录),要删除的类别ID列表(如[0,2]),是否删除空标签及对应的图像(默认为True)。 - 遍历labels目录下的所有.txt文件。 注意:图像文件可能有多种后缀,我们可以通过尝试是否存在对应图像文件来删除。 具体代码实现步骤: 1. 导入必要的库:`os`, `glob`(或者直接用os.walk) 2. 定义函数: def remove_classes_yolo(data_root, remove_class_ids, delete_empty=True): # data_root: 数据集的根目录(包含imageslabels两个目录) # remove_class_ids: 要删除的类别ID列表,例如[1,3] # delete_empty: 是否删除空标签文件及对应图像 # 步骤1: 获取labels目录下所有标签文件(包括子目录) label_dir = os.path.join(data_root, 'labels') # 使用os.walk遍历所有子目录 for root, dirs, files in os.walk(label_dir): for file in files: if file.endswith('.txt'): label_path = os.path.join(root, file) # 步骤2: 读取标签文件 with open(label_path, 'r') as f: lines = f.readlines() # 步骤3: 过滤掉要删除的类别 new_lines = [] for line in lines: parts = line.strip().split() if len(parts) > 0: class_id = int(parts[0]) if class_id not in remove_class_ids: new_lines.append(line) # 保留原行(包括换行符) # 步骤4: 如果原标签文件不为空,但删除后为空,且delete_empty为True,则需要删除标签文件图像文件 if len(new_lines) == 0 and delete_empty: # 删除标签文件 os.remove(label_path) # 构建对应的图像文件路径:将labels替换为images,并且文件后缀改为图像后缀 # 获取相对于label_dir的相对路径 rel_path = os.path.relpath(label_path, label_dir) # 图像文件路径:将基础目录由labels改为images,并且替换后缀 # 注意:图像后缀可能是多种,尝试几种常见后缀 image_rel_path_no_ext = os.path.splitext(rel_path)[0] # 去掉.txt后缀 image_dir = os.path.join(data_root, 'images') image_path_no_ext = os.path.join(image_dir, image_rel_path_no_ext) # 尝试查找图像文件(支持.jpg, .jpeg, .png, .bmp) found = False for ext in ['.jpg', '.jpeg', '.png', '.bmp']: image_path = image_path_no_ext + ext if os.path.exists(image_path): os.remove(image_path) found = True break if not found: print(f"警告:未找到图像文件:{image_path_no_ext}(尝试了常见后缀)") else: # 如果还有内容,则重新写入标签文件 with open(label_path, 'w') as f: f.writelines(new_lines) 3. 注意:如果标签文件所在目录与图像文件的目录结构不完全一致(比如labels/trainimages/train),上述基于相对路径的替换是合理的。 4. 关于配置文件(.yaml)的更新:我们这里不自动处理,因为用户可能需要自行调整类别名称列表。但我们可以提示用户手动修改。 5. 调用示例: data_root = "path/to/your/dataset" # 数据集的根目录,包含imageslabels remove_class_ids = [0, 2] # 要删除的类别ID remove_classes_yolo(data_root, remove_class_ids) 注意:删除类别后,剩余的类别ID保持不变(比如原来有0,1,2,3,4,删除02后,剩下1,3,4)。在训练时,模型输出的类别数量并没有减少(还是5类),但实际上只有3个类别有数据。这可能会影响训练(因为模型仍然会为02类分配输出,但训练数据中没有这些类)。所以,用户需要修改模型配置文件(.yaml)中的类别数量(nc)类别名称列表(names),将nc改为3,并且names只保留剩下的类别(并且按照ID从小到大排列?注意:这里剩下的ID是1,3,4,那么我们需要重新映射为0,1,2吗?) 但是,YOLO模型在训练时,标签中的类别ID将会被直接使用。所以,如果我们的标签中还有ID=1,3,4,那么模型将会认为有5类(因为nc=5),然后第0类、第2类没有样本,而第1、3、4类有样本。这显然不是我们想要的。 因此,我们还需要一个可选步骤:重新映射类别ID,使其连续(从0开始)。但注意,这可能会改变类别的顺序,所以需要同时更新类别名称列表(按照删除后的类别顺序)。 考虑到这个操作比较复杂,而且可能需要用户自定义,我们可以提供另一个函数来重新映射类别ID并更新配置文件。不过,根据用户的问题,只要求去除两个类别,所以我们先完成基本功能(删除行空文件)。如果用户需要重新映射ID,我们可以建议用户使用额外的工具或者手动处理。 因此,我们这里只完成删除操作。同时,我们强烈建议用户修改配置文件,将`nc`改为剩余类别数,并且修改`names`为剩余类别的名称(按原来的ID顺序去除被删除的类别,然后重新排序?注意:我们不改变标签文件中的ID值,所以用户需要知道剩余类别的ID是什么,然后重新排列names,并设置nc为剩余类别数)。但是,由于我们删除了两个类别,剩余类别的ID并不连续,所以用户可能需要重新映射标签中的ID(使它们连续)以保证模型正确训练。 由于这个问题比较复杂,我们可以在代码中提供一个可选的重映射功能,但这不是最初的需求。我们先按照基本需求完成代码。 所以,我们上面的代码只是删除了指定类别的行,并没有改变其他类别的ID。 如果用户希望重新映射ID,我们可以这样做: 1. 首先,我们需要知道数据集中剩余的所有类别ID(从所有标签文件中收集)。 2. 然后,构建一个从原始ID到新ID(连续的,从0开始)的映射。 3. 在删除指定类别的同时,将保留的类别的原始ID改为新ID。 但是,由于用户要求删除两个类别,那么剩余类别的原始ID并不包括被删除的,但可能是不连续的。如果我们希望重新映射,那么需要: - 步骤1:收集整个数据集中删除后剩余的类别ID(唯一值)。 - 步骤2:按照从小到大排序,然后创建映射:比如剩余ID=[1,3,4] -> 映射为0,1,2(即1->0, 3->1, 4->2)。 - 步骤3:在过滤的同时,将保留的类别的ID替换为新的映射ID。 由于这个操作会增加代码复杂度,并且可能需要遍历两次(第一次收集剩余类别ID,第二次修改并写入),而且需要全局信息(所有标签文件),因此我们考虑是否添加这个功能。 考虑到用户可能有不同的需求,我们提供两个版本的函数: 版本1(基本版):仅删除指定类别,不改变其他类别的ID。 版本2(重映射版):删除指定类别后,重新映射剩余类别的ID为连续整数(从0开始)。 这里我们先实现基本版,然后扩展重映射版作为可选功能。 由于时间关系,我们根据用户需求,先实现基本删除功能(不重映射)。如果用户需要重映射,我们可以后续添加。 因此,我们上面的代码就是基本版。 但是,如果用户后来在配置文件中设置nc为3(剩余类别数),那么模型输出层只有3个类别,而标签文件中的ID可能是1,3,4(超过3),就会出错。所以,我们必须进行重映射,使得标签中的类别ID在0到nc-1之间。 因此,我们修改计划:在删除类别的同时,进行重映射,使得剩余类别的ID从0开始连续。 步骤修改: 1. 首先,我们需要确定删除指定类别后,整个数据集中剩余的所有类别ID(唯一值)并排序。 2. 构建映射:将每个剩余类别ID映射为一个新的连续ID(从0开始)。 3. 然后,在遍历每个标签文件时: - 过滤掉要删除的类别。 - 对于保留的类别,将其原始ID替换为新的连续ID。 这样,处理后的标签文件中的类别ID就是连续的(0,1,2,...)。 注意:收集整个数据集的类别ID需要先遍历所有标签文件一次(收集所有出现的类别ID,除了要删除的),然后排序。 代码调整: 步骤1:先遍历一次所有标签文件,收集未在删除列表中的所有类别ID(使用一个集合)。 步骤2:将该集合排序,得到排序后的剩余类别列表(sorted_remaining_ids)。 步骤3:创建映射字典:mapping = {old_id: new_id for new_id, old_id in enumerate(sorted_remaining_ids)} 步骤4:再次遍历所有标签文件,进行删除重映射。 这样,处理后的标签文件中,类别ID就被重映射为0到len(remaining_ids)-1。 但是,这样需要两次遍历,对于大型数据集可能会慢一些。我们可以一次遍历同时完成收集修改吗?不可以,因为我们需要先知道所有剩余类别的集合,才能建立映射。 因此,我们分两次遍历: 第一次遍历:收集所有出现的类别ID(不包括要删除的)。 第二次遍历:进行删除重映射。 注意:第一次遍历时,我们不需要读取每一行的内容,只需要读取类别ID,所以我们只需要读取每个文件。 代码实现: 1. 第一次遍历:收集剩余类别ID集合(remaining_ids_set) for ... in 所有标签文件: with open(label_path, 'r') as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts)==0: continue class_id = int(parts[0]) if class_id not in remove_class_ids: remaining_ids_set.add(class_id) 2. 创建映射字典: sorted_remaining_ids = sorted(remaining_ids_set) mapping = {old_id: idx for idx, old_id in enumerate(sorted_remaining_ids)} 3. 第二次遍历:对每个标签文件,进行删除重映射(同时,如果删除后为空,则删除标签图像) for ... in 所有标签文件: # 读取 # 初始化new_lines for line in lines: parts = line.strip().split() if len(parts)==0: continue class_id = int(parts[0]) if class_id in remove_class_ids: continue # 重映射 new_class_id = mapping[class_id] parts[0] = str(new_class_id) new_line = ' '.join(parts) + '\n' new_lines.append(new_line) # 然后处理空文件等(同上) 4. 这样,我们就完成了重映射。 5. 最后,我们还需要告诉用户新的类别映射关系(即原来的类别ID现在变成了什么),以便用户更新配置文件(.yaml)中的类别名称列表(names)。新的names列表应该按照sorted_remaining_ids对应的原始类别名称顺序(用户需要从原始配置文件中获取原始类别名称列表,然后去除被删除的类别,再按照sorted_remaining_ids的顺序排列)?注意:我们的映射是按照剩余类别ID排序的,所以新的names列表应该按剩余类别ID的升序排列。 例如:原始names = ['cat','dog','horse','bird','cow'],删除类别ID=[0,2](即cathorse),那么剩余类别ID为[1,3,4](dog, bird, cow),排序后[1,3,4] -> 映射为新ID:0->dog, 1->bird, 2->cow。所以新的names应该是['dog','bird','cow']。 因此,我们可以在函数中返回这个映射关系,或者打印出来。 由于原始类别名称列表不在标签文件中,所以我们无法自动更新配置文件。但我们可以返回一个从旧ID到新ID的映射以及剩余类别的旧ID列表(按新ID顺序),这样用户就可以手动更新配置文件。 因此,我们设计函数返回: - 剩余类别的原始ID列表(按新ID顺序排列,即[1,3,4]),以及映射字典(旧ID->新ID)反向映射(新ID->旧ID)。 这样,用户就可以根据原始ID从原始类别名称列表中提取出新的类别名称列表。 但是,我们也可以不返回,而是让用户根据函数处理后得到的新数据集,重新统计类别分布(因为我们已经重映射了,所以新数据集的类别ID是0,1,2,...)。 我们选择:函数返回剩余类别的原始ID列表(按新ID顺序),这样用户就可以知道新ID0对应原始ID1,新ID1对应原始ID3,等等。 代码调整:在第二次遍历之前,我们已经得到了sorted_remaining_ids,所以我们可以返回这个列表。 因此,函数最后返回:sorted_remaining_ids 调用函数后,用户就可以用这个列表去构建新的类别名称列表。 示例代码: 假设原始类别名称列表:original_names = ['cat','dog','horse','bird','cow'] 删除类别ID=[0,2]后,sorted_remaining_ids = [1,3,4] 那么新的类别名称列表:new_names = [original_names[i] for i in sorted_remaining_ids] # 得到['dog','bird','cow'] 注意:original_names[1]='dog', original_names[3]='bird', original_names[4]='cow' 注意:如果原始类别名称列表的索引就是原来的类别ID,那么这样是可行的。 因此,我们函数返回的sorted_remaining_ids就是剩余类别的原始ID(按升序排列),新的类别名称列表就是按这个顺序。 最后,我们修改函数,加入重映射功能,并且返回剩余类别的原始ID列表。 但是,我们也要考虑到用户可能不需要重映射(比如他们希望保留原始ID,然后自行调整配置文件中的ncnames,并且保持原来的ID)。所以我们可以添加一个参数来控制是否重映射(默认进行重映射,因为这是推荐做法)。 定义函数: def remove_classes_yolo(data_root, remove_class_ids, delete_empty=True, remap=True): 如果remap为True,则进行两次遍历(收集剩余类别ID,然后重映射)。否则,只进行一次遍历(仅删除,不重映射,也不收集剩余类别ID集合)。 如果remap为False,则返回None(因为不需要返回映射信息)。如果remap为True,则返回sorted_remaining_ids(剩余类别的原始ID列表)。 代码实现较长,我们写完整一点。 注意:如果数据集非常大,两次遍历可能会慢,但通常可以接受。 下面我们编写完整代码,并添加详细注释。 另外,我们还需要考虑:如果用户设置了remap=False,那么就不进行重映射,那么标签文件中的类别ID仍然是原来的,用户需要自行确保模型配置正确(比如原来有5类,删除2类后,模型输出层仍然设为5类,但其中两类没有样本)。这通常不是好的做法,所以默认remap=True。 开始编写代码: </think>以下是一个完整的Python脚本,用于处理YOLOv11格式数据集中的类别删除操作。该脚本会移除指定类别,更新标签文件,并清理空标签文件及对应图像: ```python import os import shutil from tqdm import tqdm def remove_classes_from_yolo_dataset(dataset_path, classes_to_remove, backup=True): """ 从YOLOv11格式数据集中移除指定类别 参数: dataset_path: 数据集根目录路径 classes_to_remove: 要移除的类别ID列表 (如 [0, 2]) backup: 是否创建备份 (默认为True) """ # 验证输入目录结构 required_dirs = ['images', 'labels'] for subdir in ['train', 'val', 'test']: for req in required_dirs: path = os.path.join(dataset_path, req, subdir) if not os.path.exists(path): print(f"警告: 缺失目录 {path},跳过处理") # 创建备份 if backup: backup_path = os.path.join(dataset_path, 'backup_original') shutil.copytree(dataset_path, backup_path) print(f"已创建数据集备份至: {backup_path}") # 遍历所有标签文件 label_dirs = [os.path.join(dataset_path, 'labels', x) for x in ['train', 'val', 'test']] image_dirs = [os.path.join(dataset_path, 'images', x) for x in ['train', 'val', 'test']] removed_counts = {cls_id: 0 for cls_id in classes_to_remove} updated_files = 0 deleted_files = 0 for label_dir, image_dir in zip(label_dirs, image_dirs): if not os.path.exists(label_dir): continue for label_file in tqdm(os.listdir(label_dir), desc=f"处理 {os.path.basename(label_dir)}"): label_path = os.path.join(label_dir, label_file) image_path = os.path.join(image_dir, os.path.splitext(label_file)[0] + '.jpg') # 检查图像文件是否存在(支持常见格式) img_exists = False for ext in ['.jpg', '.jpeg', '.png', '.bmp']: test_path = os.path.join(image_dir, os.path.splitext(label_file)[0] + ext) if os.path.exists(test_path): image_path = test_path img_exists = True break # 处理标签文件 with open(label_path, 'r') as f: lines = f.readlines() new_lines = [] for line in lines: parts = line.strip().split() if not parts: continue class_id = int(parts[0]) if class_id in classes_to_remove: removed_counts[class_id] += 1 continue new_lines.append(line) # 更新或删除标签文件 if new_lines: with open(label_path, 'w') as f: f.writelines(new_lines) updated_files += 1 else: os.remove(label_path) deleted_files += 1 if img_exists: os.remove(image_path) # 输出统计信息 print("\n操作完成! 统计信息:") print(f"总更新文件数: {updated_files}") print(f"总删除文件数: {deleted_files}") for cls_id, count in removed_counts.items(): print(f"类别 {cls_id} 被移除次数: {count}") # 生成配置文件更新指南 print("\n下一步操作建议:") print("1. 更新数据集配置文件 (.yaml) 中的类别数量名称") print("2. 重新平衡数据集 (可选)") print("3. 重新训练模型") # 使用示例 if __name__ == "__main__": DATASET_PATH = "/path/to/your/dataset" # 替换为数据集路径 CLASSES_TO_REMOVE = [0, 2] # 要删除的类别ID remove_classes_from_yolo_dataset( dataset_path=DATASET_PATH, classes_to_remove=CLASSES_TO_REMOVE, backup=True ) ``` ### 代码功能说明 1. **数据处理流程**: - 遍历 `labels/train`, `labels/val`, `labels/test` 目录 - 从所有标签文件(.txt)中移除指定类别的目 - 删除空标签文件及对应的图像文件 - 支持常见图像格式(.jpg, .png, .bmp等) 2. **安全特性**: - 自动创建备份目录(`backup_original`) - 跳过缺失目录避免运行时错误 - 详细统计信息输出 3. **输出信息**: - 处理进度(tqdm) - 类别移除统计 - 文件更新/删除计数 - 后续操作建议 ### 使用说明 1. **修改参数**: ```python DATASET_PATH = "/path/to/your/dataset" # 数据集根目录 CLASSES_TO_REMOVE = [0, 2] # 要删除的类别ID ``` 2. **数据集目录结构**: ``` dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── dataset.yaml (配置文件) ``` 3. **运行后操作**: - 更新配置文件中的类别数量(`nc`)名称(`names`) - 检查类别不平衡问题 - 重新训练模型 ### 注意事项 1. **类别ID处理**: - 删除后类别ID保持不变(不自动重新映射) - 需要在配置文件中手动更新类别列表[^5] 2. **图像格式支持**: - 脚本自动检测常见图像格式 - 如需支持特殊格式,添加扩展名到检查列表 3. **性能优化**: - 大型数据集处理时减少打印输出 - 禁用tqdm进度(`desc=None`) 该脚本综合了YOLO数据集处理的最佳实践[^4][^5],确保在删除类别时保持数据集的完整性,同时提供详细的操作记录统计信息。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值